diff --git a/.classpath b/.classpath index b569539..ef00c41 100644 --- a/.classpath +++ b/.classpath @@ -2,7 +2,11 @@ - - + + + + + + diff --git a/.github/workflows/maven-test.yml b/.github/workflows/maven-test.yml new file mode 100644 index 0000000..457b723 --- /dev/null +++ b/.github/workflows/maven-test.yml @@ -0,0 +1,37 @@ +# This workflow will build a Java project with Maven +# For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-maven + +name: Java CI with Maven + +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] + +jobs: + jdk8: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + - name: Set up JDK 1.8 + uses: actions/setup-java@v1 + with: + java-version: 1.8 + - name: Build with Maven + run: mvn test + jdk7: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + - name: Set up JDK 1.7 + uses: actions/setup-java@v1 + with: + java-version: 1.7 + - name: Build with Maven + run: mvn test + diff --git a/.gitignore b/.gitignore index d5b2315..fabc91a 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,5 @@ bin/ target/ +.idea +*.iml diff --git a/.settings/org.eclipse.core.resources.prefs b/.settings/org.eclipse.core.resources.prefs new file mode 100644 index 0000000..99f26c0 --- /dev/null +++ b/.settings/org.eclipse.core.resources.prefs @@ -0,0 +1,2 @@ +eclipse.preferences.version=1 +encoding/=UTF-8 diff --git a/.settings/org.eclipse.jdt.core.prefs b/.settings/org.eclipse.jdt.core.prefs new file mode 100644 index 0000000..fb1077b --- /dev/null +++ b/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,8 @@ +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled +org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8 +org.eclipse.jdt.core.compiler.compliance=1.8 +org.eclipse.jdt.core.compiler.problem.assertIdentifier=error +org.eclipse.jdt.core.compiler.problem.enumIdentifier=error +org.eclipse.jdt.core.compiler.release=enabled +org.eclipse.jdt.core.compiler.source=1.8 diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..ae4ad97 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,17 @@ +language: java +jdk: + - openjdk7 + - oraclejdk8 +# We don't need an install before +# we run the unit and integration tests. +install: true +script: "mvn --show-version --errors --batch-mode clean verify" +cache: + directories: + - $HOME/.m2 +branches: + except: + - gh-pages +notifications: + email: + - dev@tchemit.fr diff --git a/CN_README.md b/CN_README.md new file mode 100644 index 0000000..2829277 --- /dev/null +++ b/CN_README.md @@ -0,0 +1,314 @@ +## YamlBeans + +若需更多支持,请移步[YamlBeans讨论组](http://groups.google.com/group/yamlbeans-users) + +## 概述 + +**YAML**是一种人性化的数据格式,使用YAML来替代XML和properties文件,可以获得更多的表现力(支持lists,maps,anchors等结构),以及更容易的手工编辑。 +而**YamlBeans**则可以让Java对象和YAML格式之间的转换(序列化和反序列化)变得更容易。 + +Maven 仓库: +https://repo1.maven.org/maven2/com/esotericsoftware/yamlbeans/yamlbeans/ + +## 基本的反序列化 + +**YamlReader**这个类用于将YAML格式数据反序列化为Java对象。下面定义了一个包含4个实体的Map,其中最后一个实体项`phone numbers`又是一个包含了2个item的List集合,而每一个item又是一个Map结构。 + +```yaml + name: Nathan Sweet + age: 28 + address: 4011 16th Ave S + phone numbers: + - name: Home + number: 206-555-5138 + - name: Work + number: 425-555-2306 +``` + +“**read()**”方法可用来读取`contact.yml`文件中描述的YAML对象,并将其反序列化为对应的HashMaps,ArrayLists和Strings。因为我们已经知道上面示例的YAML文件中定义的对象是一个Map,所以下面示例中我们可以直接转换为java对象并使用它。 + +```java + YamlReader reader = new YamlReader(new FileReader("contact.yml")); + Object object = reader.read(); + System.out.println(object); + Map map = (Map)object; + System.out.println(map.get("address")); +``` + +## 多个对象 + +一个YAML格式的文件中可以包含多个YAML对象,多个YAML对象之间用`---`隔开(开头第一个可以省略) + +```yaml + name: Nathan Sweet + age: 28 + --- + name: Some One + age: 25 +``` +下面示例中,当while循环中每次调用YamlReader类中read()方法时,就会将contact.yml文件中对应顺序的YAML对象反序列化成一个与之对应结构的Java对象。 +下面代码会**先后输出**字符串`"28"`,`"25"`: + +```java + YamlReader reader = new YamlReader(new FileReader("contact.yml")); + while (true) { + Map contact = reader.read(); + if (contact == null) break; + System.out.println(contact.get("age")); + } +``` + +## 反序列化其他类 + +有两种方法来反序列化除HashMaps,ArrayLists和Strings之外的其他自定义数据格式,例如下面这个YAML文件和Java类: + +```yaml + name: Nathan Sweet + age: 28 +``` +```java + public class Contact { + public String name; + public int age; + } +``` + +在“**read()**”方法的入参中传递一个类,这样YamlReader就可以直接反序列化为指定的类: + +```java + YamlReader reader = new YamlReader(new FileReader("contact.yml")); + Contact contact = reader.read(Contact.class); + System.out.println(contact.age); +``` + + +上面YamlReader创建了一个Contact.class的实例对象,并给“name”和“age”字段赋值,且YamlReader会把YAML中“age”的值转换为int。如果age不是int类型,则反序列化将失败。 + + +除了上面这种反序列化方法外,还可以在YAML中使用添加`!全限定类名`方式来直接指定类型: + +```yaml + !com.example.Contact + name: Nathan Sweet + age: 28 +``` + +## 序列化对象 + + **YamlWriter**这个类用来把Java对象序列化为YAML格式。且“**write()**”方法会自动识别处理public字段和getter方法(一般private属性会生成getter方法)。 + +```java + Contact contact = new Contact(); + contact.name = "Nathan Sweet"; + contact.age = 28; + YamlWriter writer = new YamlWriter(new FileWriter("output.yml")); + writer.write(contact); + writer.close(); +``` + +输出: + +```yaml + !com.example.Contact + name: Nathan Sweet + age: 28 +``` + +上面`!com.example.Contact`部分会根据需要自动输出,以便YamlReader类能够反序列化时重建对应的Java对象。但序列化ArrayList时则不会输出任何类似`!com.example.Contact`的格式内容,因为YamlReader默认就会用ArrayList。 + +```java + List list = new ArrayList(); + list.add("moo"); + list.add("cow"); +``` +```yaml + - moo + - cow +``` + +但是如果List的接口实现是LinkedList,而不是ArrayList(默认),那么YamlWriter类就会输出,例如下面: + +```java + List list = new LinkedList(); + list.add("moo"); + list.add("cow"); +``` +```yaml + !java.util.LinkedList + - moo + - cow +``` + +Note that it is not advisable to subclass Collection or Map. YamlBeans will only serialize the collection or map and its elements, not any additional fields. + +***注意***,在yaml中把集合或Map设置为子类节点是不可取的。YamlBeans只会序列化集合或Map及其中的元素,而不会对其他字段进行序列化。 + +## 复杂结构 + +**YamlBeans**可序列化任何对象。 + +```java + public class Contact { + public String name; + public int age; + public List phoneNumbers; + } + + public class Phone { + public String name; + public String number; + } +``` +```yaml + friends: + - !com.example.Contact + name: Bob + age: 29 + phoneNumbers: + - !com.example.Phone + name: Home + number: 206-555-1234 + - !com.example.Phone + name: Work + number: 206-555-5678 + - !com.example.Contact + name: Mike + age: 31 + phoneNumbers: + - !com.example.Phone + number: 206-555-4321 + enemies: + - !com.example.Contact + name: Bill + phoneNumbers: + - !com.example.Phone + name: Cell + number: 206-555-1234 +``` + +上面是一个由Contact类的List集合构成的复杂Map结构,而且Contact类中还包含phoneNumbers这个List属性。另外,public类型声明的字段也可以是java bean的属性(而不仅仅是getter方法对应的private类型字段)。 + +## 标签截取 + +`!com.example.Contact`这种形式的YAML标签有时可能会很长,会让YAML格式显得混乱不堪,不利于阅读。这时可以给类指定一个替代标签来代替,而不是用类的完整类名。 + +```java + YamlWriter writer = new YamlWriter(new FileWriter("output.yml")); + writer.getConfig().setClassTag("contact", Contact.class); + writer.write(contact); + writer.close(); +``` + +下面输出不再包含Contact类的完整类名。 + +```yaml + !contact + name: Nathan Sweet + age: 28 +``` + +## List 和 Map + +在读取或写入一个List或Map时,YamlBeans有时压根不知道List或Map中应该是什么类型的对象,因此它会输出一个类似`!com.example.Contact`的标签。 + +```yaml + !com.example.Contact + name: Bill + phoneNumbers: + - !com.example.Phone + number: 206-555-1234 + - !com.example.Phone + number: 206-555-5678 + - !com.example.Phone + number: 206-555-7654 +``` + +这样会导致YAML的可读性变的更差。为了提高可读性,你可以在List或Map对象的字段中,指定该字段所属的类型,像下面这样: + +```java + YamlWriter writer = new YamlWriter(new FileWriter("output.yml")); + writer.getConfig().setPropertyElementType(Contact.class, "phoneNumbers", Phone.class); + writer.write(contact); + writer.close(); +``` + + +现在,YamlBeans知道“phoneNumbers”字段的类型是什么了,因此不会额外输出多余的标签。 + +```yaml + !com.example.Contact + name: Bill + phoneNumbers: + - number: 206-555-1234 + - number: 206-555-5678 + - number: 206-555-7654 +``` + +Map中value值的类型,可以根据期望的情况来指定,但Map中的键总是字符串类型。 + +## 锚点 + +当一个对象的结构中包含对其他同一对象的多个引用时,可以设置一个锚点,这样这个被引用的对象只需要在YAML中定义一次。 + +```yaml + oldest friend: + &1 !contact + name: Bob + age: 29 + best friend: *1 +``` + +在上面map中,"oldest friend" 和 "best friend" 字段引用了同一个对象。在反序列化构建对象时,YamlReader会自动处理YAML中的锚点。同时,在默认情况下,YamlWriter在序列化对象时也会自动输出锚点。 + +```java + Contact contact = new Contact(); + contact.name = "Bob"; + contact.age = 29; + Map map = new HashMap(); + map.put("oldest friend", contact); + map.put("best friend", contact); +``` + +## 让重复字段生效 + +默认情况下,YAML在解析时是忽略重复字段的。例如,以下情况: + +```yaml + name: Nathan Sweet + age: 28 + address: + line1: 485 Madison Ave S + line1: 711 3rd Ave S + line2: NYC +``` + + +上面的YAML将会为`address`的`line1`字段赋值为`711 3rd Ave S`而不是`485 Madison Ave S`。这是因为上面YAML中的字段line1是重复的,因此line1的最后一个值将会被保留。但是,如果你的业务逻辑要求重复字段都生效,那么你可以在YamlConfig类进行设置。以下是设置方法: + +```java + try { + YamlConfig yamlConfig = new YamlConfig(); + yamlConfig.setAllowDuplicates(false); // default value is true + YamlReader reader = new YamlReader(new FileReader("contact.yml"), yamlConfig); + Object object = reader.read(); + System.out.println(object); + Map map = (Map)object; + System.out.println(map.get("address")); + } catch (YamlException ex) { + ex.printStackTrace(); + // or handle duplicate key case here according to your business logic + } +``` + +上面的代码不会打印任何东西,但会在第5行抛出`YamlReaderException`异常说`Duplicate key found 'line1'` + +## 体系结构 + +YAML的tokenizer,parser,emitter组件是基于JvYAML项目中的。这些功能已被重构,修复bug等。由于JvYAML的复杂性,剩下部分未被使用。 YamlBeans努力于实现简单可行的事情---让使用Java操作YAML数据格式变得更加容易。 + +YamlBeans 支持 YAML 1.0 和 1.1 版本 。 + +## 更多信息 + +有关YamlConfig类的其他更多功能,请参阅javadocs + diff --git a/README.md b/README.md index cab3b95..bad7301 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,17 @@ ## YamlBeans +![Maven Central](https://maven-badges.herokuapp.com/maven-central/com.esotericsoftware.yamlbeans/yamlbeans/badge.svg) + Please use the [YamlBeans discussion group](http://groups.google.com/group/yamlbeans-users) for support. +Chinese document [中文翻译文档](CN_README.md) . + ## Overview YamlBeans makes it easy to serialize and deserialize Java object graphs to and from YAML, a human-friendly data format. Replace XML and properties files with YAML for more expressive power (lists, maps, anchors, etc) and easier hand-editing. Maven Central: -http://repo1.maven.org/maven2/com/esotericsoftware/yamlbeans/yamlbeans/ +https://repo1.maven.org/maven2/com/esotericsoftware/yamlbeans/yamlbeans/ ## Basic deserialization @@ -263,6 +267,38 @@ In this map, the "oldest friend" and "best friend" keys reference the same objec map.put("best friend", contact); ``` +## Duplicate key validation + +By default, the behaviour of this YAML parser is to ignore duplicate keys if you have. e.g if you have the following + +```yaml + name: Nathan Sweet + age: 28 + address: + line1: 485 Madison Ave S + line1: 711 3rd Ave S + line2: NYC +``` + +The above YAML will give you an `address` object with attribute `line1` set to `711 3rd Ave S`. This is because the key `line1` in the above YAML is duplicated and thus the last value of `line1` will be retained. YAML parser will not complain about it. However, if your business logic requires you to validate YAML for such duplicates, then you can still do using `allowDuplicates` option of the `YamlConfig` object. Following is how its done: + +```java + try { + YamlConfig yamlConfig = new YamlConfig(); + yamlConfig.setAllowDuplicates(false); // default value is true + YamlReader reader = new YamlReader(new FileReader("contact.yml"), yamlConfig); + Object object = reader.read(); + System.out.println(object); + Map map = (Map)object; + System.out.println(map.get("address")); + } catch (YamlException ex) { + ex.printStackTrace(); + // or handle duplicate key case here according to your business logic + } +``` + +The above code will not print anything, but throw `YamlReaderException` at line 5 saying, `Duplicate key found 'line1'`. + ## Architecture The YAML tokenizer, parser, and emitter are based on those from the JvYAML project. They have been heavily refactored, bugs fixed, etc. The rest of the JvYAML project was not used because of its complexity. YamlBeans strives for the simplest possible thing that works, with the goal being to make it easy to use the YAML data format with Java. diff --git a/lib/junit-4.4.jar b/lib/junit-4.4.jar deleted file mode 100644 index 649b0b3..0000000 Binary files a/lib/junit-4.4.jar and /dev/null differ diff --git a/lib/junit-4.8.2.jar b/lib/junit-4.8.2.jar new file mode 100644 index 0000000..5b4bb84 Binary files /dev/null and b/lib/junit-4.8.2.jar differ diff --git a/pom.xml b/pom.xml index d2c701a..b8bb746 100644 --- a/pom.xml +++ b/pom.xml @@ -12,15 +12,15 @@ YamlBeans com.esotericsoftware.yamlbeans yamlbeans - 1.08 + 1.18-SNAPSHOT Java object graphs, to and from YAML https://github.com/EsotericSoftware/yamlbeans - New BSD License - http://www.opensource.org/licenses/bsd-license.php + MIT License + https://opensource.org/licenses/MIT repo @@ -36,7 +36,7 @@ test junit junit - 4.8.2 + 4.13.1 @@ -52,6 +52,14 @@ src test target/classes + + + test + + **/*.yml + + + maven-compiler-plugin @@ -65,37 +73,44 @@ + org.apache.maven.plugins maven-resources-plugin - 2.6 + 3.0.2 + org.apache.maven.plugins maven-surefire-plugin - 2.12.4 + 2.19.1 + org.apache.maven.plugins maven-compiler-plugin - 2.5.1 + 3.6.1 + org.apache.maven.plugins maven-source-plugin - 2.2.1 + 3.0.1 + org.apache.maven.plugins maven-javadoc-plugin - 2.9 + 2.10.4 + org.apache.maven.plugins maven-gpg-plugin - 1.4 + 1.6 + org.apache.maven.plugins maven-deploy-plugin - 2.7 + 2.8.2 org.nuiton helper-maven-plugin - 2.0 + 2.3.2 @@ -118,9 +133,9 @@ Tony Chemit - chemit at codelutin dot com - CodeLutin - http://codelutin.com + dev@tchemit.fr + Ultreia + http://ultreia.io Europe/Paris Maven packager @@ -129,49 +144,65 @@ - - release-sign-artifacts - - - performRelease - true - - - - - - org.nuiton - helper-maven-plugin - - - get-pgp-passphrase - - share-server-secret - - verify - - ${gpg.serverId} - gpg.keyname - gpg.passphrase - - - - - - maven-gpg-plugin - - - sign-artifacts - verify - - sign - - - - - - - + + release-sign-artifacts + + + nexus-release + https://oss.sonatype.org/content/repositories/snapshots/ + + + nexus-release + https://oss.sonatype.org/service/local/staging/deploy/maven2/ + + + + + + + org.apache.maven.plugins + maven-source-plugin + 2.2.1 + + + package + + jar-no-fork + + + + + + + org.apache.maven.plugins + maven-javadoc-plugin + 2.9.1 + + + package + + jar + + + + + + org.apache.maven.plugins + maven-gpg-plugin + 1.5 + + + sign-artifacts + verify + + sign + + + + + + + diff --git a/project.yaml b/project.yaml index 96b523a..79d7213 100644 --- a/project.yaml +++ b/project.yaml @@ -1,2 +1,2 @@ classpath: lib|*.jar|!junit*.jar -version: 1.08 +version: 1.15 diff --git a/src/com/esotericsoftware/yamlbeans/Beans.java b/src/com/esotericsoftware/yamlbeans/Beans.java index 7aa2a59..3582b22 100644 --- a/src/com/esotericsoftware/yamlbeans/Beans.java +++ b/src/com/esotericsoftware/yamlbeans/Beans.java @@ -16,25 +16,28 @@ package com.esotericsoftware.yamlbeans; -import com.esotericsoftware.yamlbeans.YamlConfig.ConstructorParameters; - -import java.beans.IntrospectionException; -import java.beans.Introspector; -import java.beans.PropertyDescriptor; import java.lang.annotation.Annotation; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; import java.lang.reflect.Modifier; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.lang.reflect.WildcardType; import java.util.ArrayList; +import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; +import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.TreeSet; +import com.esotericsoftware.yamlbeans.YamlConfig.ConstructorParameters; + /** Utility for dealing with beans and public fields. * @author Nathan Sweet */ class Beans { @@ -62,16 +65,6 @@ static public DeferredConstruction getDeferredConstruction (Class type, YamlConf return null; } - static private boolean canInitializeProperty (Class type, PropertyDescriptor property, YamlConfig config) { - if (property.getWriteMethod() != null) return true; - - // Check if the property can be initialized through the constructor. - DeferredConstruction deferredConstruction = getDeferredConstruction(type, config); - if (deferredConstruction != null && deferredConstruction.hasParameter(property.getName())) return true; - - return false; - } - static public Object createObject (Class type, boolean privateConstructors) throws InvocationTargetException { // Use no-arg constructor. Constructor constructor = null; @@ -117,68 +110,113 @@ static public Object createObject (Class type, boolean privateConstructors) thro } } - static public Set getProperties (Class type, boolean beanProperties, boolean privateFields, YamlConfig config) - throws IntrospectionException { + static public Set getProperties (Class type, boolean beanProperties, boolean privateFields, YamlConfig config) { if (type == null) throw new IllegalArgumentException("type cannot be null."); - Set properties = new TreeSet(); - if (beanProperties) { - for (PropertyDescriptor property : Introspector.getBeanInfo(type).getPropertyDescriptors()) - if (property.getReadMethod() != null && canInitializeProperty(type, property, config)) - properties.add(new MethodProperty(type, property)); - } + Set properties = config.writeConfig.keepBeanPropertyOrder ? new LinkedHashSet() : new TreeSet(); for (Field field : getAllFields(type)) { - int modifiers = field.getModifiers(); - if (Modifier.isStatic(modifiers) || Modifier.isTransient(modifiers)) continue; - if (!Modifier.isPublic(modifiers)) { - if (!privateFields) continue; - field.setAccessible(true); + Property property = getProperty(type, beanProperties, privateFields, config, field); + if (property != null) { + properties.add(property); } - properties.add(new FieldProperty(field)); } return properties; } - static public Property getProperty (Class type, String name, boolean beanProperties, boolean privateFields, YamlConfig config) - throws IntrospectionException { + static private String toJavaIdentifier (String name) { + StringBuilder buffer = new StringBuilder(); + for (int i = 0, n = name.length(); i < n; i++) { + char c = name.charAt(i); + if (Character.isJavaIdentifierPart(c)) buffer.append(c); + } + return buffer.toString(); + } + + static public Property getProperty (Class type, String name, boolean beanProperties, boolean privateFields, + YamlConfig config) { if (type == null) throw new IllegalArgumentException("type cannot be null."); if (name == null || name.length() == 0) throw new IllegalArgumentException("name cannot be null or empty."); + name = toJavaIdentifier(name); + + for (Field field : getAllFields(type)) + if (field.getName().equals(name)) return getProperty(type, beanProperties, privateFields, config, field); + + // If a property is not found, try prepending `_` for reserved field names (eg "class" in the data can be a "_class" field). + if (!name.startsWith("_")) return getProperty(type, "_" + name, beanProperties, privateFields, config); + + return null; + } + + private static Property getProperty(Class type, boolean beanProperties, boolean privateFields, YamlConfig config, + Field field) { + Property property = null; if (beanProperties) { - for (PropertyDescriptor property : Introspector.getBeanInfo(type).getPropertyDescriptors()) { - if (property.getName().equals(name)) { - if (property.getReadMethod() != null && canInitializeProperty(type, property, config)) - return new MethodProperty(type, property); - break; + String name = field.getName(); + DeferredConstruction deferredConstruction = getDeferredConstruction(type, config); + boolean constructorProperty = deferredConstruction != null && deferredConstruction.hasParameter(name); + + String nameUpper = Character.toUpperCase(name.charAt(0)) + name.substring(1); + String setMethodName = "set" + nameUpper; + String getMethodName = "get" + nameUpper; + if (field.getType().equals(Boolean.class) || field.getType().equals(boolean.class)) { + setMethodName = name.startsWith("is") + ? "set" + Character.toUpperCase(name.charAt(2)) + name.substring(3) + : setMethodName; + getMethodName = name.startsWith("is") ? name : "is" + nameUpper; + } + + Method getMethod = null; + Method setMethod = null; + try { + setMethod = type.getMethod(setMethodName, field.getType()); + } catch (Exception ignored) { + } + try { + getMethod = type.getMethod(getMethodName); + } catch (Exception ignored) { + } + + // field.getType() is boolean, but getMethodName is getFieldName(E.g: getBool). + if (getMethod == null && (field.getType().equals(Boolean.class) || field.getType().equals(boolean.class))) { + try { + getMethod = type.getMethod("get" + nameUpper); + } catch (Exception ignored) { } } - } - for (Field field : getAllFields(type)) { - int modifiers = field.getModifiers(); - if (Modifier.isStatic(modifiers) || Modifier.isTransient(modifiers)) continue; - if (!Modifier.isPublic(modifiers)) { - if (!privateFields) continue; - field.setAccessible(true); + if (getMethod != null && (setMethod != null || constructorProperty)) { + return new MethodProperty(name, setMethod, getMethod); } - if (field.getName().equals(name)) return new FieldProperty(field); } - return null; + + int modifiers = field.getModifiers(); + if (!Modifier.isStatic(modifiers) && !Modifier.isTransient(modifiers) + && (Modifier.isPublic(modifiers) || privateFields)) { + field.setAccessible(true); + property = new FieldProperty(field); + } + return property; } static private ArrayList getAllFields (Class type) { - ArrayList allFields = new ArrayList(); + ArrayList classes = new ArrayList(); Class nextClass = type; - while (nextClass != Object.class) { - Collections.addAll(allFields, nextClass.getDeclaredFields()); + while (nextClass != null && nextClass != Object.class) { + classes.add(nextClass); nextClass = nextClass.getSuperclass(); } + ArrayList allFields = new ArrayList(); + for (int i = classes.size() - 1; i >= 0; i--) { + Collections.addAll(allFields, classes.get(i).getDeclaredFields()); + } return allFields; } static public class MethodProperty extends Property { - private final PropertyDescriptor property; + private final Method setMethod, getMethod; - public MethodProperty (Class declaringClass, PropertyDescriptor property) throws IntrospectionException { - super(declaringClass, property); - this.property = property; + public MethodProperty (String name, Method setMethod, Method getMethod) { + super(getMethod.getDeclaringClass(), name, getMethod.getReturnType(), getMethod.getGenericReturnType()); + this.setMethod = setMethod; + this.getMethod = getMethod; } public void set (Object object, Object value) throws Exception { @@ -186,11 +224,11 @@ public void set (Object object, Object value) throws Exception { ((DeferredConstruction)object).storeProperty(this, value); return; } - property.getWriteMethod().invoke(object, value); + setMethod.invoke(object, value); } public Object get (Object object) throws Exception { - return property.getReadMethod().invoke(object); + return getMethod.invoke(object); } } @@ -198,7 +236,7 @@ static public class FieldProperty extends Property { private final Field field; public FieldProperty (Field field) { - super(field.getDeclaringClass(), field.getName(), field.getType()); + super(field.getDeclaringClass(), field.getName(), field.getType(), field.getGenericType()); this.field = field; } @@ -219,26 +257,47 @@ static public abstract class Property implements Comparable { private final Class declaringClass; private final String name; private final Class type; + private final Class elementType; - Property (Class declaringClass, String name, Class type) { + Property (Class declaringClass, String name, Class type, Type genericType) { this.declaringClass = declaringClass; this.name = name; this.type = type; + this.elementType = getElementTypeFromGenerics(genericType); } - Property (Class declaringClass, PropertyDescriptor property) throws IntrospectionException { - this.declaringClass = declaringClass; - this.name = property.getName(); - try { - // The PropertyDescriptor returns the wrong type if the getter is an implementation of a interface method with a - // generic return value. - type = property.getReadMethod().getDeclaringClass() - .getDeclaredMethod(property.getReadMethod().getName(), new Class[0]).getReturnType(); - } catch (Exception ex) { - IntrospectionException introspectionEx = new IntrospectionException("Error getting "); - introspectionEx.initCause(ex); - throw introspectionEx; + private Class getElementTypeFromGenerics (Type type) { + if (type instanceof ParameterizedType) { + ParameterizedType parameterizedType = (ParameterizedType)type; + Type rawType = parameterizedType.getRawType(); + + if (isCollection(rawType) || isMap(rawType)) { + Type[] actualTypeArguments = parameterizedType.getActualTypeArguments(); + if (actualTypeArguments.length > 0) { + final Type cType = actualTypeArguments[actualTypeArguments.length - 1]; + if (cType instanceof Class) { + return (Class)cType; + } else if (cType instanceof WildcardType) { + WildcardType t = (WildcardType)cType; + final Type bound = t.getUpperBounds()[0]; + return bound instanceof Class ? (Class)bound : null; + } else if (cType instanceof ParameterizedType) { + ParameterizedType t = (ParameterizedType)cType; + final Type rt = t.getRawType(); + return rt instanceof Class ? (Class)rt : null; + } + } + } } + return null; + } + + private boolean isMap (Type type) { + return Map.class.isAssignableFrom((Class)type); + } + + private boolean isCollection (Type type) { + return Collection.class.isAssignableFrom((Class)type); } public int hashCode () { @@ -247,6 +306,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 + ((elementType == null) ? 0 : elementType.hashCode()); return result; } @@ -264,6 +324,9 @@ public boolean equals (Object obj) { if (type == null) { if (other.type != null) return false; } else if (!type.equals(other.type)) return false; + if (elementType == null) { + if (other.elementType != null) return false; + } else if (!elementType.equals(other.elementType)) return false; return true; } @@ -271,6 +334,10 @@ public Class getDeclaringClass () { return declaringClass; } + public Class getElementType () { + return elementType; + } + public Class getType () { return type; } diff --git a/src/com/esotericsoftware/yamlbeans/DeferredConstruction.java b/src/com/esotericsoftware/yamlbeans/DeferredConstruction.java index 7f36051..eb8dd36 100644 --- a/src/com/esotericsoftware/yamlbeans/DeferredConstruction.java +++ b/src/com/esotericsoftware/yamlbeans/DeferredConstruction.java @@ -42,14 +42,25 @@ public Object construct () throws InvocationTargetException { try { Object[] parameters = new Object[parameterValues.length]; int i = 0; + boolean missingParameter = false; for (ParameterValue parameter : parameterValues) { if (parameter == null) + missingParameter = true; + else + parameters[i++] = parameter.value; + } + Object object; + if (missingParameter) { + try { + object = constructor.getDeclaringClass().getConstructor().newInstance(); + } catch (Exception ex) { throw new InvocationTargetException(new YamlException("Missing constructor property: " + parameterNames[i])); - parameters[i++] = parameter.value; + } + } else object = constructor.newInstance(parameters); + for (PropertyValue propertyValue : propertyValues) { + if (propertyValue.value != null) + propertyValue.property.set(object, propertyValue.value); } - Object object = constructor.newInstance(parameters); - for (PropertyValue propertyValue : propertyValues) - propertyValue.property.set(object, propertyValue.value); return object; } catch (Exception ex) { throw new InvocationTargetException(ex, "Error constructing instance of class: " diff --git a/src/com/esotericsoftware/yamlbeans/SafeYamlConfig.java b/src/com/esotericsoftware/yamlbeans/SafeYamlConfig.java new file mode 100644 index 0000000..e0cbad6 --- /dev/null +++ b/src/com/esotericsoftware/yamlbeans/SafeYamlConfig.java @@ -0,0 +1,42 @@ + +package com.esotericsoftware.yamlbeans; + +/** SafeYamlConfig extends YamlConfig and hard codes the read anchor and read class tag flags to false. When these flags are + * enabled, it is possible to perform a deserialization attack if the Yaml being parsed is from an untrusted source. + *

+ * Using SafeYamlConfig is the equivalent of using YamlConfig after setting: + * + *

+ * yamlConfig.readConfig.setAnchors(false);
+ * yamlConfig.readConfig.setClassTags(false);
+ * 
+ * + * It should be noted by setting these two values neither anchors or specifying class names are supported. It is still possible to + * deserialize back to a specific object, but you need to specify the Class type in the code. For example: + * + *
+ * SafeYamlConfig config = new SafeYamlConfig();
+ * YamlReader reader = new YamlReader(yamlData.toString(), config);
+ * Data data = reader.read(Data.class);
+ * 
+ */ +public class SafeYamlConfig extends YamlConfig { + public SafeYamlConfig () { + super.readConfig = new SafeReadConfig(); + } + + static public class SafeReadConfig extends ReadConfig { + public SafeReadConfig () { + super.anchors = false; + super.classTags = false; + } + + public void setClassTags (boolean classTags) { + if (classTags) throw new IllegalArgumentException("Class Tags cannot be enabled in SafeYamlConfig."); + } + + public void setAnchors (boolean anchors) { + if (anchors) throw new IllegalArgumentException("Anchors cannot be enabled in SafeYamlConfig."); + } + } +} diff --git a/src/com/esotericsoftware/yamlbeans/Version.java b/src/com/esotericsoftware/yamlbeans/Version.java index 116261c..e4c7a02 100644 --- a/src/com/esotericsoftware/yamlbeans/Version.java +++ b/src/com/esotericsoftware/yamlbeans/Version.java @@ -19,45 +19,57 @@ /** Represents the version of a YAML document. * @author Nathan Sweet */ public class Version { - public final int major; - public final int minor; - - public Version (String value) { - if (value == null) throw new IllegalArgumentException("value cannot be null."); - int dotIndex = value.indexOf('.'); - if (dotIndex == -1) throw new IllegalArgumentException("value must contain a period: " + value); - major = Integer.parseInt(value.substring(0, dotIndex)); - minor = Integer.parseInt(value.substring(dotIndex + 1)); - } - public Version (int major, int minor) { + public static final Version V1_0 = new Version(1, 0); + + public static final Version V1_1 = new Version(1, 1); + + /** + * YAML 1.1 + */ + public static final Version DEFAULT_VERSION = V1_1; + + private final int major; + private final int minor; + + private Version (int major, int minor) { this.major = major; this.minor = minor; } - public String toString () { - return major + "." + minor; + public static Version getVersion(String value) { + Version version = null; + if (value != null) { + int dotIndex = value.indexOf('.'); + int major = 0; + int minor = 0; + if (dotIndex > 0) { + try { + major = Integer.parseInt(value.substring(0, dotIndex)); + minor = Integer.parseInt(value.substring(dotIndex + 1)); + } catch (NumberFormatException e) { + return null; + } + } + + if (major == V1_0.major && minor == V1_0.minor) { + version = V1_0; + } else if (major == V1_1.major && minor == V1_1.minor) { + version = V1_1; + } + } + return version; } - public int hashCode () { - final int prime = 31; - int result = 1; - result = prime * result + major; - result = prime * result + minor; - return result; + public int getMajor() { + return major; } - public boolean equals (int minor, int major) { - return this.minor == minor && this.major == major; + public int getMinor() { + return minor; } - public boolean equals (Object obj) { - if (this == obj) return true; - if (obj == null) return false; - if (getClass() != obj.getClass()) return false; - final Version other = (Version)obj; - if (major != other.major) return false; - if (minor != other.minor) return false; - return true; + public String toString () { + return major + "." + minor; } } diff --git a/src/com/esotericsoftware/yamlbeans/YamlConfig.java b/src/com/esotericsoftware/yamlbeans/YamlConfig.java index 3a12ef0..b00053e 100644 --- a/src/com/esotericsoftware/yamlbeans/YamlConfig.java +++ b/src/com/esotericsoftware/yamlbeans/YamlConfig.java @@ -16,12 +16,6 @@ package com.esotericsoftware.yamlbeans; -import com.esotericsoftware.yamlbeans.Beans.Property; -import com.esotericsoftware.yamlbeans.emitter.EmitterConfig; -import com.esotericsoftware.yamlbeans.scalar.DateSerializer; -import com.esotericsoftware.yamlbeans.scalar.ScalarSerializer; - -import java.beans.IntrospectionException; import java.lang.reflect.Constructor; import java.util.ArrayList; import java.util.Arrays; @@ -31,6 +25,11 @@ import java.util.IdentityHashMap; import java.util.Map; +import com.esotericsoftware.yamlbeans.Beans.Property; +import com.esotericsoftware.yamlbeans.emitter.EmitterConfig; +import com.esotericsoftware.yamlbeans.scalar.DateSerializer; +import com.esotericsoftware.yamlbeans.scalar.ScalarSerializer; + /** Stores configuration for reading and writing YAML. * @author Nathan Sweet */ public class YamlConfig { @@ -38,7 +37,7 @@ public class YamlConfig { public final WriteConfig writeConfig = new WriteConfig(); /** Configuration for reading YAML. */ - public final ReadConfig readConfig = new ReadConfig(); + public ReadConfig readConfig = new ReadConfig(); final Map classNameToTag = new HashMap(); final Map tagToClass = new HashMap(); @@ -48,6 +47,8 @@ public class YamlConfig { boolean beanProperties = true; boolean privateFields; boolean privateConstructors = true; + boolean allowDuplicates = true; + String tagSuffix; public YamlConfig () { scalarSerializers.put(Date.class, new DateSerializer()); @@ -59,10 +60,16 @@ public YamlConfig () { tagToClass.put("tag:yaml.org,2002:float", Float.class); } + /** Allows duplicate keys in YAML document. Default is true. */ + public void setAllowDuplicates (boolean allowDuplicates) { + this.allowDuplicates = allowDuplicates; + } + /** Allows the specified tag to be used in YAML instead of the full class name. */ public void setClassTag (String tag, Class type) { if (tag == null) throw new IllegalArgumentException("tag cannot be null."); if (type == null) throw new IllegalArgumentException("type cannot be null."); + if (!tag.startsWith("!")) tag = "!" + tag; classNameToTag.put(type.getName(), tag); tagToClass.put(tag, type); } @@ -80,20 +87,13 @@ public void setPropertyElementType (Class type, String propertyName, Class eleme if (type == null) throw new IllegalArgumentException("type cannot be null."); if (propertyName == null) throw new IllegalArgumentException("propertyName cannot be null."); if (elementType == null) throw new IllegalArgumentException("propertyType cannot be null."); - Property property = null; - Exception cause = null; - try { - property = Beans.getProperty(type, propertyName, beanProperties, privateFields, this); - } catch (IntrospectionException ex) { - cause = ex; - } - if (property == null) { - throw new IllegalArgumentException("The class " + type.getName() + " does not have a property named: " + propertyName, - cause); - } - if (!Collection.class.isAssignableFrom(property.getType()) && !Map.class.isAssignableFrom(property.getType())) + Property property = Beans.getProperty(type, propertyName, beanProperties, privateFields, this); + if (property == null) + throw new IllegalArgumentException("The class " + type.getName() + " does not have a property named: " + propertyName); + if (!Collection.class.isAssignableFrom(property.getType()) && !Map.class.isAssignableFrom(property.getType())) { throw new IllegalArgumentException("The '" + propertyName + "' property on the " + type.getName() + " class must be a Collection or Map: " + property.getType()); + } propertyToElementType.put(property, elementType); } @@ -103,21 +103,14 @@ public void setPropertyDefaultType (Class type, String propertyName, Class defau if (type == null) throw new IllegalArgumentException("type cannot be null."); if (propertyName == null) throw new IllegalArgumentException("propertyName cannot be null."); if (defaultType == null) throw new IllegalArgumentException("defaultType cannot be null."); - Property property = null; - Exception cause = null; - try { - property = Beans.getProperty(type, propertyName, beanProperties, privateFields, this); - } catch (IntrospectionException ex) { - cause = ex; - } - if (property == null) { - throw new IllegalArgumentException("The class " + type.getName() + " does not have a property named: " + propertyName, - cause); - } + Property property = Beans.getProperty(type, propertyName, beanProperties, privateFields, this); + if (property == null) + throw new IllegalArgumentException("The class " + type.getName() + " does not have a property named: " + propertyName); propertyToDefaultType.put(property, defaultType); } - /** If true, bean properties with both a getter and setter will be used. Default is true. */ + /** If true, bean properties with both a getter and setter will be used. Note the getter and setter methods must be named the + * same as the field they get or set. Default is true. */ public void setBeanProperties (boolean beanProperties) { this.beanProperties = beanProperties; } @@ -132,6 +125,12 @@ public void setPrivateConstructors (boolean privateConstructors) { this.privateConstructors = privateConstructors; } + /** When not null, YAML read into a {@link Map} stores any value tags using key + tagSuffix, and when writing YAML the value + * tags are output. Key tags are not stored in the map. Default is null. */ + public void setTagSuffix (String tagSuffix) { + this.tagSuffix = tagSuffix; + } + static public class WriteConfig { boolean explicitFirstDocument = false; boolean explicitEndDocument = false; @@ -139,7 +138,12 @@ static public class WriteConfig { boolean writeRootTags = true; boolean writeRootElementTags = true; boolean autoAnchor = true; - boolean alwaysWriteClassName = false; + boolean keepBeanPropertyOrder = false; + WriteClassName writeClassName = WriteClassName.AUTO; + Quote quote = Quote.NONE; + Version version; + Map tags; + boolean flowStyle; EmitterConfig emitterConfig = new EmitterConfig(); WriteConfig () { @@ -185,9 +189,20 @@ public void setAutoAnchor (boolean autoAnchor) { this.autoAnchor = autoAnchor; } + /** If true, bean fields/properties are written in the same order as the fields are defined in the bean class. If false, + * they are sorted alphabetically. Default is false. */ + public void setKeepBeanPropertyOrder (boolean keepBeanPropertyOrder) { + this.keepBeanPropertyOrder = keepBeanPropertyOrder; + } + /** Sets the YAML version to output. Default is 1.1. */ public void setVersion (Version version) { - emitterConfig.setVersion(version); + this.version = version; + } + + /** Sets the YAML tags to output. */ + public void setTags (Map tags) { + this.tags = tags; } /** If true, the YAML output will be canonical. Default is false. */ @@ -205,7 +220,7 @@ public void setWrapColumn (int wrapColumn) { emitterConfig.setWrapColumn(wrapColumn); } - /** If false, tags will never be surrounded by angle brackets (eg, "!"). Default is false. */ + /** If false, tags will never be surrounded by angle brackets (eg, "!<java.util.LinkedList>"). Default is false. */ public void setUseVerbatimTags (boolean useVerbatimTags) { emitterConfig.setUseVerbatimTags(useVerbatimTags); } @@ -215,16 +230,44 @@ public void setEscapeUnicode (boolean escapeUnicode) { emitterConfig.setEscapeUnicode(escapeUnicode); } - /** If true, class name tags will always be output. */ - public void setAlwaysWriteClassname (boolean write) { - alwaysWriteClassName = write; + /** Determines when class name tags are output. */ + public void setWriteClassname (WriteClassName write) { + writeClassName = write; + } + + /** The type of quotes to use when writing YAML output. */ + public void setQuoteChar (Quote quote) { + this.quote = quote; + } + + public Quote getQuote () { + return quote; + } + + /** If true, the YAML output will be flow. Default is false. */ + public void setFlowStyle (boolean flowStyle) { + this.flowStyle = flowStyle; + } + + public boolean isFlowStyle () { + return flowStyle; + } + + /** If true, the YAML output will be pretty flow. Default is false. */ + public void setPrettyFlow (boolean prettyFlow) { + emitterConfig.setPrettyFlow(prettyFlow); } } static public class ReadConfig { - Version defaultVersion = new Version(1, 1); + Version defaultVersion = Version.DEFAULT_VERSION; ClassLoader classLoader; final Map constructorParameters = new IdentityHashMap(); + boolean ignoreUnknownProperties; + boolean autoMerge = true; + boolean classTags = true; + boolean guessNumberTypes; + boolean anchors = true; ReadConfig () { } @@ -250,16 +293,61 @@ public void setConstructorParameters (Class type, Class[] parameterTypes, String try { parameters.constructor = type.getConstructor(parameterTypes); } catch (Exception ex) { - throw new IllegalArgumentException("Unable to find constructor: " + type.getName() + "(" - + Arrays.toString(parameterTypes) + ")", ex); + throw new IllegalArgumentException( + "Unable to find constructor: " + type.getName() + "(" + Arrays.toString(parameterTypes) + ")", ex); } parameters.parameterNames = parameterNames; constructorParameters.put(type, parameters); } + + /** When true, fields in the YAML that are not found on the class will not throw a {@link YamlException}. Default is + * false. */ + public void setIgnoreUnknownProperties (boolean allowUnknownProperties) { + this.ignoreUnknownProperties = allowUnknownProperties; + } + + /** When false, tags are not used to look up classes. Default is true. */ + public void setClassTags (boolean classTags) { + this.classTags = classTags; + } + + /** When false, the merge key (<<) is not used to merge values into the current map. Default is true. */ + public void setAutoMerge (boolean autoMerge) { + this.autoMerge = autoMerge; + } + + /** When true, if the type for a scalar value is unknown and it looks like a number, it is read as a double or long. When + * false, if the type for a scalar value is unknown it is always read a string. Default is true. */ + public void setGuessNumberTypes (boolean guessNumberTypes) { + this.guessNumberTypes = guessNumberTypes; + } + + /** When false, anchors in the YAML are ignored. Default is true. */ + public void setAnchors (boolean anchors) { + this.anchors = anchors; + } } static class ConstructorParameters { public Constructor constructor; public String[] parameterNames; } + + public static enum WriteClassName { + ALWAYS, NEVER, AUTO + } + + public static enum Quote { + NONE('\0'), SINGLE('\''), DOUBLE('"'), LITERAL('|'), FOLDED('>'); + + char c; + + Quote (char c) { + this.c = c; + } + + public char getStyle () { + return c; + } + } } diff --git a/src/com/esotericsoftware/yamlbeans/YamlReader.java b/src/com/esotericsoftware/yamlbeans/YamlReader.java index a302b9a..351d4a6 100644 --- a/src/com/esotericsoftware/yamlbeans/YamlReader.java +++ b/src/com/esotericsoftware/yamlbeans/YamlReader.java @@ -18,31 +18,35 @@ import static com.esotericsoftware.yamlbeans.parser.EventType.*; -import com.esotericsoftware.yamlbeans.Beans.Property; -import com.esotericsoftware.yamlbeans.parser.AliasEvent; -import com.esotericsoftware.yamlbeans.parser.CollectionStartEvent; -import com.esotericsoftware.yamlbeans.parser.Event; -import com.esotericsoftware.yamlbeans.parser.Parser; -import com.esotericsoftware.yamlbeans.parser.Parser.ParserException; -import com.esotericsoftware.yamlbeans.parser.ScalarEvent; -import com.esotericsoftware.yamlbeans.scalar.ScalarSerializer; -import com.esotericsoftware.yamlbeans.tokenizer.Tokenizer.TokenizerException; - import java.io.FileReader; import java.io.IOException; import java.io.Reader; import java.io.StringReader; +import java.io.StringWriter; import java.lang.reflect.Array; import java.lang.reflect.InvocationTargetException; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; +import java.util.Iterator; +import java.util.LinkedHashMap; import java.util.Map; import java.util.Map.Entry; +import com.esotericsoftware.yamlbeans.Beans.Property; +import com.esotericsoftware.yamlbeans.parser.AliasEvent; +import com.esotericsoftware.yamlbeans.parser.CollectionStartEvent; +import com.esotericsoftware.yamlbeans.parser.Event; +import com.esotericsoftware.yamlbeans.parser.EventType; +import com.esotericsoftware.yamlbeans.parser.Parser; +import com.esotericsoftware.yamlbeans.parser.Parser.ParserException; +import com.esotericsoftware.yamlbeans.parser.ScalarEvent; +import com.esotericsoftware.yamlbeans.scalar.ScalarSerializer; +import com.esotericsoftware.yamlbeans.tokenizer.Tokenizer.TokenizerException; + /** Deserializes Java objects from YAML. * @author Nathan Sweet */ -public class YamlReader { +public class YamlReader implements AutoCloseable { private final YamlConfig config; Parser parser; private final Map anchors = new HashMap(); @@ -68,31 +72,42 @@ public YamlConfig getConfig () { return config; } - /** Return the object with the given alias, or null. This is only valid after objects have been read and before {@link #close()} */ + /** Return the object with the given alias, or null. This is only valid after objects have been read and before + * {@link #close()} */ public Object get (String alias) { return anchors.get(alias); } + private void addAnchor (String key, Object value) { + if (config.readConfig.anchors) anchors.put(key, value); + } + public void close () throws IOException { parser.close(); anchors.clear(); } /** Reads the next YAML document and deserializes it into an object. The type of object is defined by the YAML tag. If there is - * no YAML tag, the object will be an {@link ArrayList}, {@link HashMap}, or String. */ + * no YAML tag, the object will be an {@link ArrayList}, {@link HashMap}, or String. + *

+ * This method creates and configures an instance of a class specified by the YAML data, so should be used only with YAML data + * from a trusted source. */ public Object read () throws YamlException { return read(null); } /** Reads an object of the specified type from YAML. - * @param type The type of object to read. If null, behaves the same as {{@link #read()}. */ + * @param type The type of object to read. If null, behaves the same as {{@link #read()}. + * @throws YamlReaderException if the YAML data specifies a type that is incompatible with the specified type. */ public T read (Class type) throws YamlException { return read(type, null); } /** Reads an array, Map, List, or Collection object of the specified type from YAML, using the specified element type. - * @param type The type of object to read. If null, behaves the same as {{@link #read()}. */ + * @param type The type of object to read. If null, behaves the same as {{@link #read()}. + * @throws YamlReaderException if the YAML data specifies a type that is incompatible with the specified type. */ public T read (Class type, Class elementType) throws YamlException { + anchors.clear(); try { while (true) { Event event = parser.getNextEvent(); @@ -100,7 +115,9 @@ public T read (Class type, Class elementType) throws YamlException { if (event.type == STREAM_END) return null; if (event.type == DOCUMENT_START) break; } - return (T)readValue(type, elementType, null); + Object object = readValue(type, elementType, null); + parser.getNextEvent(); // consume it(DOCUMENT_END) + return (T)object; } catch (ParserException ex) { throw new YamlException("Error parsing YAML.", ex); } catch (TokenizerException ex) { @@ -108,9 +125,34 @@ public T read (Class type, Class elementType) throws YamlException { } } - /** Reads an object from the YAML. Can be overidden to take some action for any of the objects returned. */ - protected Object readValue (Class type, Class elementType, Class defaultType) throws YamlException, ParserException, - TokenizerException { + /** Returns an iterator that reads all documents from YAML into objects. + * @param type The type of object to read. If null, behaves the same as {{@link #read()}. */ + public Iterator readAll (final Class type) { + return new Iterator() { + public boolean hasNext () { + Event event = parser.peekNextEvent(); + return event != null && event.type != STREAM_END; + } + + public T next () { + try { + return read(type); + } catch (YamlException ex) { + throw new RuntimeException("Error reading YAML document for iterator.", ex); + } + } + + public void remove () { + throw new UnsupportedOperationException(); + } + }; + } + + /** Reads an object from the YAML. Can be overidden to take some action for any of the objects returned. + * @param type May be null. + * @throws YamlReaderException if the YAML data specifies a type that is incompatible with the specified type. */ + protected Object readValue (Class type, Class elementType, Class defaultType) + throws YamlException, ParserException, TokenizerException { String tag = null, anchor = null; Event event = parser.peekNextEvent(); @@ -119,7 +161,7 @@ protected Object readValue (Class type, Class elementType, Class defaultType) th parser.getNextEvent(); anchor = ((AliasEvent)event).anchor; Object value = anchors.get(anchor); - if (value == null) throw new YamlReaderException("Unknown anchor: " + anchor); + if (value == null && config.readConfig.anchors) throw new YamlReaderException("Unknown anchor: " + anchor); return value; case MAPPING_START: case SEQUENCE_START: @@ -133,34 +175,127 @@ protected Object readValue (Class type, Class elementType, Class defaultType) th default: } - if (tag != null) { - type = config.tagToClass.get(tag); - if (type == null) { - try { - if (config.readConfig.classLoader != null) - type = Class.forName(tag, true, config.readConfig.classLoader); - else - type = Class.forName(tag); - } catch (ClassNotFoundException ex) { - throw new YamlReaderException("Unable to find class specified by tag: " + tag); + return readValueInternal(this.chooseType(tag, defaultType, type), elementType, anchor); + } + + private Class chooseType (String tag, Class defaultType, Class providedType) throws YamlReaderException { + if (tag != null && config.readConfig.classTags) { + Class userConfiguredByTag = config.tagToClass.get(tag); + if (userConfiguredByTag != null) return userConfiguredByTag; + + ClassLoader classLoader = (config.readConfig.classLoader == null ? this.getClass().getClassLoader() + : config.readConfig.classLoader); + + tag = tag.replace("!", ""); + try { + Class loadedFromTag = findTagClass(tag, classLoader); + if (loadedFromTag != null) { + if (providedType != null && !providedType.isAssignableFrom(loadedFromTag)) { + throw new YamlReaderException("Class specified by tag is incompatible with expected type: " + + loadedFromTag.getName() + " (expected " + providedType.getName() + ")"); + } + return loadedFromTag; } + } catch (ClassNotFoundException e) { + throw new YamlReaderException("Unable to find class specified by tag: " + tag); } - } else if (defaultType != null) { - type = defaultType; } - return readValueInternal(type, elementType, anchor); + if (defaultType != null) return defaultType; + + return providedType; // May be null. + } + + /** Used during reading when a tag is present, and {@link YamlConfig#setClassTag(String, Class)} was not used for that tag. + * Attempts to load the class corresponding to that tag. + *

+ * If this returns a non-null Class, that will be used as the deserialization type regardless of whether a type was explicitly + * asked for or if a default type exists. + *

+ * If this returns null, no guidance will be provided by the tag and we will fall back to the default type or a requested + * target type, if any exist. + *

+ * If this throws a ClassNotFoundException, parsing will fail. + *

+ * The default implementation is simply {@code + * Class.forName(tag, true, classLoader); + * } and never returns null. + *

+ * You can override this to handle cases where you do not want to respect the type tags found in a document, eg if they were + * output by another program using classes that do not exist on your classpath. */ + protected Class findTagClass (String tag, ClassLoader classLoader) throws ClassNotFoundException { + return Class.forName(tag, true, classLoader); + } + + /** + * Reads a scalar value from the parser and converts it to the specified type. + * @param type The type to convert the scalar to. + * @param anchor The anchor of the scalar, or null. + * @return The converted scalar value. + * @throws YamlException + * @throws ParserException + */ + private Object readScalarValue(Class type, String anchor) throws YamlException, ParserException { + Event event = parser.getNextEvent(); + if (event.type != SCALAR) { + throw new YamlReaderException("Expected scalar for primitive type '" + type.getClass() + "' but found: " + event.type); + } + + String value = ((ScalarEvent) event).value; + try { + Object convertedValue; + if (value == null) { + convertedValue = null; + } else if (type == String.class) { + convertedValue = value; + } else if (type == Integer.TYPE || type == Integer.class) { + convertedValue = Integer.decode(value); + } else if (type == Boolean.TYPE || type == Boolean.class) { + convertedValue = Boolean.valueOf(value); + } else if (type == Float.TYPE || type == Float.class) { + convertedValue = Float.valueOf(value); + } else if (type == Double.TYPE || type == Double.class) { + convertedValue = Double.valueOf(value); + } else if (type == Long.TYPE || type == Long.class) { + convertedValue = Long.decode(value); + } else if (type == Short.TYPE || type == Short.class) { + convertedValue = Short.decode(value); + } else if (type == Character.TYPE || type == Character.class) { + convertedValue = value.charAt(0); + } else if (type == Byte.TYPE || type == Byte.class) { + convertedValue = Byte.decode(value); + } else { + throw new YamlException("Unknown field type."); + } + if (anchor != null) { + addAnchor(anchor, convertedValue); + } + return convertedValue; + } catch (Exception ex) { + throw new YamlReaderException("Unable to convert value to required type \"" + type + "\": " + value, ex); + } } - private Object readValueInternal (Class type, Class elementType, String anchor) throws YamlException, ParserException, - TokenizerException { + private Object readValueInternal (Class type, Class elementType, String anchor) + throws YamlException, ParserException, TokenizerException { if (type == null || type == Object.class) { Event event = parser.peekNextEvent(); switch (event.type) { case MAPPING_START: - type = HashMap.class; + type = LinkedHashMap.class; break; case SCALAR: + if (config.readConfig.guessNumberTypes) { + String value = ((ScalarEvent)event).value; + if (value != null) { + Number number = parseNumber(value); + if (number != null) { + if (anchor != null) addAnchor(anchor, number); + parser.getNextEvent(); + return number; + } + } + } type = String.class; break; case SEQUENCE_START: @@ -171,61 +306,19 @@ private Object readValueInternal (Class type, Class elementType, String anchor) } } - if (type == String.class) { - Event event = parser.getNextEvent(); - if (event.type != SCALAR) throw new YamlReaderException("Expected scalar for String type but found: " + event.type); - String value = ((ScalarEvent)event).value; - if (anchor != null) anchors.put(anchor, value); - return value; + if (Beans.isScalar(type)) { + return readScalarValue(type, anchor); } - if (Beans.isScalar(type)) { - Event event = parser.getNextEvent(); - if (event.type != SCALAR) - throw new YamlReaderException("Expected scalar for primitive type '" + type.getClass() + "' but found: " + event.type); - String value = ((ScalarEvent)event).value; - try { - Object convertedValue; - if (type == String.class) { - convertedValue = value; - } else if (type == Integer.TYPE) { - convertedValue = value.length() == 0 ? 0 : Integer.decode(value); - } else if (type == Integer.class) { - convertedValue = value.length() == 0 ? null : Integer.decode(value); - } else if (type == Boolean.TYPE) { - convertedValue = value.length() == 0 ? false : Boolean.valueOf(value); - } else if (type == Boolean.class) { - convertedValue = value.length() == 0 ? null : Boolean.valueOf(value); - } else if (type == Float.TYPE) { - convertedValue = value.length() == 0 ? 0 : Float.valueOf(value); - } else if (type == Float.class) { - convertedValue = value.length() == 0 ? null : Float.valueOf(value); - } else if (type == Double.TYPE) { - convertedValue = value.length() == 0 ? 0 : Double.valueOf(value); - } else if (type == Double.class) { - convertedValue = value.length() == 0 ? null : Double.valueOf(value); - } else if (type == Long.TYPE) { - convertedValue = value.length() == 0 ? 0 : Long.decode(value); - } else if (type == Long.class) { - convertedValue = value.length() == 0 ? null : Long.decode(value); - } else if (type == Short.TYPE) { - convertedValue = value.length() == 0 ? 0 : Short.decode(value); - } else if (type == Short.class) { - convertedValue = value.length() == 0 ? null : Short.decode(value); - } else if (type == Character.TYPE) { - convertedValue = value.length() == 0 ? 0 : value.charAt(0); - } else if (type == Character.class) { - convertedValue = value.length() == 0 ? null : value.charAt(0); - } else if (type == Byte.TYPE) { - convertedValue = value.length() == 0 ? 0 : Byte.decode(value); - } else if (type == Byte.class) { - convertedValue = value.length() == 0 ? null : Byte.decode(value); - } else - throw new YamlException("Unknown field type."); - if (anchor != null) anchors.put(anchor, convertedValue); - return convertedValue; - } catch (Exception ex) { - throw new YamlReaderException("Unable to convert value to required type \"" + type + "\": " + value, ex); + for (Entry entry : config.scalarSerializers.entrySet()) { + if (entry.getKey().isAssignableFrom(type)) { + ScalarSerializer serializer = entry.getValue(); + Event event = parser.getNextEvent(); + if (event.type != SCALAR) throw new YamlReaderException("Expected scalar for type '" + type + + "' to be deserialized by scalar serializer '" + serializer.getClass().getName() + "' but found: " + event.type); + Object value = serializer.read(((ScalarEvent)event).value); + if (anchor != null) addAnchor(anchor, value); + return value; } } @@ -233,7 +326,7 @@ private Object readValueInternal (Class type, Class elementType, String anchor) Event event = parser.getNextEvent(); if (event.type != SCALAR) throw new YamlReaderException("Expected scalar for enum type but found: " + event.type); String enumValueName = ((ScalarEvent)event).value; - if (enumValueName.length() == 0) return null; + if (enumValueName == null) return null; try { return Enum.valueOf(type, enumValueName); } catch (Exception ex) { @@ -241,19 +334,6 @@ private Object readValueInternal (Class type, Class elementType, String anchor) } } - for (Entry entry : config.scalarSerializers.entrySet()) { - if (entry.getKey().isAssignableFrom(type)) { - ScalarSerializer serializer = entry.getValue(); - Event event = parser.getNextEvent(); - if (event.type != SCALAR) - throw new YamlReaderException("Expected scalar for type '" + type + "' to be deserialized by scalar serializer '" - + serializer.getClass().getName() + "' but found: " + event.type); - Object value = serializer.read(((ScalarEvent)event).value); - if (anchor != null) anchors.put(anchor, value); - return value; - } - } - Event event = parser.peekNextEvent(); switch (event.type) { case MAPPING_START: { @@ -265,7 +345,8 @@ private Object readValueInternal (Class type, Class elementType, String anchor) } catch (InvocationTargetException ex) { throw new YamlReaderException("Error creating object.", ex); } - if (anchor != null) anchors.put(anchor, object); + if (anchor != null) addAnchor(anchor, object); + ArrayList keys = new ArrayList(); while (true) { if (parser.peekNextEvent().type == MAPPING_END) { parser.getNextEvent(); @@ -282,15 +363,54 @@ private Object readValueInternal (Class type, Class elementType, String anchor) } if (object instanceof Map) { // Add to map. + if (config.tagSuffix != null) { + Event nextEvent = parser.peekNextEvent(); + switch (nextEvent.type) { + case MAPPING_START: + case SEQUENCE_START: + ((Map)object).put(key + config.tagSuffix, ((CollectionStartEvent)nextEvent).tag); + break; + case SCALAR: + ((Map)object).put(key + config.tagSuffix, ((ScalarEvent)nextEvent).tag); + break; + } + } if (!isExplicitKey) value = readValue(elementType, null, null); - ((Map)object).put(key, value); + if (!config.allowDuplicates && ((Map)object).containsKey(key)) { + throw new YamlReaderException("Duplicate key found '" + key + "'"); + } + if (config.readConfig.autoMerge && "<<".equals(key) && value != null) + mergeMap((Map)object, value); + else + ((Map)object).put(key, value); } else { // Set field on object. try { + if (!config.allowDuplicates && keys.contains(key)) { + throw new YamlReaderException("Duplicate key found '" + key + "'"); + } + keys.add(key); + Property property = Beans.getProperty(type, (String)key, config.beanProperties, config.privateFields, config); - if (property == null) + if (property == null) { + if (config.readConfig.ignoreUnknownProperties) { + // if next event is sequence, mapping... start, go though all of it until + // corresponding sequence, mapping... end + Event nextEvent = parser.peekNextEvent(); + EventType nextType = nextEvent.type; + if (nextType == SEQUENCE_START || nextType == MAPPING_START) { + skipRange(); + } else { + // go though the next event, because this is a value of missing property + parser.getNextEvent(); + } + + continue; + } throw new YamlReaderException("Unable to find property '" + key + "' on class: " + type.getName()); + } Class propertyElementType = config.propertyToElementType.get(property); + if (propertyElementType == null) propertyElementType = property.getElementType(); Class propertyDefaultType = config.propertyToDefaultType.get(property); if (!isExplicitKey) value = readValue(property.getType(), propertyElementType, propertyDefaultType); property.set(object, value); @@ -303,7 +423,7 @@ private Object readValueInternal (Class type, Class elementType, String anchor) if (object instanceof DeferredConstruction) { try { object = ((DeferredConstruction)object).construct(); - if (anchor != null) anchors.put(anchor, object); // Update anchor with real object. + if (anchor != null) addAnchor(anchor, object); // Update anchor with real object. } catch (InvocationTargetException ex) { throw new YamlReaderException("Error creating object.", ex); } @@ -325,7 +445,7 @@ private Object readValueInternal (Class type, Class elementType, String anchor) elementType = type.getComponentType(); } else throw new YamlReaderException("A sequence is not a valid value for the type: " + type.getName()); - if (!type.isArray() && anchor != null) anchors.put(anchor, collection); + if (!type.isArray() && anchor != null) addAnchor(anchor, collection); while (true) { event = parser.peekNextEvent(); if (event.type == SEQUENCE_END) { @@ -339,22 +459,32 @@ private Object readValueInternal (Class type, Class elementType, String anchor) int i = 0; for (Object object : collection) Array.set(array, i++, object); - if (anchor != null) anchors.put(anchor, array); + if (anchor != null) addAnchor(anchor, array); return array; } - case SCALAR: - // Interpret an empty scalar as null. - System.out.println(((ScalarEvent)event).value); - if (((ScalarEvent)event).value.length() == 0) { - event = parser.getNextEvent(); - return null; - } - // Fall through. default: throw new YamlReaderException("Expected data for a " + type.getName() + " field but found: " + event.type); } } + /** see http://yaml.org/type/merge.html */ + @SuppressWarnings("unchecked") + private void mergeMap (Map dest, Object source) throws YamlReaderException { + if (source instanceof Collection) { + for (Object item : ((Collection)source)) + mergeMap(dest, item); + } else if (source instanceof Map) { + Map map = (Map)source; + for (Map.Entry entry : map.entrySet()) { + if (!dest.containsKey(entry.getKey())) dest.put(entry.getKey(), entry.getValue()); + + } + } else + throw new YamlReaderException("Expected a mapping or a sequence of mappings for a '<<' merge field but found: " + + source.getClass().getSimpleName()); + + } + /** Returns a new object of the requested type. */ protected Object createObject (Class type) throws InvocationTargetException { // Use deferred construction if a non-zero-arg constructor is available. @@ -373,8 +503,54 @@ public YamlReaderException (String message) { } } + private Number parseNumber(String value) { + Number number = null; + try { + number = Long.decode(value); + } catch (NumberFormatException e) { + } + if (number == null) { + try { + number = Double.parseDouble(value); + } catch (NumberFormatException e) { + } + } + return number; + } + + private void skipRange () { + Event nextEvent; + int depth = 0; + do { + nextEvent = parser.getNextEvent(); + switch (nextEvent.type) { + case SEQUENCE_START: + depth++; + break; + case MAPPING_START: + depth++; + break; + case SEQUENCE_END: + depth--; + break; + case MAPPING_END: + depth--; + break; + default: + // ignore + break; + } + } while (depth > 0); + } + public static void main (String[] args) throws Exception { YamlReader reader = new YamlReader(new FileReader("test/test.yml")); - System.out.println(reader.read()); + Object object = reader.read(); + System.out.println(object); + StringWriter string = new StringWriter(); + YamlWriter writer = new YamlWriter(string); + writer.write(object); + writer.close(); + System.out.println(string); } } diff --git a/src/com/esotericsoftware/yamlbeans/YamlWriter.java b/src/com/esotericsoftware/yamlbeans/YamlWriter.java index 6e62de8..9fee0f6 100644 --- a/src/com/esotericsoftware/yamlbeans/YamlWriter.java +++ b/src/com/esotericsoftware/yamlbeans/YamlWriter.java @@ -16,20 +16,6 @@ package com.esotericsoftware.yamlbeans; -import com.esotericsoftware.yamlbeans.Beans.Property; -import com.esotericsoftware.yamlbeans.YamlConfig.WriteConfig; -import com.esotericsoftware.yamlbeans.emitter.Emitter; -import com.esotericsoftware.yamlbeans.emitter.EmitterException; -import com.esotericsoftware.yamlbeans.parser.AliasEvent; -import com.esotericsoftware.yamlbeans.parser.DocumentEndEvent; -import com.esotericsoftware.yamlbeans.parser.DocumentStartEvent; -import com.esotericsoftware.yamlbeans.parser.Event; -import com.esotericsoftware.yamlbeans.parser.MappingStartEvent; -import com.esotericsoftware.yamlbeans.parser.ScalarEvent; -import com.esotericsoftware.yamlbeans.parser.SequenceStartEvent; -import com.esotericsoftware.yamlbeans.scalar.ScalarSerializer; - -import java.beans.IntrospectionException; import java.io.IOException; import java.io.Writer; import java.lang.reflect.Array; @@ -44,9 +30,24 @@ import java.util.Map.Entry; import java.util.Set; +import com.esotericsoftware.yamlbeans.Beans.Property; +import com.esotericsoftware.yamlbeans.YamlConfig.WriteClassName; +import com.esotericsoftware.yamlbeans.YamlConfig.WriteConfig; +import com.esotericsoftware.yamlbeans.document.YamlElement; +import com.esotericsoftware.yamlbeans.emitter.Emitter; +import com.esotericsoftware.yamlbeans.emitter.EmitterException; +import com.esotericsoftware.yamlbeans.parser.AliasEvent; +import com.esotericsoftware.yamlbeans.parser.DocumentEndEvent; +import com.esotericsoftware.yamlbeans.parser.DocumentStartEvent; +import com.esotericsoftware.yamlbeans.parser.Event; +import com.esotericsoftware.yamlbeans.parser.MappingStartEvent; +import com.esotericsoftware.yamlbeans.parser.ScalarEvent; +import com.esotericsoftware.yamlbeans.parser.SequenceStartEvent; +import com.esotericsoftware.yamlbeans.scalar.ScalarSerializer; + /** Serializes Java objects as YAML. * @author Nathan Sweet */ -public class YamlWriter { +public class YamlWriter implements AutoCloseable { private final YamlConfig config; private final Emitter emitter; private boolean started; @@ -91,7 +92,8 @@ private void writeInternal (Object object) throws YamlException { emitter.emit(Event.STREAM_START); started = true; } - emitter.emit(new DocumentStartEvent(config.writeConfig.explicitFirstDocument, null, null)); + emitter.emit(new DocumentStartEvent(config.writeConfig.explicitFirstDocument, config.writeConfig.version, + config.writeConfig.tags)); isRoot = true; writeValue(object, config.writeConfig.writeRootTags ? null : object.getClass(), null, null); emitter.emit(new DocumentEndEvent(config.writeConfig.explicitEndDocument)); @@ -102,11 +104,6 @@ private void writeInternal (Object object) throws YamlException { } } - /** Returns the YAML emitter, which allows the YAML output to be configured. */ - public Emitter getEmitter () { - return emitter; - } - /** Writes any buffered objects, then resets the list of anchored objects. * @see WriteConfig#setAutoAnchor(boolean) */ public void clearAnchors () throws YamlException { @@ -132,13 +129,17 @@ public void close () throws YamlException { } } - private void writeValue (Object object, Class fieldClass, Class elementType, Class defaultType) throws EmitterException, - IOException, YamlException { + private void writeValue (Object object, Class fieldClass, Class elementType, Class defaultType) + throws EmitterException, IOException, YamlException { boolean isRoot = this.isRoot; this.isRoot = false; - if (object == null) { - emitter.emit(new ScalarEvent(null, null, new boolean[] {true, true}, null, (char)0)); + if (object instanceof YamlElement) { + ((YamlElement)object).emitEvent(emitter, config.writeConfig); + return; + } else if (object == null) { + emitter.emit( + new ScalarEvent(null, null, new boolean[] { true, true }, null, this.config.writeConfig.quote.c)); return; } @@ -146,18 +147,13 @@ private void writeValue (Object object, Class fieldClass, Class elementType, Cla boolean unknownType = fieldClass == null; if (unknownType) fieldClass = valueClass; - if (object instanceof Enum) { - emitter.emit(new ScalarEvent(null, null, new boolean[] {true, true}, ((Enum)object).name(), (char)0)); - return; - } - String anchor = null; - if (!Beans.isScalar(valueClass)) { + if (!Beans.isScalar(valueClass) && !(object instanceof Enum)) { anchor = anchoredObjects.get(object); if (config.writeConfig.autoAnchor) { Integer count = referenceCount.get(object); if (count == null) { - emitter.emit(new AliasEvent(anchoredObjects.get(object))); + emitter.emit(new AliasEvent(anchor)); return; } if (count > 1) { @@ -172,7 +168,8 @@ private void writeValue (Object object, Class fieldClass, Class elementType, Cla String tag = null; boolean showTag = false; - if (unknownType || valueClass != fieldClass || config.writeConfig.alwaysWriteClassName) { + if ((unknownType || valueClass != fieldClass || config.writeConfig.writeClassName == WriteClassName.ALWAYS) + && config.writeConfig.writeClassName != WriteClassName.NEVER) { showTag = true; if ((unknownType || fieldClass == List.class) && valueClass == ArrayList.class) showTag = false; if ((unknownType || fieldClass == Map.class) && valueClass == HashMap.class) showTag = false; @@ -187,18 +184,27 @@ private void writeValue (Object object, Class fieldClass, Class elementType, Cla for (Entry entry : config.scalarSerializers.entrySet()) { if (entry.getKey().isAssignableFrom(valueClass)) { ScalarSerializer serializer = entry.getValue(); - emitter.emit(new ScalarEvent(null, tag, new boolean[] {tag == null, tag == null}, serializer.write(object), (char)0)); + emitter.emit(new ScalarEvent(null, tag, new boolean[] { tag == null, tag == null }, + serializer.write(object), this.config.writeConfig.quote.c)); return; } } if (Beans.isScalar(valueClass)) { - emitter.emit(new ScalarEvent(null, null, new boolean[] {true, true}, String.valueOf(object), (char)0)); + emitter.emit(new ScalarEvent(null, tag, new boolean[] { true, true }, String.valueOf(object), + this.config.writeConfig.quote.c)); + return; + } + + if (object instanceof Enum) { + emitter.emit(new ScalarEvent(null, object.getClass().getName(), + new boolean[] { object.getClass().equals(fieldClass), object.getClass().equals(fieldClass) }, + ((Enum) object).name(), this.config.writeConfig.quote.c)); return; } if (object instanceof Collection) { - emitter.emit(new SequenceStartEvent(anchor, tag, !showTag, false)); + emitter.emit(new SequenceStartEvent(anchor, tag, !showTag, config.writeConfig.isFlowStyle())); for (Object item : (Collection)object) { if (isRoot && !config.writeConfig.writeRootElementTags) elementType = item.getClass(); writeValue(item, elementType, null, null); @@ -208,12 +214,29 @@ private void writeValue (Object object, Class fieldClass, Class elementType, Cla } if (object instanceof Map) { - emitter.emit(new MappingStartEvent(anchor, tag, !showTag, false)); - for (Object item : ((Map)object).entrySet()) { + emitter.emit(new MappingStartEvent(anchor, tag, !showTag, config.writeConfig.isFlowStyle())); + Map map = (Map)object; + for (Object item : map.entrySet()) { Entry entry = (Entry)item; - writeValue(entry.getKey(), null, null, null); - if (isRoot && !config.writeConfig.writeRootElementTags) elementType = entry.getValue().getClass(); - writeValue(entry.getValue(), elementType, null, null); + Object key = entry.getKey(), value = entry.getValue(); + if (isRoot && !config.writeConfig.writeRootElementTags) elementType = value.getClass(); + if (config.tagSuffix != null && key instanceof String) { + // Skip tag keys. + if (((String)key).endsWith(config.tagSuffix)) continue; + + // Write value with tag, if found. + if (value instanceof String) { + Object valueTag = map.get(key + config.tagSuffix); + if (valueTag instanceof String) { + writeValue(key, null, null, null); + emitter.emit(new ScalarEvent(null, (String) valueTag, new boolean[] { false, false }, + (String) value, this.config.writeConfig.quote.c)); + continue; + } + } + } + writeValue(key, null, null, null); + writeValue(value, elementType, null, null); } emitter.emit(Event.MAPPING_END); return; @@ -221,7 +244,7 @@ private void writeValue (Object object, Class fieldClass, Class elementType, Cla if (fieldClass.isArray()) { elementType = fieldClass.getComponentType(); - emitter.emit(new SequenceStartEvent(anchor, null, true, false)); + emitter.emit(new SequenceStartEvent(anchor, null, true, config.writeConfig.isFlowStyle())); for (int i = 0, n = Array.getLength(object); i < n; i++) writeValue(Array.get(object, i), elementType, null, null); emitter.emit(Event.SEQUENCE_END); @@ -243,13 +266,8 @@ private void writeValue (Object object, Class fieldClass, Class elementType, Cla } } - Set properties; - try { - properties = Beans.getProperties(valueClass, config.beanProperties, config.privateFields, config); - } catch (IntrospectionException ex) { - throw new YamlException("Error inspecting class: " + valueClass.getName(), ex); - } - emitter.emit(new MappingStartEvent(anchor, tag, !showTag, false)); + Set properties = Beans.getProperties(valueClass, config.beanProperties, config.privateFields, config); + emitter.emit(new MappingStartEvent(anchor, tag, !showTag, config.writeConfig.isFlowStyle())); for (Property property : properties) { try { Object propertyValue = property.get(object); @@ -259,7 +277,8 @@ 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.getName(), this.config.writeConfig.quote.c)); Class propertyElementType = config.propertyToElementType.get(property); Class propertyDefaultType = config.propertyToDefaultType.get(property); writeValue(propertyValue, property.getType(), propertyElementType, propertyDefaultType); @@ -299,14 +318,9 @@ private void countObjectReferences (Object object) throws YamlException { return; } - // Value must be a bean. + // Value must be an object. - Set properties; - try { - properties = Beans.getProperties(object.getClass(), config.beanProperties, config.privateFields, config); - } catch (IntrospectionException ex) { - throw new YamlException("Error inspecting class: " + object.getClass().getName(), ex); - } + Set properties = Beans.getProperties(object.getClass(), config.beanProperties, config.privateFields, config); for (Property property : properties) { if (Beans.isScalar(property.getType())) continue; Object propertyValue; diff --git a/src/com/esotericsoftware/yamlbeans/constants/Unicode.java b/src/com/esotericsoftware/yamlbeans/constants/Unicode.java new file mode 100644 index 0000000..3581d7f --- /dev/null +++ b/src/com/esotericsoftware/yamlbeans/constants/Unicode.java @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2008 Nathan Sweet, Copyright (c) 2006 Ola Bini + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, + * modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software + * is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR + * IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package com.esotericsoftware.yamlbeans.constants; + +public final class Unicode { + + static public final char BELL = '\u0007'; + static public final char BACKSPACE = '\u0008'; + static public final char HORIZONTAL_TABULATION = '\u0009'; + static public final char VERTICAL_TABULATION = '\u000b'; + static public final char FORM_FEED = '\u000c'; + static public final char ESCAPE = '\u001b'; + static public final char SPACE = '\u0020'; + static public final char TILDE = '\u007e'; + static public final char NEXT_LINE = '\u0085'; + static public final char NO_BREAK_SPACE = '\u00a0'; + + private Unicode() { + } +} diff --git a/src/com/esotericsoftware/yamlbeans/document/YamlAlias.java b/src/com/esotericsoftware/yamlbeans/document/YamlAlias.java new file mode 100644 index 0000000..0078fb8 --- /dev/null +++ b/src/com/esotericsoftware/yamlbeans/document/YamlAlias.java @@ -0,0 +1,21 @@ +package com.esotericsoftware.yamlbeans.document; + +import java.io.IOException; + +import com.esotericsoftware.yamlbeans.YamlConfig.WriteConfig; +import com.esotericsoftware.yamlbeans.emitter.Emitter; +import com.esotericsoftware.yamlbeans.emitter.EmitterException; +import com.esotericsoftware.yamlbeans.parser.AliasEvent; + +public class YamlAlias extends YamlElement { + + @Override + public void emitEvent(Emitter emitter, WriteConfig config) throws EmitterException, IOException { + emitter.emit(new AliasEvent(anchor)); + } + + @Override + public String toString() { + return "*" + anchor; + } +} diff --git a/src/com/esotericsoftware/yamlbeans/document/YamlDocument.java b/src/com/esotericsoftware/yamlbeans/document/YamlDocument.java new file mode 100644 index 0000000..8401da1 --- /dev/null +++ b/src/com/esotericsoftware/yamlbeans/document/YamlDocument.java @@ -0,0 +1,31 @@ +package com.esotericsoftware.yamlbeans.document; + +import java.util.Iterator; + +import com.esotericsoftware.yamlbeans.YamlException; + +public interface YamlDocument { + + String getTag(); + int size(); + YamlEntry getEntry(String key) throws YamlException; + YamlEntry getEntry(int index) throws YamlException; + boolean deleteEntry(String key) throws YamlException; + void setEntry(String key, boolean value) throws YamlException; + void setEntry(String key, Number value) throws YamlException; + void setEntry(String key, String value) throws YamlException; + void setEntry(String key, YamlElement value) throws YamlException; + YamlElement getElement(int item) throws YamlException; + void deleteElement(int element) throws YamlException; + void setElement(int item, boolean value) throws YamlException; + void setElement(int item, Number value) throws YamlException; + void setElement(int item, String value) throws YamlException; + void setElement(int item, YamlElement element) throws YamlException; + void addElement(boolean value) throws YamlException; + void addElement(Number value) throws YamlException; + void addElement(String value) throws YamlException; + void addElement(YamlElement element) throws YamlException; + + Iterator iterator(); + +} diff --git a/src/com/esotericsoftware/yamlbeans/document/YamlDocumentReader.java b/src/com/esotericsoftware/yamlbeans/document/YamlDocumentReader.java new file mode 100644 index 0000000..2e3f76f --- /dev/null +++ b/src/com/esotericsoftware/yamlbeans/document/YamlDocumentReader.java @@ -0,0 +1,215 @@ +package com.esotericsoftware.yamlbeans.document; + +import static com.esotericsoftware.yamlbeans.parser.EventType.*; + +import java.io.Reader; +import java.io.StringReader; +import java.util.Iterator; + +import com.esotericsoftware.yamlbeans.Version; +import com.esotericsoftware.yamlbeans.YamlException; +import com.esotericsoftware.yamlbeans.parser.AliasEvent; +import com.esotericsoftware.yamlbeans.parser.Event; +import com.esotericsoftware.yamlbeans.parser.MappingStartEvent; +import com.esotericsoftware.yamlbeans.parser.Parser; +import com.esotericsoftware.yamlbeans.parser.Parser.ParserException; +import com.esotericsoftware.yamlbeans.parser.ScalarEvent; +import com.esotericsoftware.yamlbeans.parser.SequenceStartEvent; +import com.esotericsoftware.yamlbeans.tokenizer.Tokenizer.TokenizerException; + +public class YamlDocumentReader { + + Parser parser; + + public YamlDocumentReader(String yaml) { + this(new StringReader(yaml)); + } + + public YamlDocumentReader(String yaml, Version version) { + this(new StringReader(yaml), version); + } + + public YamlDocumentReader(Reader reader) { + this(reader, null); + } + + public YamlDocumentReader(Reader reader, Version version) { + if(version==null) + version = Version.DEFAULT_VERSION; + parser = new Parser(reader, version); + } + + public YamlDocument read() throws YamlException { + return read(YamlDocument.class); + } + + @SuppressWarnings("unchecked") + public T read(Class type) throws YamlException { + try { + while (true) { + Event event = parser.peekNextEvent(); + if (event == null) + return null; + switch (event.type) { + case STREAM_START: + parser.getNextEvent(); // consume it + break; + case STREAM_END: + parser.getNextEvent(); // consume it + return null; + case DOCUMENT_START: + parser.getNextEvent(); // consume it + return (T)readDocument(); + default: + throw new IllegalStateException(); + } + } + } catch (ParserException ex) { + throw new YamlException("Error parsing YAML.", ex); + } catch (TokenizerException ex) { + throw new YamlException("Error tokenizing YAML.", ex); + } + } + + public Iterator readAll(final Class type) { + Iterator iterator = new Iterator() { + + public boolean hasNext() { + Event event = parser.peekNextEvent(); + return event != null && event.type != STREAM_END; + } + + public T next() { + try { + return read(type); + } catch (YamlException e) { + throw new RuntimeException("Iterative reading documents exception", e); + } + } + + public void remove() { + throw new UnsupportedOperationException(); + } + }; + + return iterator; + } + + private YamlElement readDocument() { + YamlElement yamlElement = null; + Event event = parser.peekNextEvent(); + switch (event.type) { + case SCALAR: + yamlElement = readScalar(); + break; + case ALIAS: + yamlElement = readAlias(); + break; + case MAPPING_START: + yamlElement = readMapping(); + break; + case SEQUENCE_START: + yamlElement = readSequence(); + break; + default: + throw new IllegalStateException(); + } + parser.getNextEvent(); // consume it(DOCUMENT_END) + return yamlElement; + } + + private YamlMapping readMapping() { + Event event = parser.getNextEvent(); + if(event.type!=MAPPING_START) + throw new IllegalStateException(); + YamlMapping element = new YamlMapping(); + MappingStartEvent mapping = (MappingStartEvent)event; + element.setTag(mapping.tag); + element.setAnchor(mapping.anchor); + readMappingElements(element); + return element; + } + + private void readMappingElements(YamlMapping mapping) { + while(true) { + Event event = parser.peekNextEvent(); + if(event.type == MAPPING_END) { + parser.getNextEvent(); // consume it + return; + } else { + YamlEntry entry = readEntry(); + mapping.addEntry(entry); + } + } + } + + private YamlEntry readEntry() { + YamlScalar scalar = readScalar(); + YamlElement value = readValue(); + return new YamlEntry(scalar, value); + } + + private YamlElement readValue() { + Event event = parser.peekNextEvent(); + switch(event.type) { + case SCALAR: + return readScalar(); + case ALIAS: + return readAlias(); + case MAPPING_START: + return readMapping(); + case SEQUENCE_START: + return readSequence(); + default: + throw new IllegalStateException(); + } + } + + private YamlAlias readAlias() { + Event event = parser.getNextEvent(); + if(event.type!=ALIAS) + throw new IllegalStateException(); + YamlAlias element = new YamlAlias(); + AliasEvent alias = (AliasEvent)event; + element.setAnchor(alias.anchor); + return element; + } + + private YamlSequence readSequence() { + Event event = parser.getNextEvent(); + if(event.type!=SEQUENCE_START) + throw new IllegalStateException(); + YamlSequence element = new YamlSequence(); + SequenceStartEvent sequence = (SequenceStartEvent)event; + element.setTag(sequence.tag); + element.setAnchor(sequence.anchor); + readSequenceElements(element); + return element; + } + + private void readSequenceElements(YamlSequence sequence) { + while(true) { + Event event = parser.peekNextEvent(); + if(event.type==SEQUENCE_END) { + parser.getNextEvent(); // consume it + return; + } else { + YamlElement element = readValue(); + sequence.addElement(element); + } + } + } + + private YamlScalar readScalar() { + Event event = parser.getNextEvent(); + if(event.type!= SCALAR) + throw new IllegalStateException(); + ScalarEvent scalar = (ScalarEvent)event; + YamlScalar element = new YamlScalar(); + element.setTag(scalar.tag); + element.setAnchor(scalar.anchor); + element.setValue(scalar.value); + return element; + } + +} diff --git a/src/com/esotericsoftware/yamlbeans/document/YamlElement.java b/src/com/esotericsoftware/yamlbeans/document/YamlElement.java new file mode 100644 index 0000000..b350aa5 --- /dev/null +++ b/src/com/esotericsoftware/yamlbeans/document/YamlElement.java @@ -0,0 +1,31 @@ +package com.esotericsoftware.yamlbeans.document; + +import java.io.IOException; + +import com.esotericsoftware.yamlbeans.YamlConfig.WriteConfig; +import com.esotericsoftware.yamlbeans.emitter.Emitter; +import com.esotericsoftware.yamlbeans.emitter.EmitterException; + +public abstract class YamlElement { + + String tag; + String anchor; + + public void setTag(String tag) { + this.tag = tag; + } + + public void setAnchor(String anchor) { + this.anchor = anchor; + } + + public String getTag() { + return tag; + } + + public String getAnchor() { + return anchor; + } + + public abstract void emitEvent(Emitter emitter, WriteConfig config) throws EmitterException, IOException; +} diff --git a/src/com/esotericsoftware/yamlbeans/document/YamlEntry.java b/src/com/esotericsoftware/yamlbeans/document/YamlEntry.java new file mode 100644 index 0000000..43af578 --- /dev/null +++ b/src/com/esotericsoftware/yamlbeans/document/YamlEntry.java @@ -0,0 +1,65 @@ +package com.esotericsoftware.yamlbeans.document; + +import java.io.IOException; + +import com.esotericsoftware.yamlbeans.YamlConfig.WriteConfig; +import com.esotericsoftware.yamlbeans.emitter.Emitter; +import com.esotericsoftware.yamlbeans.emitter.EmitterException; +import com.esotericsoftware.yamlbeans.parser.ScalarEvent; + +public class YamlEntry { + + YamlScalar key; + YamlElement value; + + public YamlEntry(YamlScalar key, YamlElement value) { + this.key = key; + this.value = value; + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append(key.toString()); + sb.append(':'); + sb.append(value.toString()); + return sb.toString(); + } + + public YamlScalar getKey() { + return key; + } + + public YamlElement getValue() { + return value; + } + + public void setKey(YamlScalar key) { + this.key = key; + } + + public void setValue(YamlElement value) { + this.value = value; + } + + public void setValue(boolean value) { + this.value = new YamlScalar(value); + } + + public void setValue(Number value) { + this.value = new YamlScalar(value); + } + + public void setValue(String value) { + this.value = new YamlScalar(value); + } + + public void emitEvent(Emitter emitter, WriteConfig config) throws EmitterException, IOException { + key.emitEvent(emitter, config); + if(value==null) + emitter.emit(new ScalarEvent(null, null, new boolean[] {true, true}, null, config.getQuote().getStyle())); + else + value.emitEvent(emitter, config); + } + +} diff --git a/src/com/esotericsoftware/yamlbeans/document/YamlMapping.java b/src/com/esotericsoftware/yamlbeans/document/YamlMapping.java new file mode 100644 index 0000000..eae6097 --- /dev/null +++ b/src/com/esotericsoftware/yamlbeans/document/YamlMapping.java @@ -0,0 +1,149 @@ +package com.esotericsoftware.yamlbeans.document; + +import java.io.IOException; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; + +import com.esotericsoftware.yamlbeans.YamlException; +import com.esotericsoftware.yamlbeans.YamlConfig.WriteConfig; +import com.esotericsoftware.yamlbeans.emitter.Emitter; +import com.esotericsoftware.yamlbeans.emitter.EmitterException; +import com.esotericsoftware.yamlbeans.parser.Event; +import com.esotericsoftware.yamlbeans.parser.MappingStartEvent; + +public class YamlMapping extends YamlElement implements YamlDocument { + + // use a list to keep the sequence + List entries = new LinkedList(); + + public int size() { + return entries.size(); + } + + public void addEntry(YamlEntry entry) { + entries.add(entry); + } + + public boolean deleteEntry(String key) { + for(int index = 0; index < entries.size(); index++) { + if(key.equals(entries.get(index).getKey().getValue())) { + entries.remove(index); + return true; + } + } + return false; + } + + public YamlEntry getEntry(String key) throws YamlException { + for(YamlEntry entry : entries) { + if(key.equals(entry.getKey().getValue())) + return entry; + } + return null; + } + + public YamlEntry getEntry(int index) throws YamlException { + return entries.get(index); + } + + + @Override + public String toString() { + StringBuffer sb = new StringBuffer(); + if(anchor!=null) { + sb.append('&'); + sb.append(anchor); + sb.append(' '); + } + if(tag!=null) { + sb.append(" !"); + sb.append(tag); + } + if(!entries.isEmpty()) { + sb.append('{'); + for(YamlEntry entry : entries) { + sb.append(entry.toString()); + sb.append(','); + } + sb.setLength(sb.length() - 1); + sb.append('}'); + } + return sb.toString(); + } + + @Override + public void emitEvent(Emitter emitter, WriteConfig config) throws EmitterException, IOException { + emitter.emit(new MappingStartEvent(anchor, tag, tag==null, config.isFlowStyle())); + for(YamlEntry entry : entries) + entry.emitEvent(emitter, config); + emitter.emit(Event.MAPPING_END); + } + + public void setEntry(String key, boolean value) throws YamlException { + setEntry(key, new YamlScalar(value)); + } + + public void setEntry(String key, Number value) throws YamlException { + setEntry(key, new YamlScalar(value)); + } + + public void setEntry(String key, String value) throws YamlException { + setEntry(key, new YamlScalar(value)); + } + + public void setEntry(String key, YamlElement value) throws YamlException { + YamlEntry entry = getEntry(key); + if(entry!=null) + entry.setValue(value); + else { + entry = new YamlEntry(new YamlScalar(key), value); + addEntry(entry); + } + + } + + public YamlElement getElement(int item) throws YamlException { + throw new YamlException("Can only get element on sequence!"); + } + + public void deleteElement(int element) throws YamlException { + throw new YamlException("Can only delete element on sequence!"); + } + + public void setElement(int item, boolean element) throws YamlException { + throw new YamlException("Can only set element on sequence!"); + } + + public void setElement(int item, Number element) throws YamlException { + throw new YamlException("Can only set element on sequence!"); + } + + public void setElement(int item, String element) throws YamlException { + throw new YamlException("Can only set element on sequence!"); + } + + public void setElement(int item, YamlElement element) throws YamlException { + throw new YamlException("Can only set element on sequence!"); + } + + public void addElement(boolean element) throws YamlException { + throw new YamlException("Can only add element on sequence!"); + } + + public void addElement(Number element) throws YamlException { + throw new YamlException("Can only add element on sequence!"); + } + + public void addElement(String element) throws YamlException { + throw new YamlException("Can only add element on sequence!"); + } + + public void addElement(YamlElement element) throws YamlException { + throw new YamlException("Can only add element on sequence!"); + } + + public Iterator iterator() { + return entries.iterator(); + } +} diff --git a/src/com/esotericsoftware/yamlbeans/document/YamlScalar.java b/src/com/esotericsoftware/yamlbeans/document/YamlScalar.java new file mode 100644 index 0000000..9050d3c --- /dev/null +++ b/src/com/esotericsoftware/yamlbeans/document/YamlScalar.java @@ -0,0 +1,49 @@ +package com.esotericsoftware.yamlbeans.document; + +import java.io.IOException; + +import com.esotericsoftware.yamlbeans.YamlConfig.WriteConfig; +import com.esotericsoftware.yamlbeans.emitter.Emitter; +import com.esotericsoftware.yamlbeans.emitter.EmitterException; +import com.esotericsoftware.yamlbeans.parser.ScalarEvent; + +public class YamlScalar extends YamlElement { + + String value; + + public YamlScalar() { + } + + public YamlScalar(Object value) { + this.value = String.valueOf(value); + } + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + if(anchor!=null) { + sb.append('&'); + sb.append(anchor); + sb.append(' '); + } + sb.append(value); + if(tag!=null) { + sb.append(" !"); + sb.append(tag); + } + return sb.toString(); + } + + @Override + public void emitEvent(Emitter emitter, WriteConfig config) throws EmitterException, IOException { + emitter.emit(new ScalarEvent(anchor, tag, new boolean[] {true, true}, value, config.getQuote().getStyle())); + } +} diff --git a/src/com/esotericsoftware/yamlbeans/document/YamlSequence.java b/src/com/esotericsoftware/yamlbeans/document/YamlSequence.java new file mode 100644 index 0000000..1519ac2 --- /dev/null +++ b/src/com/esotericsoftware/yamlbeans/document/YamlSequence.java @@ -0,0 +1,127 @@ +package com.esotericsoftware.yamlbeans.document; + +import java.io.IOException; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; + +import com.esotericsoftware.yamlbeans.YamlConfig.WriteConfig; +import com.esotericsoftware.yamlbeans.YamlException; +import com.esotericsoftware.yamlbeans.emitter.Emitter; +import com.esotericsoftware.yamlbeans.emitter.EmitterException; +import com.esotericsoftware.yamlbeans.parser.Event; +import com.esotericsoftware.yamlbeans.parser.SequenceStartEvent; + +public class YamlSequence extends YamlElement implements YamlDocument { + + List elements = new LinkedList(); + + public int size() { + return elements.size(); + } + + public void addElement(YamlElement element) { + elements.add(element); + } + + public void deleteElement(int item) throws YamlException { + elements.remove(item); + } + + public YamlElement getElement(int item) throws YamlException { + return elements.get(item); + } + + @Override + public String toString() { + StringBuffer sb = new StringBuffer(); + if(anchor!=null) { + sb.append('&'); + sb.append(anchor); + sb.append(' '); + } + if(tag!=null) { + sb.append(" !"); + sb.append(tag); + } + if(!elements.isEmpty()) { + sb.append('['); + for(YamlElement element : elements) { + sb.append(element.toString()); + sb.append(','); + } + sb.setLength(sb.length() - 1); + sb.append(']'); + } + return sb.toString(); + } + + + @Override + public void emitEvent(Emitter emitter, WriteConfig config) throws EmitterException, IOException { + emitter.emit(new SequenceStartEvent(anchor, tag, tag==null, config.isFlowStyle())); + for (YamlElement element : elements) + element.emitEvent(emitter, config); + emitter.emit(Event.SEQUENCE_END); + } + + public YamlEntry getEntry(String key) throws YamlException { + throw new YamlException("Can only get entry on mapping!"); + } + + public YamlEntry getEntry(int index) throws YamlException { + throw new YamlException("Can only get entry on mapping!"); + } + + public boolean deleteEntry(String key) throws YamlException { + throw new YamlException("Can only delete entry on mapping!"); + } + + public void setEntry(String key, boolean value) throws YamlException { + throw new YamlException("Can only set entry on mapping!"); + } + + public void setEntry(String key, Number value) throws YamlException { + throw new YamlException("Can only set entry on mapping!"); + } + + public void setEntry(String key, String value) throws YamlException { + throw new YamlException("Can only set entry on mapping!"); + } + + public void setEntry(String key, YamlElement value) throws YamlException { + throw new YamlException("Can only set entry on mapping!"); + } + + public void setElement(int item, boolean value) throws YamlException { + elements.set(item, new YamlScalar(value)); + } + + public void setElement(int item, Number value) throws YamlException { + elements.set(item, new YamlScalar(value)); + } + + public void setElement(int item, String value) throws YamlException { + elements.set(item, new YamlScalar(value)); + } + + public void setElement(int item, YamlElement element) throws YamlException { + elements.set(item, element); + } + + public void addElement(boolean value) throws YamlException { + elements.add(new YamlScalar(value)); + } + + public void addElement(Number value) throws YamlException { + elements.add(new YamlScalar(value)); + } + + public void addElement(String value) throws YamlException { + elements.add(new YamlScalar(value)); + } + + public Iterator iterator() { + return elements.iterator(); + } +} diff --git a/src/com/esotericsoftware/yamlbeans/emitter/Emitter.java b/src/com/esotericsoftware/yamlbeans/emitter/Emitter.java index 30436d0..16735dc 100644 --- a/src/com/esotericsoftware/yamlbeans/emitter/Emitter.java +++ b/src/com/esotericsoftware/yamlbeans/emitter/Emitter.java @@ -18,20 +18,18 @@ import static com.esotericsoftware.yamlbeans.parser.EventType.*; +import com.esotericsoftware.yamlbeans.Version; import com.esotericsoftware.yamlbeans.parser.CollectionStartEvent; import com.esotericsoftware.yamlbeans.parser.DocumentEndEvent; import com.esotericsoftware.yamlbeans.parser.DocumentStartEvent; import com.esotericsoftware.yamlbeans.parser.Event; import com.esotericsoftware.yamlbeans.parser.MappingStartEvent; import com.esotericsoftware.yamlbeans.parser.NodeEvent; -import com.esotericsoftware.yamlbeans.parser.Parser; import com.esotericsoftware.yamlbeans.parser.ScalarEvent; import com.esotericsoftware.yamlbeans.parser.SequenceStartEvent; import java.io.BufferedWriter; -import java.io.FileReader; import java.io.IOException; -import java.io.OutputStreamWriter; import java.io.Writer; import java.util.ArrayList; import java.util.HashMap; @@ -140,11 +138,11 @@ public void expect () throws IOException { if (event.type == DOCUMENT_START) { DocumentStartEvent documentStartEvent = (DocumentStartEvent)event; if (documentStartEvent.version != null) { - if (documentStartEvent.version.major != 1) + if (documentStartEvent.version.getMajor() != 1) throw new EmitterException("Unsupported YAML version: " + documentStartEvent.version); writer.writeVersionDirective(documentStartEvent.version.toString()); } - if ((documentStartEvent.version != null && documentStartEvent.version.equals(1, 0)) || config.version.equals(1, 0)) { + if ((documentStartEvent.version == Version.V1_0)) { isVersion10 = true; tagPrefixes = new HashMap(DEFAULT_TAG_PREFIXES_1_0); } else @@ -204,6 +202,9 @@ public void expect () throws IOException { writer.writeIndicator("]", false, false, false); state = states.remove(0); } else { + if (config.prettyFlow) { + writer.writeIndent(flowLevel * config.indentSize); + } if (config.canonical || writer.column > config.wrapColumn) writer.writeIndent(indent); states.add(0, S_FLOW_SEQUENCE_ITEM); expectNode(false, true, false, false); @@ -215,14 +216,19 @@ public void expect () throws IOException { if (event.type == SEQUENCE_END) { indent = indents.remove(0); flowLevel--; + if (config.prettyFlow) { + writer.writeIndent(flowLevel * config.indentSize); + } if (config.canonical) { - writer.writeIndicator(",", false, false, false); writer.writeIndent(indent); } writer.writeIndicator("]", false, false, false); state = states.remove(0); } else { writer.writeIndicator(",", false, false, false); + if (config.prettyFlow) { + writer.writeIndent(flowLevel * config.indentSize); + } if (config.canonical || writer.column > config.wrapColumn) writer.writeIndent(indent); states.add(0, S_FLOW_SEQUENCE_ITEM); expectNode(false, true, false, false); @@ -237,6 +243,9 @@ public void expect () throws IOException { writer.writeIndicator("}", false, false, false); state = states.remove(0); } else { + if (config.prettyFlow) { + writer.writeIndent(flowLevel * config.indentSize); + } if (config.canonical || writer.column > config.wrapColumn) writer.writeIndent(indent); if (!config.canonical && checkSimpleKey()) { states.add(0, S_FLOW_MAPPING_SIMPLE_VALUE); @@ -269,14 +278,19 @@ public void expect () throws IOException { if (event.type == MAPPING_END) { indent = indents.remove(0); flowLevel--; + if (config.prettyFlow) { + writer.writeIndent(flowLevel * config.indentSize); + } if (config.canonical) { - writer.writeIndicator(",", false, false, false); writer.writeIndent(indent); } writer.writeIndicator("}", false, false, false); state = states.remove(0); } else { writer.writeIndicator(",", false, false, false); + if (config.prettyFlow) { + writer.writeIndent(flowLevel * config.indentSize); + } if (config.canonical || writer.column > config.wrapColumn) writer.writeIndent(indent); if (!config.canonical && checkSimpleKey()) { states.add(0, S_FLOW_MAPPING_SIMPLE_VALUE); @@ -478,7 +492,7 @@ boolean checkSimpleKey () { length += analysis.scalar.length(); } - return length < 128 + return length < 1024 && (event.type == ALIAS || event.type == SCALAR && !analysis.empty && !analysis.multiline || checkEmptySequence() || checkEmptyMapping()); } @@ -526,14 +540,20 @@ private char chooseScalarStyle () { ScalarEvent ev = (ScalarEvent)event; if (analysis == null) analysis = ScalarAnalysis.analyze(ev.value, config.escapeUnicode); if (ev.style == '"' || config.canonical) return '"'; - if (ev.style == 0 && !(simpleKeyContext && (analysis.empty || analysis.multiline)) - && (flowLevel != 0 && analysis.allowFlowPlain || flowLevel == 0 && analysis.allowBlockPlain)) return 0; - if (ev.style == 0 && ev.implicit[0] && !(simpleKeyContext && (analysis.empty || analysis.multiline)) - && (flowLevel != 0 && analysis.allowFlowPlain || flowLevel == 0 && analysis.allowBlockPlain)) return 0; - if ((ev.style == '|' || ev.style == '>') && flowLevel == 0 && analysis.allowBlock) return '\''; - if ((ev.style == 0 || ev.style == '\'') && analysis.allowSingleQuoted && !(simpleKeyContext && analysis.multiline)) + if ((ev.style == 0 || ev.style == '|' || ev.style == '>') + && !(simpleKeyContext && (analysis.empty || analysis.multiline)) + && ((flowLevel != 0 && analysis.allowFlowPlain) || (flowLevel == 0 && analysis.allowBlockPlain))) { + return 0; + } + if ((ev.style == 0 || ev.style == '\'') && analysis.allowSingleQuoted + && !(simpleKeyContext && analysis.multiline)) { return '\''; - if (ev.style == 0 && analysis.multiline && flowLevel == 0 && analysis.allowBlock) return '|'; + } + if ((ev.style == 0 || ev.style == '|' || ev.style == '>') && analysis.multiline && flowLevel == 0 + && analysis.allowBlock) { + return ev.style == 0 ? '|' : ev.style; + } + return '"'; } @@ -640,14 +660,4 @@ private interface EmitterState { DEFAULT_TAG_PREFIXES_1_1.put("!", "!"); DEFAULT_TAG_PREFIXES_1_1.put("tag:yaml.org,2002:", "!!"); } - - public static void main (String[] args) throws IOException { - Parser parser = new Parser(new FileReader("test/test.yml")); - Emitter emitter = new Emitter(new OutputStreamWriter(System.out)); - while (true) { - Event event = parser.getNextEvent(); - if (event == null) break; - emitter.emit(event); - } - } } diff --git a/src/com/esotericsoftware/yamlbeans/emitter/EmitterConfig.java b/src/com/esotericsoftware/yamlbeans/emitter/EmitterConfig.java index be73ae5..5e313b5 100644 --- a/src/com/esotericsoftware/yamlbeans/emitter/EmitterConfig.java +++ b/src/com/esotericsoftware/yamlbeans/emitter/EmitterConfig.java @@ -16,22 +16,14 @@ package com.esotericsoftware.yamlbeans.emitter; -import com.esotericsoftware.yamlbeans.Version; - /** @author Nathan Sweet */ public class EmitterConfig { - Version version = new Version(1, 1); boolean canonical; boolean useVerbatimTags = true; int indentSize = 3; int wrapColumn = 100; boolean escapeUnicode = true; - - /** Sets the YAML version to output. Default is 1.1. */ - public void setVersion (Version version) { - if (version == null) throw new IllegalArgumentException("version cannot be null."); - this.version = version; - } + boolean prettyFlow; /** If true, the YAML output will be canonical. Default is false. */ public void setCanonical (boolean canonical) { @@ -50,7 +42,7 @@ public void setWrapColumn (int wrapColumn) { this.wrapColumn = wrapColumn; } - /** If false, tags will never be surrounded by angle brackets (eg, "!"). Default is true. */ + /** If false, tags will never be surrounded by angle brackets (eg, "!<java.util.LinkedList>"). Default is true. */ public void setUseVerbatimTags (boolean useVerbatimTags) { this.useVerbatimTags = useVerbatimTags; } @@ -59,4 +51,9 @@ public void setUseVerbatimTags (boolean useVerbatimTags) { public void setEscapeUnicode (boolean escapeUnicode) { this.escapeUnicode = escapeUnicode; } + + /** If true, the YAML output will be pretty flow. Default is false. */ + public void setPrettyFlow(boolean prettyFlow) { + this.prettyFlow = prettyFlow; + } } diff --git a/src/com/esotericsoftware/yamlbeans/emitter/EmitterWriter.java b/src/com/esotericsoftware/yamlbeans/emitter/EmitterWriter.java index 7cdebc0..fd9efa9 100644 --- a/src/com/esotericsoftware/yamlbeans/emitter/EmitterWriter.java +++ b/src/com/esotericsoftware/yamlbeans/emitter/EmitterWriter.java @@ -21,24 +21,26 @@ import java.util.HashMap; import java.util.Map; +import com.esotericsoftware.yamlbeans.constants.Unicode; + /** @author Nathan Sweet * @author Ola Bini */ class EmitterWriter { private static final Map ESCAPE_REPLACEMENTS = new HashMap(); static { ESCAPE_REPLACEMENTS.put((int)'\0', "0"); - ESCAPE_REPLACEMENTS.put((int)'\u0007', "a"); - ESCAPE_REPLACEMENTS.put((int)'\u0008', "b"); - ESCAPE_REPLACEMENTS.put((int)'\u0009', "t"); + ESCAPE_REPLACEMENTS.put((int)Unicode.BELL, "a"); + ESCAPE_REPLACEMENTS.put((int)Unicode.BACKSPACE, "b"); + ESCAPE_REPLACEMENTS.put((int)Unicode.HORIZONTAL_TABULATION, "t"); ESCAPE_REPLACEMENTS.put((int)'\n', "n"); - ESCAPE_REPLACEMENTS.put((int)'\u000b', "v"); - ESCAPE_REPLACEMENTS.put((int)'\u000c', "f"); + ESCAPE_REPLACEMENTS.put((int)Unicode.VERTICAL_TABULATION, "v"); + ESCAPE_REPLACEMENTS.put((int)Unicode.FORM_FEED, "f"); ESCAPE_REPLACEMENTS.put((int)'\r', "r"); - ESCAPE_REPLACEMENTS.put((int)'\u001b', "e"); + ESCAPE_REPLACEMENTS.put((int)Unicode.ESCAPE, "e"); ESCAPE_REPLACEMENTS.put((int)'"', "\""); ESCAPE_REPLACEMENTS.put((int)'\\', "\\"); - ESCAPE_REPLACEMENTS.put((int)'\u0085', "N"); - ESCAPE_REPLACEMENTS.put((int)'\u00a0', "_"); + ESCAPE_REPLACEMENTS.put((int)Unicode.NEXT_LINE, "N"); + ESCAPE_REPLACEMENTS.put((int)Unicode.NO_BREAK_SPACE, "_"); } private final Writer writer; @@ -105,7 +107,7 @@ public void writeDoubleQuoted (String text, boolean split, int indent, int wrapC while (ending <= text.length()) { int ch = 0; if (ending < text.length()) ch = text.codePointAt(ending); - if (ch == 0 || "\"\\\u0085".indexOf(ch) != -1 || !('\u0020' <= ch && ch <= '\u007E')) { + if (ch == 0 || "\"\\\u0085".indexOf(ch) != -1 || !(Unicode.SPACE <= ch && ch <= Unicode.TILDE)) { if (start < ending) { data = text.substring(start, ending); column += data.length(); @@ -180,7 +182,7 @@ public void writeSingleQuoted (String text, boolean split, int indent, int wrapC start = ending; } } else if (breaks) { - if (ceh == 0 || !('\n' == ceh || '\u0085' == ceh)) { + if (ceh == 0 || !('\n' == ceh || Unicode.NEXT_LINE == ceh)) { data = text.substring(start, ending); for (int i = 0, j = data.length(); i < j; i++) { char cha = data.charAt(i); @@ -192,7 +194,7 @@ public void writeSingleQuoted (String text, boolean split, int indent, int wrapC writeIndent(indent); start = ending; } - } else if (ceh == 0 || !('\n' == ceh || '\u0085' == ceh)) { + } else if (ceh == 0 || !('\n' == ceh || Unicode.NEXT_LINE == ceh)) { if (start < ending) { data = text.substring(start, ending); column += data.length(); @@ -208,7 +210,7 @@ public void writeSingleQuoted (String text, boolean split, int indent, int wrapC } if (ceh != 0) { spaces = ceh == ' '; - breaks = ceh == '\n' || ceh == '\u0085'; + breaks = ceh == '\n' || ceh == Unicode.NEXT_LINE; } ending++; } @@ -228,7 +230,7 @@ public void writeFolded (String text, int indent, int wrapColumn) throws IOExcep char ceh = 0; if (ending < text.length()) ceh = text.charAt(ending); if (breaks) { - if (ceh == 0 || !('\n' == ceh || '\u0085' == ceh)) { + if (ceh == 0 || !('\n' == ceh || Unicode.NEXT_LINE == ceh)) { if (!leadingSpace && ceh != 0 && ceh != ' ' && text.charAt(start) == '\n') writeLineBreak(null); leadingSpace = ceh == ' '; data = text.substring(start, ending); @@ -253,14 +255,14 @@ public void writeFolded (String text, int indent, int wrapColumn) throws IOExcep } start = ending; } - } else if (ceh == 0 || ' ' == ceh || '\n' == ceh || '\u0085' == ceh) { + } else if (ceh == 0 || ' ' == ceh || '\n' == ceh || Unicode.NEXT_LINE == ceh) { data = text.substring(start, ending); writer.write(data); if (ceh == 0) writeLineBreak(null); start = ending; } if (ceh != 0) { - breaks = '\n' == ceh || '\u0085' == ceh; + breaks = '\n' == ceh || Unicode.NEXT_LINE == ceh; spaces = ceh == ' '; } ending++; @@ -278,7 +280,7 @@ public void writeLiteral (String text, int indent) throws IOException { char ceh = 0; if (ending < text.length()) ceh = text.charAt(ending); if (breaks) { - if (ceh == 0 || !('\n' == ceh || '\u0085' == ceh)) { + if (ceh == 0 || !('\n' == ceh || Unicode.NEXT_LINE == ceh)) { data = text.substring(start, ending); for (int i = 0, j = data.length(); i < j; i++) { char cha = data.charAt(i); @@ -290,13 +292,13 @@ public void writeLiteral (String text, int indent) throws IOException { if (ceh != 0) writeIndent(indent); start = ending; } - } else if (ceh == 0 || '\n' == ceh || '\u0085' == ceh) { + } else if (ceh == 0 || '\n' == ceh || Unicode.NEXT_LINE == ceh) { data = text.substring(start, ending); writer.write(data); if (ceh == 0) writeLineBreak(null); start = ending; } - if (ceh != 0) breaks = '\n' == ceh || '\u0085' == ceh; + if (ceh != 0) breaks = '\n' == ceh || Unicode.NEXT_LINE == ceh; ending++; } } @@ -330,7 +332,7 @@ public void writePlain (String text, boolean split, int indent, int wrapColumn) start = ending; } } else if (breaks) { - if (ceh != '\n' && ceh != '\u0085') { + if (ceh != '\n' && ceh != Unicode.NEXT_LINE) { if (text.charAt(start) == '\n') writeLineBreak(null); data = text.substring(start, ending); for (int i = 0, j = data.length(); i < j; i++) { @@ -345,7 +347,7 @@ public void writePlain (String text, boolean split, int indent, int wrapColumn) indentation = false; start = ending; } - } else if (ceh == 0 || ' ' == ceh || '\n' == ceh || '\u0085' == ceh) { + } else if (ceh == 0 || ' ' == ceh || '\n' == ceh || Unicode.NEXT_LINE == ceh) { data = text.substring(start, ending); column += data.length(); writer.write(data); @@ -353,14 +355,14 @@ public void writePlain (String text, boolean split, int indent, int wrapColumn) } if (ceh != 0) { spaces = ceh == ' '; - breaks = ceh == '\n' || ceh == '\u0085'; + breaks = ceh == '\n' || ceh == Unicode.NEXT_LINE; } ending++; } } public void writeLineBreak (String data) throws IOException { - if (data == null) data = "\n"; + if (data == null) data = System.getProperty("line.separator"); whitespace = true; indentation = true; column = 0; @@ -377,7 +379,7 @@ private String determineChomp (String text) { tail = " " + tail; char ceh = tail.charAt(tail.length() - 1); char ceh2 = tail.charAt(tail.length() - 2); - return ceh == '\n' || ceh == '\u0085' ? ceh2 == '\n' || ceh2 == '\u0085' ? "+" : "" : "-"; + return ceh == '\n' || ceh == Unicode.NEXT_LINE ? ceh2 == '\n' || ceh2 == Unicode.NEXT_LINE ? "+" : "" : "-"; } public void close () throws IOException { diff --git a/src/com/esotericsoftware/yamlbeans/emitter/ScalarAnalysis.java b/src/com/esotericsoftware/yamlbeans/emitter/ScalarAnalysis.java index f1b3384..02b78f4 100644 --- a/src/com/esotericsoftware/yamlbeans/emitter/ScalarAnalysis.java +++ b/src/com/esotericsoftware/yamlbeans/emitter/ScalarAnalysis.java @@ -18,6 +18,8 @@ import java.util.regex.Pattern; +import com.esotericsoftware.yamlbeans.constants.Unicode; + /** @author Nathan Sweet * @author Ola Bini */ class ScalarAnalysis { @@ -48,7 +50,8 @@ private ScalarAnalysis (String scalar, boolean empty, boolean multiline, boolean } static public ScalarAnalysis analyze (String scalar, boolean escapeUnicode) { - if (scalar == null || "".equals(scalar)) return new ScalarAnalysis(scalar, true, false, false, true, true, true, false); + if (scalar == null) return new ScalarAnalysis(scalar, true, false, false, true, true, true, false); + if ("".equals(scalar)) return new ScalarAnalysis(scalar, false, false, false, false, false, true, false); boolean blockIndicators = false; boolean flowIndicators = false; boolean lineBreaks = false; @@ -105,11 +108,11 @@ static public ScalarAnalysis analyze (String scalar, boolean escapeUnicode) { blockIndicators = true; } } - if (ceh == '\n' || '\u0085' == ceh) lineBreaks = true; + if (ceh == '\n' || Unicode.NEXT_LINE == ceh) lineBreaks = true; if (escapeUnicode) { - if (ceh != '\n' && ceh != '\t' && !('\u0020' <= ceh && ceh <= '\u007E')) specialCharacters = true; + if (ceh != '\n' && ceh != '\t' && !(Unicode.SPACE <= ceh && ceh <= Unicode.TILDE)) specialCharacters = true; } - if (' ' == ceh || '\n' == ceh || '\u0085' == ceh) { + if (' ' == ceh || '\n' == ceh || Unicode.NEXT_LINE == ceh) { if (spaces && breaks) { if (ceh != ' ') mixed = true; } else if (spaces) { diff --git a/src/com/esotericsoftware/yamlbeans/parser/Parser.java b/src/com/esotericsoftware/yamlbeans/parser/Parser.java index d387ea2..0ffedf2 100644 --- a/src/com/esotericsoftware/yamlbeans/parser/Parser.java +++ b/src/com/esotericsoftware/yamlbeans/parser/Parser.java @@ -51,7 +51,7 @@ public class Parser { Event peekedEvent; public Parser (Reader reader) { - this(reader, new Version(1, 1)); + this(reader, Version.DEFAULT_VERSION); } public Parser (Reader reader, Version defaultVersion) { @@ -79,7 +79,6 @@ public Event getNextEvent () throws ParserException, TokenizerException { while (!parseStack.isEmpty()) { Event event = parseStack.remove(0).produce(); if (event != null) { - // System.out.println("Parser: " + event); return event; } } @@ -128,15 +127,24 @@ public Event produce () { }; table[P_IMPLICIT_DOCUMENT] = new Production() { public Event produce () { - TokenType type = tokenizer.peekNextTokenType(); - if (!(type == DIRECTIVE || type == DOCUMENT_START || type == STREAM_END)) { - parseStack.add(0, table[P_DOCUMENT_END]); - parseStack.add(0, table[P_BLOCK_NODE]); - parseStack.add(0, table[P_DOCUMENT_START_IMPLICIT]); + if (isNextTokenImplicitDocument()) { + parseImplicitDocument(); } return null; } + + private boolean isNextTokenImplicitDocument() { + TokenType type = tokenizer.peekNextTokenType(); + return type != DIRECTIVE && type != DOCUMENT_START && type != STREAM_END; + } + + private void parseImplicitDocument() { + parseStack.add(0, table[P_DOCUMENT_END]); + parseStack.add(0, table[P_BLOCK_NODE]); + parseStack.add(0, table[P_DOCUMENT_START_IMPLICIT]); + } }; + table[P_EXPLICIT_DOCUMENT] = new Production() { public Event produce () { if (tokenizer.peekNextTokenType() != STREAM_END) { @@ -223,7 +231,7 @@ public Event produce () { if (tokenizer.peekNextTokenType() == ANCHOR) anchor = ((AnchorToken)tokenizer.getNextToken()).getInstanceName(); } String tag = null; - if (tagHandle != null && !tagHandle.equals("!")) { + if (tagHandle != null) { if (!tagHandles.containsKey(tagHandle)) throw new ParserException("Undefined tag handle: " + tagHandle); tag = tagHandles.get(tagHandle) + tagSuffix; } else @@ -580,7 +588,7 @@ public Event produce () { }; table[P_EMPTY_SCALAR] = new Production() { public Event produce () { - return new ScalarEvent(null, null, new boolean[] {true, false}, "", (char)0); + return new ScalarEvent(null, null, new boolean[] {true, false}, null, (char)0); } }; } @@ -591,9 +599,9 @@ DocumentStartEvent processDirectives (boolean explicit) { DirectiveToken token = (DirectiveToken)tokenizer.getNextToken(); if (token.getDirective().equals("YAML")) { if (documentVersion != null) throw new ParserException("Duplicate YAML directive."); - documentVersion = new Version(token.getValue()); - if (documentVersion.major != 1) - throw new ParserException("Unsupported YAML version (1.x is required): " + documentVersion); + documentVersion = Version.getVersion(token.getValue()); + if (documentVersion == null || documentVersion.getMajor() != 1) + throw new ParserException("Unsupported YAML version (1.x is required): " + token.getValue()); } else if (token.getDirective().equals("TAG")) { String[] values = token.getValue().split(" "); String handle = values[0]; @@ -611,7 +619,7 @@ DocumentStartEvent processDirectives (boolean explicit) { Map tags = null; if (!tagHandles.isEmpty()) tags = new HashMap(tagHandles); - Map baseTags = version.minor == 0 ? DEFAULT_TAGS_1_0 : DEFAULT_TAGS_1_1; + Map baseTags = version.getMinor() == 0 ? DEFAULT_TAGS_1_0 : DEFAULT_TAGS_1_1; for (String key : baseTags.keySet()) if (!tagHandles.containsKey(key)) tagHandles.put(key, baseTags.get(key)); return new DocumentStartEvent(explicit, version, tags); diff --git a/src/com/esotericsoftware/yamlbeans/tokenizer/Tokenizer.java b/src/com/esotericsoftware/yamlbeans/tokenizer/Tokenizer.java index 9281cff..9bdd96a 100644 --- a/src/com/esotericsoftware/yamlbeans/tokenizer/Tokenizer.java +++ b/src/com/esotericsoftware/yamlbeans/tokenizer/Tokenizer.java @@ -42,7 +42,7 @@ public class Tokenizer { private final static String BLANK_OR_LINEBR = " \r\n\u0085"; private final static String S4 = "\0 \t\r\n\u0028[]{}"; private final static String ALPHA = "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ-_"; - private final static String STRANGE_CHAR = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789][-';/?:@&=+$,.!~*()%"; + private final static String STRANGE_CHAR = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-#;/?:@&=+$,_.!~*'()[]"; private final static String RN = "\r\n"; private final static String BLANK_T = " \t"; private final static String SPACES_AND_STUFF = "'\"\\\0 \t\r\n\u0085"; @@ -51,7 +51,7 @@ public class Tokenizer { private final static Pattern NON_PRINTABLE = Pattern.compile("[^\u0009\n\r\u0020-\u007E\u0085\u00A0-\u00FF]"); private final static Pattern NOT_HEXA = Pattern.compile("[^0-9A-Fa-f]"); private final static Pattern NON_ALPHA = Pattern.compile("[^-0-9A-Za-z_]"); - private final static Pattern R_FLOWZERO = Pattern.compile("[\0 \t\r\n\u0085]|(:[\0 \t\r\n\u0028])"); + private final static Pattern R_FLOWZERO = Pattern.compile("[\0 \t\r\n\u0085]|(:[\0 \t\r\n\u0085])"); private final static Pattern R_FLOWNONZERO = Pattern.compile("[\0 \t\r\n\u0085\\[\\]{},:?]"); private final static Pattern END_OR_START = Pattern.compile("^(---|\\.\\.\\.)[\0 \t\r\n\u0085]$"); private final static Pattern ENDING = Pattern.compile("^---[\0 \t\r\n\u0085]$"); @@ -133,7 +133,6 @@ public Token getNextToken () throws TokenizerException { if (!tokens.isEmpty()) { tokensTaken++; Token token = tokens.remove(0); - // System.out.println("Tokenizer: " + token); return token; } return null; @@ -311,8 +310,8 @@ private Token fetchMoreTokens () { } if (BEG.matcher(prefix(2)).find()) return fetchPlain(); if (ch == '\t') throw new TokenizerException("Tabs cannot be used for indentation."); - throw new TokenizerException("While scanning for the next token, a character that cannot begin a token was found: " - + ch(ch)); + throw new TokenizerException( + "While scanning for the next token, a character that cannot begin a token was found: " + ch(ch)); } private int nextPossibleSimpleKey () { @@ -569,14 +568,12 @@ private String scanDirectiveName () { length++; ch = peek(length); } - if (zlen) - throw new TokenizerException("While scanning for a directive name, expected an alpha or numeric character but found: " - + ch(ch)); + if (zlen) throw new TokenizerException( + "While scanning for a directive name, expected an alpha or numeric character but found: " + ch(ch)); String value = prefixForward(length); // forward(length); - if (NULL_BL_LINEBR.indexOf(peek()) == -1) - throw new TokenizerException("While scanning for a directive name, expected an alpha or numeric character but found: " - + ch(ch)); + if (NULL_BL_LINEBR.indexOf(peek()) == -1) throw new TokenizerException( + "While scanning for a directive name, expected an alpha or numeric character but found: " + ch(ch)); return value; } @@ -657,9 +654,8 @@ private Token scanAnchor (Token tok) { throw new TokenizerException("While scanning an " + name + ", a non-alpha, non-numeric character was found."); String value = prefixForward(length); // forward(length); - if (NON_ALPHA_OR_NUM.indexOf(peek()) == -1) - throw new TokenizerException("While scanning an " + name + ", expected an alpha or numeric character but found: " - + ch(peek())); + if (NON_ALPHA_OR_NUM.indexOf(peek()) == -1) throw new TokenizerException( + "While scanning an " + name + ", expected an alpha or numeric character but found: " + ch(peek())); if (tok instanceof AnchorToken) ((AnchorToken)tok).setInstanceName(value); else @@ -709,7 +705,7 @@ private Token scanBlockScalar (char style) { StringBuilder chunks = new StringBuilder(); forward(); Object[] chompi = scanBlockScalarIndicators(); - boolean chomping = ((Boolean)chompi[0]).booleanValue(); + int chomping = ((Integer)chompi[0]).intValue(); int increment = ((Integer)chompi[1]).intValue(); scanBlockScalarIgnoredLine(); int minIndent = indent + 1; @@ -750,7 +746,9 @@ private Token scanBlockScalar (char style) { break; } - if (chomping) { + if (chomping == 0) { + chunks.append(lineBreak); + } else if (chomping == 2) { chunks.append(lineBreak); chunks.append(breaks); } @@ -759,36 +757,33 @@ private Token scanBlockScalar (char style) { } private Object[] scanBlockScalarIndicators () { - boolean chomping = false; + int chomping = 0; // 0 = clip, 1 = strip, 2 = keep int increment = -1; char ch = peek(); if (ch == '-' || ch == '+') { - chomping = ch == '+'; + chomping = ch == '-' ? 1 : 2; forward(); ch = peek(); if (Character.isDigit(ch)) { increment = Integer.parseInt(("" + ch)); - if (increment == 0) - throw new TokenizerException( - "While scanning a black scaler, expected indentation indicator between 1 and 9 but found: 0"); + if (increment == 0) throw new TokenizerException( + "While scanning a black scaler, expected indentation indicator between 1 and 9 but found: 0"); forward(); } } else if (Character.isDigit(ch)) { increment = Integer.parseInt(("" + ch)); - if (increment == 0) - throw new TokenizerException( - "While scanning a black scaler, expected indentation indicator between 1 and 9 but found: 0"); + if (increment == 0) throw new TokenizerException( + "While scanning a black scaler, expected indentation indicator between 1 and 9 but found: 0"); forward(); ch = peek(); if (ch == '-' || ch == '+') { - chomping = ch == '+'; + chomping = ch == '-' ? 1 : 2; forward(); } } - if (NULL_BL_LINEBR.indexOf(peek()) == -1) - throw new TokenizerException("While scanning a block scalar, expected chomping or indentation indicators but found: " - + ch(peek())); - return new Object[] {Boolean.valueOf(chomping), increment}; + if (NULL_BL_LINEBR.indexOf(peek()) == -1) throw new TokenizerException( + "While scanning a block scalar, expected chomping or indentation indicators but found: " + ch(peek())); + return new Object[] {Integer.valueOf(chomping), increment}; } private String scanBlockScalarIgnoredLine () { @@ -952,7 +947,7 @@ private Token scanPlain () { chunks.append(prefixForward(length)); // forward(length); spaces = scanPlainSpaces(); - if (spaces == null || flowLevel == 0 && column < ind) break; + if (spaces.length() == 0 || flowLevel == 0 && column < ind) break; } return new ScalarToken(chunks.toString(), true); } @@ -960,7 +955,8 @@ private Token scanPlain () { private String scanPlainSpaces () { StringBuilder chunks = new StringBuilder(); int length = 0; - while (peek(length) == ' ') + // YAML recognizes two white space characters: space and tab. + while (peek(length) == ' ' || peek(length) == '\t') length++; String whitespaces = prefixForward(length); // forward(length); @@ -1034,7 +1030,7 @@ private String scanUriEscapes (String name) { while (peek() == '%') { forward(); try { - bytes.append(Integer.parseInt(prefix(2), 16)); + bytes.append(Character.toChars(Integer.parseInt(prefix(2), 16))); } catch (NumberFormatException nfe) { throw new TokenizerException("While scanning a " + name + ", expected a URI escape sequence of 2 hexadecimal numbers but found: " + ch(peek(1)) + " and " + ch(peek(2))); diff --git a/test/com/esotericsoftware/yamlbeans/BeansTest.java b/test/com/esotericsoftware/yamlbeans/BeansTest.java new file mode 100644 index 0000000..6f9cde0 --- /dev/null +++ b/test/com/esotericsoftware/yamlbeans/BeansTest.java @@ -0,0 +1,734 @@ + +package com.esotericsoftware.yamlbeans; + +import org.junit.Before; +import org.junit.Test; + +import java.lang.reflect.InvocationTargetException; +import java.util.*; + +import static org.junit.Assert.*; + +@SuppressWarnings("synthetic-access") +public class BeansTest { + + private static final double DELTA = 0.0001; + + private YamlConfig yamlConfig; + + @Before + public void setup () throws Exception { + yamlConfig = new YamlConfig(); + } + + @Test + public void isScalar () throws Exception { + // Scalar Type + assertTrue(Beans.isScalar(String.class)); + assertTrue(Beans.isScalar(Integer.class)); + assertTrue(Beans.isScalar(Boolean.class)); + assertTrue(Beans.isScalar(Float.class)); + assertTrue(Beans.isScalar(Long.class)); + assertTrue(Beans.isScalar(Double.class)); + assertTrue(Beans.isScalar(Short.class)); + assertTrue(Beans.isScalar(Byte.class)); + assertTrue(Beans.isScalar(Character.class)); + // Other Type + assertFalse(Beans.isScalar(Void.class)); + assertFalse(Beans.isScalar(Number.class)); + assertFalse(Beans.isScalar(Beans.class)); + assertFalse(Beans.isScalar(YamlReader.class)); + assertFalse(Beans.isScalar(YamlWriter.class)); + } + + @Test + public void getDeferredConstruction () throws Exception { + DeferredConstruction construction = Beans.getDeferredConstruction(MockClass.class, yamlConfig); + assertEquals(null, construction); + } + + @Test + public void getDeferredConstructionWithPrivateConstructor () throws Exception { + DeferredConstruction construction = Beans.getDeferredConstruction(MockClassWithPrivateConstructor.class, yamlConfig); + assertEquals(null, construction); + } + + @Test + public void createObject () throws Exception { + MockClass mockClass = (MockClass)Beans.createObject(MockClass.class, false); + MockClassWithPrivateConstructor mockClassWithPrivateConstructor = (MockClassWithPrivateConstructor)Beans + .createObject(MockClassWithPrivateConstructor.class, true); + List listCase = (List)Beans.createObject(List.class, false); + Set setCase = (Set)Beans.createObject(Set.class, false); + Map mapCase = (Map)Beans.createObject(Map.class, false); + + assertNotNull(mockClass); + assertNotNull(mockClassWithPrivateConstructor); + assertNotNull(listCase); + assertNotNull(setCase); + assertNotNull(mapCase); + + if (!(listCase instanceof ArrayList)) { + fail(); + } + if (!(setCase instanceof HashSet)) { + fail(); + } + if (!(mapCase instanceof HashMap)) { + fail(); + } + + try { + MockClassWithoutNoArgConstructor mockClassWithoutNoArgConstructor = (MockClassWithoutNoArgConstructor)Beans + .createObject(MockClassWithoutNoArgConstructor.class, false); + fail(); + } catch (InvocationTargetException e) { + + } + try { + mockClassWithPrivateConstructor = (MockClassWithPrivateConstructor)Beans + .createObject(MockClassWithPrivateConstructor.class, false); + fail(); + } catch (InvocationTargetException e) { + + } + } + + @Test + public void getPropertiesWithNullType () throws Exception { + try { + Beans.getProperties(null, false, false, yamlConfig); + fail(); + } catch (IllegalArgumentException e) { + // nothing + } + } + + @Test + public void getPropertiesWithBeanProperties () throws Exception { + final Set properties = Beans.getProperties(MockClass.class, true, false, yamlConfig); + assertEquals(9, properties.size()); + } + + @Test + public void getPropertiesWithPrivateFields () throws Exception { + final Set properties = Beans.getProperties(MockClass.class, false, true, yamlConfig); + assertEquals(9, properties.size()); + } + + @Test + public void getPropertyWithNullType () throws Exception { + try { + Beans.getProperty(null, null, false, false, yamlConfig); + fail(); + } catch (IllegalArgumentException e) { + // nothing + } + } + + @Test + public void getPropertyWithNullName () throws Exception { + try { + Beans.getProperty(MockClass.class, null, false, false, yamlConfig); + fail(); + } catch (IllegalArgumentException e) { + // nothing + } + } + + @Test + public void getPropertyWithEmptyName () throws Exception { + try { + Beans.getProperty(MockClass.class, "", false, false, yamlConfig); + fail(); + } catch (IllegalArgumentException e) { + // nothing + } + + } + + @Test + public void getPropertyBooleanTypeWithBeanProperties () throws Exception { + final Beans.Property booleanTypeProperty = Beans.getProperty(MockClass.class, "booleanType", true, false, yamlConfig); + + assertTrue(booleanTypeProperty instanceof Beans.MethodProperty); + assertEquals(MockClass.class, booleanTypeProperty.getDeclaringClass()); + assertEquals("booleanType", booleanTypeProperty.getName()); + assertEquals(boolean.class, booleanTypeProperty.getType()); + + MockClass mockClass = new MockClass(); + assertEquals(false, mockClass.booleanType); + + booleanTypeProperty.set(mockClass, true); + assertEquals(true, booleanTypeProperty.get(mockClass)); + + booleanTypeProperty.set(mockClass, false); + assertEquals(false, booleanTypeProperty.get(mockClass)); + } + + @Test + public void getPropertyCharTypeWithBeanProperties () throws Exception { + final Beans.Property charTypeProperty = Beans.getProperty(MockClass.class, "charType", true, false, yamlConfig); + + assertTrue(charTypeProperty instanceof Beans.MethodProperty); + assertEquals(MockClass.class, charTypeProperty.getDeclaringClass()); + assertEquals("charType", charTypeProperty.getName()); + assertEquals(char.class, charTypeProperty.getType()); + + MockClass mockClass = new MockClass(); + assertEquals(0, mockClass.charType); + + charTypeProperty.set(mockClass, 'a'); + assertEquals('a', charTypeProperty.get(mockClass)); + + charTypeProperty.set(mockClass, Character.MAX_VALUE); + assertEquals(Character.MAX_VALUE, charTypeProperty.get(mockClass)); + + charTypeProperty.set(mockClass, Character.MIN_VALUE); + assertEquals(Character.MIN_VALUE, charTypeProperty.get(mockClass)); + } + + @Test + public void getPropertyByteTypeWithBeanProperties () throws Exception { + final Beans.Property byteTypeProperty = Beans.getProperty(MockClass.class, "byteType", true, false, yamlConfig); + + assertTrue(byteTypeProperty instanceof Beans.MethodProperty); + assertEquals(MockClass.class, byteTypeProperty.getDeclaringClass()); + assertEquals("byteType", byteTypeProperty.getName()); + assertEquals(byte.class, byteTypeProperty.getType()); + + MockClass mockClass = new MockClass(); + assertEquals(0, mockClass.byteType); + + byteTypeProperty.set(mockClass, (byte)1); + assertEquals((byte)1, byteTypeProperty.get(mockClass)); + + byteTypeProperty.set(mockClass, Byte.MAX_VALUE); + assertEquals(Byte.MAX_VALUE, byteTypeProperty.get(mockClass)); + + byteTypeProperty.set(mockClass, Byte.MIN_VALUE); + assertEquals(Byte.MIN_VALUE, byteTypeProperty.get(mockClass)); + } + + @Test + public void getPropertyShortTypeWithBeanProperties () throws Exception { + final Beans.Property shortTypeProperty = Beans.getProperty(MockClass.class, "shortType", true, false, yamlConfig); + + assertTrue(shortTypeProperty instanceof Beans.MethodProperty); + assertEquals(MockClass.class, shortTypeProperty.getDeclaringClass()); + assertEquals("shortType", shortTypeProperty.getName()); + assertEquals(short.class, shortTypeProperty.getType()); + + MockClass mockClass = new MockClass(); + assertEquals(0, mockClass.shortType); + + shortTypeProperty.set(mockClass, (short)1); + assertEquals((short)1, shortTypeProperty.get(mockClass)); + + shortTypeProperty.set(mockClass, Short.MAX_VALUE); + assertEquals(Short.MAX_VALUE, shortTypeProperty.get(mockClass)); + + shortTypeProperty.set(mockClass, Short.MIN_VALUE); + assertEquals(Short.MIN_VALUE, shortTypeProperty.get(mockClass)); + } + + @Test + public void getPropertyIntTypeWithBeanProperties () throws Exception { + final Beans.Property intTypeProperty = Beans.getProperty(MockClass.class, "intType", true, false, yamlConfig); + + assertTrue(intTypeProperty instanceof Beans.MethodProperty); + assertEquals(MockClass.class, intTypeProperty.getDeclaringClass()); + assertEquals("intType", intTypeProperty.getName()); + assertEquals(int.class, intTypeProperty.getType()); + + MockClass mockClass = new MockClass(); + assertEquals(0, mockClass.intType); + + intTypeProperty.set(mockClass, 1); + assertEquals(1, intTypeProperty.get(mockClass)); + + intTypeProperty.set(mockClass, Integer.MAX_VALUE); + assertEquals(Integer.MAX_VALUE, intTypeProperty.get(mockClass)); + + intTypeProperty.set(mockClass, Integer.MIN_VALUE); + assertEquals(Integer.MIN_VALUE, intTypeProperty.get(mockClass)); + } + + @Test + public void getPropertyLongTypeWithBeanProperties () throws Exception { + final Beans.Property longTypeProperty = Beans.getProperty(MockClass.class, "longType", true, false, yamlConfig); + + assertTrue(longTypeProperty instanceof Beans.MethodProperty); + assertEquals(MockClass.class, longTypeProperty.getDeclaringClass()); + assertEquals("longType", longTypeProperty.getName()); + assertEquals(long.class, longTypeProperty.getType()); + + MockClass mockClass = new MockClass(); + assertEquals(0, mockClass.longType); + + longTypeProperty.set(mockClass, 1l); + assertEquals(1l, longTypeProperty.get(mockClass)); + + longTypeProperty.set(mockClass, Long.MAX_VALUE); + assertEquals(Long.MAX_VALUE, longTypeProperty.get(mockClass)); + + longTypeProperty.set(mockClass, Long.MIN_VALUE); + assertEquals(Long.MIN_VALUE, longTypeProperty.get(mockClass)); + } + + @Test + public void getPropertyFloatTypeWithBeanProperties () throws Exception { + final Beans.Property floatTypeProperty = Beans.getProperty(MockClass.class, "floatType", true, false, yamlConfig); + + assertTrue(floatTypeProperty instanceof Beans.MethodProperty); + assertEquals(MockClass.class, floatTypeProperty.getDeclaringClass()); + assertEquals("floatType", floatTypeProperty.getName()); + assertEquals(float.class, floatTypeProperty.getType()); + + MockClass mockClass = new MockClass(); + assertEquals((float)0, mockClass.floatType, DELTA); + + floatTypeProperty.set(mockClass, (float)1.0); + assertEquals((float)1.0, floatTypeProperty.get(mockClass)); + + floatTypeProperty.set(mockClass, Float.MAX_VALUE); + assertEquals(Float.MAX_VALUE, floatTypeProperty.get(mockClass)); + + floatTypeProperty.set(mockClass, Float.MIN_VALUE); + assertEquals(Float.MIN_VALUE, floatTypeProperty.get(mockClass)); + } + + @Test + public void getPropertyDoubleTypeWithBeanProperties () throws Exception { + final Beans.Property doubleTypeProperty = Beans.getProperty(MockClass.class, "doubleType", true, false, yamlConfig); + + assertTrue(doubleTypeProperty instanceof Beans.MethodProperty); + assertEquals(MockClass.class, doubleTypeProperty.getDeclaringClass()); + assertEquals("doubleType", doubleTypeProperty.getName()); + assertEquals(double.class, doubleTypeProperty.getType()); + + MockClass mockClass = new MockClass(); + assertEquals((double)0, mockClass.doubleType, DELTA); + + doubleTypeProperty.set(mockClass, (double)1.0); + assertEquals((double)1.0, doubleTypeProperty.get(mockClass)); + + doubleTypeProperty.set(mockClass, Double.MAX_VALUE); + assertEquals(Double.MAX_VALUE, doubleTypeProperty.get(mockClass)); + + doubleTypeProperty.set(mockClass, Double.MIN_VALUE); + assertEquals(Double.MIN_VALUE, doubleTypeProperty.get(mockClass)); + } + + @Test + public void getPropertyStringTypeWithBeanProperties () throws Exception { + final Beans.Property stringTypeProperty = Beans.getProperty(MockClass.class, "stringType", true, false, yamlConfig); + + assertTrue(stringTypeProperty instanceof Beans.MethodProperty); + assertEquals(MockClass.class, stringTypeProperty.getDeclaringClass()); + assertEquals("stringType", stringTypeProperty.getName()); + assertEquals(String.class, stringTypeProperty.getType()); + + MockClass mockClass = new MockClass(); + assertEquals(null, mockClass.stringType); + + stringTypeProperty.set(mockClass, "4cho"); + assertEquals("4cho", stringTypeProperty.get(mockClass)); + + stringTypeProperty.set(mockClass, "ghkim"); + assertEquals("ghkim", stringTypeProperty.get(mockClass)); + } + + @Test + public void getPropertyNullWithBeanProperties () throws Exception { + final Beans.Property nullProperty = Beans.getProperty(MockClass.class, "nullType", true, false, yamlConfig); + + assertNull(nullProperty); + } + + @Test + public void getPropertyBooleanTypeWithPrivateFields () throws Exception { + final Beans.Property booleanTypeProperty = Beans.getProperty(MockClass.class, "booleanType", false, true, null); + + assertTrue(booleanTypeProperty instanceof Beans.FieldProperty); + assertEquals(MockClass.class, booleanTypeProperty.getDeclaringClass()); + assertEquals("booleanType", booleanTypeProperty.getName()); + assertEquals(boolean.class, booleanTypeProperty.getType()); + + MockClass mockClass = new MockClass(); + assertEquals(false, mockClass.booleanType); + + booleanTypeProperty.set(mockClass, true); + assertEquals(true, booleanTypeProperty.get(mockClass)); + + booleanTypeProperty.set(mockClass, false); + assertEquals(false, booleanTypeProperty.get(mockClass)); + } + + @Test + public void getPropertyCharTypeWithPrivateFields () throws Exception { + final Beans.Property charTypeProperty = Beans.getProperty(MockClass.class, "charType", false, true, yamlConfig); + + assertTrue(charTypeProperty instanceof Beans.FieldProperty); + assertEquals(MockClass.class, charTypeProperty.getDeclaringClass()); + assertEquals("charType", charTypeProperty.getName()); + assertEquals(char.class, charTypeProperty.getType()); + + MockClass mockClass = new MockClass(); + assertEquals(0, mockClass.charType); + + charTypeProperty.set(mockClass, 'a'); + assertEquals('a', charTypeProperty.get(mockClass)); + + charTypeProperty.set(mockClass, Character.MAX_VALUE); + assertEquals(Character.MAX_VALUE, charTypeProperty.get(mockClass)); + + charTypeProperty.set(mockClass, Character.MIN_VALUE); + assertEquals(Character.MIN_VALUE, charTypeProperty.get(mockClass)); + } + + @Test + public void getPropertyByteTypeWithPrivateFields () throws Exception { + final Beans.Property byteTypeProperty = Beans.getProperty(MockClass.class, "byteType", false, true, yamlConfig); + + assertTrue(byteTypeProperty instanceof Beans.FieldProperty); + assertEquals(MockClass.class, byteTypeProperty.getDeclaringClass()); + assertEquals("byteType", byteTypeProperty.getName()); + assertEquals(byte.class, byteTypeProperty.getType()); + + MockClass mockClass = new MockClass(); + assertEquals(0, mockClass.byteType); + + byteTypeProperty.set(mockClass, (byte)1); + assertEquals((byte)1, byteTypeProperty.get(mockClass)); + + byteTypeProperty.set(mockClass, Byte.MAX_VALUE); + assertEquals(Byte.MAX_VALUE, byteTypeProperty.get(mockClass)); + + byteTypeProperty.set(mockClass, Byte.MIN_VALUE); + assertEquals(Byte.MIN_VALUE, byteTypeProperty.get(mockClass)); + } + + @Test + public void getPropertyShortTypeWithPrivateFields () throws Exception { + final Beans.Property shortTypeProperty = Beans.getProperty(MockClass.class, "shortType", false, true, yamlConfig); + + assertTrue(shortTypeProperty instanceof Beans.FieldProperty); + assertEquals(MockClass.class, shortTypeProperty.getDeclaringClass()); + assertEquals("shortType", shortTypeProperty.getName()); + assertEquals(short.class, shortTypeProperty.getType()); + + MockClass mockClass = new MockClass(); + assertEquals(0, mockClass.shortType); + + shortTypeProperty.set(mockClass, (short)1); + assertEquals((short)1, shortTypeProperty.get(mockClass)); + + shortTypeProperty.set(mockClass, Short.MAX_VALUE); + assertEquals(Short.MAX_VALUE, shortTypeProperty.get(mockClass)); + + shortTypeProperty.set(mockClass, Short.MIN_VALUE); + assertEquals(Short.MIN_VALUE, shortTypeProperty.get(mockClass)); + } + + @Test + public void getPropertyIntTypeWithPrivateFields () throws Exception { + final Beans.Property intTypeProperty = Beans.getProperty(MockClass.class, "intType", false, true, yamlConfig); + + assertTrue(intTypeProperty instanceof Beans.FieldProperty); + assertEquals(MockClass.class, intTypeProperty.getDeclaringClass()); + assertEquals("intType", intTypeProperty.getName()); + assertEquals(int.class, intTypeProperty.getType()); + + MockClass mockClass = new MockClass(); + assertEquals(0, mockClass.intType); + + intTypeProperty.set(mockClass, 1); + assertEquals(1, intTypeProperty.get(mockClass)); + + intTypeProperty.set(mockClass, Integer.MAX_VALUE); + assertEquals(Integer.MAX_VALUE, intTypeProperty.get(mockClass)); + + intTypeProperty.set(mockClass, Integer.MIN_VALUE); + assertEquals(Integer.MIN_VALUE, intTypeProperty.get(mockClass)); + } + + @Test + public void getPropertyLongTypeWithPrivateFields () throws Exception { + final Beans.Property longTypeProperty = Beans.getProperty(MockClass.class, "longType", false, true, yamlConfig); + + assertTrue(longTypeProperty instanceof Beans.FieldProperty); + assertEquals(MockClass.class, longTypeProperty.getDeclaringClass()); + assertEquals("longType", longTypeProperty.getName()); + assertEquals(long.class, longTypeProperty.getType()); + + MockClass mockClass = new MockClass(); + assertEquals(0, mockClass.longType); + + longTypeProperty.set(mockClass, 1l); + assertEquals(1l, longTypeProperty.get(mockClass)); + + longTypeProperty.set(mockClass, Long.MAX_VALUE); + assertEquals(Long.MAX_VALUE, longTypeProperty.get(mockClass)); + + longTypeProperty.set(mockClass, Long.MIN_VALUE); + assertEquals(Long.MIN_VALUE, longTypeProperty.get(mockClass)); + } + + @Test + public void getPropertyFloatTypeWithPrivateFields () throws Exception { + final Beans.Property floatTypeProperty = Beans.getProperty(MockClass.class, "floatType", false, true, yamlConfig); + + assertTrue(floatTypeProperty instanceof Beans.FieldProperty); + assertEquals(MockClass.class, floatTypeProperty.getDeclaringClass()); + assertEquals("floatType", floatTypeProperty.getName()); + assertEquals(float.class, floatTypeProperty.getType()); + + MockClass mockClass = new MockClass(); + assertEquals((float)0, mockClass.floatType, DELTA); + + floatTypeProperty.set(mockClass, (float)1.0); + assertEquals((float)1.0, floatTypeProperty.get(mockClass)); + + floatTypeProperty.set(mockClass, Float.MAX_VALUE); + assertEquals(Float.MAX_VALUE, floatTypeProperty.get(mockClass)); + + floatTypeProperty.set(mockClass, Float.MIN_VALUE); + assertEquals(Float.MIN_VALUE, floatTypeProperty.get(mockClass)); + } + + @Test + public void getPropertyDoubleTypeWithPrivateFields () throws Exception { + final Beans.Property doubleTypeProperty = Beans.getProperty(MockClass.class, "doubleType", false, true, yamlConfig); + + assertTrue(doubleTypeProperty instanceof Beans.FieldProperty); + assertEquals(MockClass.class, doubleTypeProperty.getDeclaringClass()); + assertEquals("doubleType", doubleTypeProperty.getName()); + assertEquals(double.class, doubleTypeProperty.getType()); + + MockClass mockClass = new MockClass(); + assertEquals((double)0, mockClass.doubleType, DELTA); + + doubleTypeProperty.set(mockClass, (double)1.0); + assertEquals((double)1.0, doubleTypeProperty.get(mockClass)); + + doubleTypeProperty.set(mockClass, Double.MAX_VALUE); + assertEquals(Double.MAX_VALUE, doubleTypeProperty.get(mockClass)); + + doubleTypeProperty.set(mockClass, Double.MIN_VALUE); + assertEquals(Double.MIN_VALUE, doubleTypeProperty.get(mockClass)); + } + + @Test + public void getPropertyStringTypeWithPrivateFields () throws Exception { + final Beans.Property stringTypeProperty = Beans.getProperty(MockClass.class, "stringType", false, true, yamlConfig); + + assertTrue(stringTypeProperty instanceof Beans.FieldProperty); + assertEquals(MockClass.class, stringTypeProperty.getDeclaringClass()); + assertEquals("stringType", stringTypeProperty.getName()); + assertEquals(String.class, stringTypeProperty.getType()); + + MockClass mockClass = new MockClass(); + assertEquals(null, mockClass.stringType); + + stringTypeProperty.set(mockClass, "4cho"); + assertEquals("4cho", stringTypeProperty.get(mockClass)); + + stringTypeProperty.set(mockClass, "ghkim"); + assertEquals("ghkim", stringTypeProperty.get(mockClass)); + } + + @Test + public void getPropertyNullWithPrivateFields () throws Exception { + final Beans.Property nullProperty = Beans.getProperty(MockClass.class, "nullType", false, true, yamlConfig); + + assertNull(nullProperty); + } + + private static class MockClass { + + private boolean booleanType; + private char charType; + private byte byteType; + private short shortType; + private int intType; + private long longType; + private float floatType; + private double doubleType; + private String stringType; + + public MockClass () { + + } + + public boolean isBooleanType () { + return booleanType; + } + + public void setBooleanType (boolean booleanType) { + this.booleanType = booleanType; + } + + public char getCharType () { + return charType; + } + + public void setCharType (char charType) { + this.charType = charType; + } + + public byte getByteType () { + return byteType; + } + + public void setByteType (byte byteType) { + this.byteType = byteType; + } + + public short getShortType () { + return shortType; + } + + public void setShortType (short shortType) { + this.shortType = shortType; + } + + public int getIntType () { + return intType; + } + + public void setIntType (int intType) { + this.intType = intType; + } + + public long getLongType () { + return longType; + } + + public void setLongType (long longType) { + this.longType = longType; + } + + public float getFloatType () { + return floatType; + } + + public void setFloatType (float floatType) { + this.floatType = floatType; + } + + public double getDoubleType () { + return doubleType; + } + + public void setDoubleType (double doubleType) { + this.doubleType = doubleType; + } + + public String getStringType () { + return stringType; + } + + public void setStringType (String stringType) { + this.stringType = stringType; + } + } + + private static class MockClassWithPrivateConstructor { + + private boolean booleanType; + private char charType; + private byte byteType; + private short shortType; + private int intType; + private long longType; + private float floatType; + private double doubleType; + private String stringType; + + private MockClassWithPrivateConstructor () { + + } + + public boolean isBooleanType () { + return booleanType; + } + + public void setBooleanType (boolean booleanType) { + this.booleanType = booleanType; + } + + public char getCharType () { + return charType; + } + + public void setCharType (char charType) { + this.charType = charType; + } + + public byte getByteType () { + return byteType; + } + + public void setByteType (byte byteType) { + this.byteType = byteType; + } + + public short getShortType () { + return shortType; + } + + public void setShortType (short shortType) { + this.shortType = shortType; + } + + public int getIntType () { + return intType; + } + + public void setIntType (int intType) { + this.intType = intType; + } + + public long getLongType () { + return longType; + } + + public void setLongType (long longType) { + this.longType = longType; + } + + public float getFloatType () { + return floatType; + } + + public void setFloatType (float floatType) { + this.floatType = floatType; + } + + public double getDoubleType () { + return doubleType; + } + + public void setDoubleType (double doubleType) { + this.doubleType = doubleType; + } + + public String getStringType () { + return stringType; + } + + public void setStringType (String stringType) { + this.stringType = stringType; + } + } + + private static class MockClassWithoutNoArgConstructor { + + private Object anyType; + + public MockClassWithoutNoArgConstructor (Object anyType) { + this.anyType = anyType; + } + } +} diff --git a/test/com/esotericsoftware/yamlbeans/BooleanTest.java b/test/com/esotericsoftware/yamlbeans/BooleanTest.java new file mode 100644 index 0000000..ac7a69d --- /dev/null +++ b/test/com/esotericsoftware/yamlbeans/BooleanTest.java @@ -0,0 +1,91 @@ +package com.esotericsoftware.yamlbeans; + +import junit.framework.TestCase; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.InputStreamReader; +import java.io.OutputStreamWriter; + +public class BooleanTest extends TestCase { + public void testBooleanBean() throws Exception { + // Create a bean with a value differing from it's default + BeanWithBoolean val = new BeanWithBoolean(); + val.setBool(true); + val.setBoolObj(true); + val.setBoolean(true); + + // Store the bean + ByteArrayOutputStream out = new ByteArrayOutputStream(); + YamlWriter yamlWriter = new YamlWriter(new OutputStreamWriter(out)); + yamlWriter.write(val); + yamlWriter.close(); + + // Load the bean + ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray()); + YamlReader yamlReader = new YamlReader(new InputStreamReader(in)); + BeanWithBoolean got = yamlReader.read(BeanWithBoolean.class); + + assertEquals(val, got); + } + + public static class BeanWithBoolean { + private boolean bool = false; + private Boolean boolObj = false; + private boolean isBoolean = false; + + public boolean isBool() { + return bool; + } + + public void setBool(boolean bool) { + this.bool = bool; + } + + public Boolean getBoolObj() { + return boolObj; + } + + public void setBoolObj(Boolean boolObj) { + this.boolObj = boolObj; + } + + public boolean isBoolean() { + return isBoolean; + } + + public void setBoolean(boolean isBoolean) { + this.isBoolean = isBoolean; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + BeanWithBoolean that = (BeanWithBoolean) o; + + if (bool != that.bool) return false; + if (isBoolean != that.isBoolean) return false; + return !(boolObj != null ? !boolObj.equals(that.boolObj) : that.boolObj != null); + + } + + @Override + public int hashCode() { + int result = (bool ? 1 : 0); + result += (isBoolean ? 2 : 0); + result = 31 * result + (boolObj != null ? boolObj.hashCode() : 0); + return result; + } + + @Override + public String toString() { + return "BeanWithBoolean{" + + "bool=" + bool + + ", boolObj=" + boolObj + + ", isBoolean=" + isBoolean + + '}'; + } + } +} diff --git a/test/com/esotericsoftware/yamlbeans/GenericTest.java b/test/com/esotericsoftware/yamlbeans/GenericTest.java new file mode 100644 index 0000000..a04d3a8 --- /dev/null +++ b/test/com/esotericsoftware/yamlbeans/GenericTest.java @@ -0,0 +1,179 @@ +package com.esotericsoftware.yamlbeans; + +import com.esotericsoftware.yamlbeans.YamlConfig.WriteClassName; +import junit.framework.Assert; +import junit.framework.TestCase; + +import java.io.StringWriter; +import java.util.LinkedHashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + +public class GenericTest extends TestCase { + + private static final String LINE_SEPARATOR = System.getProperty("line.separator"); + + private static final String YAML + = "integerList: " + LINE_SEPARATOR + + "- 1" + LINE_SEPARATOR + + "- 100500" + LINE_SEPARATOR + + "- 10" + LINE_SEPARATOR + + "stringMap: " + LINE_SEPARATOR + + " a: av" + LINE_SEPARATOR + + " b: bv" + LINE_SEPARATOR + + "structList: " + LINE_SEPARATOR + + "- i: 10" + LINE_SEPARATOR + + " str: aaa" + LINE_SEPARATOR + + "- i: 20" + LINE_SEPARATOR + + " str: bbb" + LINE_SEPARATOR + + "structMap: " + LINE_SEPARATOR + + " a: " + LINE_SEPARATOR + + " i: 1" + LINE_SEPARATOR + + " str: aa" + LINE_SEPARATOR + + " b: " + LINE_SEPARATOR + + " i: 2" + LINE_SEPARATOR + + " str: ab" + LINE_SEPARATOR; + + public void testRead() throws YamlException { + Test test = createTest(); + + Test read = new YamlReader(YAML).read(Test.class); + Assert.assertEquals(test, read); + } + + public void testWrite() throws YamlException { + Test test = createTest(); + StringWriter stringWriter = new StringWriter(); + + YamlWriter yamlWriter = new YamlWriter(stringWriter); + yamlWriter.getConfig().writeConfig.setWriteClassname(WriteClassName.NEVER); + yamlWriter.write(test); + yamlWriter.close(); + + Assert.assertEquals(YAML, stringWriter.getBuffer().toString()); + } + + private Test createTest() { + Map stringMap = new LinkedHashMap(); + stringMap.put("a", "av"); + stringMap.put("b", "bv"); + + Map structMap = new LinkedHashMap(); + structMap.put("a", new Struct(1, "aa")); + structMap.put("b", new Struct(2, "ab")); + + List integerList = new LinkedList(); + integerList.add(1); + integerList.add(100500); + integerList.add(10); + + List structList = new LinkedList(); + structList.add(new Struct(10, "aaa")); + structList.add(new Struct(20, "bbb")); + + Test test = new Test(); + test.stringMap = stringMap; + test.structMap = structMap; + test.integerList = integerList; + test.structList = structList; + + return test; + } + + static class Struct { + + public int i; + public String str; + + Struct(int i, String str) { + this.i = i; + this.str = str; + } + + Struct() { + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof Struct)) { + return false; + } + + Struct struct = (Struct) o; + + if (i != struct.i) { + return false; + } + if (str != null ? !str.equals(struct.str) : struct.str != null) { + return false; + } + + return true; + } + + @Override + public int hashCode() { + int result = i; + result = 31 * result + (str != null ? str.hashCode() : 0); + return result; + } + } + + static class Test { + + public Map stringMap; + public Map structMap; + public List integerList; + public List structList; + public Map> multiMap; + public Map wildMap; + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof Test)) { + return false; + } + + Test test = (Test) o; + + if (integerList != null ? !integerList.equals(test.integerList) : test.integerList != null) { + return false; + } + if (stringMap != null ? !stringMap.equals(test.stringMap) : test.stringMap != null) { + return false; + } + if (structList != null ? !structList.equals(test.structList) : test.structList != null) { + return false; + } + if (structMap != null ? !structMap.equals(test.structMap) : test.structMap != null) { + return false; + } + if (multiMap != null ? !multiMap.equals(test.multiMap) : test.multiMap != null) { + return false; + } + if (wildMap != null ? !wildMap.equals(test.wildMap) : test.wildMap != null) { + return false; + } + + return true; + } + + @Override + public int hashCode() { + int result = stringMap != null ? stringMap.hashCode() : 0; + result = 31 * result + (structMap != null ? structMap.hashCode() : 0); + result = 31 * result + (integerList != null ? integerList.hashCode() : 0); + result = 31 * result + (structList != null ? structList.hashCode() : 0); + result = 31 * result + (multiMap != null ? multiMap.hashCode() : 0); + result = 31 * result + (wildMap != null ? wildMap.hashCode() : 0); + return result; + } + } +} diff --git a/test/com/esotericsoftware/yamlbeans/MergeTest.java b/test/com/esotericsoftware/yamlbeans/MergeTest.java new file mode 100644 index 0000000..48e448b --- /dev/null +++ b/test/com/esotericsoftware/yamlbeans/MergeTest.java @@ -0,0 +1,72 @@ +package com.esotericsoftware.yamlbeans; + +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.Reader; +import java.util.Map; + +import org.junit.Test; + +import com.esotericsoftware.yamlbeans.YamlReader.YamlReaderException; + +import static org.junit.Assert.*; + +public class MergeTest { + + @SuppressWarnings("rawtypes") + @Test + public void testMerge() throws FileNotFoundException, YamlException { + InputStream input = new FileInputStream("test/test-merge.yml"); + Reader reader = new InputStreamReader(input); + Map data = new YamlReader(reader).read(Map.class); + Map stuff = (Map)data.get("merged"); + assertEquals("v1", stuff.get("v1")); + assertEquals("v2", stuff.get("v2")); + assertEquals("v3", stuff.get("v3")); + assertNull(stuff.get("<<")); + } + + @SuppressWarnings("unchecked") + @Test + public void testMergeMultipleMaps() throws YamlException { + StringBuilder sb = new StringBuilder(); + sb.append("test1: &1\n").append(" - key1: value1\n").append(" - key2: value2\n"); + sb.append("test2:\n").append(" << : *1"); + + YamlReader yamlReader = new YamlReader(sb.toString()); + Map map = (Map) yamlReader.read(); + assertEquals("value1", ((Map) map.get("test2")).get("key1")); + assertEquals("value2", ((Map) map.get("test2")).get("key2")); + } + + @SuppressWarnings("unchecked") + @Test + public void testMergeUpdateValue() throws YamlException { + StringBuilder sb = new StringBuilder(); + sb.append("test1: &1\n").append(" - key1: value1\n").append(" - key2: value2\n").append(" - key3: value3\n"); + + sb.append("test2:\n").append(" key2: value22\n").append(" << : *1\n").append(" key3: value33\n"); + + YamlReader yamlReader = new YamlReader(sb.toString()); + Map map = (Map) yamlReader.read(); + assertEquals("value1", ((Map) map.get("test2")).get("key1")); + assertEquals("value22", ((Map) map.get("test2")).get("key2")); + assertEquals("value33", ((Map) map.get("test2")).get("key3")); + } + + @Test + public void testMergeExpectThrowYamlReaderException() throws YamlException { + StringBuilder sb = new StringBuilder(); + sb.append("test1: &1 123\n"); + sb.append("<< : *1\n"); + + YamlReader yamlReader = new YamlReader(sb.toString()); + try { + yamlReader.read(); + fail("Expected a mapping or a sequence of mappings"); + } catch (YamlReaderException e) { + } + } +} diff --git a/test/com/esotericsoftware/yamlbeans/SafeYamlConfigTest.java b/test/com/esotericsoftware/yamlbeans/SafeYamlConfigTest.java new file mode 100644 index 0000000..7d59e3c --- /dev/null +++ b/test/com/esotericsoftware/yamlbeans/SafeYamlConfigTest.java @@ -0,0 +1,69 @@ + +package com.esotericsoftware.yamlbeans; + +import static junit.framework.Assert.*; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.junit.Test; + +public class SafeYamlConfigTest { + private static final String TESTOBJECT_TAG = "!com.esotericsoftware.yamlbeans.SafeYamlConfigTest$TestObject"; + private static final String LINE_SEPARATOR = System.getProperty("line.separator"); + + @Test + public void testDeserializationOfClassTag () throws YamlException { + SafeYamlConfig yamlConfig = new SafeYamlConfig(); + StringBuilder yamlData = new StringBuilder(); + yamlData.append(TESTOBJECT_TAG).append(LINE_SEPARATOR).append("a: test").append(LINE_SEPARATOR); + YamlReader reader = new YamlReader(yamlData.toString(), yamlConfig); + Object data = reader.read(); + assertTrue(data instanceof HashMap); + Map dataMap = (Map)data; + assertTrue(dataMap.containsKey("a")); + assertEquals("test", dataMap.get("a")); + } + + @Test + public void testIgnoreAnchor () throws YamlException { + SafeYamlConfig yamlConfig = new SafeYamlConfig(); + StringBuilder yamlData = new StringBuilder(); + yamlData.append("oldest friend:").append(LINE_SEPARATOR).append(" &1 !contact").append(LINE_SEPARATOR) + .append(" name: Bob").append(LINE_SEPARATOR).append(" age: 29").append(LINE_SEPARATOR).append("best friend: *1") + .append(LINE_SEPARATOR); + YamlReader reader = new YamlReader(yamlData.toString(), yamlConfig); + Object data = reader.read(); + assertTrue(data instanceof HashMap); + Map dataMap = (Map)data; + assertTrue(dataMap.containsKey("oldest friend")); + Map old = (Map)dataMap.get("oldest friend"); + assertTrue(old.containsKey("name")); + assertEquals("Bob", old.get("name")); + assertNull(dataMap.get("best friend")); + } + + static class TestObject { + private String a; + public int age; + public String name; + public Object object; + public List objects; + + private TestObject () { + } + + public TestObject (String a) { + this.a = a; + } + + public String getA () { + return a; + } + + public void setA (String a) { + this.a = a; + } + } +} diff --git a/test/com/esotericsoftware/yamlbeans/TagDirectiveTest.java b/test/com/esotericsoftware/yamlbeans/TagDirectiveTest.java new file mode 100644 index 0000000..1ab6adf --- /dev/null +++ b/test/com/esotericsoftware/yamlbeans/TagDirectiveTest.java @@ -0,0 +1,84 @@ +package com.esotericsoftware.yamlbeans; + +import static org.junit.Assert.assertEquals; + +import java.util.Iterator; + +import org.junit.Test; + +import com.esotericsoftware.yamlbeans.document.YamlDocumentReader; +import com.esotericsoftware.yamlbeans.document.YamlElement; +import com.esotericsoftware.yamlbeans.document.YamlSequence; + +public class TagDirectiveTest { + + private static final String LINE_SEPARATOR = System.getProperty("line.separator"); + + @Test + public void testTagDirective() throws YamlException { + StringBuilder sb = new StringBuilder(); + sb.append("%TAG !yaml! tag:yaml.org,2002:").append(LINE_SEPARATOR); + sb.append("---").append(LINE_SEPARATOR); + sb.append("!yaml!str \"foo\""); + + YamlReader yamlReader = new YamlReader(sb.toString()); + assertEquals("foo", yamlReader.read().toString()); + } + + @Test(expected = YamlException.class) + public void testRepeatedTagDirective() throws YamlException { + StringBuilder sb = new StringBuilder(); + sb.append("%TAG %TAG ! !foo").append(LINE_SEPARATOR); + sb.append("%TAG %TAG ! !foo").append(LINE_SEPARATOR); + sb.append("---").append(LINE_SEPARATOR); + sb.append("bar"); + + new YamlReader(sb.toString()).read(); + } + + @Test + public void testTagHandles() throws YamlException { + StringBuilder sb = new StringBuilder(); + sb.append("%TAG ! !").append(LINE_SEPARATOR); + sb.append("%TAG !yaml! tag:yaml.org,2002:").append(LINE_SEPARATOR); + sb.append("%TAG !o! tag:ben-kiki.org,2000:").append(LINE_SEPARATOR); + sb.append("---").append(LINE_SEPARATOR); + sb.append("- !foo bar").append(LINE_SEPARATOR); + sb.append("- !!str string").append(LINE_SEPARATOR); + sb.append("- !o!type baz").append(LINE_SEPARATOR); + YamlDocumentReader yamlDocumentReader = new YamlDocumentReader(sb.toString()); + Iterator iterator = yamlDocumentReader.read(YamlSequence.class).iterator(); + + YamlElement yamlElement = iterator.next(); + assertEquals("!foo", yamlElement.getTag()); + + yamlElement = iterator.next(); + assertEquals("tag:yaml.org,2002:str", yamlElement.getTag()); + + yamlElement = iterator.next(); + assertEquals("tag:ben-kiki.org,2000:type", yamlElement.getTag()); + } + + @Test + public void testVersion() throws YamlException { + String yaml = "!str test"; + YamlConfig yamlConfig = new YamlConfig(); + yamlConfig.readConfig.setDefaultVersion(Version.V1_0); + YamlReader yamlReader = new YamlReader(yaml, yamlConfig); + assertEquals("test", yamlReader.read()); + + yaml = "!!str test"; + yamlConfig.readConfig.setDefaultVersion(Version.V1_1); + yamlReader = new YamlReader(yaml, yamlConfig); + assertEquals("test", yamlReader.read()); + + yaml = "!!str test"; + yamlConfig.readConfig.setDefaultVersion(Version.V1_0); + yamlReader = new YamlReader(yaml, yamlConfig); + try { + yamlReader.read(); + } catch (YamlException e) { + assertEquals("Error parsing YAML.", e.getMessage()); + } + } +} diff --git a/test/com/esotericsoftware/yamlbeans/VersionTest.java b/test/com/esotericsoftware/yamlbeans/VersionTest.java new file mode 100644 index 0000000..d763010 --- /dev/null +++ b/test/com/esotericsoftware/yamlbeans/VersionTest.java @@ -0,0 +1,39 @@ +package com.esotericsoftware.yamlbeans; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; + +import org.junit.Test; + +public class VersionTest { + + @Test + public void testV1_0() { + Version version = Version.V1_0; + assertEquals(1, version.getMajor()); + assertEquals(0, version.getMinor()); + assertEquals("1.0", version.toString()); + } + + @Test + public void testV1_1() { + Version version = Version.V1_1; + assertEquals(1, version.getMajor()); + assertEquals(1, version.getMinor()); + assertEquals("1.1", version.toString()); + } + + @Test + public void testGetVersion() { + assertEquals(Version.V1_0, Version.getVersion("1.0")); + assertEquals(Version.V1_1, Version.getVersion("1.1")); + assertNull(Version.getVersion(null)); + assertNull(Version.getVersion("0")); + assertNull(Version.getVersion(".0")); + assertNull(Version.getVersion("0.1")); + assertNull(Version.getVersion("1.2")); + assertNull(Version.getVersion("2.0")); + assertNull(Version.getVersion("1.a")); + assertNull(Version.getVersion("a.1")); + } +} diff --git a/test/com/esotericsoftware/yamlbeans/YamlConfigTest.java b/test/com/esotericsoftware/yamlbeans/YamlConfigTest.java new file mode 100644 index 0000000..ddb2bb2 --- /dev/null +++ b/test/com/esotericsoftware/yamlbeans/YamlConfigTest.java @@ -0,0 +1,677 @@ +package com.esotericsoftware.yamlbeans; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import java.io.StringWriter; +import java.util.ArrayList; +import java.util.Date; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.TimeZone; + +import org.junit.Before; +import org.junit.Test; + +import com.esotericsoftware.yamlbeans.YamlConfig.Quote; +import com.esotericsoftware.yamlbeans.YamlReader.YamlReaderException; +import com.esotericsoftware.yamlbeans.scalar.DateSerializer; + +@SuppressWarnings("synthetic-access") +public class YamlConfigTest { + + private static final String LINE_SEPARATOR = System.getProperty("line.separator"); + + private static final String TESTOBJECT_TAG = "!com.esotericsoftware.yamlbeans.YamlConfigTest$TestObject"; + + private YamlConfig yamlConfig; + + @Before + public void setup() throws Exception { + yamlConfig = new YamlConfig(); + } + + @Test + public void testSetClassTag() throws YamlException { + yamlConfig.setClassTag("String", String.class); + yamlConfig.setClassTag("!Int", Integer.class); + String yaml = "!String test\n---\n!Int 1"; + YamlReader yamlReader = new YamlReader(yaml, yamlConfig); + assertEquals("test", yamlReader.read()); + assertEquals(1, yamlReader.read()); + + try { + yamlConfig.setClassTag(null, String.class); + } catch (IllegalArgumentException e) { + assertEquals("tag cannot be null.", e.getMessage()); + } + + try { + yamlConfig.setClassTag("str", null); + } catch (IllegalArgumentException e) { + assertEquals("type cannot be null.", e.getMessage()); + } + } + + @Test + public void testSetScalarSerializer() throws YamlException { + TimeZone timeZone = TimeZone.getTimeZone("GMT+0"); + TimeZone.setDefault(timeZone); + + yamlConfig.setScalarSerializer(Date.class, new DateSerializer()); + String yaml = "!java.util.Date 1970-01-01 00:00:00"; + YamlReader yamlReader = new YamlReader(yaml, yamlConfig); + Object object = yamlReader.read(); + assertEquals(Date.class, object.getClass()); + assertEquals(0, ((Date) object).getTime()); + + try { + yamlConfig.setScalarSerializer(null, new DateSerializer()); + } catch (IllegalArgumentException e) { + assertEquals("type cannot be null.", e.getMessage()); + } + + try { + yamlConfig.setScalarSerializer(Date.class, null); + } catch (IllegalArgumentException e) { + assertEquals("serializer cannot be null.", e.getMessage()); + } + } + + @Test + public void testSetPropertyElementType() throws YamlException { + yamlConfig.setPropertyElementType(TestObject.class, "objects", Date.class); + + StringBuilder sb = new StringBuilder(); + sb.append("objects:" + LINE_SEPARATOR); + sb.append("- 2020-06-30 00:00:00" + LINE_SEPARATOR); + + YamlReader yamlReader = new YamlReader(sb.toString(), yamlConfig); + TestObject testObject = yamlReader.read(TestObject.class); + assertEquals(Date.class, testObject.objects.get(0).getClass()); + + try { + yamlConfig.setPropertyElementType(null, "objects", Date.class); + } catch (IllegalArgumentException e) { + assertEquals("type cannot be null.", e.getMessage()); + } + + try { + yamlConfig.setPropertyElementType(TestObject.class, null, Date.class); + } catch (IllegalArgumentException e) { + assertEquals("propertyName cannot be null.", e.getMessage()); + } + + try { + yamlConfig.setPropertyElementType(TestObject.class, "objects", null); + } catch (IllegalArgumentException e) { + assertEquals("propertyType cannot be null.", e.getMessage()); + } + + try { + yamlConfig.setPropertyElementType(TestObject.class, "aaa", Object.class); + } catch (IllegalArgumentException e) { + assertTrue(e.getMessage().contains("does not have a property named")); + } + + try { + yamlConfig.setPropertyElementType(TestObject.class, "object", Date.class); + } catch (IllegalArgumentException e) { + assertTrue(e.getMessage().contains("class must be a Collection or Map")); + } + } + + @Test + public void testSetPropertyDefaultType() throws YamlException { + yamlConfig.setPropertyDefaultType(TestObject.class, "object", Date.class); + + String yaml = "object: 2020-06-30 00:00:00"; + + YamlReader yamlReader = new YamlReader(yaml, yamlConfig); + TestObject testObject = yamlReader.read(TestObject.class); + assertEquals(Date.class, testObject.object.getClass()); + + try { + yamlConfig.setPropertyDefaultType(null, "object", Date.class); + } catch (IllegalArgumentException e) { + assertEquals("type cannot be null.", e.getMessage()); + } + + try { + yamlConfig.setPropertyDefaultType(TestObject.class, null, Date.class); + } catch (IllegalArgumentException e) { + assertEquals("propertyName cannot be null.", e.getMessage()); + } + + try { + yamlConfig.setPropertyDefaultType(TestObject.class, "object", null); + } catch (IllegalArgumentException e) { + assertEquals("defaultType cannot be null.", e.getMessage()); + } + + try { + yamlConfig.setPropertyDefaultType(TestObject.class, "aaa", Date.class); + } catch (IllegalArgumentException e) { + assertTrue(e.getMessage().contains("does not have a property named")); + } + } + + @Test + public void testSetBeanProperties() throws YamlException { + String yaml = "a: test"; + YamlReader yamlReader = new YamlReader(yaml, yamlConfig); + TestObject testObject = yamlReader.read(TestObject.class); + assertEquals("test", testObject.a); + + yamlConfig.setBeanProperties(false); + yamlReader = new YamlReader(yaml, yamlConfig); + try { + yamlReader.read(TestObject.class); + } catch (YamlReaderException e) { + assertTrue(e.getMessage().contains("Unable to find property")); + } + } + + @Test + public void testSetPrivateConstructors() throws YamlException { + String yaml = "a: test"; + YamlReader yamlReader = new YamlReader(yaml, yamlConfig); + TestObject testObject = yamlReader.read(TestObject.class); + assertEquals("test", testObject.a); + + yamlConfig.setPrivateConstructors(false); + yamlReader = new YamlReader(yaml, yamlConfig); + try { + yamlReader.read(TestObject.class); + } catch (YamlReaderException e) { + assertTrue(e.getMessage().contains("Error creating object")); + } + } + + @SuppressWarnings("unchecked") + @Test + public void testSetTagSuffix() throws YamlException { + StringWriter stringWriter = new StringWriter(); + yamlConfig.setTagSuffix("tag"); + YamlWriter yamlWriter = new YamlWriter(stringWriter, yamlConfig); + Map map = new HashMap(); + map.put("a", "111"); + map.put("atag", "!222"); + yamlWriter.write(map); + yamlWriter.close(); + + assertEquals("a: !222 111" + LINE_SEPARATOR, stringWriter.toString()); + + yamlConfig.setClassTag("!222", String.class); + YamlReader yamlReader = new YamlReader(stringWriter.toString(), yamlConfig); + Map result = yamlReader.read(Map.class); + assertEquals(map.toString(), result.toString()); + } + + @Test + public void testSetExplicitEndDocument() throws YamlException { + StringWriter stringWriter = new StringWriter(); + YamlWriter yamlWriter = new YamlWriter(stringWriter, yamlConfig); + yamlWriter.write("test"); + yamlWriter.close(); + + assertEquals("test" + LINE_SEPARATOR, stringWriter.toString()); + + stringWriter = new StringWriter(); + yamlConfig.writeConfig.setExplicitEndDocument(true); + yamlWriter = new YamlWriter(stringWriter, yamlConfig); + yamlWriter.write("test"); + yamlWriter.close(); + assertEquals("test" + LINE_SEPARATOR + "..." + LINE_SEPARATOR, stringWriter.toString()); + } + + @Test + public void testSetWriteRootTags() throws YamlException { + TestObject to = new TestObject(); + to.a = "test"; + + StringWriter stringWriter = new StringWriter(); + YamlWriter yamlWriter = new YamlWriter(stringWriter, yamlConfig); + yamlWriter.write(to); + yamlWriter.close(); + + assertEquals(TESTOBJECT_TAG + LINE_SEPARATOR + "a: test" + LINE_SEPARATOR, stringWriter.toString()); + + yamlConfig.writeConfig.setWriteRootTags(false); + stringWriter = new StringWriter(); + yamlWriter = new YamlWriter(stringWriter, yamlConfig); + yamlWriter.write(to); + yamlWriter.close(); + assertEquals("a: test" + LINE_SEPARATOR, stringWriter.toString()); + } + + @Test + public void testSetWriteRootElementTags() throws YamlException { + TestObject to1 = new TestObject(); + to1.a = "test"; + List list = new ArrayList(); + list.add(to1); + + TestObject to2 = new TestObject(); + to2.a = "test"; + Map map = new HashMap(); + map.put("test", to2); + + yamlConfig.writeConfig.setWriteRootElementTags(true); + int indentSize = 3; + StringWriter stringWriter = new StringWriter(); + yamlConfig.writeConfig.setIndentSize(indentSize); + YamlWriter yamlWriter = new YamlWriter(stringWriter, yamlConfig); + yamlWriter.write(list); + yamlWriter.write(map); + yamlWriter.close(); + + assertEquals("- " + TESTOBJECT_TAG + LINE_SEPARATOR + multipleSpaces(indentSize) + "a: test" + LINE_SEPARATOR + + "--- " + LINE_SEPARATOR + "test: " + TESTOBJECT_TAG + LINE_SEPARATOR + multipleSpaces(indentSize) + + "a: test" + LINE_SEPARATOR, stringWriter.toString()); + + yamlConfig.writeConfig.setWriteRootElementTags(false); + stringWriter = new StringWriter(); + yamlWriter = new YamlWriter(stringWriter, yamlConfig); + yamlWriter.write(list); + yamlWriter.write(map); + yamlWriter.close(); + + assertEquals( + "-" + multipleSpaces(indentSize - 1) + "a: test" + LINE_SEPARATOR + "--- " + LINE_SEPARATOR + "test: " + + LINE_SEPARATOR + multipleSpaces(indentSize) + "a: test" + LINE_SEPARATOR, + stringWriter.toString()); + } + + @Test + public void testSetWriteDefaultValues() throws YamlException { + StringWriter stringWriter = new StringWriter(); + YamlWriter yamlWriter = new YamlWriter(stringWriter, yamlConfig); + TestObject testObject = new TestObject(); + yamlWriter.write(testObject); + yamlWriter.close(); + + assertEquals(TESTOBJECT_TAG + " {}" + LINE_SEPARATOR, stringWriter.toString()); + + yamlConfig.writeConfig.setWriteDefaultValues(true); + yamlWriter = new YamlWriter(stringWriter, yamlConfig); + yamlWriter.write(testObject); + yamlWriter.close(); + assertFalse((TESTOBJECT_TAG + " {}" + LINE_SEPARATOR).equals(stringWriter.toString())); + assertTrue(stringWriter.toString().contains(TESTOBJECT_TAG + LINE_SEPARATOR)); + assertTrue(stringWriter.toString().contains("a: " + LINE_SEPARATOR)); + + assertTrue(stringWriter.toString().contains("object: " + LINE_SEPARATOR)); + } + + @Test + public void testSetKeepBeanPropertyOrder() throws YamlException { + StringWriter stringWriter = new StringWriter(); + YamlWriter yamlWriter = new YamlWriter(stringWriter, yamlConfig); + TestObject testObject = new TestObject(); + testObject.name = "Jack"; + testObject.age = 18; + yamlWriter.write(testObject); + yamlWriter.close(); + + assertEquals(TESTOBJECT_TAG + LINE_SEPARATOR + "name: Jack" + LINE_SEPARATOR + "age: 18" + LINE_SEPARATOR, + stringWriter.toString()); + + yamlConfig.writeConfig.setKeepBeanPropertyOrder(true); + stringWriter = new StringWriter(); + yamlWriter = new YamlWriter(stringWriter, yamlConfig); + yamlWriter.write(testObject); + yamlWriter.close(); + assertEquals(TESTOBJECT_TAG + LINE_SEPARATOR + "age: 18" + LINE_SEPARATOR + "name: Jack" + LINE_SEPARATOR, + stringWriter.toString()); + } + + @Test + public void testSetVersion() throws YamlException { + + StringWriter stringWriter = new StringWriter(); + YamlWriter yamlWriter = new YamlWriter(stringWriter, yamlConfig); + yamlWriter.write("test"); + yamlWriter.close(); + assertEquals("test" + LINE_SEPARATOR, stringWriter.toString()); + + Version version = Version.V1_0; + yamlConfig.writeConfig.setVersion(version); + stringWriter = new StringWriter(); + yamlWriter = new YamlWriter(stringWriter, yamlConfig); + yamlWriter.write("test"); + yamlWriter.close(); + assertEquals("%YAML 1.0" + LINE_SEPARATOR + "--- test" + LINE_SEPARATOR, stringWriter.toString()); + + version = Version.V1_1; + yamlConfig.writeConfig.setVersion(version); + stringWriter = new StringWriter(); + yamlWriter = new YamlWriter(stringWriter, yamlConfig); + yamlWriter.write("test"); + yamlWriter.close(); + assertEquals("%YAML 1.1" + LINE_SEPARATOR + "--- test" + LINE_SEPARATOR, stringWriter.toString()); + } + + @Test + public void testSetTags() throws YamlException { + Map tags = new HashMap(); + tags.put("!foo!", "bar"); + yamlConfig.writeConfig.setTags(tags); + StringWriter stringWriter = new StringWriter(); + YamlWriter yamlWriter = new YamlWriter(stringWriter, yamlConfig); + yamlWriter.write("test"); + yamlWriter.close(); + assertEquals("%TAG !foo! bar" + LINE_SEPARATOR + "--- test" + LINE_SEPARATOR, stringWriter.toString()); + } + + @Test + public void testSetCanonical() throws YamlException { + int indentSize = 3; + yamlConfig.writeConfig.setIndentSize(indentSize); + yamlConfig.writeConfig.setCanonical(true); + StringWriter stringWriter = new StringWriter(); + YamlWriter yamlWriter = new YamlWriter(stringWriter, yamlConfig); + yamlWriter.write("test"); + yamlWriter.close(); + assertEquals("--- " + LINE_SEPARATOR + "!java.lang.String \"test\"" + LINE_SEPARATOR, stringWriter.toString()); + + List list = new ArrayList(); + list.add("111"); + list.add("222"); + stringWriter = new StringWriter(); + yamlWriter = new YamlWriter(stringWriter, yamlConfig); + yamlWriter.write(list); + yamlWriter.close(); + assertEquals("--- " + LINE_SEPARATOR + "[" + LINE_SEPARATOR + multipleSpaces(indentSize) + + "!java.lang.String \"111\"," + LINE_SEPARATOR + multipleSpaces(indentSize) + + "!java.lang.String \"222\"" + LINE_SEPARATOR + "]" + LINE_SEPARATOR, stringWriter.toString()); + + Map map = new HashMap(); + map.put("key", "value"); + stringWriter = new StringWriter(); + yamlWriter = new YamlWriter(stringWriter, yamlConfig); + yamlWriter.write(map); + yamlWriter.close(); + assertEquals( + "--- " + LINE_SEPARATOR + "{" + LINE_SEPARATOR + multipleSpaces(indentSize) + + "? !java.lang.String \"key\"" + LINE_SEPARATOR + multipleSpaces(indentSize) + + ": !java.lang.String \"value\"" + LINE_SEPARATOR + "}" + LINE_SEPARATOR, + stringWriter.toString()); + } + + @Test + public void testSetIndentSize() throws YamlException { + int indentSize = 5; + List list = new ArrayList(); + list.add(1); + + StringWriter stringWriter = new StringWriter(); + yamlConfig.writeConfig.setCanonical(true); + yamlConfig.writeConfig.setIndentSize(indentSize); + YamlWriter yamlWriter = new YamlWriter(stringWriter, yamlConfig); + yamlWriter.write(list); + yamlWriter.close(); + assertEquals("--- " + LINE_SEPARATOR + "[" + LINE_SEPARATOR + multipleSpaces(indentSize) + + "!java.lang.Integer \"1\"" + LINE_SEPARATOR + "]" + LINE_SEPARATOR, stringWriter.toString()); + + try { + yamlConfig.writeConfig.setIndentSize(1); + } catch (Exception e) { + assertEquals("indentSize cannot be less than 2.", e.getMessage()); + } + } + + @Test + public void testSetWrapColumn() throws YamlException { + String yaml = "aaaaaa aaaaa"; + int indentSize = 3; + yamlConfig.writeConfig.setIndentSize(indentSize); + yamlConfig.writeConfig.setWrapColumn(5); + StringWriter stringWriter = new StringWriter(); + YamlWriter yamlWriter = new YamlWriter(stringWriter, yamlConfig); + yamlWriter.write(yaml); + yamlWriter.close(); + + assertEquals("aaaaaa" + LINE_SEPARATOR + multipleSpaces(indentSize) + "aaaaa" + LINE_SEPARATOR, + stringWriter.toString()); + + YamlReader yamlReader = new YamlReader(stringWriter.toString()); + assertEquals(yaml, yamlReader.read(String.class)); + + try { + yamlConfig.writeConfig.setWrapColumn(3); + } catch (IllegalArgumentException e) { + assertEquals("wrapColumn must be greater than 4.", e.getMessage()); + } + + } + + @Test + public void testSetUseVerbatimTags() throws YamlException { + List list = new LinkedList(); + list.add(1); + StringWriter stringWriter = new StringWriter(); + YamlWriter yamlWriter = new YamlWriter(stringWriter, yamlConfig); + yamlWriter.write(list); + yamlWriter.close(); + + assertEquals("!java.util.LinkedList" + LINE_SEPARATOR + "- 1" + LINE_SEPARATOR, stringWriter.toString()); + + stringWriter = new StringWriter(); + yamlConfig.writeConfig.setUseVerbatimTags(true); + yamlWriter = new YamlWriter(stringWriter, yamlConfig); + yamlWriter.write(list); + yamlWriter.close(); + + assertEquals("!" + LINE_SEPARATOR + "- 1" + LINE_SEPARATOR, stringWriter.toString()); + } + + @Test + public void testSetQuoteChar() throws YamlException { + TestObject testObject = new TestObject(); + testObject.age = 18; + testObject.name = "xxx"; + StringWriter stringWriter = new StringWriter(); + yamlConfig.writeConfig.setWriteRootTags(false); + yamlConfig.writeConfig.setQuoteChar(Quote.NONE); + YamlWriter yamlWriter = new YamlWriter(stringWriter, yamlConfig); + yamlWriter.write(testObject); + yamlWriter.close(); + assertEquals("name: xxx" + LINE_SEPARATOR + "age: 18" + LINE_SEPARATOR, stringWriter.toString()); + + stringWriter = new StringWriter(); + yamlConfig.writeConfig.setQuoteChar(Quote.SINGLE); + yamlWriter = new YamlWriter(stringWriter, yamlConfig); + yamlWriter.write(testObject); + yamlWriter.close(); + assertEquals("\'name\': \'xxx\'" + LINE_SEPARATOR + "\'age\': \'18\'" + LINE_SEPARATOR, + stringWriter.toString()); + + stringWriter = new StringWriter(); + yamlConfig.writeConfig.setQuoteChar(Quote.DOUBLE); + yamlWriter = new YamlWriter(stringWriter, yamlConfig); + yamlWriter.write(testObject); + yamlWriter.close(); + assertEquals("\"name\": \"xxx\"" + LINE_SEPARATOR + "\"age\": \"18\"" + LINE_SEPARATOR, + stringWriter.toString()); + + stringWriter = new StringWriter(); + yamlConfig.writeConfig.setQuoteChar(Quote.LITERAL); + yamlWriter = new YamlWriter(stringWriter, yamlConfig); + yamlWriter.write("test\ntest"); + yamlWriter.close(); + assertEquals("|-" + LINE_SEPARATOR + " test" + LINE_SEPARATOR + " test" + LINE_SEPARATOR, + stringWriter.toString()); + + stringWriter = new StringWriter(); + yamlConfig.writeConfig.setQuoteChar(Quote.FOLDED); + yamlWriter = new YamlWriter(stringWriter, yamlConfig); + yamlWriter.write("test\ntest"); + yamlWriter.close(); + assertEquals(">-" + LINE_SEPARATOR + " test" + LINE_SEPARATOR + LINE_SEPARATOR + " test" + LINE_SEPARATOR, + stringWriter.toString()); + } + + @Test + public void testSetDefaultVersion() throws YamlException { + String yaml = "!!str test"; + yamlConfig.readConfig.setDefaultVersion(Version.V1_1); + YamlReader yamlReader = new YamlReader(yaml, yamlConfig); + assertEquals("test", yamlReader.read(String.class)); + + try { + yamlConfig.readConfig.setDefaultVersion(null); + } catch (IllegalArgumentException e) { + assertEquals("defaultVersion cannot be null.", e.getMessage()); + } + + } + + @Test + public void testConstructorParameters() throws YamlException { + yamlConfig.readConfig.setConstructorParameters(TestObject.class, new Class[] { String.class }, + new String[] { "a" }); + YamlReader yamlReader = new YamlReader("a: test", yamlConfig); + assertEquals("test", yamlReader.read(TestObject.class).a); + + try { + yamlConfig.readConfig.setConstructorParameters(null, new Class[] { String.class }, new String[] { "a" }); + } catch (IllegalArgumentException e) { + assertEquals("type cannot be null.", e.getMessage()); + } + + try { + yamlConfig.readConfig.setConstructorParameters(TestObject.class, null, new String[] { "a" }); + } catch (IllegalArgumentException e) { + assertEquals("parameterTypes cannot be null.", e.getMessage()); + } + + try { + yamlConfig.readConfig.setConstructorParameters(TestObject.class, new Class[] { String.class }, null); + } catch (IllegalArgumentException e) { + assertEquals("parameterNames cannot be null.", e.getMessage()); + } + + try { + yamlConfig.readConfig.setConstructorParameters(TestObject.class, new Class[] { String.class, String.class }, + new String[] { "a", "name" }); + } catch (IllegalArgumentException e) { + assertTrue(e.getMessage().startsWith("Unable to find constructor: ")); + } + + } + + @Test + public void testSetFlowStyle() throws YamlException { + List list = new ArrayList(); + list.add("111"); + list.add("222"); + list.add("333"); + + Map map = new HashMap(); + map.put("key", "value"); + + StringWriter stringWriter = new StringWriter(); + YamlWriter yamlWriter = new YamlWriter(stringWriter, yamlConfig); + yamlWriter.write(list); + yamlWriter.close(); + + assertEquals("- 111" + LINE_SEPARATOR + "- 222" + LINE_SEPARATOR + "- 333" + LINE_SEPARATOR, + stringWriter.toString()); + + stringWriter = new StringWriter(); + yamlWriter = new YamlWriter(stringWriter, yamlConfig); + yamlWriter.write(map); + yamlWriter.close(); + assertEquals("key: value" + LINE_SEPARATOR, stringWriter.toString()); + + yamlConfig.writeConfig.setFlowStyle(true); + stringWriter = new StringWriter(); + yamlWriter = new YamlWriter(stringWriter, yamlConfig); + yamlWriter.write(list); + yamlWriter.close(); + assertEquals("[111, 222, 333]" + LINE_SEPARATOR, stringWriter.toString()); + + stringWriter = new StringWriter(); + yamlWriter = new YamlWriter(stringWriter, yamlConfig); + yamlWriter.write(map); + yamlWriter.close(); + assertEquals("{key: value}" + LINE_SEPARATOR, stringWriter.toString()); + } + + @Test + public void testSetPrettyFlow() throws YamlException { + Map map = new LinkedHashMap(); + map.put("key1", "value1"); + map.put("key2", "value2"); + + List list = new ArrayList(); + list.add("111"); + list.add("222"); + list.add("333"); + + yamlConfig.writeConfig.setWriteRootTags(false); + yamlConfig.writeConfig.setFlowStyle(true); + StringWriter stringWriter = new StringWriter(); + YamlWriter yamlWriter = new YamlWriter(stringWriter, yamlConfig); + yamlWriter.write(map); + yamlWriter.close(); + assertEquals("{key1: value1, key2: value2}" + LINE_SEPARATOR, stringWriter.toString()); + + stringWriter = new StringWriter(); + yamlWriter = new YamlWriter(stringWriter, yamlConfig); + yamlWriter.write(list); + yamlWriter.close(); + assertEquals("[111, 222, 333]" + LINE_SEPARATOR, stringWriter.toString()); + + yamlConfig.writeConfig.setPrettyFlow(true); + stringWriter = new StringWriter(); + yamlWriter = new YamlWriter(stringWriter, yamlConfig); + yamlWriter.write(map); + yamlWriter.close(); + assertEquals("{" + LINE_SEPARATOR + " key1: value1," + LINE_SEPARATOR + " key2: value2" + LINE_SEPARATOR + + "}" + LINE_SEPARATOR, stringWriter.toString()); + + stringWriter = new StringWriter(); + yamlWriter = new YamlWriter(stringWriter, yamlConfig); + yamlWriter.write(list); + yamlWriter.close(); + assertEquals("[" + LINE_SEPARATOR + " 111," + LINE_SEPARATOR + " 222," + LINE_SEPARATOR + " 333" + + LINE_SEPARATOR + "]" + LINE_SEPARATOR, stringWriter.toString()); + } + + private String multipleSpaces(int indentSize) { + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < indentSize; i++) { + sb.append(" "); + } + return sb.toString(); + } + + static class TestObject { + private String a; + public int age; + public String name; + public Object object; + public List objects; + + private TestObject() { + } + + public TestObject(String a) { + this.a = a; + } + + public String getA() { + return a; + } + + public void setA(String a) { + this.a = a; + } + } +} diff --git a/test/com/esotericsoftware/yamlbeans/YamlReaderTest.java b/test/com/esotericsoftware/yamlbeans/YamlReaderTest.java index 6b94992..1af7479 100644 --- a/test/com/esotericsoftware/yamlbeans/YamlReaderTest.java +++ b/test/com/esotericsoftware/yamlbeans/YamlReaderTest.java @@ -16,9 +16,16 @@ package com.esotericsoftware.yamlbeans; +import java.io.StringWriter; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; import java.util.List; import java.util.Map; + +import com.esotericsoftware.yamlbeans.YamlReader.YamlReaderException; + import junit.framework.TestCase; /** @author Nathan Sweet */ @@ -28,7 +35,7 @@ public void testSimpleFields () throws Exception { "stringValue: moo\ufec9moo\n" + // "intValue: !!int 123\n" + // "floatValue: 0.3\n" + // - "doubleValue: 0.0002\n" + // + "doubleValue: 0.0002 # comment\n" + // "longValue: 999999\n" + // "shortValue: 125\n" + // "charValue: j\n" + // @@ -155,11 +162,30 @@ public void testCyclicReferences () throws Exception { assertTrue(root.right.right == null); } + public void testReaderMaintainsOrderWhenReadingMap() throws Exception { + YamlReader reader = new YamlReader("a: b\nkey1: value1\nc: d\n"); + Map map = (Map)reader.read(); + Iterator entrySetIterator = map.entrySet().iterator(); + + Map.Entry mapEntry = (Map.Entry)entrySetIterator.next(); + assertEquals(mapEntry.getKey(), "a"); + assertEquals(mapEntry.getValue(), "b"); + + mapEntry = (Map.Entry)entrySetIterator.next(); + assertEquals(mapEntry.getKey(), "key1"); + assertEquals(mapEntry.getValue(), "value1"); + + mapEntry = (Map.Entry)entrySetIterator.next(); + assertEquals(mapEntry.getKey(), "c"); + assertEquals(mapEntry.getValue(), "d"); + } + private Test read (String yaml) throws Exception { if (true) { System.out.println(yaml); System.out.println("==="); - System.out.println(new YamlReader(yaml).read(null)); + Object obj = new YamlReader(yaml).read(null); + System.out.println(obj); System.out.println(); System.out.println(); } @@ -292,4 +318,499 @@ public void setZ (int z) { this.z = z; } } + + static class ACD { + public int a, c, d = 4; + } + + public void testIgnoreUnknownProperties () { + String input = "a: 1\nb: 2\nc: 3"; + YamlConfig config = new YamlConfig(); + try { + new YamlReader(input, config).read(ACD.class); + fail("Unknown properties were not supposed to be allowed."); + } catch (YamlException e) { + } + + config.readConfig.setIgnoreUnknownProperties(true); + try { + ACD pojo = new YamlReader(input, config).read(ACD.class); + assertEquals(1, pojo.a); + assertEquals(3, pojo.c); + assertEquals(4, pojo.d); + } catch (YamlException e) { + fail("Unknown properties were supposed to be allowed."); + } + } + + public void testIgnoreUnknownPropertiesSprcialType() throws Exception { + String input = "a: 1\nb: 2\nc: 3\n" + "sequence: \n" + " - 1\n" + " - 2\n" + "mapping: \n key: value\n" + + "stream: {a: 7}\ndocument: ---\na: 9\n...\n"; + + YamlConfig config = new YamlConfig(); + config.readConfig.setIgnoreUnknownProperties(true); + try { + ACD pojo = new YamlReader(input, config).read(ACD.class); + assertEquals(1, pojo.a); + assertEquals(3, pojo.c); + assertEquals(4, pojo.d); + } catch (YamlException e) { + fail("Unknown properties were supposed to be allowed."); + } + } + + public void testDuplicateKeysAreNotAllowedIfAllowDuplicateIsSetSo () { + String inputWithDuplicates = "a: 1\na: 2\nc: 3"; + YamlConfig yamlConfig = new YamlConfig(); + yamlConfig.setAllowDuplicates(false); + YamlReader yamlReader = new YamlReader(inputWithDuplicates, yamlConfig); + try { + yamlReader.read(); + fail("Duplicates should not have been allowed."); + } catch (YamlException e) { + } + + String inputWithoutDuplicates = "a: 1\nb: 2\nc: 3"; + YamlReader yamlReader1 = new YamlReader(inputWithoutDuplicates, yamlConfig); + try { + yamlReader1.read(); + } catch (YamlException e) { + fail("Duplicates should have been allowed."); + } + } + + public void testDuplicateKeysAreAllowedIfAllowDuplicateIsSetSo () { + String inputWithDuplicates = "a: 1\na: 2\nc: 3"; + YamlReader yamlReader = new YamlReader(inputWithDuplicates); + try { + yamlReader.read(); + } catch (YamlException e) { + e.printStackTrace(); + fail("Duplicates should have been allowed."); + } + + String inputWithoutDuplicates = "a: 1\nb: 2\nc: 3"; + YamlConfig yamlConfig = new YamlConfig(); + yamlConfig.setAllowDuplicates(false); + YamlReader yamlReader1 = new YamlReader(inputWithoutDuplicates, yamlConfig); + try { + yamlReader1.read(); + } catch (YamlException e) { + e.printStackTrace(); + fail("Duplicates should have been allowed."); + } + } + + private static class TypeTagIgnoringReader extends YamlReader { + + public TypeTagIgnoringReader(String yaml) { + super(yaml); + } + + @Override + protected Class findTagClass(String tag, ClassLoader classLoader) { + // Ignore all tags. + return null; + } + + } + + public void testIgnoreTypeTagsTopLevel () throws YamlException { + // We are parsing this document that was output by another program using YamlBeans, that includes + // a type tag for a class that we don't have on our classpath. + String input = "!com.example.not.on.classpath.Fish\n" + // + "species: Walleye\n" + // + "weight: 24"; + + try { + new YamlReader(input).read(Map.class); + fail("A type tag with an unknown class fails to parse, even if we ask for it as a simple Map."); + } catch (YamlException e) { + // Expected behavior. + } + + // If we ignore type tags, the type tag in the document does not override our request for a Map. + Map map = new TypeTagIgnoringReader(input).read(Map.class); + assertEquals("Walleye", map.get("species")); + } + + static class Lake { + public String name; + public List fish; + } + + static class Fish { + public String species; + public int weight; + } + + public void testIgnoreTypeTagsEmbedded () throws YamlException { + // We are parsing this document that was output by another program using YamlBeans, that includes + // type tags for multiple classes that we don't have on our classpath. One of those type tags is + // embedded within the document, not at the top level. + String input = "!com.example.not.on.classpath.Lake\n" + // + "name: Superior\n" + // + "fish:\n" + + " - !com.example.not.on.classpath.Fish\n" + // + " species: Walleye\n" + // + " weight: 24"; + + try { + new YamlReader(input).read(Lake.class); + fail("A type tag with an unknown class fails to parse, even if we ask for it as a known class."); + } catch (YamlException e) { + // Expected behavior. + } + + // If we ignore type tags, we can specify the return type & embedded types instead of failing to parse. + Lake lake = new TypeTagIgnoringReader(input).read(Lake.class); + assertEquals("Superior", lake.name); + assertEquals(1, lake.fish.size()); + assertEquals("Walleye", lake.fish.get(0).species); + } + + /** + * issue #105 + * + * @throws YamlException + */ + public void testGuessNumberTypes() throws YamlException { + + long invoice = 3484312312313131l; + Map map = new HashMap(); + map.put("invoice", invoice); + map.put("date", "2001-01-23"); + + YamlConfig config = new YamlConfig(); + config.readConfig.setGuessNumberTypes(true); + StringWriter sw = new StringWriter(); + YamlWriter writer = new YamlWriter(sw, config); + writer.write(map); + writer.close(); + YamlReader reader = new YamlReader(sw.toString(), config); + Map result = (Map) reader.read(); + long invoiceValue = (Long) result.get("invoice"); + assertEquals(invoice, invoiceValue); + + Double money = 123.456; + map.put("money", money); + + writer = new YamlWriter(sw, config); + writer.write(map); + writer.close(); + + reader = new YamlReader(sw.toString(), config); + result = (Map) reader.read(); + double moneyValue = (Double) result.get("money"); + assertEquals(money, moneyValue); + } + + public void testGuessOctNumberAndHexNumber() throws YamlException { + + // Octal number string + String str = "{octNumber: 011}"; + YamlConfig config = new YamlConfig(); + config.readConfig.guessNumberTypes = true; + YamlReader reader = new YamlReader(str, config); + int octNumber = ((Map) reader.read()).get("octNumber").intValue(); + assertEquals(9, octNumber); + + // Hex number string + str = "{hexNumber: 0x11}"; + reader = new YamlReader(str, config); + int hexNumber = ((Map) reader.read()).get("hexNumber").intValue(); + assertEquals(17, hexNumber); + } + + /** + * issue #34 + * + * @throws YamlException + */ + public void testWhiteSpaceCharacters() throws YamlException { + + String stringWithSpace = "test test"; + YamlReader reader = new YamlReader(stringWithSpace); + assertEquals(stringWithSpace, reader.read(String.class)); + + String stringWithTab = "test\ttesttest"; + reader = new YamlReader(stringWithTab); + assertEquals(stringWithTab, reader.read(String.class)); + + String stringWithSpaceAndTab = "test test\ttest \ttest"; + reader = new YamlReader(stringWithSpaceAndTab); + assertEquals(stringWithSpaceAndTab, reader.read(String.class)); + } + + /** + * Test multiple documents, the document is a scalar, followed by "---" or + * "...", the test read is normal. + * + * @throws YamlException + */ + public void testReadMultiTpyeDocuments() throws YamlException { + String yaml = "scalar\n---\nkey: value\n---\nscalar\n...\n"; + YamlReader reader = new YamlReader(yaml); + String str = reader.read(String.class); + assertEquals("scalar", str); + + Map map = (Map) reader.read(HashMap.class); + assertEquals("value", map.get("key")); + + str = reader.read(String.class); + assertEquals("scalar", str); + } + + public void testRead() throws YamlException { + String yaml = "test"; + YamlReader reader = new YamlReader(yaml); + assertEquals("test", reader.read(String.class)); + assertEquals(null, reader.read()); + assertEquals(null, reader.read()); + } + + public void testReadAll() throws YamlException { + + StringBuilder sb = new StringBuilder(); + sb.append("- 1").append("\n"); + sb.append("- 2").append("\n"); + sb.append("- 3").append("\n"); + sb.append("---\n").append("key: value").append("\n"); + sb.append("--- !com.esotericsoftware.yamlbeans.YamlReaderTest$Test\n"); + sb.append("stringValue: test\n"); + YamlReader reader = new YamlReader(sb.toString()); + Iterator iterator = reader.readAll(Object.class); + List list = new ArrayList(); + while (iterator.hasNext()) { + list.add(iterator.next()); + } + assertEquals(3, list.size()); + assertEquals("test", ((Test) list.get(2)).stringValue); + } + + public void testReadAllCallingNextExpectThrowsRuntimeException() { + String yaml = "\ttest"; + YamlReader reader = new YamlReader(yaml); + Iterator iterator = reader.readAll(Object.class); + try { + iterator.next(); + fail(); + } catch (RuntimeException e) { + } + } + + public void testReadAllCallingRemoveExpectThrowsUnsupportedOperationException() { + String yaml = "test"; + YamlReader reader = new YamlReader(yaml); + Iterator iterator = reader.readAll(Object.class); + try { + iterator.remove(); + fail(); + } catch (Exception e) { + } + } + + public void testGetAnchor() throws YamlException { + String yaml = "&1 test\n---\naaa: &2 111\nbbb: &1 222"; + YamlReader reader = new YamlReader(yaml); + assertEquals(null, reader.get("1")); + reader.read(); + assertEquals("test", reader.get("1")); + + reader.read(); + assertEquals("222", reader.get("1")); + assertEquals("111", reader.get("2")); + } + + public void testGetAnchorsUseGuessNumberTypes() throws YamlException { + String yaml = "number: &1 123"; + YamlReader reader = new YamlReader(yaml); + reader.read(); + assertEquals("123", reader.get("1")); + + YamlConfig yamlConfig = new YamlConfig(); + yamlConfig.readConfig.guessNumberTypes = true; + reader = new YamlReader(yaml, yamlConfig); + reader.read(); + assertEquals(123, ((Long) reader.get("1")).intValue()); + } + + public void testAnchorAndAlias() throws YamlException { + String yaml = "key: &1 value\nkey1: *1\n---\nkey: *1"; + YamlReader reader = new YamlReader(yaml); + assertEquals("value", ((Map) reader.read()).get("key1")); + assertEquals("value", reader.get("1")); + + try { + reader.read(); + } catch (YamlReaderException e) { + assertTrue(e.getMessage().contains("Unknown anchor: ")); + } + } + + public void testReadExpectExpectThrowsParserException() { + String yaml = "[1,2,3"; + YamlReader reader = new YamlReader(yaml); + try { + reader.read(); + fail("Expect to throw ParserException."); + } catch (YamlException e) { + } + } + + public void testYamlConfigReadConfigClassLoader() throws YamlException { + StringBuilder sb = new StringBuilder(); + sb.append("--- !com.esotericsoftware.yamlbeans.YamlReaderTest$Test\n"); + sb.append("stringValue: test\n"); + YamlConfig yamlConfig = new YamlConfig(); + yamlConfig.readConfig.classLoader = this.getClass().getClassLoader(); + YamlReader reader = new YamlReader(sb.toString(), yamlConfig); + assertEquals("test", reader.read(Test.class).stringValue); + } + + public void testYamlConfigReadConfigClassTags() throws YamlException { + StringBuilder sb = new StringBuilder(); + sb.append("--- !com.esotericsoftware.yamlbeans.YamlReaderTest$Test\n"); + sb.append("stringValue: test\n"); + YamlConfig yamlConfig = new YamlConfig(); + yamlConfig.readConfig.setClassTags(false); + yamlConfig.setPropertyDefaultType(Test.class, "stringValue", String.class); + YamlReader reader = new YamlReader(sb.toString(), yamlConfig); + assertEquals("test", reader.read(Test.class).stringValue); + } + + public void testReadScalarTypeIsNull() throws YamlException { + String yaml = "key: "; + YamlReader reader = new YamlReader(yaml); + assertEquals(null, ((Map) reader.read()).get("key")); + } + + public void testReadScalarTypeIsString() throws YamlException { + String yaml = "String"; + YamlReader reader = new YamlReader(yaml); + assertEquals("String", reader.read(String.class)); + } + + public void testReadScalarTypeIsInteger() throws YamlException { + String yaml = "123"; + YamlReader reader = new YamlReader(yaml); + assertEquals(123, (int) reader.read(Integer.class)); + + reader = new YamlReader(yaml); + assertEquals(123, (int) reader.read(int.class)); + } + + public void testReadScalarTypeIsBoolean() throws YamlException { + String yaml = "true"; + YamlReader reader = new YamlReader(yaml); + assertEquals(true, (boolean) reader.read(Boolean.class)); + + reader = new YamlReader(yaml); + assertEquals(true, (boolean) reader.read(boolean.class)); + } + + public void testReadScalarTypeIsFloat() throws YamlException { + String yaml = "1.23"; + YamlReader reader = new YamlReader(yaml); + assertEquals(1.23f, reader.read(Float.class)); + + reader = new YamlReader(yaml); + assertEquals(1.23f, reader.read(float.class)); + } + + public void testReadScalarTypeIsDouble() throws YamlException { + String yaml = "1.23"; + YamlReader reader = new YamlReader(yaml); + assertEquals(1.23, reader.read(Double.class)); + + reader = new YamlReader(yaml); + assertEquals(1.23, reader.read(double.class)); + } + + public void testReadScalarTypeIsLong() throws YamlException { + String yaml = "100"; + YamlReader reader = new YamlReader(yaml); + assertEquals(100, (long) reader.read(Long.class)); + + reader = new YamlReader(yaml); + assertEquals(100, (long) reader.read(long.class)); + } + + public void testReadScalarTypeIsShort() throws YamlException { + String yaml = "100"; + YamlReader reader = new YamlReader(yaml); + assertEquals(100, (short) reader.read(Short.class)); + + reader = new YamlReader(yaml); + assertEquals(100, (short) reader.read(short.class)); + } + + public void testReadScalarTypeIsCharacter() throws YamlException { + String yaml = "A"; + YamlReader reader = new YamlReader(yaml); + assertEquals('A', (char) reader.read(Character.class)); + + reader = new YamlReader(yaml); + assertEquals('A', (char) reader.read(char.class)); + } + + public void testReadScalarTypeIsByte() throws YamlException { + String yaml = "1"; + YamlReader reader = new YamlReader(yaml); + assertEquals(1, (byte) reader.read(Byte.class)); + + reader = new YamlReader(yaml); + assertEquals(1, (byte) reader.read(byte.class)); + } + + public void testReadEnumExpectThrowsYamlReaderException() throws YamlException { + StringBuilder sb = new StringBuilder(); + sb.append("--- !com.esotericsoftware.yamlbeans.YamlReaderTest$Test\n"); + sb.append("testEnum: d\n"); + YamlReader reader = new YamlReader(sb.toString()); + try { + reader.read(); + fail("Unable to find enum value"); + } catch (YamlReaderException e) { + } + } + + public void testReadExplicitKey() throws YamlException { + String yaml = "? key: value"; + YamlReader reader = new YamlReader(yaml); + assertEquals("value", ((Map) reader.read()).get("key")); + } + + public void testReadYamlConfigTagSuffix() throws YamlException { + String yaml = "key: !!str 123456"; + YamlConfig yamlConfig = new YamlConfig(); + yamlConfig.tagSuffix = "-test"; + YamlReader reader = new YamlReader(yaml, yamlConfig); + Map map = (Map) reader.read(); + assertEquals("tag:yaml.org,2002:str", map.get("key-test")); + + yaml = "info: !!map\n !!str key: !!str 123456"; + reader = new YamlReader(yaml, yamlConfig); + map = (Map) reader.read(); + assertEquals("tag:yaml.org,2002:map", map.get("info-test")); + } + + public void testReadYamlConfigAllowDuplicates() throws YamlException { + StringBuilder sb = new StringBuilder(); + sb.append("--- !com.esotericsoftware.yamlbeans.YamlReaderTest$Test\n"); + sb.append("stringValue: test\n"); + sb.append("stringValue: test\n"); + YamlConfig yamlConfig = new YamlConfig(); + yamlConfig.allowDuplicates = true; + YamlReader reader = new YamlReader(sb.toString(), yamlConfig); + reader.read(); + + yamlConfig.allowDuplicates = false; + reader = new YamlReader(sb.toString(), yamlConfig); + try { + reader.read(); + fail("must be throws YamlReaderException"); + } catch (YamlReaderException e) { + } + } } diff --git a/test/com/esotericsoftware/yamlbeans/YamlWriterTest.java b/test/com/esotericsoftware/yamlbeans/YamlWriterTest.java index fdabc9e..9bf6d91 100644 --- a/test/com/esotericsoftware/yamlbeans/YamlWriterTest.java +++ b/test/com/esotericsoftware/yamlbeans/YamlWriterTest.java @@ -24,6 +24,7 @@ import java.util.ArrayList; import java.util.Date; import java.util.HashMap; +import java.util.LinkedHashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; @@ -32,7 +33,12 @@ /** @author Nathan Sweet */ public class YamlWriterTest extends TestCase { + + private static final String LINE_SEPARATOR = System.getProperty("line.separator"); + public void testPrivateFields () throws Exception { + YamlWriter yamlWriter = new com.esotericsoftware.yamlbeans.YamlWriter(new java.io.OutputStreamWriter(System.out)); + ArrayList list = new ArrayList(); list.add("abc"); list.add("123"); @@ -212,14 +218,14 @@ public void testExplicitDocumentEnd () throws Exception { YamlWriter writer = new YamlWriter(buffer, config); writer.write("test"); writer.close(); - assertEquals("test\n...\n", buffer.toString()); + assertEquals("test" + LINE_SEPARATOR + "..." + LINE_SEPARATOR, buffer.toString()); buffer = new StringWriter(); writer = new YamlWriter(buffer, config); writer.write("test"); writer.write("test"); writer.close(); - assertEquals("test\n...\n--- test\n...\n", buffer.toString()); + assertEquals("test" + LINE_SEPARATOR + "..." + LINE_SEPARATOR + "--- test" + LINE_SEPARATOR + "..." + LINE_SEPARATOR, buffer.toString()); } void checkWriterOutput (String example, String expected) throws Exception { @@ -231,14 +237,14 @@ void checkWriterOutput (String example, String expected) throws Exception { } public void testSingleQuotesDoubling () throws Exception { - checkWriterOutput("abc", "abc\n"); - checkWriterOutput("abc def", "abc def\n"); + checkWriterOutput("abc", "abc" + LINE_SEPARATOR); + checkWriterOutput("abc def", "abc def" + LINE_SEPARATOR); // single quotes around output as soon as we have a colon in the input - checkWriterOutput("abc: def", "'abc: def'\n"); - checkWriterOutput("abc: def'ghi", "'abc: def''ghi'\n"); - checkWriterOutput("abc: def 'ghi", "'abc: def ''ghi'\n"); + checkWriterOutput("abc: def", "'abc: def'" + LINE_SEPARATOR); + checkWriterOutput("abc: def'ghi", "'abc: def''ghi'" + LINE_SEPARATOR); + checkWriterOutput("abc: def 'ghi", "'abc: def ''ghi'" + LINE_SEPARATOR); // That was the initial bug: - checkWriterOutput("A: 'X'", "'A: ''X'''\n"); + checkWriterOutput("A: 'X'", "'A: ''X'''" + LINE_SEPARATOR); } public void testObjectField () throws Exception { @@ -304,6 +310,23 @@ public String write (File file) throws YamlException { assertEquals(object.file, roundTrip.file); } + public void testSetAlias() throws YamlException { + Map map = new LinkedHashMap(); + Test test = new Test(); + test.stringValue = "test"; + map.put("key1", test); + map.put("key2", test); + + StringWriter sw = new StringWriter(); + YamlWriter yamlWriter = new YamlWriter(sw); + yamlWriter.setAlias(test, "t"); + yamlWriter.write(map); + yamlWriter.close(); + assertEquals("!java.util.LinkedHashMap" + LINE_SEPARATOR + + "key1: &t !com.esotericsoftware.yamlbeans.YamlWriterTest$Test" + LINE_SEPARATOR + + " stringValue: test" + LINE_SEPARATOR + "key2: *t" + LINE_SEPARATOR, sw.toString()); + } + private Object roundTrip (Object object) throws Exception { return roundTrip(object, null, new YamlConfig()); } diff --git a/test/com/esotericsoftware/yamlbeans/document/YamlDocumentTest.java b/test/com/esotericsoftware/yamlbeans/document/YamlDocumentTest.java new file mode 100644 index 0000000..c2b6523 --- /dev/null +++ b/test/com/esotericsoftware/yamlbeans/document/YamlDocumentTest.java @@ -0,0 +1,292 @@ +package com.esotericsoftware.yamlbeans.document; + +import java.io.StringWriter; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +import org.junit.Test; + +import com.esotericsoftware.yamlbeans.Version; +import com.esotericsoftware.yamlbeans.YamlConfig; +import com.esotericsoftware.yamlbeans.YamlConfig.WriteClassName; +import com.esotericsoftware.yamlbeans.YamlException; +import com.esotericsoftware.yamlbeans.YamlWriter; + +import junit.framework.TestCase; + +public class YamlDocumentTest extends TestCase { + protected void setUp () throws Exception { + System.setProperty("line.separator", "\n"); + } + + @Test + public void testThatTaggedDocumentIsCopied() throws Exception { + testEquals("--- !someTag\nscalar: value\n"); + } + + @Test + public void testThatScalarValueIsCopied() throws Exception { + testEquals("scalar: value\n"); + } + + @Test + public void testThatTaggedScalarValueIsCopied() throws Exception { + testEquals("scalar: value !someTag\n"); + } + + @Test + public void testThatAnchoredScalarValueIsCopied() throws Exception { + testEquals("scalar: &anchor value\n"); + } + + @Test + public void testThatAliasedScalarValueIsCopied() throws Exception { + testEquals("scalar: *alias\n"); + } + + @Test + public void testThatSequenceValueIsCopied() throws Exception { + testEquals("- scalar1: value\n- scalar2: value\n"); + } + + @Test + public void testThatTaggedSequenceValueIsCopied() throws Exception { + testEquals("- scalar1: value !someTag1\n- scalar2: value !someTag2\n"); + } + + @Test + public void testThatAnchoredSequenceValueIsCopied() throws Exception { + testEquals("sequence: &anchor\n- scalar1: value\n- scalar2: value\n"); + } + + @Test + public void testThatAliasedSequenceValueIsCopied() throws Exception { + testEquals("sequence: *alias\n"); + } + + @Test + public void testThatMappingValueIsCopied() throws Exception { + testEquals("mapping: \n scalar1: value\n scalar2: value\n"); + } + + @Test + public void testThatTaggedMappingValueIsCopied() throws Exception { + testEquals("mapping: !someTag1\n scalar1: value\n scalar2: value !someTag2\n"); + } + + + @Test + public void testThatAnchoredMappingValueIsCopied() throws Exception { + testEquals("mapping: &anchor\n scalar1: value\n scalar2: value\n"); + } + + @Test + public void testThatAliasedMappingValueIsCopied() throws Exception { + testEquals("mapping: *alias\n"); + } + + @Test + public void testThatMappingEntryIsChanged() throws YamlException { + YamlDocument yaml = readDocument("scalar: value\n"); + yaml.setEntry("scalar", 123); + String actual = writeDocument(yaml); + assertEquals("scalar: 123\n", actual); + } + + @Test + public void testThatMappingEntryIsAdded() throws YamlException { + YamlDocument yaml = readDocument("scalar: value\n"); + yaml.setEntry("scalar2", 123); + String actual = writeDocument(yaml); + assertEquals("scalar: value\nscalar2: 123\n", actual); + } + + @Test + public void testThatMappingEntryIsRemoved() throws YamlException { + YamlDocument yaml = readDocument("scalar: value\nscalar2: 123\n"); + yaml.deleteEntry("scalar2"); + String actual = writeDocument(yaml); + assertEquals("scalar: value\n", actual); + } + + @Test + public void testThatSequenceItemIsChanged() throws YamlException { + YamlDocument yaml = readDocument("- value\n"); + yaml.setElement(0, 123); + String actual = writeDocument(yaml); + assertEquals("- 123\n", actual); + } + + @Test + public void testThatSequenceItemIsAdded() throws YamlException { + YamlDocument yaml = readDocument("- value\n"); + yaml.addElement(123); + String actual = writeDocument(yaml); + assertEquals("- value\n- 123\n", actual); + } + + @Test + public void testThatSequenceItemIsRemoved() throws YamlException { + YamlDocument yaml = readDocument("- value\n- 123\n"); + yaml.deleteElement(0); + String actual = writeDocument(yaml); + assertEquals("- 123\n", actual); + } + + @Test + public void testYamlSequenceIterator() throws YamlException { + YamlDocument yaml = readDocument("- 111\n- 222\n"); + @SuppressWarnings("unchecked") + Iterator iterator = yaml.iterator(); + assertEquals(true, iterator.hasNext()); + YamlScalar yamlScalar = iterator.next(); + assertEquals("111", yamlScalar.getValue()); + assertEquals(true, iterator.hasNext()); + yamlScalar = iterator.next(); + assertEquals("222", yamlScalar.getValue()); + assertEquals(false, iterator.hasNext()); + try { + iterator.next(); + fail("Already read to the end."); + } catch (Exception e) { + } + } + + @Test + public void testYamlMappingIterator() throws YamlException { + YamlDocument yaml = readDocument("name: Andi\nage: 18\n"); + @SuppressWarnings("unchecked") + Iterator iterator = yaml.iterator(); + assertEquals(true, iterator.hasNext()); + YamlEntry yamlEntry = iterator.next(); + assertEquals("Andi", ((YamlScalar) yamlEntry.getValue()).getValue()); + assertEquals(true, iterator.hasNext()); + yamlEntry = iterator.next(); + assertEquals("18", ((YamlScalar) yamlEntry.getValue()).getValue()); + assertEquals(false, iterator.hasNext()); + try { + iterator.next(); + fail("Already read to the end."); + } catch (Exception e) { + } + } + + @Test + public void testVersion1_0() throws YamlException { + String yaml = "version: !str 1.0"; + YamlDocumentReader reader = new YamlDocumentReader(yaml, Version.V1_0); + YamlMapping yamlMapping = (YamlMapping) reader.read(); + assertEquals("1.0", ((YamlScalar) yamlMapping.getEntry("version").getValue()).getValue()); + } + + @Test + public void testVersion1_0ThrowsYamlException() { + String yaml = "version: !!str 1.1"; + YamlDocumentReader reader = new YamlDocumentReader(yaml, Version.V1_0); + try { + reader.read(); + fail("1.0 Version tag is single '!'"); + } catch (YamlException e) { + } + } + + @Test + public void testVersion1_1() throws YamlException { + String yaml = "version: !!str 1.1"; + YamlDocumentReader reader = new YamlDocumentReader(yaml, Version.V1_1); + YamlMapping yamlMapping = (YamlMapping) reader.read(); + assertEquals("1.1", ((YamlScalar) yamlMapping.getEntry("version").getValue()).getValue()); + } + + @Test + public void testReadMultipleDocuments() throws YamlException { + String yaml = "key: 111\n---\nkey: 222"; + YamlDocumentReader reader = new YamlDocumentReader(yaml); + assertEquals(true, reader.read() != null); + assertEquals(true, reader.read() != null); + assertEquals(true, reader.read() == null); + assertEquals(true, reader.read() == null); + } + + @Test + public void testReadThrowsYamlException() { + String yaml = "\tkey: value"; + YamlDocumentReader reader = new YamlDocumentReader(yaml); + try { + reader.read(); + fail("Tabs cannot be used for indentation."); + } catch (YamlException e) { + } + } + + @Test + public void testReadAll() throws YamlException { + + StringBuilder sb = new StringBuilder(); + sb.append("&1 scalar").append("\n"); + sb.append("---\n").append("*1").append("\n"); + sb.append("---\n").append("key: value").append("\n"); + sb.append("---\n").append("- 1\n").append("- 2\n").append("- 3\n"); + YamlDocumentReader reader = new YamlDocumentReader(sb.toString()); + Iterator iterator = reader.readAll(YamlElement.class); + List list = new ArrayList(); + while (iterator.hasNext()) { + list.add(iterator.next()); + } + assertEquals(4, list.size()); + assertEquals(true, YamlScalar.class == list.get(0).getClass()); + assertEquals(true, YamlAlias.class == list.get(1).getClass()); + assertEquals(true, YamlMapping.class == list.get(2).getClass()); + assertEquals(true, YamlSequence.class == list.get(3).getClass()); + } + + @Test + public void testReadAllCallingNextThrowsRuntimeException() { + String yaml = "\ttest"; + YamlDocumentReader reader = new YamlDocumentReader(yaml); + Iterator iterator = reader.readAll(YamlElement.class); + try { + iterator.next(); + fail(""); + } catch (Exception e) { + } + } + + @Test + public void testReadAllCallingRemoveUnsupportedOperationException() { + String yaml = "test"; + YamlDocumentReader reader = new YamlDocumentReader(yaml); + Iterator iterator = reader.readAll(YamlElement.class); + try { + iterator.remove(); + fail(""); + } catch (Exception e) { + } + } + + private YamlDocument readDocument(String yaml) throws YamlException { + YamlDocumentReader reader = new YamlDocumentReader(yaml); + return reader.read(); + } + + private String writeDocument(YamlDocument yaml) throws YamlException { + StringWriter writer = new StringWriter(); + YamlConfig config = new YamlConfig(); + config.writeConfig.setExplicitFirstDocument(yaml.getTag()!=null); + config.writeConfig.setWriteClassname(WriteClassName.NEVER); + config.writeConfig.setAutoAnchor(false); + YamlWriter yamlWriter = new YamlWriter(writer, config); + yamlWriter.write(yaml); + yamlWriter.close(); + return writer.toString(); + } + + private void testEquals(String yaml) throws Exception { + YamlDocument document = readDocument(yaml); + String actual = writeDocument(document); + assertEquals(yaml, actual); + } + + +} diff --git a/test/com/esotericsoftware/yamlbeans/document/YamlMappingTest.java b/test/com/esotericsoftware/yamlbeans/document/YamlMappingTest.java new file mode 100644 index 0000000..938d2d2 --- /dev/null +++ b/test/com/esotericsoftware/yamlbeans/document/YamlMappingTest.java @@ -0,0 +1,136 @@ +package com.esotericsoftware.yamlbeans.document; + +import static org.junit.Assert.assertEquals; + +import org.junit.Before; +import org.junit.Test; + +import com.esotericsoftware.yamlbeans.YamlException; + +public class YamlMappingTest { + + private YamlMapping yamlMapping = null; + + @Before + public void setUp() throws YamlException { + String yaml = "key1: value1\nkey2: value2\nkey3: value3\n"; + YamlDocumentReader reader = new YamlDocumentReader(yaml); + yamlMapping = (YamlMapping) reader.read(); + yamlMapping.setAnchor("mapping"); + yamlMapping.setTag("HashMap"); + } + + @Test + public void testSize() { + assertEquals(3, yamlMapping.size()); + } + + @Test + public void testAddEntry() throws YamlException { + YamlScalar key = new YamlScalar(); + key.setValue("test"); + YamlScalar value = new YamlScalar(); + value.setValue("111"); + YamlEntry entry = new YamlEntry(key, value); + yamlMapping.addEntry(entry); + + assertEquals(entry, yamlMapping.getEntry("test")); + } + + @Test + public void testDeleteEntry() { + assertEquals(true, yamlMapping.deleteEntry("key1")); + assertEquals(false, yamlMapping.deleteEntry("key4")); + } + + @Test + public void testGetEntryByKey() throws YamlException { + assertEquals(true, yamlMapping.getEntry("key1") != null); + assertEquals(true, yamlMapping.getEntry("key4") == null); + } + + @Test + public void testGetEntryByIndex() throws YamlException { + assertEquals(true, yamlMapping.getEntry(0) != null); + } + + @Test(expected = IndexOutOfBoundsException.class) + public void testGetEntryByIndexThrowsIndexOutOfBoundsException() throws YamlException { + yamlMapping.getEntry(3); + } + + @Test + public void testToString() { + assertEquals("&mapping !HashMap{key1:value1,key2:value2,key3:value3}", yamlMapping.toString()); + } + + @Test + public void testSetEntryUseBooleanValue() throws YamlException { + yamlMapping.setEntry("test", true); + assertEquals("true", ((YamlScalar) yamlMapping.getEntry("test").getValue()).getValue()); + } + + @Test + public void testSetEntryUseNumberValue() throws YamlException { + yamlMapping.setEntry("test", 1); + assertEquals("1", ((YamlScalar) yamlMapping.getEntry("test").getValue()).getValue()); + } + + @Test + public void testSetEntryUseStringValue() throws YamlException { + yamlMapping.setEntry("test", "test"); + assertEquals("test", ((YamlScalar) yamlMapping.getEntry("test").getValue()).getValue()); + } + + @Test(expected = YamlException.class) + public void testGetElementByItemThrowsYamlException() throws YamlException { + yamlMapping.getElement(0); + } + + @Test(expected = YamlException.class) + public void testDeleteElementThrowsYamlException() throws YamlException { + yamlMapping.deleteElement(0); + } + + @Test(expected = YamlException.class) + public void testSetElementUseBooleanThrowsYamlException() throws YamlException { + yamlMapping.setElement(0, true); + } + + @Test(expected = YamlException.class) + public void testSetElementUseNumberThrowsYamlException() throws YamlException { + yamlMapping.setElement(0, 1); + } + + @Test(expected = YamlException.class) + public void testSetElementUseStringThrowsYamlException() throws YamlException { + yamlMapping.setElement(0, "test"); + } + + @Test(expected = YamlException.class) + public void testSetElementUseYamlElementThrowsYamlException() throws YamlException { + YamlElement YamlElement = new YamlScalar(); + yamlMapping.setElement(0, YamlElement); + } + + @Test(expected = YamlException.class) + public void testAddElementUseBooleanThrowsYamlException() throws YamlException { + yamlMapping.addElement(true); + } + + @Test(expected = YamlException.class) + public void testAddElementUseNumberThrowsYamlException() throws YamlException { + yamlMapping.addElement(1); + } + + @Test(expected = YamlException.class) + public void testAddElementUseStringThrowsYamlException() throws YamlException { + yamlMapping.addElement("test"); + } + + @Test(expected = YamlException.class) + public void testAddElementUseYamlElementThrowsYamlException() throws YamlException { + YamlElement YamlElement = new YamlScalar(); + yamlMapping.addElement(YamlElement); + } +} diff --git a/test/com/esotericsoftware/yamlbeans/document/YamlSequenceTest.java b/test/com/esotericsoftware/yamlbeans/document/YamlSequenceTest.java new file mode 100644 index 0000000..3ab6f3b --- /dev/null +++ b/test/com/esotericsoftware/yamlbeans/document/YamlSequenceTest.java @@ -0,0 +1,129 @@ +package com.esotericsoftware.yamlbeans.document; + +import static org.junit.Assert.assertEquals; + +import org.junit.Before; +import org.junit.Test; + +import com.esotericsoftware.yamlbeans.YamlException; + +public class YamlSequenceTest { + private YamlSequence yamlSequence = null; + + @Before + public void setUp() throws YamlException { + String yaml = "- 1\n- 2\n- 3\n"; + YamlDocumentReader reader = new YamlDocumentReader(yaml); + yamlSequence = (YamlSequence) reader.read(); + yamlSequence.setAnchor("sequence"); + yamlSequence.setTag("LinkedList"); + } + + @Test + public void testSize() { + assertEquals(3, yamlSequence.size()); + } + + @Test + public void testAddElement() throws YamlException { + YamlElement yamlElement = new YamlScalar(); + yamlSequence.addElement(yamlElement); + assertEquals(yamlElement, yamlSequence.getElement(3)); + } + + @Test + public void testDeleteElement() throws YamlException { + yamlSequence.deleteElement(0); + assertEquals(2, yamlSequence.size()); + } + + @Test + public void testToStrig() { + assertEquals("&sequence !LinkedList[1,2,3]", yamlSequence.toString()); + } + + @Test(expected = YamlException.class) + public void testGetEntryByKeyThrowsYamlException() throws YamlException { + yamlSequence.getEntry("key"); + } + + @Test(expected = YamlException.class) + public void testGetEntryByIndexThrowsYamlException() throws YamlException { + yamlSequence.getEntry(0); + } + + @Test(expected = YamlException.class) + public void testDeleteEntryByKeyThrowsYamlException() throws YamlException { + yamlSequence.deleteEntry("key"); + } + + @Test(expected = YamlException.class) + public void testSetEntryUseBooleanValueThrowsYamlException() throws YamlException { + yamlSequence.setEntry("key", true); + } + + @Test(expected = YamlException.class) + public void testSetEntryUseNumberValueThrowsYamlException() throws YamlException { + yamlSequence.setEntry("key", 1); + } + + @Test(expected = YamlException.class) + public void testSetEntryUseStringValueThrowsYamlException() throws YamlException { + yamlSequence.setEntry("key", "111"); + } + + @Test(expected = YamlException.class) + public void testSetEntryUseYamlElementValueThrowsYamlException() throws YamlException { + YamlElement yamlElement = new YamlScalar(); + yamlSequence.setEntry("key", yamlElement); + } + + @Test + public void testSetElementUseBooleanValue() throws YamlException { + yamlSequence.setElement(0, true); + YamlScalar yamlScalar = (YamlScalar) yamlSequence.getElement(0); + assertEquals("true", yamlScalar.getValue()); + } + + @Test + public void testSetElementUseNumberValue() throws YamlException { + yamlSequence.setElement(0, 1); + YamlScalar yamlScalar = (YamlScalar) yamlSequence.getElement(0); + assertEquals("1", yamlScalar.getValue()); + } + + @Test + public void testSetElementUseStringValue() throws YamlException { + yamlSequence.setElement(0, "testStr"); + YamlScalar yamlScalar = (YamlScalar) yamlSequence.getElement(0); + assertEquals("testStr", yamlScalar.getValue()); + } + + @Test + public void testSetElementUseYamlElementValue() throws YamlException { + YamlElement yamlElement = new YamlScalar(); + yamlSequence.setElement(0, yamlElement); + assertEquals(yamlElement, yamlSequence.getElement(0)); + } + + @Test + public void testAddElementUseBoolean() throws YamlException { + yamlSequence.addElement(true); + YamlScalar yamlScalar = (YamlScalar) yamlSequence.getElement(yamlSequence.size() - 1); + assertEquals("true", yamlScalar.getValue()); + } + + @Test + public void testAddElementUseNumber() throws YamlException { + yamlSequence.addElement(1); + YamlScalar yamlScalar = (YamlScalar) yamlSequence.getElement(yamlSequence.size() - 1); + assertEquals("1", yamlScalar.getValue()); + } + + @Test + public void testAddElementUseString() throws YamlException { + yamlSequence.addElement("testStr"); + YamlScalar yamlScalar = (YamlScalar) yamlSequence.getElement(yamlSequence.size() - 1); + assertEquals("testStr", yamlScalar.getValue()); + } +} diff --git a/test/com/esotericsoftware/yamlbeans/emitter/EmitterTest.java b/test/com/esotericsoftware/yamlbeans/emitter/EmitterTest.java new file mode 100644 index 0000000..aa773b8 --- /dev/null +++ b/test/com/esotericsoftware/yamlbeans/emitter/EmitterTest.java @@ -0,0 +1,160 @@ +package com.esotericsoftware.yamlbeans.emitter; + +import static org.junit.Assert.assertEquals; + +import java.io.BufferedWriter; +import java.io.FileReader; +import java.io.IOException; +import java.io.OutputStreamWriter; +import java.io.StringWriter; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +import org.junit.Before; +import org.junit.Test; + +import com.esotericsoftware.yamlbeans.YamlConfig; +import com.esotericsoftware.yamlbeans.YamlException; +import com.esotericsoftware.yamlbeans.YamlWriter; +import com.esotericsoftware.yamlbeans.parser.DocumentStartEvent; +import com.esotericsoftware.yamlbeans.parser.Event; +import com.esotericsoftware.yamlbeans.parser.Parser; +import com.esotericsoftware.yamlbeans.parser.ScalarEvent; + +public class EmitterTest { + private static final String LINE_SEPARATOR = System.getProperty("line.separator"); + + private StringWriter stringWriter = null; + private Emitter emitter = null; + + @Before + public void setup() { + stringWriter = new StringWriter(); + emitter = new Emitter(stringWriter); + } + + @Test + public void testEmitterConstructor() throws IOException { + try { + new Emitter(null); + } catch (IllegalArgumentException e) { + assertEquals("stream cannot be null.", e.getMessage()); + } + + StringWriter writer = new StringWriter(); + new Emitter(writer); + + BufferedWriter bufferedWriter = new BufferedWriter(writer); + EmitterConfig emitterConfig = new EmitterConfig(); + new Emitter(bufferedWriter, emitterConfig); + } + + @Test(expected = EmitterException.class) + public void testStateStreamStart() throws IOException { + emitter.emit(Event.STREAM_START); + emitter.emit(new DocumentStartEvent(false, null, null)); + emitter.emit(Event.DOCUMENT_END_TRUE); + } + + @Test(expected = EmitterException.class) + public void testStateNOTHING() throws EmitterException, IOException { + emitter.emit(Event.STREAM_START); + emitter.emit(Event.STREAM_END); + assertEquals(3, emitter.state); + emitter.emit(Event.DOCUMENT_END_TRUE); + } + + @Test(expected = EmitterException.class) + public void testDocumentEnd() throws EmitterException, IOException { + emitter.emit(Event.STREAM_START); + emitter.emit(new DocumentStartEvent(false, null, null)); + emitter.emit(new ScalarEvent(null, null, new boolean[] { true, true }, "test", '\0')); + emitter.emit(Event.STREAM_END); + } + + @Test + public void testEmptySequence() throws YamlException { + YamlConfig yamlConfig = new YamlConfig(); + yamlConfig.writeConfig.setFlowStyle(true); + YamlWriter yamlWriter = new YamlWriter(stringWriter, yamlConfig); + List list = new ArrayList(); + yamlWriter.write(list); + yamlWriter.close(); + assertEquals("[]" + LINE_SEPARATOR, stringWriter.toString()); + } + + @Test + public void testStateFlowMapingKey() throws YamlException { + Map map = new LinkedHashMap(); + map.put("key1", "value1"); + map.put("key2", "value2"); + YamlConfig yamlConfig = new YamlConfig(); + yamlConfig.writeConfig.setFlowStyle(true); + yamlConfig.writeConfig.setWriteRootTags(false); + YamlWriter yamlWriter = new YamlWriter(stringWriter, yamlConfig); + yamlWriter.write(map); + yamlWriter.close(); + assertEquals("{key1: value1, key2: value2}" + LINE_SEPARATOR, stringWriter.toString()); + + } + + @Test + public void testStateFlowMapingKeyCanonicalIsTrue() throws EmitterException, IOException { + Map map = new LinkedHashMap(); + map.put("key1", "value1"); + map.put("key2", "value2"); + YamlConfig yamlConfig = new YamlConfig(); + yamlConfig.writeConfig.setFlowStyle(true); + yamlConfig.writeConfig.setWriteRootTags(false); + yamlConfig.writeConfig.setCanonical(true); + YamlWriter yamlWriter = new YamlWriter(stringWriter, yamlConfig); + yamlWriter.write(map); + yamlWriter.close(); + assertEquals( + "--- " + LINE_SEPARATOR + "{" + LINE_SEPARATOR + " ? !java.lang.String \"key1\"" + LINE_SEPARATOR + + " : !java.lang.String \"value1\"," + LINE_SEPARATOR + " ? !java.lang.String \"key2\"" + + LINE_SEPARATOR + " : !java.lang.String \"value2\"" + LINE_SEPARATOR + "}" + LINE_SEPARATOR, + stringWriter.toString()); + } + + @Test(expected = EmitterException.class) + public void testDocumentStart() throws EmitterException, IOException { + emitter.emit(Event.STREAM_START); + emitter.emit(new ScalarEvent(null, null, new boolean[] { true, true }, "test", '\0')); + } + + @Test + public void testBlockMappingValueMultiline() throws EmitterException, IOException { + Map map = new HashMap(); + map.put("555\n666", "value"); + YamlWriter yamlWriter = new YamlWriter(stringWriter); + yamlWriter.write(map); + yamlWriter.close(); + + assertEquals("? |-" + LINE_SEPARATOR + " 555" + LINE_SEPARATOR + " 666" + LINE_SEPARATOR + ": value" + + LINE_SEPARATOR, stringWriter.toString()); + } + + @Test(expected = EmitterException.class) + public void testExpectNode() throws EmitterException, IOException { + emitter.emit(Event.STREAM_START); + emitter.emit(new DocumentStartEvent(false, null, null)); + emitter.emit(Event.MAPPING_END); + } + + @Test + public void testParserToEmitter() throws EmitterException, IOException { + Parser parser = new Parser(new FileReader("test/test.yml")); + Emitter emitter = new Emitter(new OutputStreamWriter(System.out)); + while (true) { + Event event = parser.getNextEvent(); + if (event == null) + break; + emitter.emit(event); + } + emitter.close(); + } +} diff --git a/test/com/esotericsoftware/yamlbeans/emitter/EmitterWriterTest.java b/test/com/esotericsoftware/yamlbeans/emitter/EmitterWriterTest.java new file mode 100644 index 0000000..44e742e --- /dev/null +++ b/test/com/esotericsoftware/yamlbeans/emitter/EmitterWriterTest.java @@ -0,0 +1,73 @@ +package com.esotericsoftware.yamlbeans.emitter; + +import static org.junit.Assert.assertEquals; + +import java.io.StringWriter; +import org.junit.Test; + +import com.esotericsoftware.yamlbeans.YamlConfig; +import com.esotericsoftware.yamlbeans.YamlException; +import com.esotericsoftware.yamlbeans.YamlWriter; +import com.esotericsoftware.yamlbeans.YamlConfig.Quote; + +public class EmitterWriterTest { + + private static final String LINE_SEPARATOR = System.getProperty("line.separator"); + + @Test + public void testWriteDoubleQuoted() throws YamlException { + YamlConfig yamlConfig = new YamlConfig(); + yamlConfig.writeConfig.setQuoteChar(Quote.DOUBLE); + StringWriter stringWriter = new StringWriter(); + YamlWriter yamlWriter = new YamlWriter(stringWriter, yamlConfig); + yamlWriter.write("\u0001\u0011\u0111\u1111\u0007"); + yamlWriter.close(); + + assertEquals("\"\\u0001\\u0011\\u0111\\u1111\\a\"" + LINE_SEPARATOR, stringWriter.toString()); + + stringWriter = new StringWriter(); + yamlConfig.writeConfig.setWrapColumn(5); + yamlWriter = new YamlWriter(stringWriter, yamlConfig); + yamlWriter.write("testt est"); + yamlWriter.close(); + assertEquals("\"testt\\" + LINE_SEPARATOR + " \\ e\\" + LINE_SEPARATOR + " st\"" + LINE_SEPARATOR, + stringWriter.toString()); + } + + @Test + public void testWriteSingleQuoted() throws YamlException { + YamlConfig yamlConfig = new YamlConfig(); + yamlConfig.writeConfig.setQuoteChar(Quote.SINGLE); + + StringWriter stringWriter = new StringWriter(); + YamlWriter yamlWriter = new YamlWriter(stringWriter, yamlConfig); + yamlWriter.write("\ntest"); + yamlWriter.close(); + assertEquals("\'" + LINE_SEPARATOR + " test\'" + LINE_SEPARATOR, stringWriter.toString()); + + } + + @Test + public void testWriteFolded() throws YamlException { + YamlConfig yamlConfig = new YamlConfig(); + yamlConfig.writeConfig.setQuoteChar(Quote.FOLDED); + StringWriter stringWriter = new StringWriter(); + YamlWriter yamlWriter = new YamlWriter(stringWriter, yamlConfig); + yamlWriter.write("111\n222 333"); + yamlWriter.close(); + assertEquals(">-" + LINE_SEPARATOR + " 111" + LINE_SEPARATOR + LINE_SEPARATOR + " 222 333" + LINE_SEPARATOR, + stringWriter.toString()); + } + + @Test + public void testWriteLiteral() throws YamlException { + YamlConfig yamlConfig = new YamlConfig(); + yamlConfig.writeConfig.setQuoteChar(Quote.LITERAL); + StringWriter stringWriter = new StringWriter(); + YamlWriter yamlWriter = new YamlWriter(stringWriter, yamlConfig); + yamlWriter.write("111\n222"); + yamlWriter.close(); + assertEquals("|-" + LINE_SEPARATOR + " 111" + LINE_SEPARATOR + " 222" + LINE_SEPARATOR, + stringWriter.toString()); + } +} diff --git a/test/com/esotericsoftware/yamlbeans/issues/issue101/Issue101Test.java b/test/com/esotericsoftware/yamlbeans/issues/issue101/Issue101Test.java new file mode 100644 index 0000000..30791ed --- /dev/null +++ b/test/com/esotericsoftware/yamlbeans/issues/issue101/Issue101Test.java @@ -0,0 +1,34 @@ +package com.esotericsoftware.yamlbeans.issues.issue101; + +import static org.junit.Assert.assertEquals; + +import org.junit.Test; + +import com.esotericsoftware.yamlbeans.YamlException; +import com.esotericsoftware.yamlbeans.YamlReader; + +public class Issue101Test { + + private static final String LINE_SEPARATOR = System.getProperty("line.separator"); + + @Test + public void test() throws YamlException { + StringBuilder sb = new StringBuilder(); + sb.append("!com.esotericsoftware.yamlbeans.issues.issue101.Issue101Test$TestObject").append(LINE_SEPARATOR); + sb.append("test: test"); + YamlReader yamlReader = new YamlReader(sb.toString()); + TestObject testObject = (TestObject) yamlReader.read(); + assertEquals("test", testObject.test); + + sb = new StringBuilder(); + sb.append("!").append(LINE_SEPARATOR); + sb.append("test: test"); + yamlReader = new YamlReader(sb.toString()); + testObject = (TestObject) yamlReader.read(); + assertEquals("test", testObject.test); + } + + static class TestObject { + public String test; + } +} diff --git a/test/com/esotericsoftware/yamlbeans/issues/issue37/Issue37Test.java b/test/com/esotericsoftware/yamlbeans/issues/issue37/Issue37Test.java new file mode 100644 index 0000000..a1c6e92 --- /dev/null +++ b/test/com/esotericsoftware/yamlbeans/issues/issue37/Issue37Test.java @@ -0,0 +1,39 @@ +package com.esotericsoftware.yamlbeans.issues.issue37; + +import static org.junit.Assert.assertEquals; + +import java.io.StringWriter; + +import org.junit.Test; + +import com.esotericsoftware.yamlbeans.YamlConfig; +import com.esotericsoftware.yamlbeans.YamlException; +import com.esotericsoftware.yamlbeans.YamlReader; +import com.esotericsoftware.yamlbeans.YamlWriter; + +public class Issue37Test { + + private static final String LINE_SEPARATOR = System.getProperty("line.separator"); + + @Test + public void test() throws YamlException { + + TestObject testObject = new TestObject(); + testObject.sexType = SexType.FEMALE; + + YamlConfig yamlConfig = new YamlConfig(); + yamlConfig.setScalarSerializer(SexType.class, new SexTypeSerializer()); + StringWriter sw = new StringWriter(); + YamlWriter writer = new YamlWriter(sw, yamlConfig); + writer.write(testObject); + writer.close(); + System.out.println(sw.toString()); + + assertEquals("!com.esotericsoftware.yamlbeans.issues.issue37.TestObject" + LINE_SEPARATOR + "sexType: female" + + LINE_SEPARATOR, sw.toString()); + + YamlReader reader = new YamlReader(sw.toString(), yamlConfig); + TestObject obj = reader.read(TestObject.class); + assertEquals(SexType.FEMALE, obj.sexType); + } +} diff --git a/test/com/esotericsoftware/yamlbeans/issues/issue37/SexType.java b/test/com/esotericsoftware/yamlbeans/issues/issue37/SexType.java new file mode 100644 index 0000000..8e4833a --- /dev/null +++ b/test/com/esotericsoftware/yamlbeans/issues/issue37/SexType.java @@ -0,0 +1,6 @@ +package com.esotericsoftware.yamlbeans.issues.issue37; + +public enum SexType { + + MALE, FEMALE; +} diff --git a/test/com/esotericsoftware/yamlbeans/issues/issue37/SexTypeSerializer.java b/test/com/esotericsoftware/yamlbeans/issues/issue37/SexTypeSerializer.java new file mode 100644 index 0000000..91784b7 --- /dev/null +++ b/test/com/esotericsoftware/yamlbeans/issues/issue37/SexTypeSerializer.java @@ -0,0 +1,15 @@ +package com.esotericsoftware.yamlbeans.issues.issue37; + +import com.esotericsoftware.yamlbeans.YamlException; +import com.esotericsoftware.yamlbeans.scalar.ScalarSerializer; + +public class SexTypeSerializer implements ScalarSerializer { + + public String write(SexType object) throws YamlException { + return object.name().toLowerCase(); + } + + public SexType read(String value) throws YamlException { + return SexType.valueOf(value.toUpperCase()); + } +} diff --git a/test/com/esotericsoftware/yamlbeans/issues/issue37/TestObject.java b/test/com/esotericsoftware/yamlbeans/issues/issue37/TestObject.java new file mode 100644 index 0000000..22c7fb4 --- /dev/null +++ b/test/com/esotericsoftware/yamlbeans/issues/issue37/TestObject.java @@ -0,0 +1,7 @@ +package com.esotericsoftware.yamlbeans.issues.issue37; + +public class TestObject { + + public SexType sexType; + +} diff --git a/test/com/esotericsoftware/yamlbeans/issues/issue52/YamlEnumTest.java b/test/com/esotericsoftware/yamlbeans/issues/issue52/YamlEnumTest.java new file mode 100644 index 0000000..e9d21e0 --- /dev/null +++ b/test/com/esotericsoftware/yamlbeans/issues/issue52/YamlEnumTest.java @@ -0,0 +1,85 @@ +package com.esotericsoftware.yamlbeans.issues.issue52; + +import com.esotericsoftware.yamlbeans.YamlReader; +import com.esotericsoftware.yamlbeans.YamlWriter; +import junit.framework.TestCase; + +import java.io.IOException; +import java.io.StringWriter; + +public class YamlEnumTest extends TestCase { + + public static class TestObject { + public EnumIface[] enumArray = new EnumIface[] { CustomEnum.V1, CustomEnum.V2 }; + } + + public interface EnumIface { + + } + + public enum CustomEnum implements EnumIface { + V1, V2 + } + + public static class NonEnum implements EnumIface { + public String v1, v2; + + public NonEnum() { + } + + public NonEnum(String v1, String v2) { + this.v1 = v1; + this.v2 = v2; + } + } + + public YamlEnumTest() { + } + + public static String write(Object object) throws IOException { + StringWriter stringWriter = new StringWriter(); + YamlWriter writer = new YamlWriter(stringWriter); + + writer.getConfig().setPrivateFields(false); + writer.write(object); + writer.close(); + + String string = stringWriter.toString(); + System.out.println("Wrote: \n" + string); + + return string; + } + + public void read(String string) throws IOException { + YamlReader reader = new YamlReader(string); + reader.read(TestObject.class); + reader.close(); + } + + public void testNonEnum() throws IOException { + TestObject testObject = new TestObject(); + testObject.enumArray = new EnumIface[] { new NonEnum("v1", "v2"), new NonEnum("v1", "v2") }; + + read(write(testObject)); + } + + public void testOnlyEnums() throws IOException { + TestObject testObject = new TestObject(); + testObject.enumArray = new EnumIface[] { CustomEnum.V1, CustomEnum.V2 }; + + read(write(testObject)); + } + + public void testMixEnumsAndNonEnums() throws IOException { + TestObject testObject = new TestObject(); + testObject.enumArray = new EnumIface[] { new NonEnum("v1", "v2"), CustomEnum.V2 }; + + read(write(testObject)); + } + + public void testFixedEnum() throws IOException { + read("!com.esotericsoftware.yamlbeans.issues.issue52.YamlEnumTest$TestObject\n" + "enumArray:\n" + + "- !com.esotericsoftware.yamlbeans.issues.issue52.YamlEnumTest$CustomEnum V1\n" + + "- !com.esotericsoftware.yamlbeans.issues.issue52.YamlEnumTest$CustomEnum V2\n"); + } +} diff --git a/test/com/esotericsoftware/yamlbeans/tokenizer/TokenizerTest.java b/test/com/esotericsoftware/yamlbeans/tokenizer/TokenizerTest.java new file mode 100644 index 0000000..6a328bf --- /dev/null +++ b/test/com/esotericsoftware/yamlbeans/tokenizer/TokenizerTest.java @@ -0,0 +1,198 @@ +package com.esotericsoftware.yamlbeans.tokenizer; + +import com.esotericsoftware.yamlbeans.tokenizer.Tokenizer.TokenizerException; +import org.junit.Test; + +import java.io.BufferedReader; +import java.io.FileNotFoundException; +import java.io.FileReader; +import java.io.IOException; +import java.util.Iterator; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; + +public class TokenizerTest { + + private Token STREAM_START = new Token(TokenType.STREAM_START); + private Token STREAM_END = new Token(TokenType.STREAM_END); + private Token VALUE = new Token(TokenType.VALUE); + private Token KEY = new Token(TokenType.KEY); + private Token BLOCK_MAPPING_START = new Token(TokenType.BLOCK_MAPPING_START); + private Token BLOCK_END = new Token(TokenType.BLOCK_END); + + /** + * Purpose: Getting the next token correctly + * Input: getNextToken() gets the next token and moves to the next token + * Expected: + * return Token.STREAM_START + * return Token.BLOCK_MAPPING_START + * return Token.KEY + * ... + * return Token.STREAM.END + * return null + */ + @Test + public void testGetNextToken() throws FileNotFoundException { + Tokenizer tokenizer = new Tokenizer(new FileReader("test/test1.yml")); + assertEquals(tokenizer.getNextToken() + "", STREAM_START + ""); + assertEquals(tokenizer.getNextToken() + "", BLOCK_MAPPING_START + ""); + assertEquals(tokenizer.getNextToken() + "", KEY + ""); + assertEquals(tokenizer.getNextToken() + "", ""); + assertEquals(tokenizer.getNextToken() + "", VALUE + ""); + assertEquals(tokenizer.getNextToken() + "", ""); + assertEquals(tokenizer.getNextToken() + "", BLOCK_END + ""); + assertEquals(tokenizer.getNextToken() + "", STREAM_END + ""); + assertNull(tokenizer.getNextToken()); + } + + /** + * Purpose: Ensuring that the constructor Tokenizer(Reader) is working properly + * Input: Tokenizer(Reader) FileReader("test/test1.yml") -> Tokenizer(FileReader("test/test1.yml")) + * Expected: + * peekNextToken() = Token.STREAM_START + * Tokenizer(BufferedReader(FileReader("test/test1.yml"))) = Tokenizer(FileReader("test/test1.yml")) + * Tokenizer(null) throws IllegalArgumentException.class + */ + @Test(expected = IllegalArgumentException.class) + public void testTokenizerReader() throws FileNotFoundException { + Tokenizer tokenizer = new Tokenizer(new FileReader("test/test1.yml")); + assertEquals(tokenizer.peekNextToken() + "", STREAM_START + ""); + Tokenizer tokenizer_nbuffered = new Tokenizer((new BufferedReader(new FileReader("test/test1.yml")))); + + Iterator tokenizer_iter = tokenizer.iterator(); + Iterator tokenizer_nbuffered_iter = tokenizer_nbuffered.iterator(); + + while (tokenizer_iter.hasNext() || tokenizer_nbuffered_iter.hasNext()) { + assertEquals(tokenizer_iter.next() + "", tokenizer_nbuffered_iter.next() + ""); + } + + Tokenizer tokenizer_null = new Tokenizer((FileReader) null); + } + + /** + * Purpose: Ensuring that the constructor Tokenizer(String) is working properly + * Input: Tokenizer(String) FileReader("12: 13") = Tokenizer(FileReader("test/test1.yml")), The content of "test/test1.yml" is "12: 13" + * Expected: + * Tokenizer(FileReader("12: 13")) = Tokenizer(FileReader("test/test1.yml")), The content of "test/test1.yml" is "12: 13" + */ + @Test + public void testTokenizerString() throws FileNotFoundException { + Iterator tokenizer_iter = new Tokenizer(new FileReader("test/test1.yml")).iterator(); + Iterator tokenizer_string_iter = new Tokenizer("12: 13").iterator(); + + while (tokenizer_iter.hasNext() || tokenizer_string_iter.hasNext()) { + assertEquals(tokenizer_iter.next() + "", tokenizer_string_iter.next() + ""); + } + } + + /** + * Purpose: peeking the next token correctly + * Input: peekNextToken() peeks the next token and does not move to the next token + * Expected: + * return Token.STREAM_START + * (moves by another function) + * return Token.BLOCK_MAPPING_START + * (moves by another function) + * return Token.KEY + * (moves by another function) + * ... + * return Token.STREAM.END + * (moves by another function) + * return null + */ + @Test + public void testPeekNextToken() throws FileNotFoundException { + Tokenizer tokenizer = new Tokenizer(new FileReader("test/test1.yml")); + assertEquals(tokenizer.peekNextToken() + "", STREAM_START + ""); + tokenizer.getNextToken(); + assertEquals(tokenizer.peekNextToken() + "", BLOCK_MAPPING_START + ""); + tokenizer.getNextToken(); + assertEquals(tokenizer.peekNextToken() + "", KEY + ""); + tokenizer.getNextToken(); + assertEquals(tokenizer.peekNextToken() + "", ""); + tokenizer.getNextToken(); + assertEquals(tokenizer.peekNextToken() + "", VALUE + ""); + tokenizer.getNextToken(); + assertEquals(tokenizer.peekNextToken() + "", ""); + tokenizer.getNextToken(); + assertEquals(tokenizer.peekNextToken() + "", BLOCK_END + ""); + tokenizer.getNextToken(); + assertEquals(tokenizer.peekNextToken() + "", STREAM_END + ""); + tokenizer.getNextToken(); + assertNull(tokenizer.peekNextToken()); + } + + /** + * Purpose: peeking the type of the next token correctly + * Input: peekNextToken() peeks the type of the next token and does not move to the next token + * Expected: + * return TokenType.STREAM_START + * (moves by another function) + * return TokenType.BLOCK_MAPPING_START + * (moves by another function) + * return TokenType.KEY + * (moves by another function) + * ... + * return TokenType.STREAM.END + * (moves by another function) + * return null + */ + @Test + public void testPeekNextTokenType() throws FileNotFoundException { + Tokenizer tokenizer = new Tokenizer(new FileReader("test/test1.yml")); + assertEquals(tokenizer.peekNextTokenType(), TokenType.STREAM_START); + tokenizer.getNextToken(); + assertEquals(tokenizer.peekNextTokenType(), TokenType.BLOCK_MAPPING_START); + tokenizer.getNextToken(); + assertEquals(tokenizer.peekNextTokenType(), TokenType.KEY); + tokenizer.getNextToken(); + assertEquals(tokenizer.peekNextTokenType(), TokenType.SCALAR); + tokenizer.getNextToken(); + assertEquals(tokenizer.peekNextTokenType(), TokenType.VALUE); + tokenizer.getNextToken(); + assertEquals(tokenizer.peekNextTokenType(), TokenType.SCALAR); + tokenizer.getNextToken(); + assertEquals(tokenizer.peekNextTokenType(), TokenType.BLOCK_END); + tokenizer.getNextToken(); + assertEquals(tokenizer.peekNextTokenType(), TokenType.STREAM_END); + tokenizer.getNextToken(); + assertNull(tokenizer.peekNextTokenType()); + } + + + /** + * Purpose: Ensuring that the iterator's functions are working properly + * Input: iterator().hasNext() returns the same value as (Tokenizer.peekNextToken != null) + * iterator().next() returns the same value as Tokenizer.getNextToken() + * Expected: + * Tokenizer.iterator().hasNext() = (Tokenizer.peekNextToken != null) + * Tokenizer.iterator().next() = Tokenizer.getNextToken() + * Tokenizer.iterator().remove() throws UnsupportedOperationException.class + */ + @Test(expected = UnsupportedOperationException.class) + public void testIterator() throws FileNotFoundException { + Tokenizer tokenizer = new Tokenizer(new FileReader("test/test1.yml")); + Iterator iter = new Tokenizer(new FileReader("test/test1.yml")).iterator(); + assertEquals(iter.hasNext(), tokenizer.peekNextToken() != null); + assertEquals(iter.next(), tokenizer.getNextToken()); + iter.remove(); + } + + /** + * Purpose: Getting the next token in a closed reader + * Input: close() Tokenizer.reader -> Tokenizer.reader.close() + * Expected: + * Tokenizer.getNextToken() = Token.STREAM_START + * Tokenizer.getNextToken() throws TokenizerException.class + */ + @Test(expected = TokenizerException.class) + public void testClose() throws IOException { + Tokenizer tokenizer = new Tokenizer(new FileReader("test/test1.yml")); + tokenizer.close(); + + assertEquals(tokenizer.getNextToken() + "", STREAM_START + ""); + tokenizer.getNextToken(); + } + +} \ No newline at end of file diff --git a/test/test-merge.yml b/test/test-merge.yml new file mode 100644 index 0000000..c32529c --- /dev/null +++ b/test/test-merge.yml @@ -0,0 +1,6 @@ +stuff: &anchor + v1: v1 + v2: v2 +merged: + <<: *anchor + v3: v3 \ No newline at end of file diff --git a/test/test.yml b/test/test.yml index c1bd737..f31b51b 100644 --- a/test/test.yml +++ b/test/test.yml @@ -1,14 +1,13 @@ ---- !net.sourceforge.yamlbeans.YamlWriterTest$Test booleanValue: true date: 2008-08-13 03:26:24 -child: &1 !net.sourceforge.yamlbeans.YamlWriterTest$Test2 +child: &1 value: weeeee doubleValue: 1.5 floatValue: 1.2 intValue: 123 -listValues: !java.util.LinkedList +listValues: - woo -- !net.sourceforge.yamlbeans.YamlWriterTest$Test {} +- {} - 123 - *1 mooCow: yay diff --git a/test/test1.yml b/test/test1.yml new file mode 100644 index 0000000..8068c9c --- /dev/null +++ b/test/test1.yml @@ -0,0 +1 @@ +12: 13 \ No newline at end of file