001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017 018package org.apache.commons.beanutils2; 019 020import java.util.List; 021import java.util.Map; 022import java.util.Objects; 023 024/** 025 * <p> 026 * The metadata describing an individual property of a DynaBean. 027 * </p> 028 * 029 * <p> 030 * The meta contains an <em>optional</em> content type property ({@link #getContentType}) for use by mapped and iterated properties. A mapped or iterated 031 * property may choose to indicate the type it expects. The DynaBean implementation may choose to enforce this type on its entries. Alternatively, an 032 * implementation may choose to ignore this property. All keys for maps must be of type String so no meta data is needed for map keys. 033 * </p> 034 */ 035public class DynaProperty { 036 037 /* 038 * There are issues with serializing primitive class types on certain JVM versions (including Java 1.3). This class uses a custom serialization 039 * implementation that writes an integer for these primitive class. This list of constants are the ones used in serialization. If these values are changed, 040 * then older versions will no longer be read correctly 041 */ 042 private static final int BOOLEAN_TYPE = 1; 043 private static final int BYTE_TYPE = 2; 044 private static final int CHAR_TYPE = 3; 045 private static final int DOUBLE_TYPE = 4; 046 private static final int FLOAT_TYPE = 5; 047 private static final int INT_TYPE = 6; 048 private static final int LONG_TYPE = 7; 049 private static final int SHORT_TYPE = 8; 050 051 /** 052 * Empty array. 053 */ 054 public static final DynaProperty[] EMPTY_ARRAY = {}; 055 056 /** Property name */ 057 protected String name; 058 059 /** Property type */ 060 protected transient Class<?> type; 061 062 /** The <em>(optional)</em> type of content elements for indexed {@code DynaProperty} */ 063 protected transient Class<?> contentType; 064 065 /** 066 * Constructs a property that accepts any data type. 067 * 068 * @param name Name of the property being described 069 */ 070 public DynaProperty(final String name) { 071 this(name, Object.class); 072 } 073 074 /** 075 * Constructs a property of the specified data type. 076 * 077 * @param name Name of the property being described 078 * @param type Java class representing the property data type 079 */ 080 public DynaProperty(final String name, final Class<?> type) { 081 this.name = name; 082 this.type = type; 083 if (type != null && type.isArray()) { 084 this.contentType = type.getComponentType(); 085 } 086 } 087 088 /** 089 * Constructs an indexed or mapped {@code DynaProperty} that supports (pseudo)-introspection of the content type. 090 * 091 * @param name Name of the property being described 092 * @param type Java class representing the property data type 093 * @param contentType Class that all indexed or mapped elements are instances of 094 */ 095 public DynaProperty(final String name, final Class<?> type, final Class<?> contentType) { 096 this.name = name; 097 this.type = type; 098 this.contentType = contentType; 099 } 100 101 /** 102 * Checks this instance against the specified Object for equality. Overrides the default reference test for equality provided by 103 * {@link Object#equals(Object)} 104 * 105 * @param obj The object to compare to 106 * @return {@code true} if object is a dyna property with the same name type and content type, otherwise {@code false} 107 * @since 1.8.0 108 */ 109 @Override 110 public boolean equals(final Object obj) { 111 boolean result; 112 113 result = obj == this; 114 115 if (!result && obj instanceof DynaProperty) { 116 final DynaProperty that = (DynaProperty) obj; 117 result = Objects.equals(this.name, that.name) && Objects.equals(this.type, that.type) && Objects.equals(this.contentType, that.contentType); 118 } 119 120 return result; 121 } 122 123 /** 124 * Gets the <em>(optional)</em> type of the indexed content for {@code DynaProperty}'s that support this feature. 125 * 126 * <p> 127 * There are issues with serializing primitive class types on certain JVM versions (including Java 1.3). Therefore, this field <strong>must not be 128 * serialized using the standard methods</strong>. 129 * </p> 130 * 131 * @return the Class for the content type if this is an indexed {@code DynaProperty} and this feature is supported. Otherwise null. 132 */ 133 public Class<?> getContentType() { 134 return contentType; 135 } 136 137 /** 138 * Gets the name of this property. 139 * 140 * @return the name of the property 141 */ 142 public String getName() { 143 return this.name; 144 } 145 146 /** 147 * <p> 148 * Gets the Java class representing the data type of the underlying property values. 149 * </p> 150 * 151 * <p> 152 * There are issues with serializing primitive class types on certain JVM versions (including Java 1.3). Therefore, this field <strong>must not be 153 * serialized using the standard methods</strong>. 154 * </p> 155 * 156 * <p> 157 * <strong>Please leave this field as {@code transient}</strong> 158 * </p> 159 * 160 * @return the property type 161 */ 162 public Class<?> getType() { 163 return this.type; 164 } 165 166 /** 167 * @return the hash code for this dyna property 168 * @see Object#hashCode 169 * @since 1.8.0 170 */ 171 @Override 172 public int hashCode() { 173 int result = 1; 174 175 result = result * 31 + (name == null ? 0 : name.hashCode()); 176 result = result * 31 + (type == null ? 0 : type.hashCode()); 177 result = result * 31 + (contentType == null ? 0 : contentType.hashCode()); 178 179 return result; 180 } 181 182 /** 183 * Does this property represent an indexed value (ie an array or List)? 184 * 185 * @return {@code true} if the property is indexed (i.e. is a List or array), otherwise {@code false} 186 */ 187 public boolean isIndexed() { 188 if (type == null) { 189 return false; 190 } 191 if (type.isArray() || List.class.isAssignableFrom(type)) { 192 return true; 193 } 194 return false; 195 } 196 197 /** 198 * Does this property represent a mapped value (ie a Map)? 199 * 200 * @return {@code true} if the property is a Map otherwise {@code false} 201 */ 202 public boolean isMapped() { 203 if (type == null) { 204 return false; 205 } 206 return Map.class.isAssignableFrom(type); 207 208 } 209 210 /** 211 * Gets a String representation of this Object. 212 * 213 * @return a String representation of the dyna property 214 */ 215 @Override 216 public String toString() { 217 final StringBuilder sb = new StringBuilder("DynaProperty[name="); 218 sb.append(this.name); 219 sb.append(",type="); 220 sb.append(this.type); 221 if (isMapped() || isIndexed()) { 222 sb.append(" <").append(this.contentType).append(">"); 223 } 224 sb.append("]"); 225 return sb.toString(); 226 } 227 228}