1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one or more
3 * contributor license agreements. See the NOTICE file distributed with
4 * this work for additional information regarding copyright ownership.
5 * The ASF licenses this file to You under the Apache License, Version 2.0
6 * (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 *
9 * https://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17
18 package org.apache.commons.beanutils2;
19
20 import java.util.List;
21 import java.util.Map;
22 import java.util.Objects;
23
24 /**
25 * <p>
26 * The metadata describing an individual property of a DynaBean.
27 * </p>
28 *
29 * <p>
30 * The meta contains an <em>optional</em> content type property ({@link #getContentType}) for use by mapped and iterated properties. A mapped or iterated
31 * property may choose to indicate the type it expects. The DynaBean implementation may choose to enforce this type on its entries. Alternatively, an
32 * 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.
33 * </p>
34 */
35 public class DynaProperty {
36
37 /*
38 * There are issues with serializing primitive class types on certain JVM versions (including Java 1.3). This class uses a custom serialization
39 * implementation that writes an integer for these primitive class. This list of constants are the ones used in serialization. If these values are changed,
40 * then older versions will no longer be read correctly
41 */
42 private static final int BOOLEAN_TYPE = 1;
43 private static final int BYTE_TYPE = 2;
44 private static final int CHAR_TYPE = 3;
45 private static final int DOUBLE_TYPE = 4;
46 private static final int FLOAT_TYPE = 5;
47 private static final int INT_TYPE = 6;
48 private static final int LONG_TYPE = 7;
49 private static final int SHORT_TYPE = 8;
50
51 /**
52 * Empty array.
53 */
54 public static final DynaProperty[] EMPTY_ARRAY = {};
55
56 /** Property name */
57 protected String name;
58
59 /** Property type */
60 protected transient Class<?> type;
61
62 /** The <em>(optional)</em> type of content elements for indexed {@code DynaProperty} */
63 protected transient Class<?> contentType;
64
65 /**
66 * Constructs a property that accepts any data type.
67 *
68 * @param name Name of the property being described
69 */
70 public DynaProperty(final String name) {
71 this(name, Object.class);
72 }
73
74 /**
75 * Constructs a property of the specified data type.
76 *
77 * @param name Name of the property being described
78 * @param type Java class representing the property data type
79 */
80 public DynaProperty(final String name, final Class<?> type) {
81 this.name = name;
82 this.type = type;
83 if (type != null && type.isArray()) {
84 this.contentType = type.getComponentType();
85 }
86 }
87
88 /**
89 * Constructs an indexed or mapped {@code DynaProperty} that supports (pseudo)-introspection of the content type.
90 *
91 * @param name Name of the property being described
92 * @param type Java class representing the property data type
93 * @param contentType Class that all indexed or mapped elements are instances of
94 */
95 public DynaProperty(final String name, final Class<?> type, final Class<?> contentType) {
96 this.name = name;
97 this.type = type;
98 this.contentType = contentType;
99 }
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 }