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 }