001    /**
002     * Copyright 2005-2014 The Kuali Foundation
003     *
004     * Licensed under the Educational Community License, Version 2.0 (the "License");
005     * you may not use this file except in compliance with the License.
006     * You may obtain a copy of the License at
007     *
008     * http://www.opensource.org/licenses/ecl2.php
009     *
010     * Unless required by applicable law or agreed to in writing, software
011     * distributed under the License is distributed on an "AS IS" BASIS,
012     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013     * See the License for the specific language governing permissions and
014     * limitations under the License.
015     */
016    package org.kuali.rice.krad.uif.util;
017    
018    import java.lang.ref.Reference;
019    import java.lang.ref.WeakReference;
020    import java.lang.reflect.Field;
021    import java.lang.reflect.Modifier;
022    import java.util.ArrayList;
023    import java.util.Collections;
024    import java.util.List;
025    import java.util.Map;
026    import java.util.Queue;
027    import java.util.WeakHashMap;
028    import java.util.concurrent.ConcurrentLinkedQueue;
029    
030    /**
031     * Simple utility class for implementing an object recycling factory pattern.
032     * 
033     * <p>
034     * Weak references to objects are held by a thread-local queue. When a process has finished working
035     * with an object, the {@link #recycle} method may be offer the recycled object to the queue for
036     * consideration as reusable on the same thread.
037     * </p>
038     * 
039     * @author Kuali Rice Team (rice.collab@kuali.org)
040     */
041    public final class RecycleUtils {
042    
043        /**
044         * Thread local reference to recycled objects.
045         */
046        private final static Map<Class<?>, Reference<Queue<Object>>> RECYCLE =
047                Collections.synchronizedMap(new WeakHashMap<Class<?>, Reference<Queue<Object>>>());
048    
049        /**
050         * Field cache to reduce reflection overhead during clean operations.
051         */
052        private final static Map<Class<?>, List<Field>> FIELD_CACHE = 
053                Collections.synchronizedMap(new WeakHashMap<Class<?>, List<Field>>());
054    
055        /**
056         * Get an instance of the given class that has previously been recycled on the same thread, if
057         * an instance of available.
058         * 
059         * @param <T> recycled instance type
060         * @param c The class.
061         * @return An instance of the given class previously recycled on the same thread, if one is
062         *         available. If no instance is available, then null is returned.
063         */
064        public static <T> T getRecycledInstance(Class<T> c) {
065            return c.cast(getRecycleQueue(c).poll());
066        }
067    
068        /**
069         * Get an instance of the given class that has previously been recycled on the same thread, or a
070         * new instance using a default constructor if a recycled instance is not available.
071         * 
072         * @param <T> recycled instance type
073         * @param c The class.
074         * @return An instance of the given class previously recycled on the same thread, if one is
075         *         available. If no instance is available, then null is returned.
076         */
077        public static <T> T getInstance(Class<T> c) {
078            T rv = getRecycledInstance(c);
079            
080            if (rv == null) {
081                try {
082                    rv = c.newInstance();
083                } catch (InstantiationException e) {
084                    throw new IllegalStateException("Unabled to instantiate " + c);
085                } catch (IllegalAccessException e) {
086                    throw new IllegalStateException("Unabled to instantiate " + c);
087                }
088            }
089    
090            return rv;
091        }
092    
093        /**
094         * Recycle a instance, for later retrieval in the same thread.
095         * 
096         * <p>
097         * Note that this method does not clean the instance, it only queues it for later retrieval by
098         * {@link #getRecycledInstance(Class)}. The state of the instance should be cleared before
099         * passing to this method. For a flexible means to clean instances using reflection
100         * {@link #clean(Object, Class)} may be considered, however note that a manually implemented
101         * clean operation will generally perform faster.
102         * </p>
103         * 
104         * @param instance The instance to recycle.
105         */
106        public static void recycle(Object instance) {
107            if (instance != null) {
108                getRecycleQueue(instance.getClass()).offer(instance);
109            }
110        }
111    
112        /**
113         * Clean all instance fields.
114         * 
115         * @param <T> recycled instance type
116         * @param instance The instance to clean.
117         */
118        public static <T> void clean(T instance) {
119            clean(instance, Object.class);
120        }
121    
122        /**
123         * Clean all instance fields, walking up the class hierarchy to the indicated super class.
124         * 
125         * @param <T> recycled instance type
126         * @param instance The instance to clean.
127         * @param top The point in the class hierarchy at which to stop cleaning fields.
128         */
129        public static <T> void clean(T instance, Class<? super T> top) {
130            Class<?> c = instance.getClass();
131            while (c != null && c != top && top.isAssignableFrom(c)) {
132    
133                List<Field> fields;
134                synchronized (FIELD_CACHE) {
135                    // Get within synchronized, because FIELD_CACHE is a WeakHashMap
136                    fields = FIELD_CACHE.get(c);
137                    if (fields == null) {
138                        Field[] declared = c.getDeclaredFields();
139                        fields = new ArrayList<Field>(declared.length);
140    
141                        // Don't clean static fields.
142                        for (Field field : fields) {
143                            if ((field.getModifiers() & Modifier.STATIC) != Modifier.STATIC) {
144                                field.setAccessible(true);
145                                fields.add(field);
146                            }
147                        }
148    
149                        fields = Collections.unmodifiableList(fields);
150                        FIELD_CACHE.put(c, fields);
151                    }
152                }
153    
154                for (Field field : fields) {
155                    try {
156                        Class<?> type = field.getType();
157    
158                        if (type.isPrimitive()) {
159    
160                            if (type == Integer.TYPE) {
161                                field.set(instance, 0);
162                            } else if (type == Boolean.TYPE) {
163                                field.set(instance, false);
164                            } else if (type == Long.TYPE) {
165                                field.set(instance, 0L);
166                            } else if (type == Character.TYPE) {
167                                field.set(instance, '\0');
168                            } else if (type == Double.TYPE) {
169                                field.set(instance, 0.0);
170                            } else if (type == Float.TYPE) {
171                                field.set(instance, 0.0f);
172                            } else if (type == Short.TYPE) {
173                                field.set(instance, (short) 0);
174                            } else if (type == Byte.TYPE) {
175                                field.set(instance, (byte) 0);
176                            }
177    
178                        } else {
179                            field.set(instance, null);
180                        }
181    
182                    } catch (IllegalAccessException e) {
183                        throw new IllegalStateException("Unexpected error setting " + field, e);
184                    }
185                }
186    
187                c = c.getSuperclass();
188            }
189        }
190    
191        /**
192         * Get a recycle queue by class.
193         * 
194         * @param c The class to get a recycle queue for.
195         */
196        private static Queue<Object> getRecycleQueue(Class<?> c) {
197            synchronized (RECYCLE) {
198                Reference<Queue<Object>> recycleQueueRef = RECYCLE.get(c);
199                Queue<Object> recycleQueue = recycleQueueRef == null ? null : recycleQueueRef.get();
200                if (recycleQueue == null) {
201                    recycleQueue = new ConcurrentLinkedQueue<Object>();
202                    RECYCLE.put(c, new WeakReference<Queue<Object>>(recycleQueue));
203                }
204    
205                return recycleQueue;
206            }
207        }
208    
209        /**
210         * Private constructor - utility class only.
211         */
212        private RecycleUtils() {}
213    
214    }