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}