Issue 60: Simplify the way how the order of JavaBean properties is specified
diff --git a/src/changes/changes.xml b/src/changes/changes.xml
index d498d2a..fd19336 100644
--- a/src/changes/changes.xml
+++ b/src/changes/changes.xml
@@ -7,6 +7,11 @@
 	</properties>

 	<body>

           <release version="1.8-SNAPSHOT" date="in Mercurial" description="Performance improvement">

+            <action dev="py4fun" type="update" issue="60">

+                Simplify the way how the order of JavaBean properties is specified. Introduce 

+                PropertyUtils.createPropertySet() method to be overridden when a specific order

+                is expected (2010-11-23)

+            </action>

             <action dev="maslovalex" type="fix" issue="95">

                 Fix: Loading of generic collections with Array parameter(s). (2010-11-16)

             </action>

diff --git a/src/main/java/org/yaml/snakeyaml/introspector/PropertyUtils.java b/src/main/java/org/yaml/snakeyaml/introspector/PropertyUtils.java
index 5cb7835..ed26d82 100644
--- a/src/main/java/org/yaml/snakeyaml/introspector/PropertyUtils.java
+++ b/src/main/java/org/yaml/snakeyaml/introspector/PropertyUtils.java
@@ -24,6 +24,7 @@
 import java.lang.reflect.Modifier;
 import java.util.Collection;
 import java.util.HashMap;
+import java.util.LinkedHashMap;
 import java.util.Map;
 import java.util.Set;
 import java.util.TreeSet;
@@ -37,83 +38,77 @@
     private BeanAccess beanAccess = BeanAccess.DEFAULT;
     private boolean allowReadOnlyProperties = false;
 
-    private Map<String, Property> getPropertiesMap(Class<?> type,
-            BeanAccess bAccess) throws IntrospectionException {
+    protected Map<String, Property> getPropertiesMap(Class<?> type, BeanAccess bAccess)
+            throws IntrospectionException {
         if (propertiesCache.containsKey(type)) {
             return propertiesCache.get(type);
         }
 
-        Map<String, Property> properties = new HashMap<String, Property>();
+        Map<String, Property> properties = new LinkedHashMap<String, Property>();
         switch (bAccess) {
-            case FIELD:
-                for (Class<?> c = type; c != null; c = c.getSuperclass()) {
-                    for (Field field : c.getDeclaredFields()) {
-                        int modifiers = field.getModifiers();
-                        if (!Modifier.isStatic(modifiers)
-                                && !Modifier.isTransient(modifiers)
-                                && !properties.containsKey(field.getName())) {
-                            properties.put(field.getName(), new FieldProperty(
-                                    field));
-                        }
-                    }
-                }
-                break;
-            default:
-                // add JavaBean properties
-                for (PropertyDescriptor property : Introspector.getBeanInfo(type)
-                                                               .getPropertyDescriptors()) {
-                    Method readMethod = property.getReadMethod();
-                    if (readMethod == null
-                            || !readMethod.getName().equals("getClass")) {
-                        properties.put(property.getName(), new MethodProperty(
-                                property));
-                    }
-                }
-
-                // add public fields
-                for (Field field : type.getFields()) {
+        case FIELD:
+            for (Class<?> c = type; c != null; c = c.getSuperclass()) {
+                for (Field field : c.getDeclaredFields()) {
                     int modifiers = field.getModifiers();
-                    if (!Modifier.isStatic(modifiers)
-                            && !Modifier.isTransient(modifiers)) {
-                        properties.put(field.getName(),
-                                       new FieldProperty(field));
+                    if (!Modifier.isStatic(modifiers) && !Modifier.isTransient(modifiers)
+                            && !properties.containsKey(field.getName())) {
+                        properties.put(field.getName(), new FieldProperty(field));
                     }
                 }
-                break;
+            }
+            break;
+        default:
+            // add JavaBean properties
+            for (PropertyDescriptor property : Introspector.getBeanInfo(type)
+                    .getPropertyDescriptors()) {
+                Method readMethod = property.getReadMethod();
+                if (readMethod == null || !readMethod.getName().equals("getClass")) {
+                    properties.put(property.getName(), new MethodProperty(property));
+                }
+            }
+
+            // add public fields
+            for (Field field : type.getFields()) {
+                int modifiers = field.getModifiers();
+                if (!Modifier.isStatic(modifiers) && !Modifier.isTransient(modifiers)) {
+                    properties.put(field.getName(), new FieldProperty(field));
+                }
+            }
+            break;
         }
         if (properties.isEmpty()) {
-            throw new YAMLException("No JavaBean properties found in "
-                    + type.getName());
+            throw new YAMLException("No JavaBean properties found in " + type.getName());
         }
         propertiesCache.put(type, properties);
         return properties;
     }
 
-    public Set<Property> getProperties(Class<? extends Object> type)
-            throws IntrospectionException {
+    public Set<Property> getProperties(Class<? extends Object> type) throws IntrospectionException {
         return getProperties(type, beanAccess);
     }
 
-    public Set<Property> getProperties(Class<? extends Object> type,
-            BeanAccess bAccess) throws IntrospectionException {
+    public Set<Property> getProperties(Class<? extends Object> type, BeanAccess bAccess)
+            throws IntrospectionException {
         if (readableProperties.containsKey(type)) {
             return readableProperties.get(type);
         }
+        Set<Property> properties = createPropertySet(type, bAccess);
+        if (properties.isEmpty()) {
+            throw new YAMLException("No JavaBean properties found in " + type.getName());
+        }
+        readableProperties.put(type, properties);
+        return properties;
+    }
+
+    protected Set<Property> createPropertySet(Class<? extends Object> type, BeanAccess bAccess)
+            throws IntrospectionException {
         Set<Property> properties = new TreeSet<Property>();
         Collection<Property> props = getPropertiesMap(type, bAccess).values();
         for (Property property : props) {
-            if (property.isReadable()
-                    && (allowReadOnlyProperties || property.isWritable())) {
+            if (property.isReadable() && (allowReadOnlyProperties || property.isWritable())) {
                 properties.add(property);
             }
         }
-
-        if (properties.isEmpty()) {
-            throw new YAMLException("No JavaBean properties found in "
-                    + type.getName());
-        }
-
-        readableProperties.put(type, properties);
         return properties;
     }
 
@@ -122,13 +117,13 @@
         return getProperty(type, name, beanAccess);
     }
 
-    public Property getProperty(Class<? extends Object> type, String name,
-            BeanAccess bAccess) throws IntrospectionException {
+    public Property getProperty(Class<? extends Object> type, String name, BeanAccess bAccess)
+            throws IntrospectionException {
         Map<String, Property> properties = getPropertiesMap(type, bAccess);
         Property property = properties.get(name);
         if (property == null || !property.isWritable()) {
-            throw new YAMLException("Unable to find property '" + name
-                    + "' on class: " + type.getName());
+            throw new YAMLException("Unable to find property '" + name + "' on class: "
+                    + type.getName());
         }
         return property;
     }
@@ -147,17 +142,4 @@
             readableProperties.clear();
         }
     }
-
-//    private volatile static PropertyUtils singleton;
-//
-//    public static PropertyUtils getSingleton() {
-//        if (singleton == null) {
-//            synchronized (PropertyUtils.class) {
-//                if (singleton == null) {
-//                    singleton = new PropertyUtils();
-//                }
-//            }
-//        }
-//        return singleton;
-//    }
 }
diff --git a/src/test/java/org/yaml/snakeyaml/issues/issue60/CustomOrderTest.java b/src/test/java/org/yaml/snakeyaml/issues/issue60/CustomOrderTest.java
index ad441c0..9abe748 100644
--- a/src/test/java/org/yaml/snakeyaml/issues/issue60/CustomOrderTest.java
+++ b/src/test/java/org/yaml/snakeyaml/issues/issue60/CustomOrderTest.java
@@ -19,6 +19,7 @@
 import java.beans.IntrospectionException;

 import java.util.Arrays;

 import java.util.Collections;

+import java.util.LinkedHashSet;

 import java.util.Set;

 import java.util.TreeSet;

 

@@ -26,24 +27,48 @@
 

 import org.yaml.snakeyaml.Util;

 import org.yaml.snakeyaml.Yaml;

+import org.yaml.snakeyaml.introspector.BeanAccess;

 import org.yaml.snakeyaml.introspector.Property;

+import org.yaml.snakeyaml.introspector.PropertyUtils;

 import org.yaml.snakeyaml.representer.Representer;

 

 public class CustomOrderTest extends TestCase {

 

     public void testReversedOrder() {

-        Yaml yaml = new Yaml(new ReversedRepresenter());

+        Representer repr = new Representer();

+        repr.setPropertyUtils(new ReversedPropertyUtils());

+        Yaml yaml = new Yaml(repr);

         String output = yaml.dump(getBean());

         // System.out.println(output);

         assertEquals(Util.getLocalResource("issues/issue59-1.yaml"), output);

     }

 

-    private class ReversedRepresenter extends Representer {

+    private class ReversedPropertyUtils extends PropertyUtils {

         @Override

-        protected Set<Property> getProperties(Class<? extends Object> type)

+        protected Set<Property> createPropertySet(Class<? extends Object> type, BeanAccess bAccess)

                 throws IntrospectionException {

             Set<Property> result = new TreeSet<Property>(Collections.reverseOrder());

-            result.addAll(super.getProperties(type));

+            result.addAll(super.createPropertySet(type, bAccess));

+            return result;

+        }

+    }

+

+    public void testUnsorted() {

+        Representer repr = new Representer();

+        repr.setPropertyUtils(new UnsortedPropertyUtils());

+        Yaml yaml = new Yaml(repr);

+        String output = yaml.dump(getBean());

+        // System.out.println(output);

+        assertEquals(Util.getLocalResource("issues/issue60-3.yaml"), output);

+    }

+

+    private class UnsortedPropertyUtils extends PropertyUtils {

+        @Override

+        protected Set<Property> createPropertySet(Class<? extends Object> type, BeanAccess bAccess)

+                throws IntrospectionException {

+            Set<Property> result = new LinkedHashSet<Property>(getPropertiesMap(type, bAccess)

+                    .values());

+            result.remove(result.iterator().next());// drop 'empty' property

             return result;

         }

     }

diff --git a/src/test/resources/issues/issue60-3.yaml b/src/test/resources/issues/issue60-3.yaml
new file mode 100644
index 0000000..ba0806f
--- /dev/null
+++ b/src/test/resources/issues/issue60-3.yaml
@@ -0,0 +1,7 @@
+!!org.yaml.snakeyaml.issues.issue60.SkipBean
+listDate: null
+listInt: [null, 1, 2, 3]
+listStr: [bar, null, foo, null]
+map: {}
+number: null
+text: foo