Teaser Image

qnoid

Markos Charatzas - Edinburgh, UK




/**
     * Each {@link Element} has one format which is used to return its string 
     * representation.
     * 
     * Implementations should provide the corresponding format.
     * 
     * @see ElementFormats#defaultFormat()
     * @see ElementFormats#newDateFormat(java.text.DateFormat) 
     */
    public interface ElementFormat<T>
    {
        /**
         * 
         * @param <T> the type of the element
         * @param element the element related to the value
         * @param value its value
         * @return a String representation
         */
        public String format(Element<T> element, T value);
    }

    /**
     * A resource for various element formats
     */
    public class ElementFormats
    {
        /**
         * A default JSON format as
         * <code>
         *  "name":"value"
         * </code>
         */
        public static final <T> ElementFormat<T> defaultFormat()
        {
        return new ElementFormat<T>() {
                @Override
                public String format(Element<T> element, T value){
                return String.format("\"%s\":\"%s\"", element.name(), value);
                }
            };
        }

        /**
         * Creates a new format for Date {@link Element}s. 
         * The specified date format will be used to format the date of the element. 
         * 
         * @see DateFormat#format(Date)
         */
        public static final ElementFormat<Date> newDateFormat(final DateFormat dateFormat)
        {
        return new ElementFormat<Date>() {        
                @Override
                public String format(Element<Date> element, Date value) {
                return String.format("\"%s\":\"%s\"", element.name(), dateFormat.format(value));
                }
            };    
        }    
    }


    /**
     * A JSON pair that is associated with an element and has a value
     * 
     * @param <T> the type of the pair which derives from the Element is associated with
     * @see Element
     */
    public final class Pair<T>
    {
        /**
         * Create a new JSON pair
         * 
         * @param <T> the type of the JSON pair 
         * @param element the element of the pair
         * @param value its value
         * @return a new Pair
         * @see Elements
         */
        public static final <T> Pair<T> newPair(Element<T> element, T value)
        {
            Preconditions.checkNotNull(element);
            Preconditions.checkNotNull(value);
        return new Pair<T>(element, value);
        }
        
        private final Element<T> element;
        private final T value;
        
        /**
         * @param element
         * @param value
         */
        private Pair(Element<T> element, T value)
        {
            this.element = element;
            this.value = value;
        }
        
        public Element<T> element() {
        return this.element;
        }

        public T value() {
        return value;
        }

        @Override
        public String toString() {
        return this.element.format(this.value);
        }

        /**
         * Effective Java 2nd Edition, Joshua Bloch, Item 8
         */
        @Override
        public int hashCode() 
        {
            int result = 17;
        result = 37 * result + this.element.hashCode();
        result = 37 * result + this.value.hashCode();
        return result;
        }

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


    /**
     * Testing the format of elements
     */
    public class ElementTest
    {
        private static final Date DATE_VALUE = Dates.of(22, 1, 2010);
        
        @Test
        public void formatId() throws Exception
        {
            Assert.assertEquals("\"id\":\"1\"", Elements.ID.format(1));
        }
        
        @Test
        public void formatName() throws Exception
        {
            Assert.assertEquals("\"name\":\"Kyle\"", Elements.NAME.format("Kyle"));
        }
        
        @Test
        public void formatType() throws Exception
        {
            Assert.assertEquals("\"type\":\"developer\"", Elements.TYPE.format(Type.DEVELOPER));
        }
        
        @Test
        public void formatDate() throws Exception
        {
            Assert.assertEquals("\"date\":\"Jan 22, '10\"", Elements.DATE.format(DATE_VALUE));
        }
    }

During the first part on "How to think of JSON" we identified the Element as the heterogeneous type that we need to handle. Also talked briefly about ElementFormat.

In this part we introduce the Pair class which merely holds an element and its value. This will allow us to come up with a JSON object next.

As you can see anonymous classes support the ElementFormat interface with their dependencies listed as arguments to the static method (e.g. DateFormat)

In this case there is no reason to have separate classes since all your relationships are tied to the interface and as a result you reduce the API noise.

Bare in mind that you have to use final for arguments that are used in an anonymous class or you get a compiler error.

A nice way to concatenate strings is to use the String#format() method which sort of mimics the C way

String.format("\"%s\":\"%s\"", element.name(), value);

Notice the backslash used for escaping a quote.

There is also 2 things to consider here about generics. The method

public static final <T> ElementFormat<T> defaultFormat()

has a type which makes it a generic method allowing us to use it in the generic Element class as the default. Note however that T of Element and T of defaultFormat are still considered different types and in no way enforced by the Java compiler. It's our implementation of the Element class that makes the connection between them.

public final class Element<T>
    {
        public static class ElementBuilder<T>
        {
            private ElementFormat<T> format = ElementFormats.defaultFormat(); 

            public Element<T> build(){
            return new Element<T>(this.type, this.name, this.order, this.format);
            }
        }

        private final ElementFormat<T> format; 
    }

In the same way the type of the Pair defines the type for the value which is also the same as that of the Element

Element<Date> dateElement = Elements.DATE;
        Date value = Calendar.getInstance().getTime();
        Pair<Date> pair = Pair.newPair(dateElement, value);

Continue to Part 3

Source