View Javadoc
1   /**
2    * Copyright 2005-2014 The Kuali Foundation
3    *
4    * Licensed under the Educational Community License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    * http://www.opensource.org/licenses/ecl2.php
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package org.kuali.rice.krad.uif.util;
17  
18  import java.lang.ref.Reference;
19  import java.lang.ref.WeakReference;
20  import java.lang.reflect.Field;
21  import java.lang.reflect.Modifier;
22  import java.util.ArrayList;
23  import java.util.Collections;
24  import java.util.List;
25  import java.util.Map;
26  import java.util.Queue;
27  import java.util.WeakHashMap;
28  import java.util.concurrent.ConcurrentLinkedQueue;
29  
30  /**
31   * Simple utility class for implementing an object recycling factory pattern.
32   * 
33   * <p>
34   * Weak references to objects are held by a thread-local queue. When a process has finished working
35   * with an object, the {@link #recycle} method may be offer the recycled object to the queue for
36   * consideration as reusable on the same thread.
37   * </p>
38   * 
39   * @author Kuali Rice Team (rice.collab@kuali.org)
40   */
41  public final class RecycleUtils {
42  
43      /**
44       * Thread local reference to recycled objects.
45       */
46      private final static Map<Class<?>, Reference<Queue<Object>>> RECYCLE =
47              Collections.synchronizedMap(new WeakHashMap<Class<?>, Reference<Queue<Object>>>());
48  
49      /**
50       * Field cache to reduce reflection overhead during clean operations.
51       */
52      private final static Map<Class<?>, List<Field>> FIELD_CACHE = 
53              Collections.synchronizedMap(new WeakHashMap<Class<?>, List<Field>>());
54  
55      /**
56       * Get an instance of the given class that has previously been recycled on the same thread, if
57       * an instance of available.
58       * 
59       * @param <T> recycled instance type
60       * @param c The class.
61       * @return An instance of the given class previously recycled on the same thread, if one is
62       *         available. If no instance is available, then null is returned.
63       */
64      public static <T> T getRecycledInstance(Class<T> c) {
65          return c.cast(getRecycleQueue(c).poll());
66      }
67  
68      /**
69       * Get an instance of the given class that has previously been recycled on the same thread, or a
70       * new instance using a default constructor if a recycled instance is not available.
71       * 
72       * @param <T> recycled instance type
73       * @param c The class.
74       * @return An instance of the given class previously recycled on the same thread, if one is
75       *         available. If no instance is available, then null is returned.
76       */
77      public static <T> T getInstance(Class<T> c) {
78          T rv = getRecycledInstance(c);
79          
80          if (rv == null) {
81              try {
82                  rv = c.newInstance();
83              } catch (InstantiationException e) {
84                  throw new IllegalStateException("Unabled to instantiate " + c);
85              } catch (IllegalAccessException e) {
86                  throw new IllegalStateException("Unabled to instantiate " + c);
87              }
88          }
89  
90          return rv;
91      }
92  
93      /**
94       * Recycle a instance, for later retrieval in the same thread.
95       * 
96       * <p>
97       * Note that this method does not clean the instance, it only queues it for later retrieval by
98       * {@link #getRecycledInstance(Class)}. The state of the instance should be cleared before
99       * 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 }