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 */
011package com.thoughtworks.proxy.toys.nullobject;
012
013import java.lang.reflect.Array;
014import java.util.Collection;
015import java.util.Collections;
016import java.util.List;
017import java.util.Map;
018import java.util.Set;
019import java.util.SortedMap;
020import java.util.SortedSet;
021import java.util.TreeMap;
022import java.util.TreeSet;
023
024import com.thoughtworks.proxy.ProxyFactory;
025import 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 */
038public 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}