/* | |
* Licensed to the Apache Software Foundation (ASF) under one or more | |
* contributor license agreements. See the NOTICE file distributed with | |
* this work for additional information regarding copyright ownership. | |
* The ASF licenses this file to You under the Apache License, Version 2.0 | |
* (the "License"); you may not use this file except in compliance with | |
* the License. You may obtain a copy of the License at | |
* | |
* http://www.apache.org/licenses/LICENSE-2.0 | |
* | |
* Unless required by applicable law or agreed to in writing, software | |
* distributed under the License is distributed on an "AS IS" BASIS, | |
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
* See the License for the specific language governing permissions and | |
* limitations under the License. | |
*/ | |
package org.apache.commons.beanutils2; | |
import java.io.IOException; | |
import java.io.ObjectInputStream; | |
import java.io.ObjectOutputStream; | |
import java.io.Serializable; | |
import java.io.StreamCorruptedException; | |
import java.util.List; | |
import java.util.Map; | |
import java.util.Objects; | |
/** | |
* <p>The metadata describing an individual property of a DynaBean.</p> | |
* | |
* <p>The meta contains an <em>optional</em> content type property ({@link #getContentType}) | |
* for use by mapped and iterated properties. | |
* A mapped or iterated property may choose to indicate the type it expects. | |
* The DynaBean implementation may choose to enforce this type on its entries. | |
* Alternatively, an 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.</p> | |
*/ | |
public class DynaProperty implements Serializable { | |
private static final long serialVersionUID = -3084907613499830175L; | |
/* | |
* There are issues with serializing primitive class types on certain JVM versions | |
* (including java 1.3). | |
* This class uses a custom serialization implementation that writes an integer | |
* for these primitive class. | |
* This list of constants are the ones used in serialization. | |
* If these values are changed, then older versions will no longer be read correctly | |
*/ | |
private static final int BOOLEAN_TYPE = 1; | |
private static final int BYTE_TYPE = 2; | |
private static final int CHAR_TYPE = 3; | |
private static final int DOUBLE_TYPE = 4; | |
private static final int FLOAT_TYPE = 5; | |
private static final int INT_TYPE = 6; | |
private static final int LONG_TYPE = 7; | |
private static final int SHORT_TYPE = 8; | |
/** | |
* Empty array. | |
*/ | |
public static final DynaProperty[] EMPTY_ARRAY = {}; | |
/** Property name */ | |
protected String name; | |
/** Property type */ | |
protected transient Class<?> type; | |
/** The <em>(optional)</em> type of content elements for indexed {@code DynaProperty} */ | |
protected transient Class<?> contentType; | |
/** | |
* Constructs a property that accepts any data type. | |
* | |
* @param name Name of the property being described | |
*/ | |
public DynaProperty(final String name) { | |
this(name, Object.class); | |
} | |
/** | |
* Constructs a property of the specified data type. | |
* | |
* @param name Name of the property being described | |
* @param type Java class representing the property data type | |
*/ | |
public DynaProperty(final String name, final Class<?> type) { | |
this.name = name; | |
this.type = type; | |
if (type != null && type.isArray()) { | |
this.contentType = type.getComponentType(); | |
} | |
} | |
/** | |
* Constructs an indexed or mapped {@code DynaProperty} that supports (pseudo)-introspection | |
* of the content type. | |
* | |
* @param name Name of the property being described | |
* @param type Java class representing the property data type | |
* @param contentType Class that all indexed or mapped elements are instances of | |
*/ | |
public DynaProperty(final String name, final Class<?> type, final Class<?> contentType) { | |
this.name = name; | |
this.type = type; | |
this.contentType = contentType; | |
} | |
/** | |
* Checks this instance against the specified Object for equality. Overrides the | |
* default reference test for equality provided by {@link java.lang.Object#equals(Object)} | |
* @param obj The object to compare to | |
* @return {@code true} if object is a dyna property with the same name | |
* type and content type, otherwise {@code false} | |
* @since 1.8.0 | |
*/ | |
@Override | |
public boolean equals(final Object obj) { | |
boolean result; | |
result = obj == this; | |
if (!result && obj instanceof DynaProperty) { | |
final DynaProperty that = (DynaProperty) obj; | |
result = | |
(Objects.equals(this.name, that.name)) && | |
(Objects.equals(this.type, that.type)) && | |
(Objects.equals(this.contentType, that.contentType)); | |
} | |
return result; | |
} | |
/** | |
* Gets the <em>(optional)</em> type of the indexed content for {@code DynaProperty}'s | |
* that support this feature. | |
* | |
* <p>There are issues with serializing primitive class types on certain JVM versions | |
* (including java 1.3). | |
* Therefore, this field <strong>must not be serialized using the standard methods</strong>.</p> | |
* | |
* @return the Class for the content type if this is an indexed {@code DynaProperty} | |
* and this feature is supported. Otherwise null. | |
*/ | |
public Class<?> getContentType() { | |
return contentType; | |
} | |
/** | |
* Get the name of this property. | |
* @return the name of the property | |
*/ | |
public String getName() { | |
return this.name; | |
} | |
/** | |
* <p>Gets the Java class representing the data type of the underlying property | |
* values.</p> | |
* | |
* <p>There are issues with serializing primitive class types on certain JVM versions | |
* (including java 1.3). | |
* Therefore, this field <strong>must not be serialized using the standard methods</strong>.</p> | |
* | |
* <p><strong>Please leave this field as {@code transient}</strong></p> | |
* | |
* @return the property type | |
*/ | |
public Class<?> getType() { | |
return this.type; | |
} | |
/** | |
* @return the hash code for this dyna property | |
* @see Object#hashCode | |
* @since 1.8.0 | |
*/ | |
@Override | |
public int hashCode() { | |
int result = 1; | |
result = result * 31 + (name == null ? 0 : name.hashCode()); | |
result = result * 31 + (type == null ? 0 : type.hashCode()); | |
result = result * 31 + (contentType == null ? 0 : contentType.hashCode()); | |
return result; | |
} | |
/** | |
* Does this property represent an indexed value (ie an array or List)? | |
* | |
* @return {@code true} if the property is indexed (i.e. is a List or | |
* array), otherwise {@code false} | |
*/ | |
public boolean isIndexed() { | |
if (type == null) { | |
return false; | |
} | |
if (type.isArray() || List.class.isAssignableFrom(type)) { | |
return true; | |
} | |
return false; | |
} | |
/** | |
* Does this property represent a mapped value (ie a Map)? | |
* | |
* @return {@code true} if the property is a Map | |
* otherwise {@code false} | |
*/ | |
public boolean isMapped() { | |
if (type == null) { | |
return false; | |
} | |
return Map.class.isAssignableFrom(type); | |
} | |
/** | |
* Reads a class using safe encoding to workaround java 1.3 serialization bug. | |
*/ | |
private Class<?> readAnyClass(final ObjectInputStream in) throws IOException, ClassNotFoundException { | |
// read back type class safely | |
if (in.readBoolean()) { | |
// it's a type constant | |
switch (in.readInt()) { | |
case BOOLEAN_TYPE: return Boolean.TYPE; | |
case BYTE_TYPE: return Byte.TYPE; | |
case CHAR_TYPE: return Character.TYPE; | |
case DOUBLE_TYPE: return Double.TYPE; | |
case FLOAT_TYPE: return Float.TYPE; | |
case INT_TYPE: return Integer.TYPE; | |
case LONG_TYPE: return Long.TYPE; | |
case SHORT_TYPE: return Short.TYPE; | |
default: | |
// something's gone wrong | |
throw new StreamCorruptedException( | |
"Invalid primitive type. " | |
+ "Check version of beanutils used to serialize is compatible."); | |
} | |
} | |
// it's another class | |
return (Class<?>) in.readObject(); | |
} | |
/** | |
* Reads field values for this object safely. | |
* There are issues with serializing primitive class types on certain JVM versions | |
* (including java 1.3). | |
* This method provides a workaround. | |
* | |
* @param in {@link ObjectInputStream} to read object from | |
* @throws StreamCorruptedException when the stream data values are outside expected range | |
* @throws IOException if the input stream can't be read | |
* @throws ClassNotFoundException When trying to read an object of class that is not on the classpath | |
*/ | |
private void readObject(final ObjectInputStream in) throws IOException, ClassNotFoundException { | |
this.type = readAnyClass(in); | |
if (isMapped() || isIndexed()) { | |
this.contentType = readAnyClass(in); | |
} | |
// read other values | |
in.defaultReadObject(); | |
} | |
/** | |
* Gets a String representation of this Object. | |
* @return a String representation of the dyna property | |
*/ | |
@Override | |
public String toString() { | |
final StringBuilder sb = new StringBuilder("DynaProperty[name="); | |
sb.append(this.name); | |
sb.append(",type="); | |
sb.append(this.type); | |
if (isMapped() || isIndexed()) { | |
sb.append(" <").append(this.contentType).append(">"); | |
} | |
sb.append("]"); | |
return sb.toString(); | |
} | |
/** | |
* Write a class using safe encoding to workaround java 1.3 serialization bug. | |
*/ | |
private void writeAnyClass(final Class<?> clazz, final ObjectOutputStream out) throws IOException { | |
// safely write out any class | |
int primitiveType = 0; | |
if (Boolean.TYPE.equals(clazz)) { | |
primitiveType = BOOLEAN_TYPE; | |
} else if (Byte.TYPE.equals(clazz)) { | |
primitiveType = BYTE_TYPE; | |
} else if (Character.TYPE.equals(clazz)) { | |
primitiveType = CHAR_TYPE; | |
} else if (Double.TYPE.equals(clazz)) { | |
primitiveType = DOUBLE_TYPE; | |
} else if (Float.TYPE.equals(clazz)) { | |
primitiveType = FLOAT_TYPE; | |
} else if (Integer.TYPE.equals(clazz)) { | |
primitiveType = INT_TYPE; | |
} else if (Long.TYPE.equals(clazz)) { | |
primitiveType = LONG_TYPE; | |
} else if (Short.TYPE.equals(clazz)) { | |
primitiveType = SHORT_TYPE; | |
} | |
if (primitiveType == 0) { | |
// then it's not a primitive type | |
out.writeBoolean(false); | |
out.writeObject(clazz); | |
} else { | |
// we'll write out a constant instead | |
out.writeBoolean(true); | |
out.writeInt(primitiveType); | |
} | |
} | |
/** | |
* Writes this object safely. | |
* There are issues with serializing primitive class types on certain JVM versions | |
* (including java 1.3). | |
* This method provides a workaround. | |
* | |
* @param out {@link ObjectOutputStream} to write object to | |
* @throws IOException if the object can't be written | |
*/ | |
private void writeObject(final ObjectOutputStream out) throws IOException { | |
writeAnyClass(this.type,out); | |
if (isMapped() || isIndexed()) { | |
writeAnyClass(this.contentType,out); | |
} | |
// write out other values | |
out.defaultWriteObject(); | |
} | |
} |