场景和问题描述:
为了解决SnakeYAML 反序列化漏洞(CVE-2022-1471),按照要求将snakeyaml1.*升级到2以上。直接替换springboot 项目jar包内的依赖jar包、和在项目pom中重新build后,都出现了启动时报错“snakeyaml.constructor.*: method ‘void <init>()‘ not found”。
解决方案:
在自己项目中创建包org.yaml.snakeyaml.,并编译下面3个类,把编译出来的class文件按照包路径放到自己项目jar中。
Constructor
/**
* Copyright (c) 2008, SnakeYAML
*
* 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.constructor;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collection;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import org.springframework.dao.DuplicateKeyException;
import org.yaml.snakeyaml.LoaderOptions;
import org.yaml.snakeyaml.TypeDescription;
import org.yaml.snakeyaml.error.YAMLException;
import org.yaml.snakeyaml.introspector.Property;
import org.yaml.snakeyaml.nodes.MappingNode;
import org.yaml.snakeyaml.nodes.Node;
import org.yaml.snakeyaml.nodes.NodeId;
import org.yaml.snakeyaml.nodes.NodeTuple;
import org.yaml.snakeyaml.nodes.ScalarNode;
import org.yaml.snakeyaml.nodes.SequenceNode;
import org.yaml.snakeyaml.nodes.Tag;
import org.yaml.snakeyaml.util.EnumUtils;
/**
* Construct a custom Java instance.
*/
public class Constructor extends SafeConstructor {
public Constructor(){
this(Object.class, new LoaderOptions());
this.loadingConfig.setAllowDuplicateKeys(false);
}
/**
* Create with options
*
* @param loadingConfig - config
*/
public Constructor(LoaderOptions loadingConfig) {
this(Object.class, loadingConfig);
}
/**
* Create
*
* @param theRoot - the class to create (to be the root of the YAML document)
* @param loadingConfig - options
*/
public Constructor(Class<? extends Object> theRoot, LoaderOptions loadingConfig) {
this(new TypeDescription(checkRoot(theRoot)), null, loadingConfig);
}
/**
* Ugly Java way to check the argument in the constructor
*/
private static Class<? extends Object> checkRoot(Class<? extends Object> theRoot) {
if (theRoot == null) {
throw new NullPointerException("Root class must be provided.");
} else {
return theRoot;
}
}
/**
* Create
*
* @param theRoot - the root class to create
* @param loadingConfig options
*/
public Constructor(TypeDescription theRoot, LoaderOptions loadingConfig) {
this(theRoot, null, loadingConfig);
}
/**
* Create with all possible arguments
*
* @param theRoot - the class (usually JavaBean) to be constructed
* @param moreTDs - collection of classes used by the root class
* @param loadingConfig - configuration
*/
public Constructor(TypeDescription theRoot, Collection<TypeDescription> moreTDs,
LoaderOptions loadingConfig) {
super(loadingConfig);
if (theRoot == null) {
throw new NullPointerException("Root type must be provided.");
}
// register a general Construct when the explicit one was not found
this.yamlConstructors.put(null, new ConstructYamlObject());
// register the root tag to begin with its Construct
if (!Object.class.equals(theRoot.getType())) {
rootTag = new Tag(theRoot.getType());
}
yamlClassConstructors.put(NodeId.scalar, new ConstructScalar());
yamlClassConstructors.put(NodeId.mapping, new ConstructMapping());
yamlClassConstructors.put(NodeId.sequence, new ConstructSequence());
addTypeDescription(theRoot);
if (moreTDs != null) {
for (TypeDescription td : moreTDs) {
addTypeDescription(td);
}
}
}
/**
* Create
*
* @param theRoot - the main class to crate
* @param loadingConfig - options
* @throws ClassNotFoundException if something goes wrong
*/
public Constructor(String theRoot, LoaderOptions loadingConfig) throws ClassNotFoundException {
this(Class.forName(check(theRoot)), loadingConfig);
}
private static String check(String s) {
if (s == null) {
throw new NullPointerException("Root type must be provided.");
}
if (s.trim().isEmpty()) {
throw new YAMLException("Root type must be provided.");
}
return s;
}
/**
* Construct mapping instance (Map, JavaBean) when the runtime class is known.
*/
protected class ConstructMapping implements Construct {
/**
* Construct JavaBean. If type safe collections are used please look at
* <code>TypeDescription</code>.
*
* @param node node where the keys are property names (they can only be <code>String</code>s)
* and values are objects to be created
* @return constructed JavaBean
*/
public Object construct(Node node) {
MappingNode mnode = (MappingNode) node;
if (Map.class.isAssignableFrom(node.getType())) {
if (node.isTwoStepsConstruction()) {
return newMap(mnode);
} else {
return constructMapping(mnode);
}
} else if (Collection.class.isAssignableFrom(node.getType())) {
if (node.isTwoStepsConstruction()) {
return newSet(mnode);
} else {
return constructSet(mnode);
}
} else {
Object obj = Constructor.this.newInstance(mnode);
if (obj != NOT_INSTANTIATED_OBJECT) {
if (node.isTwoStepsConstruction()) {
return obj;
} else {
return constructJavaBean2ndStep(mnode, obj);
}
} else {
throw new ConstructorException(null, null,
"Can't create an instance for " + mnode.getTag(), node.getStartMark());
}
}
}
@SuppressWarnings("unchecked")
public void construct2ndStep(Node node, Object object) {
if (Map.class.isAssignableFrom(node.getType())) {
constructMapping2ndStep((MappingNode) node, (Map<Object, Object>) object);
} else if (Set.class.isAssignableFrom(node.getType())) {
constructSet2ndStep((MappingNode) node, (Set<Object>) object);
} else {
constructJavaBean2ndStep((MappingNode) node, object);
}
}
// protected Object createEmptyJavaBean(MappingNode node) {
// try {
// Object instance = Constructor.this.newInstance(node);
// if (instance != null) {
// return instance;
// }
//
// /**
// * Using only default constructor. Everything else will be
// * initialized on 2nd step. If we do here some partial
// * initialization, how do we then track what need to be done on
// * 2nd step? I think it is better to get only object here (to
// * have it as reference for recursion) and do all other thing on
// * 2nd step.
// */
// java.lang.reflect.Constructor<?> c =
// node.getType().getDeclaredConstructor();
// c.setAccessible(true);
// return c.newInstance();
// } catch (Exception e) {
// throw new YAMLException(e);
// }
// }
protected Object constructJavaBean2ndStep(MappingNode node, Object object) {
flattenMapping(node, true);
Class<? extends Object> beanType = node.getType();
List<NodeTuple> nodeValue = node.getValue();
for (NodeTuple tuple : nodeValue) {
Node valueNode = tuple.getValueNode();
// flattenMapping enforces keys to be Strings
String key = (String) constructObject(tuple.getKeyNode());
try {
TypeDescription memberDescription = typeDefinitions.get(beanType);
Property property = memberDescription == null ? getProperty(beanType, key)
: memberDescription.getProperty(key);
if (!property.isWritable()) {
throw new YAMLException(
"No writable property '" + key + "' on class: " + beanType.getName());
}
valueNode.setType(property.getType());
final boolean typeDetected =
memberDescription != null && memberDescription.setupPropertyType(key, valueNode);
if (!typeDetected && valueNode.getNodeId() != NodeId.scalar) {
// only if there is no explicit TypeDescription
Class<?>[] arguments = property.getActualTypeArguments();
if (arguments != null && arguments.length > 0) {
// type safe (generic) collection may contain the
// proper class
if (valueNode.getNodeId() == NodeId.sequence) {
Class<?> t = arguments[0];
SequenceNode snode = (SequenceNode) valueNode;
snode.setListType(t);
} else if (Map.class.isAssignableFrom(valueNode.getType())) {
Class<?> keyType = arguments[0];
Class<?> valueType = arguments[1];
MappingNode mnode = (MappingNode) valueNode;
mnode.setTypes(keyType, valueType);
mnode.setUseClassConstructor(true);
} else if (Collection.class.isAssignableFrom(valueNode.getType())) {
Class<?> t = arguments[0];
MappingNode mnode = (MappingNode) valueNode;
mnode.setOnlyKeyType(t);
mnode.setUseClassConstructor(true);
}
}
}
Object value =
(memberDescription != null) ? newInstance(memberDescription, key, valueNode)
: constructObject(valueNode);
// Correct when the property expects float but double was
// constructed
if ((property.getType() == Float.TYPE || property.getType() == Float.class)
&& (value instanceof Double)) {
value = ((Double) value).floatValue();
}
// Correct when the property a String but the value is binary
if (property.getType() == String.class && Tag.BINARY.equals(valueNode.getTag())
&& value instanceof byte[]) {
value = new String((byte[]) value);
}
if (memberDescription == null || !memberDescription.setProperty(object, key, value)) {
property.set(object, value);
}
} catch (DuplicateKeyException e) {
throw e;
} catch (Exception e) {
throw new ConstructorException(
"Cannot create property=" + key + " for JavaBean=" + object, node.getStartMark(),
e.getMessage(), valueNode.getStartMark(), e);
}
}
return object;
}
private Object newInstance(TypeDescription memberDescription, String propertyName, Node node) {
Object newInstance = memberDescription.newInstance(propertyName, node);
if (newInstance != null) {
constructedObjects.put(node, newInstance);
return constructObjectNoCheck(node);
}
return constructObject(node);
}
protected Property getProperty(Class<? extends Object> type, String name) {
return getPropertyUtils().getProperty(type, name);
}
}
/**
* Construct an instance when the runtime class is not known but a global tag with a class name is
* defined. It delegates the construction to the appropriate constructor based on the node kind
* (scalar, sequence, mapping)
*/
protected class ConstructYamlObject implements Construct {
private Construct getConstructor(Node node) {
Class<?> cl = getClassForNode(node);
node.setType(cl);
// call the constructor as if the runtime class is defined
Construct constructor = yamlClassConstructors.get(node.getNodeId());
return constructor;
}
public Object construct(Node node) {
try {
return getConstructor(node).construct(node);
} catch (ConstructorException e) {
throw e;
} catch (Exception e) {
throw new ConstructorException(null, null,
"Can't construct a java object for " + node.getTag() + "; exception=" + e.getMessage(),
node.getStartMark(), e);
}
}
public void construct2ndStep(Node node, Object object) {
try {
getConstructor(node).construct2ndStep(node, object);
} catch (Exception e) {
throw new ConstructorException(null, null,
"Can't construct a second step for a java object for " + node.getTag() + "; exception="
+ e.getMessage(),
node.getStartMark(), e);
}
}
}
/**
* Construct scalar instance when the runtime class is known. Recursive structures are not
* supported.
*/
protected class ConstructScalar extends AbstractConstruct {
public Object construct(Node nnode) {
ScalarNode node = (ScalarNode) nnode;
Class<?> type = node.getType();
// In case there is TypeDefinition for the 'type'
Object instance = newInstance(type, node, false);
if (instance != NOT_INSTANTIATED_OBJECT) {
return instance;
}
Object result;
if (type.isPrimitive() || type == String.class || Number.class.isAssignableFrom(type)
|| type == Boolean.class || Date.class.isAssignableFrom(type) || type == Character.class
|| type == BigInteger.class || type == BigDecimal.class
|| Enum.class.isAssignableFrom(type) || Tag.BINARY.equals(node.getTag())
|| Calendar.class.isAssignableFrom(type) || type == UUID.class) {
// standard classes created directly
result = constructStandardJavaInstance(type, node);
} else {
// there must be only 1 constructor with 1 argument
java.lang.reflect.Constructor<?>[] javaConstructors = type.getDeclaredConstructors();
int oneArgCount = 0;
java.lang.reflect.Constructor<?> javaConstructor = null;
for (java.lang.reflect.Constructor<?> c : javaConstructors) {
if (c.getParameterTypes().length == 1) {
oneArgCount++;
javaConstructor = c;
}
}
Object argument;
if (javaConstructor == null) {
throw new YAMLException("No single argument constructor found for " + type);
} else if (oneArgCount == 1) {
argument = constructStandardJavaInstance(javaConstructor.getParameterTypes()[0], node);
} else {
// TODO it should be possible to use implicit types instead
// of forcing String. Resolver must be available here to
// obtain the implicit tag. Then we can set the tag and call
// callConstructor(node) to create the argument instance.
// On the other hand it may be safer to require a custom
// constructor to avoid guessing the argument class
argument = constructScalar(node);
try {
javaConstructor = type.getDeclaredConstructor(String.class);
} catch (Exception e) {
throw new YAMLException("Can't construct a java object for scalar " + node.getTag()
+ "; No String constructor found. Exception=" + e.getMessage(), e);
}
}
try {
javaConstructor.setAccessible(true);
result = javaConstructor.newInstance(argument);
} catch (Exception e) {
throw new ConstructorException(null, null, "Can't construct a java object for scalar "
+ node.getTag() + "; exception=" + e.getMessage(), node.getStartMark(), e);
}
}
return result;
}
@SuppressWarnings("unchecked")
private Object constructStandardJavaInstance(@SuppressWarnings("rawtypes") Class type,
ScalarNode node) {
Object result;
if (type == String.class) {
Construct stringConstructor = yamlConstructors.get(Tag.STR);
result = stringConstructor.construct(node);
} else if (type == Boolean.class || type == Boolean.TYPE) {
Construct boolConstructor = yamlConstructors.get(Tag.BOOL);
result = boolConstructor.construct(node);
} else if (type == Character.class || type == Character.TYPE) {
Construct charConstructor = yamlConstructors.get(Tag.STR);
String ch = (String) charConstructor.construct(node);
if (ch.isEmpty()) {
result = null;
} else if (ch.length() != 1) {
throw new YAMLException("Invalid node Character: '" + ch + "'; length: " + ch.length());
} else {
result = Character.valueOf(ch.charAt(0));
}
} else if (Date.class.isAssignableFrom(type)) {
Construct dateConstructor = yamlConstructors.get(Tag.TIMESTAMP);
Date date = (Date) dateConstructor.construct(node);
if (type == Date.class) {
result = date;
} else {
try {
java.lang.reflect.Constructor<?> constr = type.getConstructor(long.class);
result = constr.newInstance(date.getTime());
} catch (RuntimeException e) {
throw e;
} catch (Exception e) {
throw new YAMLException("Cannot construct: '" + type + "'");
}
}
} else if (type == Float.class || type == Double.class || type == Float.TYPE
|| type == Double.TYPE || type == BigDecimal.class) {
if (type == BigDecimal.class) {
result = new BigDecimal(node.getValue());
} else {
Construct doubleConstructor = yamlConstructors.get(Tag.FLOAT);
result = doubleConstructor.construct(node);
if (type == Float.class || type == Float.TYPE) {
result = Float.valueOf(((Double) result).floatValue());
}
}
} else if (type == Byte.class || type == Short.class || type == Integer.class
|| type == Long.class || type == BigInteger.class || type == Byte.TYPE
|| type == Short.TYPE || type == Integer.TYPE || type == Long.TYPE) {
Construct intConstructor = yamlConstructors.get(Tag.INT);
result = intConstructor.construct(node);
if (type == Byte.class || type == Byte.TYPE) {
result = Integer.valueOf(result.toString()).byteValue();
} else if (type == Short.class || type == Short.TYPE) {
result = Integer.valueOf(result.toString()).shortValue();
} else if (type == Integer.class || type == Integer.TYPE) {
result = Integer.parseInt(result.toString());
} else if (type == Long.class || type == Long.TYPE) {
result = Long.valueOf(result.toString());
} else {
// only BigInteger left
result = new BigInteger(result.toString());
}
} else if (Enum.class.isAssignableFrom(type)) {
String enumValueName = node.getValue();
try {
if (loadingConfig.isEnumCaseSensitive()) {
result = Enum.valueOf(type, enumValueName);
} else {
result = EnumUtils.findEnumInsensitiveCase(type, enumValueName);
}
} catch (Exception ex) {
throw new YAMLException("Unable to find enum value '" + enumValueName
+ "' for enum class: " + type.getName());
}
} else if (Calendar.class.isAssignableFrom(type)) {
ConstructYamlTimestamp contr = new ConstructYamlTimestamp();
contr.construct(node);
result = contr.getCalendar();
} else if (Number.class.isAssignableFrom(type)) {
// since we do not know the exact type we create Float
ConstructYamlFloat contr = new ConstructYamlFloat();
result = contr.construct(node);
} else if (UUID.class == type) {
result = UUID.fromString(node.getValue());
} else {
if (yamlConstructors.containsKey(node.getTag())) {
result = yamlConstructors.get(node.getTag()).construct(node);
} else {
throw new YAMLException("Unsupported class: " + type);
}
}
return result;
}
}
/**
* Construct sequence (List, Array, or immutable object) when the runtime class is known.
*/
protected class ConstructSequence implements Construct {
@SuppressWarnings("unchecked")
public Object construct(Node node) {
SequenceNode snode = (SequenceNode) node;
if (Set.class.isAssignableFrom(node.getType())) {
if (node.isTwoStepsConstruction()) {
throw new YAMLException("Set cannot be recursive.");
} else {
return constructSet(snode);
}
} else if (Collection.class.isAssignableFrom(node.getType())) {
if (node.isTwoStepsConstruction()) {
return newList(snode);
} else {
return constructSequence(snode);
}
} else if (node.getType().isArray()) {
if (node.isTwoStepsConstruction()) {
return createArray(node.getType(), snode.getValue().size());
} else {
return constructArray(snode);
}
} else {
// create immutable object
List<java.lang.reflect.Constructor<?>> possibleConstructors =
new ArrayList<java.lang.reflect.Constructor<?>>(snode.getValue().size());
for (java.lang.reflect.Constructor<?> constructor : node.getType()
.getDeclaredConstructors()) {
if (snode.getValue().size() == constructor.getParameterTypes().length) {
possibleConstructors.add(constructor);
}
}
if (!possibleConstructors.isEmpty()) {
if (possibleConstructors.size() == 1) {
Object[] argumentList = new Object[snode.getValue().size()];
java.lang.reflect.Constructor<?> c = possibleConstructors.get(0);
int index = 0;
for (Node argumentNode : snode.getValue()) {
Class<?> type = c.getParameterTypes()[index];
// set runtime classes for arguments
argumentNode.setType(type);
argumentList[index++] = constructObject(argumentNode);
}
try {
c.setAccessible(true);
return c.newInstance(argumentList);
} catch (Exception e) {
throw new YAMLException(e);
}
}
// use BaseConstructor
List<Object> argumentList = (List<Object>) constructSequence(snode);
Class<?>[] parameterTypes = new Class[argumentList.size()];
int index = 0;
for (Object parameter : argumentList) {
parameterTypes[index] = parameter.getClass();
index++;
}
for (java.lang.reflect.Constructor<?> c : possibleConstructors) {
Class<?>[] argTypes = c.getParameterTypes();
boolean foundConstructor = true;
for (int i = 0; i < argTypes.length; i++) {
if (!wrapIfPrimitive(argTypes[i]).isAssignableFrom(parameterTypes[i])) {
foundConstructor = false;
break;
}
}
if (foundConstructor) {
try {
c.setAccessible(true);
return c.newInstance(argumentList.toArray());
} catch (Exception e) {
throw new YAMLException(e);
}
}
}
}
throw new YAMLException("No suitable constructor with " + snode.getValue().size()
+ " arguments found for " + node.getType());
}
}
private Class<? extends Object> wrapIfPrimitive(Class<?> clazz) {
if (!clazz.isPrimitive()) {
return clazz;
}
if (clazz == Integer.TYPE) {
return Integer.class;
}
if (clazz == Float.TYPE) {
return Float.class;
}
if (clazz == Double.TYPE) {
return Double.class;
}
if (clazz == Boolean.TYPE) {
return Boolean.class;
}
if (clazz == Long.TYPE) {
return Long.class;
}
if (clazz == Character.TYPE) {
return Character.class;
}
if (clazz == Short.TYPE) {
return Short.class;
}
if (clazz == Byte.TYPE) {
return Byte.class;
}
throw new YAMLException("Unexpected primitive " + clazz);
}
@SuppressWarnings("unchecked")
public void construct2ndStep(Node node, Object object) {
SequenceNode snode = (SequenceNode) node;
if (List.class.isAssignableFrom(node.getType())) {
List<Object> list = (List<Object>) object;
constructSequenceStep2(snode, list);
} else if (node.getType().isArray()) {
constructArrayStep2(snode, object);
} else {
throw new YAMLException("Immutable objects cannot be recursive.");
}
}
}
protected Class<?> getClassForNode(Node node) {
Class<? extends Object> classForTag = typeTags.get(node.getTag());
if (classForTag == null) {
String name = node.getTag().getClassName();
Class<?> cl;
try {
cl = getClassForName(name);
} catch (ClassNotFoundException e) {
throw new YAMLException("Class not found: " + name);
}
typeTags.put(node.getTag(), cl);
return cl;
} else {
return classForTag;
}
}
protected Class<?> getClassForName(String name) throws ClassNotFoundException {
try {
return Class.forName(name, true, Thread.currentThread().getContextClassLoader());
} catch (ClassNotFoundException e) {
return Class.forName(name);
}
}
}
SafeConstructor
/**
* Copyright (c) 2008, SnakeYAML
*
* 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.constructor;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TimeZone;
import java.util.TreeSet;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.yaml.snakeyaml.LoaderOptions;
import org.yaml.snakeyaml.error.YAMLException;
import org.yaml.snakeyaml.external.biz.base64Coder.Base64Coder;
import org.yaml.snakeyaml.nodes.MappingNode;
import org.yaml.snakeyaml.nodes.Node;
import org.yaml.snakeyaml.nodes.NodeId;
import org.yaml.snakeyaml.nodes.NodeTuple;
import org.yaml.snakeyaml.nodes.ScalarNode;
import org.yaml.snakeyaml.nodes.SequenceNode;
import org.yaml.snakeyaml.nodes.Tag;
/**
* Construct standard Java classes
*/
public class SafeConstructor extends BaseConstructor {
public static final ConstructUndefined undefinedConstructor = new ConstructUndefined();
public SafeConstructor() {
this(new LoaderOptions());
}
/**
* Create an instance
*
* @param loaderOptions - the configuration options
*/
public SafeConstructor(LoaderOptions loaderOptions) {
super(loaderOptions);
this.yamlConstructors.put(Tag.NULL, new ConstructYamlNull());
this.yamlConstructors.put(Tag.BOOL, new ConstructYamlBool());
this.yamlConstructors.put(Tag.INT, new ConstructYamlInt());
this.yamlConstructors.put(Tag.FLOAT, new ConstructYamlFloat());
this.yamlConstructors.put(Tag.BINARY, new ConstructYamlBinary());
this.yamlConstructors.put(Tag.TIMESTAMP, new ConstructYamlTimestamp());
this.yamlConstructors.put(Tag.OMAP, new ConstructYamlOmap());
this.yamlConstructors.put(Tag.PAIRS, new ConstructYamlPairs());
this.yamlConstructors.put(Tag.SET, new ConstructYamlSet());
this.yamlConstructors.put(Tag.STR, new ConstructYamlStr());
this.yamlConstructors.put(Tag.SEQ, new ConstructYamlSeq());
this.yamlConstructors.put(Tag.MAP, new ConstructYamlMap());
this.yamlConstructors.put(null, undefinedConstructor);
this.yamlClassConstructors.put(NodeId.scalar, undefinedConstructor);
this.yamlClassConstructors.put(NodeId.sequence, undefinedConstructor);
this.yamlClassConstructors.put(NodeId.mapping, undefinedConstructor);
}
protected void flattenMapping(MappingNode node) {
flattenMapping(node, false);
}
protected void flattenMapping(MappingNode node, boolean forceStringKeys) {
// perform merging only on nodes containing merge node(s)
processDuplicateKeys(node, forceStringKeys);
if (node.isMerged()) {
node.setValue(mergeNode(node, true, new HashMap<Object, Integer>(),
new ArrayList<NodeTuple>(), forceStringKeys));
}
}
protected void processDuplicateKeys(MappingNode node) {
processDuplicateKeys(node, false);
}
protected void processDuplicateKeys(MappingNode node, boolean forceStringKeys) {
List<NodeTuple> nodeValue = node.getValue();
Map<Object, Integer> keys = new HashMap<Object, Integer>(nodeValue.size());
TreeSet<Integer> toRemove = new TreeSet<Integer>();
int i = 0;
for (NodeTuple tuple : nodeValue) {
Node keyNode = tuple.getKeyNode();
if (!keyNode.getTag().equals(Tag.MERGE)) {
if (forceStringKeys) {
if (keyNode instanceof ScalarNode) {
keyNode.setType(String.class);
keyNode.setTag(Tag.STR);
} else {
throw new YAMLException("Keys must be scalars but found: " + keyNode);
}
}
Object key = constructObject(keyNode);
if (key != null && !forceStringKeys) {
if (keyNode.isTwoStepsConstruction()) {
if (!loadingConfig.getAllowRecursiveKeys()) {
throw new YAMLException(
"Recursive key for mapping is detected but it is not configured to be allowed.");
} else {
try {
key.hashCode();// check circular dependencies
} catch (Exception e) {
throw new ConstructorException("while constructing a mapping", node.getStartMark(),
"found unacceptable key " + key, tuple.getKeyNode().getStartMark(), e);
}
}
}
}
Integer prevIndex = keys.put(key, i);
if (prevIndex != null) {
if (!isAllowDuplicateKeys()) {
throw new DuplicateKeyException(node.getStartMark(), key,
tuple.getKeyNode().getStartMark());
}
toRemove.add(prevIndex);
}
}
i = i + 1;
}
Iterator<Integer> indices2remove = toRemove.descendingIterator();
while (indices2remove.hasNext()) {
nodeValue.remove(indices2remove.next().intValue());
}
}
/**
* Does merge for supplied mapping node.
*
* @param node where to merge
* @param isPreffered true if keys of node should take precedence over others...
* @param key2index maps already merged keys to index from values
* @param values collects merged NodeTuple
* @return list of the merged NodeTuple (to be set as value for the MappingNode)
*/
private List<NodeTuple> mergeNode(MappingNode node, boolean isPreffered,
Map<Object, Integer> key2index, List<NodeTuple> values, boolean forceStringKeys) {
Iterator<NodeTuple> iter = node.getValue().iterator();
while (iter.hasNext()) {
final NodeTuple nodeTuple = iter.next();
final Node keyNode = nodeTuple.getKeyNode();
final Node valueNode = nodeTuple.getValueNode();
if (keyNode.getTag().equals(Tag.MERGE)) {
iter.remove();
switch (valueNode.getNodeId()) {
case mapping:
MappingNode mn = (MappingNode) valueNode;
mergeNode(mn, false, key2index, values, forceStringKeys);
break;
case sequence:
SequenceNode sn = (SequenceNode) valueNode;
List<Node> vals = sn.getValue();
for (Node subnode : vals) {
if (!(subnode instanceof MappingNode)) {
throw new ConstructorException("while constructing a mapping", node.getStartMark(),
"expected a mapping for merging, but found " + subnode.getNodeId(),
subnode.getStartMark());
}
MappingNode mnode = (MappingNode) subnode;
mergeNode(mnode, false, key2index, values, forceStringKeys);
}
break;
default:
throw new ConstructorException("while constructing a mapping", node.getStartMark(),
"expected a mapping or list of mappings for merging, but found "
+ valueNode.getNodeId(),
valueNode.getStartMark());
}
} else {
// we need to construct keys to avoid duplications
if (forceStringKeys) {
if (keyNode instanceof ScalarNode) {
keyNode.setType(String.class);
keyNode.setTag(Tag.STR);
} else {
throw new YAMLException("Keys must be scalars but found: " + keyNode);
}
}
Object key = constructObject(keyNode);
if (!key2index.containsKey(key)) { // 1st time merging key
values.add(nodeTuple);
// keep track where tuple for the key is
key2index.put(key, values.size() - 1);
} else if (isPreffered) { // there is value for the key, but we
// need to override it
// change value for the key using saved position
values.set(key2index.get(key), nodeTuple);
}
}
}
return values;
}
@Override
protected void constructMapping2ndStep(MappingNode node, Map<Object, Object> mapping) {
flattenMapping(node);
super.constructMapping2ndStep(node, mapping);
}
@Override
protected void constructSet2ndStep(MappingNode node, Set<Object> set) {
flattenMapping(node);
super.constructSet2ndStep(node, set);
}
public class ConstructYamlNull extends AbstractConstruct {
@Override
public Object construct(Node node) {
if (node != null) {
constructScalar((ScalarNode) node);
}
return null;
}
}
private final static Map<String, Boolean> BOOL_VALUES = new HashMap<String, Boolean>();
static {
BOOL_VALUES.put("yes", Boolean.TRUE);
BOOL_VALUES.put("no", Boolean.FALSE);
BOOL_VALUES.put("true", Boolean.TRUE);
BOOL_VALUES.put("false", Boolean.FALSE);
BOOL_VALUES.put("on", Boolean.TRUE);
BOOL_VALUES.put("off", Boolean.FALSE);
}
public class ConstructYamlBool extends AbstractConstruct {
@Override
public Object construct(Node node) {
String val = constructScalar((ScalarNode) node);
return BOOL_VALUES.get(val.toLowerCase());
}
}
public class ConstructYamlInt extends AbstractConstruct {
@Override
public Object construct(Node node) {
String value = constructScalar((ScalarNode) node).replaceAll("_", "");
if (value.isEmpty()) {
throw new ConstructorException("while constructing an int", node.getStartMark(),
"found empty value", node.getStartMark());
}
int sign = +1;
char first = value.charAt(0);
if (first == '-') {
sign = -1;
value = value.substring(1);
} else if (first == '+') {
value = value.substring(1);
}
int base = 10;
if ("0".equals(value)) {
return Integer.valueOf(0);
} else if (value.startsWith("0b")) {
value = value.substring(2);
base = 2;
} else if (value.startsWith("0x")) {
value = value.substring(2);
base = 16;
} else if (value.startsWith("0")) {
value = value.substring(1);
base = 8;
} else if (value.indexOf(':') != -1) {
String[] digits = value.split(":");
int bes = 1;
int val = 0;
for (int i = 0, j = digits.length; i < j; i++) {
val += Long.parseLong(digits[j - i - 1]) * bes;
bes *= 60;
}
return createNumber(sign, String.valueOf(val), 10);
} else {
return createNumber(sign, value, 10);
}
return createNumber(sign, value, base);
}
}
private static final int[][] RADIX_MAX = new int[17][2];
static {
int[] radixList = new int[] {2, 8, 10, 16};
for (int radix : radixList) {
RADIX_MAX[radix] =
new int[] {maxLen(Integer.MAX_VALUE, radix), maxLen(Long.MAX_VALUE, radix)};
}
}
private static int maxLen(final int max, final int radix) {
return Integer.toString(max, radix).length();
}
private static int maxLen(final long max, final int radix) {
return Long.toString(max, radix).length();
}
private Number createNumber(int sign, String number, int radix) {
final int len = number != null ? number.length() : 0;
if (sign < 0) {
number = "-" + number;
}
final int[] maxArr = radix < RADIX_MAX.length ? RADIX_MAX[radix] : null;
if (maxArr != null) {
final boolean gtInt = len > maxArr[0];
if (gtInt) {
if (len > maxArr[1]) {
return new BigInteger(number, radix);
}
return createLongOrBigInteger(number, radix);
}
}
Number result;
try {
result = Integer.valueOf(number, radix);
} catch (NumberFormatException e) {
result = createLongOrBigInteger(number, radix);
}
return result;
}
protected static Number createLongOrBigInteger(final String number, final int radix) {
try {
return Long.valueOf(number, radix);
} catch (NumberFormatException e1) {
return new BigInteger(number, radix);
}
}
public class ConstructYamlFloat extends AbstractConstruct {
@Override
public Object construct(Node node) {
String value = constructScalar((ScalarNode) node).replaceAll("_", "");
if (value.isEmpty()) {
throw new ConstructorException("while constructing a float", node.getStartMark(),
"found empty value", node.getStartMark());
}
int sign = +1;
char first = value.charAt(0);
if (first == '-') {
sign = -1;
value = value.substring(1);
} else if (first == '+') {
value = value.substring(1);
}
String valLower = value.toLowerCase();
if (".inf".equals(valLower)) {
return Double.valueOf(sign == -1 ? Double.NEGATIVE_INFINITY : Double.POSITIVE_INFINITY);
} else if (".nan".equals(valLower)) {
return Double.valueOf(Double.NaN);
} else if (value.indexOf(':') != -1) {
String[] digits = value.split(":");
int bes = 1;
double val = 0.0;
for (int i = 0, j = digits.length; i < j; i++) {
val += Double.parseDouble(digits[j - i - 1]) * bes;
bes *= 60;
}
return Double.valueOf(sign * val);
} else {
Double d = Double.valueOf(value);
return Double.valueOf(d.doubleValue() * sign);
}
}
}
public class ConstructYamlBinary extends AbstractConstruct {
@Override
public Object construct(Node node) {
// Ignore white spaces for base64 encoded scalar
String noWhiteSpaces = constructScalar((ScalarNode) node).replaceAll("\\s", "");
byte[] decoded = Base64Coder.decode(noWhiteSpaces.toCharArray());
return decoded;
}
}
private final static Pattern TIMESTAMP_REGEXP = Pattern.compile(
"^([0-9][0-9][0-9][0-9])-([0-9][0-9]?)-([0-9][0-9]?)(?:(?:[Tt]|[ \t]+)([0-9][0-9]?):([0-9][0-9]):([0-9][0-9])(?:\\.([0-9]*))?(?:[ \t]*(?:Z|([-+][0-9][0-9]?)(?::([0-9][0-9])?)?))?)?$");
private final static Pattern YMD_REGEXP =
Pattern.compile("^([0-9][0-9][0-9][0-9])-([0-9][0-9]?)-([0-9][0-9]?)$");
public static class ConstructYamlTimestamp extends AbstractConstruct {
private Calendar calendar;
public Calendar getCalendar() {
return calendar;
}
@Override
public Object construct(Node node) {
ScalarNode scalar = (ScalarNode) node;
String nodeValue = scalar.getValue();
Matcher match = YMD_REGEXP.matcher(nodeValue);
if (match.matches()) {
String year_s = match.group(1);
String month_s = match.group(2);
String day_s = match.group(3);
calendar = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
calendar.clear();
calendar.set(Calendar.YEAR, Integer.parseInt(year_s));
// Java's months are zero-based...
calendar.set(Calendar.MONTH, Integer.parseInt(month_s) - 1); // x
calendar.set(Calendar.DAY_OF_MONTH, Integer.parseInt(day_s));
return calendar.getTime();
} else {
match = TIMESTAMP_REGEXP.matcher(nodeValue);
if (!match.matches()) {
throw new YAMLException("Unexpected timestamp: " + nodeValue);
}
String year_s = match.group(1);
String month_s = match.group(2);
String day_s = match.group(3);
String hour_s = match.group(4);
String min_s = match.group(5);
// seconds and milliseconds
String seconds = match.group(6);
String millis = match.group(7);
if (millis != null) {
seconds = seconds + "." + millis;
}
double fractions = Double.parseDouble(seconds);
int sec_s = (int) Math.round(Math.floor(fractions));
int usec = (int) Math.round((fractions - sec_s) * 1000);
// timezone
String timezoneh_s = match.group(8);
String timezonem_s = match.group(9);
TimeZone timeZone;
if (timezoneh_s != null) {
String time = timezonem_s != null ? ":" + timezonem_s : "00";
timeZone = TimeZone.getTimeZone("GMT" + timezoneh_s + time);
} else {
// no time zone provided
timeZone = TimeZone.getTimeZone("UTC");
}
calendar = Calendar.getInstance(timeZone);
calendar.set(Calendar.YEAR, Integer.parseInt(year_s));
// Java's months are zero-based...
calendar.set(Calendar.MONTH, Integer.parseInt(month_s) - 1);
calendar.set(Calendar.DAY_OF_MONTH, Integer.parseInt(day_s));
calendar.set(Calendar.HOUR_OF_DAY, Integer.parseInt(hour_s));
calendar.set(Calendar.MINUTE, Integer.parseInt(min_s));
calendar.set(Calendar.SECOND, sec_s);
calendar.set(Calendar.MILLISECOND, usec);
return calendar.getTime();
}
}
}
public class ConstructYamlOmap extends AbstractConstruct {
@Override
public Object construct(Node node) {
// Note: we do not check for duplicate keys, because it's too
// CPU-expensive.
Map<Object, Object> omap = new LinkedHashMap<Object, Object>();
if (!(node instanceof SequenceNode)) {
throw new ConstructorException("while constructing an ordered map", node.getStartMark(),
"expected a sequence, but found " + node.getNodeId(), node.getStartMark());
}
SequenceNode snode = (SequenceNode) node;
for (Node subnode : snode.getValue()) {
if (!(subnode instanceof MappingNode)) {
throw new ConstructorException("while constructing an ordered map", node.getStartMark(),
"expected a mapping of length 1, but found " + subnode.getNodeId(),
subnode.getStartMark());
}
MappingNode mnode = (MappingNode) subnode;
if (mnode.getValue().size() != 1) {
throw new ConstructorException("while constructing an ordered map", node.getStartMark(),
"expected a single mapping item, but found " + mnode.getValue().size() + " items",
mnode.getStartMark());
}
Node keyNode = mnode.getValue().get(0).getKeyNode();
Node valueNode = mnode.getValue().get(0).getValueNode();
Object key = constructObject(keyNode);
Object value = constructObject(valueNode);
omap.put(key, value);
}
return omap;
}
}
public class ConstructYamlPairs extends AbstractConstruct {
@Override
public Object construct(Node node) {
// Note: we do not check for duplicate keys, because it's too
// CPU-expensive.
if (!(node instanceof SequenceNode)) {
throw new ConstructorException("while constructing pairs", node.getStartMark(),
"expected a sequence, but found " + node.getNodeId(), node.getStartMark());
}
SequenceNode snode = (SequenceNode) node;
List<Object[]> pairs = new ArrayList<Object[]>(snode.getValue().size());
for (Node subnode : snode.getValue()) {
if (!(subnode instanceof MappingNode)) {
throw new ConstructorException("while constructingpairs", node.getStartMark(),
"expected a mapping of length 1, but found " + subnode.getNodeId(),
subnode.getStartMark());
}
MappingNode mnode = (MappingNode) subnode;
if (mnode.getValue().size() != 1) {
throw new ConstructorException("while constructing pairs", node.getStartMark(),
"expected a single mapping item, but found " + mnode.getValue().size() + " items",
mnode.getStartMark());
}
Node keyNode = mnode.getValue().get(0).getKeyNode();
Node valueNode = mnode.getValue().get(0).getValueNode();
Object key = constructObject(keyNode);
Object value = constructObject(valueNode);
pairs.add(new Object[] {key, value});
}
return pairs;
}
}
public class ConstructYamlSet implements Construct {
@Override
public Object construct(Node node) {
if (node.isTwoStepsConstruction()) {
return (constructedObjects.containsKey(node) ? constructedObjects.get(node)
: createDefaultSet(((MappingNode) node).getValue().size()));
} else {
return constructSet((MappingNode) node);
}
}
@Override
@SuppressWarnings("unchecked")
public void construct2ndStep(Node node, Object object) {
if (node.isTwoStepsConstruction()) {
constructSet2ndStep((MappingNode) node, (Set<Object>) object);
} else {
throw new YAMLException("Unexpected recursive set structure. Node: " + node);
}
}
}
public class ConstructYamlStr extends AbstractConstruct {
@Override
public Object construct(Node node) {
return constructScalar((ScalarNode) node);
}
}
public class ConstructYamlSeq implements Construct {
@Override
public Object construct(Node node) {
SequenceNode seqNode = (SequenceNode) node;
if (node.isTwoStepsConstruction()) {
return newList(seqNode);
} else {
return constructSequence(seqNode);
}
}
@Override
@SuppressWarnings("unchecked")
public void construct2ndStep(Node node, Object data) {
if (node.isTwoStepsConstruction()) {
constructSequenceStep2((SequenceNode) node, (List<Object>) data);
} else {
throw new YAMLException("Unexpected recursive sequence structure. Node: " + node);
}
}
}
public class ConstructYamlMap implements Construct {
@Override
public Object construct(Node node) {
MappingNode mnode = (MappingNode) node;
if (node.isTwoStepsConstruction()) {
return createDefaultMap(mnode.getValue().size());
} else {
return constructMapping(mnode);
}
}
@Override
@SuppressWarnings("unchecked")
public void construct2ndStep(Node node, Object object) {
if (node.isTwoStepsConstruction()) {
constructMapping2ndStep((MappingNode) node, (Map<Object, Object>) object);
} else {
throw new YAMLException("Unexpected recursive mapping structure. Node: " + node);
}
}
}
public static final class ConstructUndefined extends AbstractConstruct {
@Override
public Object construct(Node node) {
throw new ConstructorException(null, null,
"could not determine a constructor for the tag " + node.getTag(), node.getStartMark());
}
}
}
Representer
/**
* Copyright (c) 2008, SnakeYAML
*
* 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.representer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.yaml.snakeyaml.DumperOptions;
import org.yaml.snakeyaml.DumperOptions.FlowStyle;
import org.yaml.snakeyaml.TypeDescription;
import org.yaml.snakeyaml.introspector.Property;
import org.yaml.snakeyaml.introspector.PropertyUtils;
import org.yaml.snakeyaml.nodes.MappingNode;
import org.yaml.snakeyaml.nodes.Node;
import org.yaml.snakeyaml.nodes.NodeId;
import org.yaml.snakeyaml.nodes.NodeTuple;
import org.yaml.snakeyaml.nodes.ScalarNode;
import org.yaml.snakeyaml.nodes.SequenceNode;
import org.yaml.snakeyaml.nodes.Tag;
/**
* Represent JavaBeans
*/
public class Representer extends SafeRepresenter {
public Representer(){
super(new DumperOptions());
this.representers.put(null,new RepresentJavaBean());
}
protected Map<Class<? extends Object>, TypeDescription> typeDefinitions = Collections.emptyMap();
public Representer(DumperOptions options) {
super(options);
this.representers.put(null, new RepresentJavaBean());
}
public TypeDescription addTypeDescription(TypeDescription td) {
if (Collections.EMPTY_MAP == typeDefinitions) {
typeDefinitions = new HashMap<Class<? extends Object>, TypeDescription>();
}
if (td.getTag() != null) {
addClassTag(td.getType(), td.getTag());
}
td.setPropertyUtils(getPropertyUtils());
return typeDefinitions.put(td.getType(), td);
}
@Override
public void setPropertyUtils(PropertyUtils propertyUtils) {
super.setPropertyUtils(propertyUtils);
Collection<TypeDescription> tds = typeDefinitions.values();
for (TypeDescription typeDescription : tds) {
typeDescription.setPropertyUtils(propertyUtils);
}
}
protected class RepresentJavaBean implements Represent {
public Node representData(Object data) {
return representJavaBean(getProperties(data.getClass()), data);
}
}
/**
* Tag logic: - explicit root tag is set in serializer - if there is a predefined class tag it is
* used - a global tag with class name is always used as tag. The JavaBean parent of the specified
* JavaBean may set another tag (tag:yaml.org,2002:map) when the property class is the same as
* runtime class
*
* @param properties JavaBean getters
* @param javaBean instance for Node
* @return Node to get serialized
*/
protected MappingNode representJavaBean(Set<Property> properties, Object javaBean) {
List<NodeTuple> value = new ArrayList<NodeTuple>(properties.size());
Tag tag;
Tag customTag = classTags.get(javaBean.getClass());
tag = customTag != null ? customTag : new Tag(javaBean.getClass());
// flow style will be chosen by BaseRepresenter
MappingNode node = new MappingNode(tag, value, FlowStyle.AUTO);
representedObjects.put(javaBean, node);
FlowStyle bestStyle = FlowStyle.FLOW;
for (Property property : properties) {
Object memberValue = property.get(javaBean);
Tag customPropertyTag = memberValue == null ? null : classTags.get(memberValue.getClass());
NodeTuple tuple =
representJavaBeanProperty(javaBean, property, memberValue, customPropertyTag);
if (tuple == null) {
continue;
}
if (!((ScalarNode) tuple.getKeyNode()).isPlain()) {
bestStyle = FlowStyle.BLOCK;
}
Node nodeValue = tuple.getValueNode();
if (!(nodeValue instanceof ScalarNode && ((ScalarNode) nodeValue).isPlain())) {
bestStyle = FlowStyle.BLOCK;
}
value.add(tuple);
}
if (defaultFlowStyle != FlowStyle.AUTO) {
node.setFlowStyle(defaultFlowStyle);
} else {
node.setFlowStyle(bestStyle);
}
return node;
}
/**
* Represent one JavaBean property.
*
* @param javaBean - the instance to be represented
* @param property - the property of the instance
* @param propertyValue - value to be represented
* @param customTag - user defined Tag
* @return NodeTuple to be used in a MappingNode. Return null to skip the property
*/
protected NodeTuple representJavaBeanProperty(Object javaBean, Property property,
Object propertyValue, Tag customTag) {
ScalarNode nodeKey = (ScalarNode) representData(property.getName());
// the first occurrence of the node must keep the tag
boolean hasAlias = this.representedObjects.containsKey(propertyValue);
Node nodeValue = representData(propertyValue);
if (propertyValue != null && !hasAlias) {
NodeId nodeId = nodeValue.getNodeId();
if (customTag == null) {
if (nodeId == NodeId.scalar) {
// generic Enum requires the full tag
if (property.getType() != Enum.class) {
if (propertyValue instanceof Enum<?>) {
nodeValue.setTag(Tag.STR);
}
}
} else {
if (nodeId == NodeId.mapping) {
if (property.getType() == propertyValue.getClass()) {
if (!(propertyValue instanceof Map<?, ?>)) {
if (!nodeValue.getTag().equals(Tag.SET)) {
nodeValue.setTag(Tag.MAP);
}
}
}
}
checkGlobalTag(property, nodeValue, propertyValue);
}
}
}
return new NodeTuple(nodeKey, nodeValue);
}
/**
* Remove redundant global tag for a type safe (generic) collection if it is the same as defined
* by the JavaBean property
*
* @param property - JavaBean property
* @param node - representation of the property
* @param object - instance represented by the node
*/
@SuppressWarnings("unchecked")
protected void checkGlobalTag(Property property, Node node, Object object) {
// Skip primitive arrays.
if (object.getClass().isArray() && object.getClass().getComponentType().isPrimitive()) {
return;
}
Class<?>[] arguments = property.getActualTypeArguments();
if (arguments != null) {
if (node.getNodeId() == NodeId.sequence) {
// apply map tag where class is the same
Class<? extends Object> t = arguments[0];
SequenceNode snode = (SequenceNode) node;
Iterable<Object> memberList = Collections.emptyList();
if (object.getClass().isArray()) {
memberList = Arrays.asList((Object[]) object);
} else if (object instanceof Iterable<?>) {
// list
memberList = (Iterable<Object>) object;
}
Iterator<Object> iter = memberList.iterator();
if (iter.hasNext()) {
for (Node childNode : snode.getValue()) {
Object member = iter.next();
if (member != null) {
if (t.equals(member.getClass())) {
if (childNode.getNodeId() == NodeId.mapping) {
childNode.setTag(Tag.MAP);
}
}
}
}
}
} else if (object instanceof Set) {
Class<?> t = arguments[0];
MappingNode mnode = (MappingNode) node;
Iterator<NodeTuple> iter = mnode.getValue().iterator();
Set<?> set = (Set<?>) object;
for (Object member : set) {
NodeTuple tuple = iter.next();
Node keyNode = tuple.getKeyNode();
if (t.equals(member.getClass())) {
if (keyNode.getNodeId() == NodeId.mapping) {
keyNode.setTag(Tag.MAP);
}
}
}
} else if (object instanceof Map) { // NodeId.mapping ends-up here
Class<?> keyType = arguments[0];
Class<?> valueType = arguments[1];
MappingNode mnode = (MappingNode) node;
for (NodeTuple tuple : mnode.getValue()) {
resetTag(keyType, tuple.getKeyNode());
resetTag(valueType, tuple.getValueNode());
}
} else {
// the type for collection entries cannot be
// detected
}
}
}
private void resetTag(Class<? extends Object> type, Node node) {
Tag tag = node.getTag();
if (tag.matches(type)) {
if (Enum.class.isAssignableFrom(type)) {
node.setTag(Tag.STR);
} else {
node.setTag(Tag.MAP);
}
}
}
/**
* Get JavaBean properties to be serialised. The order is respected. This method may be overridden
* to provide custom property selection or order.
*
* @param type - JavaBean to inspect the properties
* @return properties to serialise
*/
protected Set<Property> getProperties(Class<? extends Object> type) {
if (typeDefinitions.containsKey(type)) {
return typeDefinitions.get(type).getProperties();
}
return getPropertyUtils().getProperties(type);
}
}