blob: 33c3e04ecb02cdf2643bab112774f123cb3d3c32 [file] [log] [blame]
/**
* Copyright (c) 2008, http://www.snakeyaml.org
*
* Licensed 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.yaml.snakeyaml;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
import org.yaml.snakeyaml.error.YAMLException;
import org.yaml.snakeyaml.introspector.BeanAccess;
import org.yaml.snakeyaml.introspector.Property;
import org.yaml.snakeyaml.introspector.PropertySubstitute;
import org.yaml.snakeyaml.introspector.PropertyUtils;
import org.yaml.snakeyaml.nodes.Node;
import org.yaml.snakeyaml.nodes.Tag;
/**
* Provides additional runtime information necessary to create a custom Java
* instance.
*
* In general this class is thread-safe and can be used as a singleton, the only
* exception being the PropertyUtils field. A singleton PropertyUtils should be
* constructed and shared between all YAML Constructors used if a singleton
* TypeDescription is used, since Constructor sets its propertyUtils to the
* TypeDescription that is passed to it, hence you may end up in a situation
* when propertyUtils in TypeDescription is from different Constructor.
*/
public class TypeDescription {
private final Class<? extends Object> type;
//class that implements the described type; if set, will be used as a source for constructor. If not set - TypeDescription will leave instantiation of an entity to the YAML Constructor
private Class<?> impl;
private Tag tag;
transient private Set<Property> dumpProperties;
transient private PropertyUtils propertyUtils;
transient private boolean delegatesChecked;
private Map<String, PropertySubstitute> properties = Collections.emptyMap();
protected Set<String> excludes = Collections.emptySet();
protected String[] includes = null;
protected BeanAccess beanAccess;
public TypeDescription(Class<? extends Object> clazz, Tag tag) {
this(clazz, tag, null);
}
public TypeDescription(Class<? extends Object> clazz, Tag tag, Class<?> impl) {
this.type = clazz;
this.tag = tag;
this.impl = impl;
beanAccess = null;
}
public TypeDescription(Class<? extends Object> clazz, String tag) {
this(clazz, new Tag(tag), null);
}
public TypeDescription(Class<? extends Object> clazz) {
this(clazz, (Tag) null, null);
}
public TypeDescription(Class<? extends Object> clazz, Class<?> impl) {
this(clazz, null, impl);
}
/**
* Get tag which shall be used to load or dump the type (class).
*
* @return tag to be used. It may be a tag for Language-Independent Types
* (http://www.yaml.org/type/)
*/
public Tag getTag() {
return tag;
}
/**
* Set tag to be used to load or dump the type (class).
*
* @param tag
* local or global tag
*/
public void setTag(Tag tag) {
this.tag = tag;
}
public void setTag(String tag) {
setTag(new Tag(tag));
}
/**
* Get represented type (class)
*
* @return type (class) to be described.
*/
public Class<? extends Object> getType() {
return type;
}
/**
* Specify that the property is a type-safe <code>List</code>.
*
* @param property
* name of the JavaBean property
* @param type
* class of List values
*/
@Deprecated
public void putListPropertyType(String property, Class<? extends Object> type) {
addPropertyParameters(property, type);
}
/**
* Get class of List values for provided JavaBean property.
*
* @param property
* property name
* @return class of List values
*/
@Deprecated
public Class<? extends Object> getListPropertyType(String property) {
if (properties.containsKey(property)) {
Class<?>[] typeArguments = properties.get(property).getActualTypeArguments();
if (typeArguments != null && typeArguments.length > 0) {
return typeArguments[0];
}
}
return null;
}
/**
* Specify that the property is a type-safe <code>Map</code>.
*
* @param property
* property name of this JavaBean
* @param key
* class of keys in Map
* @param value
* class of values in Map
*/
@Deprecated
public void putMapPropertyType(String property, Class<? extends Object> key,
Class<? extends Object> value) {
addPropertyParameters(property, key, value);
}
/**
* Get keys type info for this JavaBean
*
* @param property
* property name of this JavaBean
* @return class of keys in the Map
*/
@Deprecated
public Class<? extends Object> getMapKeyType(String property) {
if (properties.containsKey(property)) {
Class<?>[] typeArguments = properties.get(property).getActualTypeArguments();
if (typeArguments != null && typeArguments.length > 0) {
return typeArguments[0];
}
}
return null;
}
/**
* Get values type info for this JavaBean
*
* @param property
* property name of this JavaBean
* @return class of values in the Map
*/
@Deprecated
public Class<? extends Object> getMapValueType(String property) {
if (properties.containsKey(property)) {
Class<?>[] typeArguments = properties.get(property).getActualTypeArguments();
if (typeArguments != null && typeArguments.length > 1) {
return typeArguments[1];
}
}
return null;
}
/**
* Adds new substitute for property <code>pName</code> parameterized by
* <code>classes</classes> to this <code>TypeDescription</code>. If
* <code>pName</code> has been added before - updates parameters with
* <code>classes</code>.
*
* @param pName
* @param classes
*/
public void addPropertyParameters(String pName, Class<?>... classes) {
if (!properties.containsKey(pName)) {
substituteProperty(pName, null, null, null, classes);
} else {
PropertySubstitute pr = properties.get(pName);
pr.setActualTypeArguments(classes);
}
}
@Override
public String toString() {
return "TypeDescription for " + getType() + " (tag='" + getTag() + "')";
}
private void checkDelegates() {
Collection<PropertySubstitute> values = properties.values();
for (PropertySubstitute p : values) {
try {
p.setDelegate(discoverProperty(p.getName()));
} catch (YAMLException e) {
}
}
delegatesChecked = true;
}
private Property discoverProperty(String name) {
if (propertyUtils != null) {
if (beanAccess == null) {
return propertyUtils.getProperty(type, name);
}
return propertyUtils.getProperty(type, name, beanAccess);
}
return null;
}
public Property getProperty(String name) {
if (!delegatesChecked) {
checkDelegates();
}
return properties.containsKey(name) ? properties.get(name) : discoverProperty(name);
}
/**
* Adds property substitute for <code>pName</code>
*
* @param pName
* property name
* @param pType
* property type
* @param getter
* method name for getter
* @param setter
* method name for setter
* @param argParams
* actual types for parameterized type (List<?>, Map<?>)
*/
public void substituteProperty(String pName, Class<?> pType, String getter, String setter,
Class<?>... argParams) {
substituteProperty(new PropertySubstitute(pName, pType, getter, setter, argParams));
}
public void substituteProperty(PropertySubstitute substitute) {
if (Collections.EMPTY_MAP == properties) {
properties = new LinkedHashMap<String, PropertySubstitute>();
}
substitute.setTargetType(type);
properties.put(substitute.getName(), substitute);
}
public void setPropertyUtils(PropertyUtils propertyUtils) {
this.propertyUtils = propertyUtils;
}
/* begin: Representer */
public void setIncludes(String... propNames) {
this.includes = (propNames != null && propNames.length > 0) ? propNames : null;
}
public void setExcludes(String... propNames) {
if (propNames != null && propNames.length > 0) {
excludes = new HashSet<String>();
for (String name : propNames) {
excludes.add(name);
}
} else {
excludes = Collections.emptySet();
}
}
public Set<Property> getProperties() {
if (dumpProperties != null) {
return dumpProperties;
}
if (propertyUtils != null) {
if (includes != null) {
dumpProperties = new LinkedHashSet<Property>();
for (String propertyName : includes) {
if (!excludes.contains(propertyName)) {
dumpProperties.add(getProperty(propertyName));
}
}
return dumpProperties;
}
final Set<Property> readableProps = (beanAccess == null) ? propertyUtils
.getProperties(type) : propertyUtils.getProperties(type, beanAccess);
if (properties.isEmpty()) {
if (excludes.isEmpty()) {
return dumpProperties = readableProps;
}
dumpProperties = new LinkedHashSet<Property>();
for (Property property : readableProps) {
if (!excludes.contains(property.getName())) {
dumpProperties.add(property);
}
}
return dumpProperties;
}
if (!delegatesChecked) {
checkDelegates();
}
dumpProperties = new LinkedHashSet<Property>();
for (Property property : properties.values()) {
if (!excludes.contains(property.getName()) && property.isReadable()) {
dumpProperties.add(property);
}
}
for (Property property : readableProps) {
if (!excludes.contains(property.getName())) {
dumpProperties.add(property);
}
}
return dumpProperties;
}
return null;
}
/* end: Representer */
/*------------ Maybe something useful to override :) ---------*/
public boolean setupPropertyType(String key, Node valueNode) {
return false;
}
public boolean setProperty(Object targetBean, String propertyName, Object value)
throws Exception {
return false;
}
/**
* This method should be overriden for TypeDescription implementations that are supposed to implement instantiation logic that is different from default one as
* implemented in YAML constructors.
* Note that even if you override this method, default filling of fields with variables from parsed YAML will still occur later.
* @param node
* @return
*/
public Object newInstance(Node node) {
if (impl != null) {
try {
java.lang.reflect.Constructor<?> c = impl.getDeclaredConstructor();
c.setAccessible(true);
return c.newInstance();
} catch (Exception e) {
e.printStackTrace();
impl = null;
}
}
return null;
}
public Object newInstance(String propertyName, Node node) {
return null;
}
/**
* Is invoked after entity is filled with values from deserialized YAML
* @param obj - deserialized entity
* @return postprocessed deserialized entity
*/
public Object finalizeConstruction(Object obj) {
return obj;
}
}