View Javadoc
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    *      http://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 }