001/* 002 * **** BEGIN LICENSE BLOCK ***** 003 * Version: MPL 1.1/GPL 2.0/LGPL 2.1 004 * 005 * The contents of this file are subject to the Mozilla Public License Version 006 * 1.1 (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * http://www.mozilla.org/MPL/ 009 * 010 * Software distributed under the License is distributed on an "AS IS" basis, 011 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License 012 * for the specific language governing rights and limitations under the 013 * License. 014 * 015 * The Original Code is part of dcm4che, an implementation of DICOM(TM) in 016 * Java(TM), hosted at https://github.com/gunterze/dcm4che. 017 * 018 * The Initial Developer of the Original Code is 019 * Agfa Healthcare. 020 * Portions created by the Initial Developer are Copyright (C) 2014 021 * the Initial Developer. All Rights Reserved. 022 * 023 * Contributor(s): 024 * See @authors listed below 025 * 026 * Alternatively, the contents of this file may be used under the terms of 027 * either the GNU General Public License Version 2 or later (the "GPL"), or 028 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), 029 * in which case the provisions of the GPL or the LGPL are applicable instead 030 * of those above. If you wish to allow use of your version of this file only 031 * under the terms of either the GPL or the LGPL, and not to allow others to 032 * use your version of this file under the terms of the MPL, indicate your 033 * decision by deleting the provisions above and replace them with the notice 034 * and other provisions required by the GPL or the LGPL. If you do not delete 035 * the provisions above, a recipient may use your version of this file under 036 * the terms of any one of the MPL, the GPL or the LGPL. 037 * 038 * ***** END LICENSE BLOCK ***** 039 */ 040package org.dcm4che3.conf.core.api.internal; 041 042 043import org.apache.commons.beanutils.PropertyUtils; 044import org.dcm4che3.conf.core.api.*; 045 046import java.lang.annotation.Annotation; 047import java.lang.reflect.Field; 048import java.lang.reflect.InvocationTargetException; 049import java.lang.reflect.Type; 050import java.util.*; 051 052/** 053 * This class shall NOT be referenced externally, it will be removed/renamed/refactored without notice. 054 * Caches to minimize reflection access. 055 * 056 * @author Roman K 057 */ 058public class ConfigReflection { 059 060 061 private static final Map<Class, ClassInfo> classInfoCache = Collections.synchronizedMap(new HashMap<Class, ClassInfo>()); 062 private static final Map<Class, Boolean> isClassConfigurable = Collections.synchronizedMap(new HashMap<Class, Boolean>()); 063 064 private static final Map<Class, ConfigProperty> dummyPropsCache = Collections.synchronizedMap(new HashMap<Class, ConfigProperty>()); 065 066 public static List<ConfigProperty> getAllConfigurableFields(Class clazz) { 067 return getClassInfo(clazz).configurableProperties; 068 } 069 070 public static ConfigProperty getDummyPropertyForClass(Class clazz) { 071 ConfigProperty found = dummyPropsCache.get(clazz); 072 073 if (found != null) { 074 return found; 075 } else { 076 ConfigProperty property = new ConfigProperty(clazz); 077 dummyPropsCache.put(clazz, property); 078 return property; 079 } 080 } 081 082 private static ClassInfo getClassInfo(Class clazz) { 083 ClassInfo classInfo = classInfoCache.get(clazz); 084 085 if (classInfo != null) { 086 return classInfo; 087 } else { 088 return processAndCacheClassInfo(clazz); 089 } 090 } 091 092 public static boolean isConfigurableClass(Class clazz) { 093 094 if (isClassConfigurable.containsKey(clazz)) 095 return isClassConfigurable.get(clazz); 096 097 boolean isItForReal = clazz.getAnnotation(ConfigurableClass.class) != null; 098 isClassConfigurable.put(clazz, isItForReal); 099 100 return isItForReal; 101 } 102 103 public static ConfigProperty getUUIDPropertyForClass(Class clazz) { 104 return getClassInfo(clazz).uuidProperty; 105 } 106 107 108 private static ClassInfo processAndCacheClassInfo(Class clazz) { 109 110 ConfigurableClass configClassAnno = (ConfigurableClass) clazz.getAnnotation(ConfigurableClass.class); 111 if (configClassAnno == null) 112 throw new IllegalArgumentException("Class '" + clazz.getName() + "' is not a configurable class. Make sure the a dependency to org.dcm4che.conf.core-api exists."); 113 114 115 ClassInfo classInfo = scanClass(clazz); 116 117 // some restrictions on extensions 118 if (ConfigurableClassExtension.class.isAssignableFrom(clazz)) { 119 120 if (configClassAnno.referable()) { 121 throw new IllegalArgumentException("A configurable extension class MUST NOT be referable - violated by class " + clazz.getName()); 122 } 123 124 if (classInfo.uuidProperty != null) { 125 throw new IllegalArgumentException("A configurable extension class MUST NOT have a uuid - violated by class " + clazz.getName()); 126 } 127 } 128 129 classInfoCache.put(clazz, classInfo); 130 131 return classInfo; 132 } 133 134 135 private static ClassInfo scanClass(Class clazz) { 136 137 ClassInfo classInfo = new ClassInfo(); 138 classInfo.configurableProperties = new ArrayList<ConfigProperty>(); 139 140 // scan all fields from this class and superclasses 141 for (Field field : getAllFields(clazz)) { 142 if (field.getAnnotation(ConfigurableProperty.class) != null) { 143 144 ConfigProperty ap = new ConfigProperty( 145 annotationsArrayToMap(field.getAnnotations()), 146 field.getName(), 147 field.getGenericType() 148 ); 149 classInfo.configurableProperties.add(ap); 150 151 if (ap.isUuid()) { 152 if (classInfo.uuidProperty != null) { 153 throw new IllegalArgumentException("A configurable class MUST NOT have more than one UUID field - violated by class " + clazz.getName()); 154 } 155 156 classInfo.uuidProperty = ap; 157 } 158 159 if (ap.isOlockHash()) { 160 if (classInfo.olockHashProperty != null) { 161 throw new IllegalArgumentException("A configurable class MUST NOT have more than one optimistic locking hash field - violated by class " + clazz.getName()); 162 } 163 164 classInfo.olockHashProperty = ap; 165 } 166 } else if (field.getAnnotation(Parent.class) != null) { 167 if (classInfo.parentField != null) { 168 throw new IllegalArgumentException("A configurable class MUST NOT have more than one field annotated with @Parent - violated by class " + clazz.getName()); 169 } 170 171 classInfo.parentField = field; 172 } 173 } 174 175 176 if (!ConfigurableClassExtension.class.isAssignableFrom(clazz) && classInfo.uuidProperty == null && classInfo.parentField != null) { 177 throw new IllegalArgumentException("A configurable class that refers to a @Parent must have a uuid property defined (except the extensions). Violated by " + clazz.getName()); 178 } 179 180 return classInfo; 181 } 182 183 public static Map<Type, Annotation> annotationsArrayToMap(Annotation[] annos) { 184 HashMap<Type, Annotation> annotations = new HashMap<Type, Annotation>(); 185 for (Annotation anno : annos) 186 annotations.put(anno.annotationType(), anno); 187 return annotations; 188 } 189 190 public static Field getParentPropertyForClass(Class<?> extensionClass) { 191 return getClassInfo(extensionClass).parentField; 192 } 193 194 195 /** 196 * Gets all the fields for the class and it's superclass(es) 197 */ 198 private static List<Field> getAllFields(Class clazz) { 199 200 List<Field> fields = new ArrayList<Field>(); 201 202 // get all fields of the current class (includes public, protected, default, and private fields) 203 fields.addAll(Arrays.asList(clazz.getDeclaredFields())); 204 205 // go to the parent recursively 206 Class<?> parent = clazz.getSuperclass(); 207 if (parent != null) 208 fields.addAll(getAllFields(parent)); 209 210 return fields; 211 } 212 213 public static void setProperty(Object object, ConfigProperty property, Object value) throws ReflectionAccessException { 214 setProperty(object, property.getName(), value); 215 } 216 217 public static void setProperty(Object object, String propertyName, Object value) throws ReflectionAccessException { 218 try { 219 PropertyUtils.setSimpleProperty(object, propertyName, value); 220 } catch (IllegalAccessException e) { 221 throw new ReflectionAccessException("Could not set property " + propertyName + " in class " + object.getClass().toString(), e); 222 } catch (InvocationTargetException e) { 223 throw new ReflectionAccessException("Could not set property " + propertyName + " in class " + object.getClass().toString(), e); 224 } catch (NoSuchMethodException e) { 225 throw new ReflectionAccessException("Could not set property " + propertyName + " in class " + object.getClass().toString(), e); 226 } 227 } 228 229 public static Object getProperty(Object object, ConfigProperty property) throws ReflectionAccessException { 230 try { 231 return PropertyUtils.getSimpleProperty(object, property.getName()); 232 } catch (IllegalAccessException e) { 233 throw new ReflectionAccessException("Could not get property " + property + " in class " + object.getClass().toString(), e); 234 } catch (InvocationTargetException e) { 235 throw new ReflectionAccessException("Could not get property " + property + " in class " + object.getClass().toString(), e); 236 } catch (NoSuchMethodException e) { 237 throw new ReflectionAccessException("Could not get property " + property + " in class " + object.getClass().toString(), e); 238 } 239 } 240 241 private static class ClassInfo { 242 243 List<ConfigProperty> configurableProperties; 244 ConfigProperty uuidProperty; 245 ConfigProperty olockHashProperty; 246 Field parentField; 247 248 } 249}