Teaser Image

qnoid

Markos Charatzas - London, UK




/**
     * A JSON element describing its type and name.
     * 
     * @param <T> the type of the element
     * 
     * @see Elements#newStringType(String)
     * @see Elements#newNumericType(String)
     * @see Elements#newDateType(String, java.text.DateFormat)
     * @see Elements#newBooleanType(String)
     * @see Elements#newEnumType(Class, String)
     */
    public final class Element<T> implements Comparable<Element<T>>
    {
        /**
         * By default ordering isn't taken into account and the element's format is
         * {@link ElementFormats#defaultFormat()}
         * 
         * @see Element#format(Object)
         */
        public static class ElementBuilder<T>
        {
            private final Class<T> type;
            private final String name;
            private ElementFormat<T> format = ElementFormats.defaultFormat(); 
            private Integer order = 0;
            
            /**
             * @param type the element type
             * @param name its name
             */
            public ElementBuilder(Class<T> type, String name)
            {
                Preconditions.checkNotNull(type);
                Preconditions.checkNotNull(name);
                this.type = type;
                this.name = name;
            }

            /**
             * Use to specify a custom format
             * @param format the format of the element
             * @see ElementFormats
             */
            public ElementBuilder<T> format(ElementFormat<T> format)
            {
                Preconditions.checkNotNull(format);
                this.format = format;
                
            return this;
            }

            /**
             * Use to specify the ordering of the elements
             * @param order will be taken into account when iterating over a 
             * {@link PairSet}
             */
            public ElementBuilder<T> order(Integer order)
            {
                Preconditions.checkNotNull(order);
                this.order = order;
            return this;
            }

            /**
             * @return a new Element the specified type
             */
            public Element<T> build(){
            return new Element<T>(this.type, this.name, this.order, this.format);
            }
        }
        
        private final ElementFormat<T> format; 
        private final Class<T> type;
        private final String name;
        private final Integer order;
        
        Element(Class<T> type, String name, Integer order, ElementFormat<T> format)
        {
            this.type = type;
            this.name = name;
            this.order = order;
            this.format = format;
        }

        public Class<T> type() {
        return type;
        }

        public String name() {
        return name;
        }    
        
        @Override
        public String toString(){
        return this.name;
        }
        
        /**
         * Formats the value of this element based on the format specified. 
         * This will be used when creating its string representation  
         * 
         * @param value the value to format
         * @return a String formatted as specified by the {@link ElementFormat}
         */
        public String format(T value) {
        return this.format.format(this, value);
        }

        @Override
        public boolean equals(Object that) 
        {
        if(this == that){
            return true;
        }
            
        if(!(that instanceof Element)){
            return false;
        }
            
        Element<?> element = (Element<?>)that;
            
        return this.name.equals(element.name);
        }

        @Override
        public int hashCode() {
        return this.name.hashCode();
        }

        /* (non-Javadoc)
         * @see java.lang.Comparable#compareTo(java.lang.Object)
         */
        @Override
        public int compareTo(Element<T> element) {
        return this.order.compareTo(element.order);
        }  
    }

    /**
     * Access to constant JSON elements and creation
     */
    public class Elements
    {
        public static final Element<Integer> ID = newNumericType("id", 1);
        
        public static final Element<String> NAME = 
            newStringType("name", 2);
        
        public static final Element<Date> DATE = 
            newDateType("date", new SimpleDateFormat("MMM dd, ''yy"), 3);
        
        public static final Element<Type> TYPE = 
            newEnumType(Type.class, "type", 4);

        public static final <E extends Enum<E>> Element<E> newEnumType(Class<E> type, String name, int order){
        return new ElementBuilder<E>(type, name).order(order).build();
        }
        
        public static final Element<String> newStringType(String name, int order){
        return new ElementBuilder<String>(String.class, name).order(order).build();
        }
        
        public static final Element<Date> newDateType(String name, final DateFormat dateFormat, int order){
        return new ElementBuilder<Date>(Date.class, name).order(order).format( ElementFormats.newDateFormat(dateFormat) ).build();
        }
        
        public static final Element<Integer> newNumericType(String name, int order){
        return new ElementBuilder<Integer>(Integer.class, name).order(order).build();
        }
    }

You should already be familiar with the idea of heterogeneous containers and what it takes to kick off. In the next series of posts we'll see how we can think of JSON in such terms.

In the Element class above there are a couple more things other than the type and the name like a builder, an ElementFormat, an equals/hashCode implementation and a Comparable interface so let's talk about them one at a time.

The builder is there to help us create new Element(s). The main advantage over using the constructor is that it provides reasonable defaults for the ordering of an element and its format. Thus from the client code it looks like only a type and a name is needed for an Element.

        public ElementBuilder(Class<T> type, String name)
        {
            Preconditions.checkNotNull(type);
            Preconditions.checkNotNull(name);
            this.type = type;
            this.name = name;
        }

The ElementFormat is used for creating the string representation of an Element for a given value. The need for a formatter comes from the fact that some values (e.g. a Date) need to be formatted outside of their #toString implementation. Nevertheless the string format is always of

    "name":"value"

with the value being formatted each time. Bare in mind that the Element class merely describes a JSON element in terms of its type and name without holding its actual value.

You should do well to override equals/hashCode, especially when dealing with the Collections framework as you might be getting some unexpected results when doing add/remove operations.

    if(!(that instanceof Element)){
    return false;
    }
Element<?> element = (Element<?>)that;

Notice how the equals/hashCode implementation uses the raw type of the Element class for the instanceOf operator but the wildcard type for the cast. Josh Bloch once again has a thorough explanation on this.

Finally Comparable used with the order property is merely for making testing slightly easier (coming later on) and not really a requirement. Unless you do want your JSON elements sorted in which case is an easy way to go :) In that case you could have instead opted for the builder to increment the ordering for every element created.

Kudos to Google for the guava-libraries (see Preconditions)

Continue to Part 2

Source