001    /*
002     * (c) 2003-2005, 2009, 2010 ThoughtWorks Ltd
003     * All rights reserved.
004     *
005     * The software in this package is published under the terms of the BSD
006     * style license a copy of which has been included with this distribution in
007     * the LICENSE.txt file.
008     * 
009     * Created on 24-Mar-2004
010     */
011    package com.thoughtworks.proxy.toys.nullobject;
012    
013    import java.lang.reflect.Array;
014    import java.util.Collection;
015    import java.util.Collections;
016    import java.util.List;
017    import java.util.Map;
018    import java.util.Set;
019    import java.util.SortedMap;
020    import java.util.SortedSet;
021    import java.util.TreeMap;
022    import java.util.TreeSet;
023    
024    import com.thoughtworks.proxy.ProxyFactory;
025    import com.thoughtworks.proxy.factory.StandardProxyFactory;
026    
027    
028    /**
029     * Toy factory to create proxies acting as Null Objects.
030     *
031     * @author Dan North
032     * @author Aslak Hellesøy
033     * @author Juan Li
034     * @author Jörg Schaible
035     * @see com.thoughtworks.proxy.toys.nullobject
036     * @since 0.1
037     */
038    public class Null<T> {
039    
040        /**
041         * The Null {@link Object}.
042         * @since 0.1
043         */
044        public static final Object NULL_OBJECT = new Object();
045    
046        /**
047         * Immutable Null Object implementation of {@link SortedMap}.
048         * @since 0.1
049         */
050        public static final SortedMap<Object, Object> NULL_SORTED_MAP = new TreeMap<Object, Object>() {
051            private static final long serialVersionUID = -4388170961744587609L;
052    
053            @Override
054            public Object put(Object key, Object value) {
055                throw new UnsupportedOperationException();
056            }
057    
058            @Override
059            public void clear() {
060                // nothing to do
061            }
062    
063            @Override
064            public Object remove(Object key) {
065                return null;
066            }
067    
068            @Override
069            public Set<Object> keySet() {
070                return Collections.emptySet();
071            }
072    
073            @Override
074            public Collection<Object> values() {
075                return Collections.emptyList();
076            }
077    
078            @Override
079            public Set<Map.Entry<Object, Object>> entrySet() {
080                return Collections.emptySet();
081            }
082        };
083    
084        /**
085         * Immutable Null Object implementation of {@link SortedSet}.
086         * @since 0.1
087         */
088        public static final SortedSet<Object> NULL_SORTED_SET = new TreeSet<Object>() {
089            private static final long serialVersionUID = 809722154285517876L;
090    
091            @Override
092            public boolean add(Object o) {
093                throw new UnsupportedOperationException();
094            }
095    
096            @Override
097            public void clear() {
098                // nothing to do
099            }
100    
101            @Override
102            public boolean remove(Object o) {
103                return false;
104            }
105    
106            @Override
107            public boolean removeAll(Collection<?> c) {
108                return false;
109            }
110    
111            @Override
112            public boolean retainAll(Collection<?> c) {
113                return false;
114            }
115        };
116        private Class<T> type;
117    
118        private Null(Class<T> type) {
119            this.type = type;
120        }
121    
122    
123        /**
124         * Creates a factory for proxy instances that is nullable.
125         *
126         * @param type the type implemented by the proxy
127         * @return the factory
128         * @since 1.0
129         */
130        public static <T> NullBuild<T> proxy(Class<T> type) {
131            return new NullBuild<T>(new Null<T>(type));
132        }
133    
134        public static class NullBuild<T> {
135    
136            private final Null<T> nullObject;
137    
138            private NullBuild(Null<T> nullObject) {
139                this.nullObject = nullObject;
140            }
141    
142            /**
143             * Generate a Null Object proxy for a specific type using the{@link StandardProxyFactory}.
144             * <p>
145             * Note that the method will only return a proxy if it cannot handle the type itself or <code>null</code> if the
146             * type cannot be proxied.
147             * </p>
148             *
149             * @return object, proxy or <code>null</code>
150             * @see com.thoughtworks.proxy.toys.nullobject
151             * @since 1.0
152             */
153            public T build() {
154                return nullObject.build(new StandardProxyFactory());
155            }
156    
157            /**
158             * Generate a Null Object proxy for a specific type using a special {@link ProxyFactory}.
159             * <p>
160             * Note that the method will only return a proxy if it cannot handle the type itself or <code>null</code> if the
161             * type cannot be proxied.
162             * </p>
163             *
164             * @param factory the {@link ProxyFactory} in use
165             * @return object, proxy or <code>null</code>
166             * @see com.thoughtworks.proxy.toys.nullobject
167             */
168            public T build(ProxyFactory factory) {
169                return nullObject.build(factory);
170            }
171            
172        }
173        
174        private T build(ProxyFactory proxyFactory) {
175            final Object result;
176    
177            // Primitives
178            if (boolean.class.equals(type) || Boolean.class.equals(type)) {
179                result = Boolean.FALSE;
180            } else if (byte.class.equals(type) || Byte.class.equals(type)) {
181                result = (byte) 0;
182            } else if (char.class.equals(type) || Character.class.equals(type)) {
183                result = (char) 0;
184            } else if (int.class.equals(type) || Integer.class.equals(type)) {
185                result = 0;
186            } else if (long.class.equals(type) || Long.class.equals(type)) {
187                result = (long) 0;
188            } else if (float.class.equals(type) || Float.class.equals(type)) {
189                result = 0.0f;
190            } else if (double.class.equals(type) || Double.class.equals(type)) {
191                result = 0.0;
192            }
193    
194            // String
195            else if (String.class.equals(type)) {
196                result = "";
197            }
198    
199            // Object
200            else if (Object.class.equals(type)) {
201                result = NULL_OBJECT;
202            }
203    
204            // Arrays
205            else if (type.isArray()) {
206                result = Array.newInstance(type.getComponentType(), 0);
207            }
208    
209            // Collections
210            else if (Set.class == type) {
211                result = Collections.EMPTY_SET;
212            } else if (Map.class == type) {
213                result = Collections.EMPTY_MAP;
214            } else if (List.class == type) {
215                result = Collections.EMPTY_LIST;
216            } else if (SortedSet.class == type) {
217                result = NULL_SORTED_SET;
218            } else if (SortedMap.class == type) {
219                result = NULL_SORTED_MAP;
220            } else if (proxyFactory.canProxy(type)) {
221                result = proxyFactory.createProxy(new NullInvoker(type, proxyFactory), type);
222            } else {
223                result = null;
224            }
225            @SuppressWarnings("unchecked")
226            T typedResult = (T) result;
227            return typedResult;
228        }
229    
230    
231        /**
232         * Determine whether an object was created by {@link Null#proxy(Class)}.
233         *
234         * @param object the object to examine
235         * @return <code>true</code> if the object is a Null proxy.
236         * @since 0.1
237         */
238        public static boolean isNullObject(final Object object) {
239            return isNullObject(object, new StandardProxyFactory());
240        }
241    
242        /**
243         * Determine whether an object was created by {@link Null#proxy(Class)} using a special ProxyFactory with the builder.
244         *
245         * @param object       the object to examine
246         * @param proxyFactory the {@link ProxyFactory} to use
247         * @return <code>true</code> if the object is a Null proxy.
248         * @since 0.1
249         */
250        public static boolean isNullObject(final Object object, final ProxyFactory proxyFactory) {
251            return isStandardNullObject(object) || isNullProxyObject(object, proxyFactory);
252        }
253    
254        private static boolean isStandardNullObject(Object object) {
255            return object == Collections.EMPTY_LIST
256                    || object == Collections.EMPTY_SET
257                    || object == Collections.EMPTY_MAP
258                    || object == NULL_SORTED_SET
259                    || object == NULL_SORTED_MAP
260                    || object == NULL_OBJECT;
261        }
262    
263        private static boolean isNullProxyObject(final Object object, final ProxyFactory proxyFactory) {
264            return proxyFactory.isProxyClass(object.getClass()) && proxyFactory.getInvoker(object) instanceof NullInvoker;
265        }
266    }