001/**
002 * Copyright 2005-2018 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 */
016package org.kuali.rice.krad.uif.util;
017
018import java.lang.ref.Reference;
019import java.lang.ref.WeakReference;
020import java.lang.reflect.Field;
021import java.lang.reflect.Modifier;
022import java.util.ArrayList;
023import java.util.Collections;
024import java.util.HashMap;
025import java.util.List;
026import java.util.Map;
027import java.util.Queue;
028import java.util.WeakHashMap;
029import java.util.concurrent.ConcurrentLinkedQueue;
030
031/**
032 * Simple utility class for implementing an object recycling factory pattern.
033 *
034 * <p>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.</p>
037 *
038 * @author Kuali Rice Team (rice.collab@kuali.org)
039 */
040public final class RecycleUtils {
041
042    // Thread local reference to recycled objects.
043    private static final Map<Class<?>, Reference<Map<String, Queue<Object>>>> RECYCLE = Collections.synchronizedMap(
044            new WeakHashMap<Class<?>, Reference<Map<String, Queue<Object>>>>());
045
046    // Field cache to reduce reflection overhead during clean operations.
047    private static final Map<Class<?>, List<Field>> FIELD_CACHE = Collections.synchronizedMap(
048            new WeakHashMap<Class<?>, List<Field>>());
049
050    /**
051     * Private constructor - utility class only.
052     */
053    private RecycleUtils() {}
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 class to get instance of
061     * @return 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 getRecycledInstance(null, c);
066    }
067
068    /**
069     * Get an instance of the given class that has previously been recycled on the same thread, if
070     * an instance of available.
071     *
072     * @param <T> recycled instance type
073     * @param name bean name
074     * @param c class to get instance of
075     * @return instance of the given class previously recycled on the same thread, if one is
076     * available. If no instance is available, then null is returned.
077     */
078    public static <T> T getRecycledInstance(String name, Class<T> c) {
079        return c.cast(getRecycleQueue(name, c).poll());
080    }
081
082    /**
083     * Get an instance of the given class that has previously been recycled on the same thread, or a
084     * new instance using a default constructor if a recycled instance is not available.
085     *
086     * @param <T> recycled instance type
087     * @param c class to get instance of
088     * @return An instance of the given class previously recycled on the same thread, if one is
089     * available. If no instance is available, then null is returned
090     */
091    public static <T> T getInstance(Class<T> c) {
092        T rv = getRecycledInstance(c);
093
094        if (rv == null) {
095            try {
096                rv = c.newInstance();
097            } catch (InstantiationException | IllegalAccessException e) {
098                throw new IllegalStateException("Unabled to instantiate " + c, e);
099            }
100        }
101
102        return rv;
103    }
104
105    /**
106     * Recycle a instance by its class, for later retrieval in the same thread.
107     *
108     * <p>Note that this method does not clean the instance, it only queues it for later retrieval by
109     * {@link #getRecycledInstance(Class)}. The state of the instance should be cleared before
110     * passing to this method. For a flexible means to clean instances using reflection
111     * {@link #clean(Object, Class)} may be considered, however note that a manually implemented
112     * clean operation will generally perform faster.</p>
113     *
114     * @param instance instance to recycle
115     */
116    public static void recycle(Object instance) {
117        recycle(null, instance);
118    }
119
120    /**
121     * Recycle a instance by its class and given name, for later retrieval in the same thread.
122     *
123     * @see RecycleUtils#recycle(java.lang.Object)
124     *
125     * @param name bean name
126     * @param instance instance to recycle
127     */
128    public static void recycle(String name, Object instance) {
129        if (instance != null) {
130            getRecycleQueue(name, instance.getClass()).offer(instance);
131        }
132    }
133
134    /**
135     * Recycle a instance by the given class and given name, for later retrieval in the same thread.
136     *
137     * @see RecycleUtils#recycle(java.lang.Object)
138     *
139     * @param name bean name
140     * @param instance instance to recycle
141     * @param recycleClass class to register the instance under
142     */
143    public static void recycle(String name, Object instance, Class<?> recycleClass) {
144        if (instance != null) {
145            getRecycleQueue(name, recycleClass).offer(instance);
146        }
147    }
148
149    /**
150     * Clean all instance fields.
151     *
152     * @param <T> recycled instance type
153     * @param instance instance to clean
154     */
155    public static <T> void clean(T instance) {
156        clean(instance, Object.class);
157    }
158
159    /**
160     * Clean all instance fields, walking up the class hierarchy to the indicated super class.
161     *
162     * @param <T> recycled instance type
163     * @param instance instance to clean
164     * @param top point in the class hierarchy at which to stop cleaning fields
165     */
166    public static <T> void clean(T instance, Class<? super T> top) {
167        Class<?> c = instance.getClass();
168        while (c != null && c != top && top.isAssignableFrom(c)) {
169
170            List<Field> fields;
171            synchronized (FIELD_CACHE) {
172                // Get within synchronized, because FIELD_CACHE is a WeakHashMap
173                fields = FIELD_CACHE.get(c);
174                if (fields == null) {
175                    Field[] declared = c.getDeclaredFields();
176                    fields = new ArrayList<Field>(declared.length);
177
178                    // Don't clean static fields.
179                    for (Field field : fields) {
180                        if ((field.getModifiers() & Modifier.STATIC) != Modifier.STATIC) {
181                            field.setAccessible(true);
182                            fields.add(field);
183                        }
184                    }
185
186                    fields = Collections.unmodifiableList(fields);
187                    FIELD_CACHE.put(c, fields);
188                }
189            }
190
191            for (Field field : fields) {
192                try {
193                    Class<?> type = field.getType();
194
195                    if (type.isPrimitive()) {
196
197                        if (type == Integer.TYPE) {
198                            field.set(instance, 0);
199                        } else if (type == Boolean.TYPE) {
200                            field.set(instance, false);
201                        } else if (type == Long.TYPE) {
202                            field.set(instance, 0L);
203                        } else if (type == Character.TYPE) {
204                            field.set(instance, '\0');
205                        } else if (type == Double.TYPE) {
206                            field.set(instance, 0.0);
207                        } else if (type == Float.TYPE) {
208                            field.set(instance, 0.0f);
209                        } else if (type == Short.TYPE) {
210                            field.set(instance, (short) 0);
211                        } else if (type == Byte.TYPE) {
212                            field.set(instance, (byte) 0);
213                        }
214                    } else {
215                        field.set(instance, null);
216                    }
217                } catch (IllegalAccessException e) {
218                    throw new IllegalStateException("Unexpected error setting " + field, e);
219                }
220            }
221
222            c = c.getSuperclass();
223        }
224    }
225
226    /**
227     * Get a recycle queue by class.
228     *
229     * @param c class to get a recycle queue for
230     */
231    private static Queue<Object> getRecycleQueue(String name, Class<?> c) {
232        Reference<Map<String, Queue<Object>>> recycleMapRef = RECYCLE.get(c);
233        Map<String, Queue<Object>> recycleMap = null;
234
235        if (recycleMapRef != null) {
236            recycleMap = recycleMapRef.get();
237        }
238
239        if (recycleMap == null) {
240            recycleMap = new HashMap<String, Queue<Object>>();
241            recycleMapRef = new WeakReference<Map<String, Queue<Object>>>(recycleMap);
242            synchronized (RECYCLE) {
243                RECYCLE.put(c, recycleMapRef);
244            }
245        }
246
247        Queue<Object> recycleQueue = recycleMap.get(name);
248        if (recycleQueue == null) {
249            recycleQueue = new ConcurrentLinkedQueue<Object>();
250            synchronized (recycleMap) {
251                recycleMap.put(name, recycleQueue);
252            }
253        }
254
255        return recycleQueue;
256    }
257}