diff --git a/src/com/esotericsoftware/yamlbeans/Beans.java b/src/com/esotericsoftware/yamlbeans/Beans.java index 060a88d..6a06c18 100644 --- a/src/com/esotericsoftware/yamlbeans/Beans.java +++ b/src/com/esotericsoftware/yamlbeans/Beans.java @@ -111,6 +111,7 @@ static public Set getProperties (Class type, boolean beanProperties, b Set properties = new TreeSet(); for (Field field : getAllFields(type)) { String name = field.getName(); + String convertedName = config.propertyNameConverter.convertFieldToPropertyName(field); if (beanProperties) { DeferredConstruction deferredConstruction = getDeferredConstruction(type, config); @@ -128,7 +129,7 @@ static public Set getProperties (Class type, boolean beanProperties, b } catch (Exception ignored) { } if (getMethod != null && (setMethod != null || constructorProperty)) { - properties.add(new MethodProperty(name, setMethod, getMethod)); + properties.add(new MethodProperty(name, setMethod, getMethod, convertedName)); continue; } } @@ -139,7 +140,7 @@ static public Set getProperties (Class type, boolean beanProperties, b if (!privateFields) continue; field.setAccessible(true); } - properties.add(new FieldProperty(field)); + properties.add(new FieldProperty(field, convertedName)); } return properties; } @@ -149,13 +150,14 @@ static public Property getProperty (Class type, String name, boolean beanPropert if (name == null || name.length() == 0) throw new IllegalArgumentException("name cannot be null or empty."); Class[] noArgs = new Class[0], oneArg = new Class[1]; for (Field field : getAllFields(type)) { - if (!field.getName().equals(name)) continue; + String convertedName = config.propertyNameConverter.convertFieldToPropertyName(field); + if (!name.equals(convertedName)) continue; if (beanProperties) { DeferredConstruction deferredConstruction = getDeferredConstruction(type, config); - boolean constructorProperty = deferredConstruction != null && deferredConstruction.hasParameter(name); + boolean constructorProperty = deferredConstruction != null && deferredConstruction.hasParameter(field.getName()); - String nameUpper = Character.toUpperCase(name.charAt(0)) + name.substring(1); + String nameUpper = Character.toUpperCase(field.getName().charAt(0)) + field.getName().substring(1); Method getMethod = null, setMethod = null; try { oneArg[0] = field.getType(); @@ -167,7 +169,7 @@ static public Property getProperty (Class type, String name, boolean beanPropert } catch (Exception ignored) { } if (getMethod != null && (setMethod != null || constructorProperty)) - return new MethodProperty(name, setMethod, getMethod); + return new MethodProperty(field.getName(), setMethod, getMethod, convertedName); } int modifiers = field.getModifiers(); @@ -176,7 +178,7 @@ static public Property getProperty (Class type, String name, boolean beanPropert if (!privateFields) continue; field.setAccessible(true); } - return new FieldProperty(field); + return new FieldProperty(field, convertedName); } return null; } @@ -194,8 +196,8 @@ static private ArrayList getAllFields (Class type) { static public class MethodProperty extends Property { private final Method setMethod, getMethod; - public MethodProperty (String name, Method setMethod, Method getMethod) { - super(getMethod.getDeclaringClass(), name, getMethod.getReturnType()); + public MethodProperty (String name, Method setMethod, Method getMethod, String convertedName) { + super(getMethod.getDeclaringClass(), name, getMethod.getReturnType(), convertedName); this.setMethod = setMethod; this.getMethod = getMethod; } @@ -216,8 +218,8 @@ public Object get (Object object) throws Exception { static public class FieldProperty extends Property { private final Field field; - public FieldProperty (Field field) { - super(field.getDeclaringClass(), field.getName(), field.getType()); + public FieldProperty (Field field, String convertedName) { + super(field.getDeclaringClass(), field.getName(), field.getType(), convertedName); this.field = field; } @@ -238,11 +240,13 @@ static public abstract class Property implements Comparable { private final Class declaringClass; private final String name; private final Class type; + private final String convertedName; - Property (Class declaringClass, String name, Class type) { + Property (Class declaringClass, String name, Class type, String convertedName) { this.declaringClass = declaringClass; this.name = name; this.type = type; + this.convertedName = convertedName; } public int hashCode () { @@ -251,6 +255,7 @@ public int hashCode () { result = prime * result + ((declaringClass == null) ? 0 : declaringClass.hashCode()); result = prime * result + ((name == null) ? 0 : name.hashCode()); result = prime * result + ((type == null) ? 0 : type.hashCode()); + result = prime * result + ((convertedName == null) ? 0 : convertedName.hashCode()); return result; } @@ -268,6 +273,9 @@ public boolean equals (Object obj) { if (type == null) { if (other.type != null) return false; } else if (!type.equals(other.type)) return false; + if (convertedName == null) { + if (other.convertedName != null) return false; + } else if (!convertedName.equals(other.convertedName)) return false; return true; } @@ -283,6 +291,10 @@ public String getName () { return name; } + public String getConvertedName() { + return convertedName; + } + public String toString () { return name; } diff --git a/src/com/esotericsoftware/yamlbeans/PropertyNameConverter.java b/src/com/esotericsoftware/yamlbeans/PropertyNameConverter.java new file mode 100644 index 0000000..6e0dbe1 --- /dev/null +++ b/src/com/esotericsoftware/yamlbeans/PropertyNameConverter.java @@ -0,0 +1,19 @@ +package com.esotericsoftware.yamlbeans; + +import java.lang.reflect.Field; + +public interface PropertyNameConverter { + + PropertyNameConverter DEFAULT = new DefaultPropertyNameConverter(); + + /** + * Converts the Java field name into the YAML representation + */ + String convertFieldToPropertyName (Field field); + + class DefaultPropertyNameConverter implements PropertyNameConverter { + public String convertFieldToPropertyName (Field field) { + return field.getName(); + } + } +} diff --git a/src/com/esotericsoftware/yamlbeans/YamlConfig.java b/src/com/esotericsoftware/yamlbeans/YamlConfig.java index 1dbffcb..ea9e214 100644 --- a/src/com/esotericsoftware/yamlbeans/YamlConfig.java +++ b/src/com/esotericsoftware/yamlbeans/YamlConfig.java @@ -44,6 +44,7 @@ public class YamlConfig { final Map scalarSerializers = new IdentityHashMap(); final Map propertyToElementType = new HashMap(); final Map propertyToDefaultType = new HashMap(); + PropertyNameConverter propertyNameConverter = PropertyNameConverter.DEFAULT; boolean beanProperties = true; boolean privateFields; boolean privateConstructors = true; @@ -74,7 +75,9 @@ public void setScalarSerializer (Class type, ScalarSerializer serializer) { } /** Sets the default type of elements in a Collection or Map property. No tag will be output for elements of this type. This - * type will be used for each element if no tag is found. */ + * type will be used for each element if no tag is found. + * If a PropertyNameConverter is in use then propertyName should be in the converted format + * */ public void setPropertyElementType (Class type, String propertyName, Class elementType) { if (type == null) throw new IllegalArgumentException("type cannot be null."); if (propertyName == null) throw new IllegalArgumentException("propertyName cannot be null."); @@ -89,8 +92,10 @@ public void setPropertyElementType (Class type, String propertyName, Class eleme propertyToElementType.put(property, elementType); } - /** Sets the default type of a property. No tag will be output for values of this type. This type will be used if no tag is - * found. */ + /** Sets the default type of a property. No tag will be output for values of this type. + * This type will be used if no tag is found. + * If a PropertyNameConverter is in use then propertyName should be in the converted format + */ public void setPropertyDefaultType (Class type, String propertyName, Class defaultType) { if (type == null) throw new IllegalArgumentException("type cannot be null."); if (propertyName == null) throw new IllegalArgumentException("propertyName cannot be null."); @@ -117,6 +122,12 @@ public void setPrivateConstructors (boolean privateConstructors) { this.privateConstructors = privateConstructors; } + /** Sets the strategy to use when converting between YAML property and Java field names. + * Default is no conversion. (Yaml properties will be matched to Java fields with the same name)*/ + public void setPropertyNameConverter (PropertyNameConverter propertyNameConverter) { + this.propertyNameConverter = propertyNameConverter; + } + static public class WriteConfig { boolean explicitFirstDocument = false; boolean explicitEndDocument = false; diff --git a/src/com/esotericsoftware/yamlbeans/YamlWriter.java b/src/com/esotericsoftware/yamlbeans/YamlWriter.java index 96d6c05..cccd067 100644 --- a/src/com/esotericsoftware/yamlbeans/YamlWriter.java +++ b/src/com/esotericsoftware/yamlbeans/YamlWriter.java @@ -262,7 +262,7 @@ private void writeValue (Object object, Class fieldClass, Class elementType, Cla if (propertyValue == null && prototypeValue == null) continue; if (propertyValue != null && prototypeValue != null && prototypeValue.equals(propertyValue)) continue; } - emitter.emit(new ScalarEvent(null, null, new boolean[] {true, true}, property.getName(), (char)0)); + emitter.emit(new ScalarEvent(null, null, new boolean[] {true, true}, property.getConvertedName(), (char)0)); Class propertyElementType = config.propertyToElementType.get(property); Class propertyDefaultType = config.propertyToDefaultType.get(property); writeValue(propertyValue, property.getType(), propertyElementType, propertyDefaultType); diff --git a/test/com/esotericsoftware/yamlbeans/TestPropertyNameConverter.java b/test/com/esotericsoftware/yamlbeans/TestPropertyNameConverter.java new file mode 100644 index 0000000..77d4513 --- /dev/null +++ b/test/com/esotericsoftware/yamlbeans/TestPropertyNameConverter.java @@ -0,0 +1,12 @@ +package com.esotericsoftware.yamlbeans; + +import java.lang.reflect.Field; + +/** + * Converts properties into upper case, for unit testing + */ +public class TestPropertyNameConverter implements PropertyNameConverter { + public String convertFieldToPropertyName (Field field) { + return field.getName().toUpperCase(); + } +} diff --git a/test/com/esotericsoftware/yamlbeans/YamlReaderTest.java b/test/com/esotericsoftware/yamlbeans/YamlReaderTest.java index 6b94992..62e3765 100644 --- a/test/com/esotericsoftware/yamlbeans/YamlReaderTest.java +++ b/test/com/esotericsoftware/yamlbeans/YamlReaderTest.java @@ -16,6 +16,7 @@ package com.esotericsoftware.yamlbeans; +import java.util.HashMap; import java.util.List; import java.util.Map; @@ -166,6 +167,21 @@ private Test read (String yaml) throws Exception { return new YamlReader(yaml).read(Test.class); } + private Test readUpperCase (String yaml) throws Exception { + if (true) { + System.out.println(yaml); + System.out.println("==="); + YamlReader reader = new YamlReader(yaml); + reader.getConfig().setPropertyNameConverter(new TestPropertyNameConverter()); + System.out.println(reader.read(null)); + System.out.println(); + System.out.println(); + } + YamlReader reader = new YamlReader(yaml); + reader.getConfig().setPropertyNameConverter(new TestPropertyNameConverter()); + return reader.read(Test.class); + } + static public class Test { public String stringValue; public int intValue; @@ -215,6 +231,97 @@ public void testConstructorArgs () throws Exception { assertEquals(5, object.getZ()); } + public void testReadMap_WithPropertyNameConverter () throws Exception { + YamlConfig config = new YamlConfig(); + config.setPropertyNameConverter(new TestPropertyNameConverter()); + Map actual = (Map) new YamlReader("foo: bar\nbaz:\n buz: qux", config).read(); + + Map expected = new HashMap(); + expected.put("foo", "bar"); + + Map expectedBaz = new HashMap(); + expectedBaz.put("buz", "qux"); + expected.put("baz", expectedBaz); + + assertEquals(expected, actual); + } + + public void testSimpleFields_WithPropertyNameConverter () throws Exception { + Test test = readUpperCase( // + "STRINGVALUE: moo\ufec9moo\n" + // + "INTVALUE: !!int 123\n" + // + "FLOATVALUE: 0.3\n" + // + "DOUBLEVALUE: 0.0002\n" + // + "LONGVALUE: 999999\n" + // + "SHORTVALUE: 125\n" + // + "CHARVALUE: j\n" + // + "TESTENUM: b\n" + // + "BYTEVALUE: 14" // + ); + + assertEquals("moo\ufec9moo", test.stringValue); + assertEquals(123, test.intValue); + assertEquals(0.3f, test.floatValue); + assertEquals(0.0002d, test.doubleValue); + assertEquals(999999, test.longValue); + assertEquals(125, test.shortValue); + assertEquals('j', test.charValue); + assertEquals(14, test.byteValue); + assertEquals(TestEnum.b, test.testEnum); + + assertEquals(true, readUpperCase("BOOLEANVALUE: true").booleanValue); + assertEquals(false, readUpperCase("BOOLEANVALUE: 123").booleanValue); + assertEquals(false, readUpperCase("BOOLEANVALUE: 0").booleanValue); + assertEquals(false, readUpperCase("BOOLEANVALUE: false").booleanValue); + } + + public void testSequence_WithPropertyNameConverter () throws Exception { + Test test = readUpperCase("LISTVALUES: [moo, 2]"); + assertEquals(2, test.listValues.size()); + assertEquals("moo", test.listValues.get(0)); + assertEquals("2", test.listValues.get(1)); + + test = readUpperCase("ARRAYOBJECTS: [moo, 2]"); + assertEquals(2, test.arrayObjects.length); + assertEquals("moo", test.arrayObjects[0]); + assertEquals("2", test.arrayObjects[1]); + + test = readUpperCase("ARRAYSTRINGS: [moo, 2]"); + assertEquals(2, test.arrayStrings.length); + assertEquals("moo", test.arrayStrings[0]); + assertEquals("2", test.arrayStrings[1]); + + test = readUpperCase("ARRAYINTS: [34, 21]"); + assertEquals(2, test.arrayInts.length); + assertEquals(34, test.arrayInts[0]); + assertEquals(21, test.arrayInts[1]); + + test = readUpperCase("LISTVALUES:\n- moo\n- 2"); + assertEquals(2, test.listValues.size()); + assertEquals("moo", test.listValues.get(0)); + assertEquals("2", test.listValues.get(1)); + + test = readUpperCase("LISTVALUES:\n - moo\n - 2"); + assertEquals(2, test.listValues.size()); + assertEquals("moo", test.listValues.get(0)); + assertEquals("2", test.listValues.get(1)); + + test = readUpperCase("ARRAYOBJECTS:\n - moo\n - 2"); + assertEquals(2, test.arrayObjects.length); + assertEquals("moo", test.arrayObjects[0]); + assertEquals("2", test.arrayObjects[1]); + + test = readUpperCase("ARRAYSTRINGS:\n - moo\n - 2"); + assertEquals(2, test.arrayStrings.length); + assertEquals("moo", test.arrayStrings[0]); + assertEquals("2", test.arrayStrings[1]); + + test = readUpperCase("ARRAYINTS:\n - 34\n - 21"); + assertEquals(2, test.arrayInts.length); + assertEquals(34, test.arrayInts[0]); + assertEquals(21, test.arrayInts[1]); + } + static public class Moo1 { private List ints; public List strings; diff --git a/test/com/esotericsoftware/yamlbeans/YamlWriterTest.java b/test/com/esotericsoftware/yamlbeans/YamlWriterTest.java index fdabc9e..1057c16 100644 --- a/test/com/esotericsoftware/yamlbeans/YamlWriterTest.java +++ b/test/com/esotericsoftware/yamlbeans/YamlWriterTest.java @@ -21,12 +21,7 @@ import java.beans.ConstructorProperties; import java.io.File; import java.io.StringWriter; -import java.util.ArrayList; -import java.util.Date; -import java.util.HashMap; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; +import java.util.*; import junit.framework.TestCase; @@ -304,6 +299,155 @@ public String write (File file) throws YamlException { assertEquals(object.file, roundTrip.file); } + public void testSimpleTypes_String_WithWriteConverter () throws Exception { + String expected = "simple string\n"; + + String string = "simple string"; + assertEquals(string, roundTripUpperCase(string, expected)); + } + + public void testSimpleTypes_Map_WithWriteConverter () throws Exception { + String expected = "moo: cow\n"; + + Map map = new HashMap(); + map.put("moo", "cow"); + assertEquals(map, roundTripUpperCase(map, expected)); + } + + public void testSimpleTypes_List_WithWriteConverter () throws Exception { + String expected = "- moo\n" + + "- cow\n"; + + List list = new ArrayList(); + list.add("moo"); + list.add("cow"); + assertEquals(list, roundTripUpperCase(list, expected)); + } + + public void testSimpleTypes_NestedList_WithWriteConverter () throws Exception { + String expected = "moo: cow\n" + + "fubar: \n" + + "- moo\n" + + "- cow\n"; + + List list = new ArrayList(); + list.add("moo"); + list.add("cow"); + + Map map = new HashMap(); + map.put("moo", "cow"); + map.put("fubar", list); + + assertEquals(map, roundTripUpperCase(map, expected)); + } + + public void testSimpleTypes_StringArray_WithWriteConverter () throws Exception { + String expected = "- moo\n" + + "- meow\n" + + "- cow\n" + + "- gato\n"; + + String[] stringArray = new String[] {"moo", "meow", "cow", "gato"}; + List result = (List)roundTripUpperCase(stringArray, expected); + assertEquals(stringArray[0], result.get(0)); + assertEquals(stringArray[1], result.get(1)); + assertEquals(stringArray[2], result.get(2)); + assertEquals(stringArray[3], result.get(3)); + } + + public void testObjects_WithWriteConverter () throws Exception { + String expected = "!com.esotericsoftware.yamlbeans.YamlWriterTest$Test\n" + + "BOOLEANVALUE: true\n" + + "CHILD: &1 !com.esotericsoftware.yamlbeans.YamlWriterTest$Test2\n" + + " VALUE: weeeee\n" + + "DATE: 2015-07-10 17:45:56\n" + + "DOUBLEVALUE: 1.5\n" + + "FLOATVALUE: 1.2\n" + + "INTVALUE: 123\n" + + "LISTVALUES: !java.util.LinkedList\n" + + "- woo\n" + + "- !com.esotericsoftware.yamlbeans.YamlWriterTest$Test {}\n" + + "- 123\n" + + "- *1\n" + + "STRINGVALUE: yay\n" + + "VALUE: c\n"; + + Test test = new Test(); + test.stringValue = "yay"; + test.intValue = 123; + test.booleanValue = true; + test.floatValue = 1.2f; + test.doubleValue = 1.5; + test.child = new Test2(); + ((Test2)test.child).setValue("weeeee"); + test.listValues = new LinkedList(); + test.listValues.add("woo"); + test.listValues.add(new Test()); + test.listValues.add(123); + test.listValues.add(test.child); + test.value = Value.c; + test.date = new GregorianCalendar(2015, 6, 10, 17, 45, 56).getTime(); + + + YamlConfig config = new YamlConfig(); + config.setPropertyNameConverter(new TestPropertyNameConverter()); + config.writeConfig.setAutoAnchor(true); + Test result = roundTrip(test, Test.class, config, expected); + assertEquals(test.stringValue, result.stringValue); + assertEquals(test.intValue, result.intValue); + assertEquals(test.booleanValue, result.booleanValue); + assertEquals(test.floatValue, result.floatValue); + assertEquals(test.doubleValue, result.doubleValue); + assertEquals(test.listValues.size(), result.listValues.size()); + assertEquals(test.listValues.getClass(), result.listValues.getClass()); + assertEquals(((Test2)test.child).getValue(), "weeeee"); + assertEquals(test.value, result.value); + assertTrue(result.child == result.listValues.get(3)); + } + + public void testObjectField_WithWriteConverter () throws Exception { + String expected = "!com.esotericsoftware.yamlbeans.YamlWriterTest$ValueHolder\n" + + "VALUE: XYZ\n"; + + ValueHolder object = new ValueHolder(); + object.value = "XYZ"; + ValueHolder roundTrip = (ValueHolder)roundTripUpperCase(object, expected); + assertEquals("XYZ", roundTrip.value); + } + + public void testConstructorProperties_WithWriteConverter () throws Exception { + String expected = "!com.esotericsoftware.yamlbeans.YamlWriterTest$ConstructorPropertiesSample\n" + + "X: 1\n" + + "Y: 2\n" + + "Z: 3\n"; + + ConstructorPropertiesSample object = new ConstructorPropertiesSample(1, 2, 3); + ConstructorPropertiesSample roundTrip = (ConstructorPropertiesSample)roundTripUpperCase(object, expected); + assertEquals(1, roundTrip.getX()); + assertEquals(2, roundTrip.getY()); + assertEquals(3, roundTrip.getZ()); + } + + public void testConstructorPropertiesMixed_WithPropertyConverter () throws Exception { + String expected = "!com.esotericsoftware.yamlbeans.YamlWriterTest$ConstructorPropertiesSampleMixed\n" + + "X: 1\n" + + "Y: 2\n" + + "Z: 3\n"; + + ConstructorPropertiesSampleMixed object = new ConstructorPropertiesSampleMixed(1, 2); + object.setZ(3); + ConstructorPropertiesSampleMixed roundTrip = (ConstructorPropertiesSampleMixed)roundTripUpperCase(object, expected); + assertEquals(1, roundTrip.getX()); + assertEquals(2, roundTrip.getY()); + assertEquals(3, roundTrip.getZ()); + } + + private Object roundTripUpperCase (Object object, String expectedOutput) throws Exception { + YamlConfig config = new YamlConfig(); + config.setPropertyNameConverter(new TestPropertyNameConverter()); + return roundTrip(object, null, config, expectedOutput); + } + private Object roundTrip (Object object) throws Exception { return roundTrip(object, null, new YamlConfig()); } @@ -321,6 +465,21 @@ private T roundTrip (Object object, Class type, YamlConfig config) throws return reader.read(type); } + private T roundTrip (Object object, Class type, YamlConfig config, String expectedOutput) throws Exception { + StringWriter buffer = new StringWriter(); + + YamlWriter writer = new YamlWriter(buffer, config); + writer.write(object); + writer.close(); + + if (true) System.out.println(buffer); + + assertEquals(expectedOutput, buffer.toString()); + + YamlReader reader = new YamlReader(buffer.toString(), config); + return reader.read(type); + } + static public class Test { public String stringValue; public int intValue;