View Javadoc

1   /*
2    * Copyright 2015 Data Archiving and Networked Services (an institute of
3    * Koninklijke Nederlandse Akademie van Wetenschappen), King's College London,
4    * Georg-August-Universitaet Goettingen Stiftung Oeffentlichen Rechts
5    *
6    * Licensed under the EUPL, Version 1.1 or – as soon they will be approved by
7    * the European Commission - subsequent versions of the EUPL (the "Licence");
8    * You may not use this work except in compliance with the Licence.
9    * You may obtain a copy of the Licence at:
10   *
11   * https://joinup.ec.europa.eu/software/page/eupl
12   *
13   * Unless required by applicable law or agreed to in writing, software
14   * distributed under the Licence is distributed on an "AS IS" basis,
15   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16   * See the Licence for the specific language governing
17   * permissions and limitations under the Licence.
18   */
19  
20  package eu.ehri.project.models.utils;
21  
22  import com.google.common.collect.ImmutableSet;
23  import com.google.common.collect.Lists;
24  import com.google.common.collect.Maps;
25  import com.google.common.collect.Sets;
26  import com.tinkerpop.blueprints.Direction;
27  import com.tinkerpop.frames.Adjacency;
28  import com.tinkerpop.frames.Property;
29  import eu.ehri.project.models.EntityClass;
30  import eu.ehri.project.models.annotations.Dependent;
31  import eu.ehri.project.models.annotations.EntityType;
32  import eu.ehri.project.models.annotations.Fetch;
33  import eu.ehri.project.models.annotations.Indexed;
34  import eu.ehri.project.models.annotations.Mandatory;
35  import eu.ehri.project.models.annotations.Meta;
36  import eu.ehri.project.models.annotations.Unique;
37  import org.slf4j.Logger;
38  import org.slf4j.LoggerFactory;
39  
40  import java.lang.annotation.Annotation;
41  import java.lang.reflect.Method;
42  import java.util.Collection;
43  import java.util.List;
44  import java.util.Map;
45  import java.util.Set;
46  
47  /**
48   * Helper functions for managing EntityType classes via reflection.
49   */
50  public class ClassUtils {
51  
52      public static final String FETCH_METHOD_PREFIX = "get";
53  
54      private static final Logger logger = LoggerFactory.getLogger(ClassUtils.class);
55  
56      private static final Map<Class<?>,Map<String,Method>> fetchMethodCache = Maps.newHashMap();
57      private static final Map<Class<?>,Map<String,Method>> metaMethodCache = Maps.newHashMap();
58      private static final Map<Class<?>,Map<String,Set<String>>> enumPropertyValuesCache = Maps.newHashMap();
59      private static final Map<Class<?>,Collection<String>> propertyKeysCache = Maps.newHashMap();
60      private static final Map<Class<?>,Collection<String>> mandatoryPropertyKeysCache = Maps.newHashMap();
61      private static final Map<Class<?>,Collection<String>> indexedPropertyKeysCache = Maps.newHashMap();
62      private static final Map<Class<?>,Collection<String>> uniquePropertyKeysCache = Maps.newHashMap();
63      private static final Map<Class<?>,Map<String, Direction>> dependentRelationsCache = Maps.newHashMap();
64      private static final Map<Class<?>,EntityClass> entityClassCache = Maps.newHashMap();
65  
66      /**
67       * Get the entity type string for a given class.
68       *
69       * @param cls the entity type's Java class
70       * @return the entity enum
71       */
72      public static EntityClass getEntityType(Class<?> cls) {
73          if (!entityClassCache.containsKey(cls)) {
74              entityClassCache.put(cls, getEntityTypeInternal(cls));
75          }
76          return entityClassCache.get(cls);
77      }
78  
79      /**
80       * Get a map of relationships keyed against their direction for
81       * a given class.
82       *
83       * @param cls the entity's Java class
84       * @return a relationship-direction map
85       */
86      public static Map<String,Direction> getDependentRelations(Class<?> cls) {
87          if (!dependentRelationsCache.containsKey(cls)) {
88              dependentRelationsCache.put(cls, getDependentRelationsInternal(cls));
89          }
90          return dependentRelationsCache.get(cls);
91      }
92  
93      /**
94       * Get a map of relationship-names keyed against the method to
95       * instantiate them.
96       *
97       * @param cls the entity's Java class
98       * @return a relationship-name-method map
99       */
100     public static Map<String, Method> getFetchMethods(Class<?> cls) {
101         if (!fetchMethodCache.containsKey(cls)) {
102             fetchMethodCache.put(cls, getFetchMethodsInternal(cls));
103         }
104         return fetchMethodCache.get(cls);
105     }
106 
107     /**
108      * Get a map of relationship-names keyed against the method to
109      * instantiate them.
110      *
111      * @param cls the entity's Java class
112      * @return a relationship-name-method map
113      */
114     public static Map<String, Method> getMetaMethods(Class<?> cls) {
115         if (!metaMethodCache.containsKey(cls)) {
116             metaMethodCache.put(cls, getMetaMethodsInternal(cls));
117         }
118         return metaMethodCache.get(cls);
119     }
120 
121     /**
122      * Get a collection of names for methods marked as properties.
123      *
124      * @param cls the entity's Java class
125      * @return a collection of property names
126      */
127     public static Collection<String> getPropertyKeys(Class<?> cls) {
128         if (!propertyKeysCache.containsKey(cls)) {
129             propertyKeysCache.put(cls, getAnnotatedPropertyKeys(cls, Property.class, true));
130         }
131         return propertyKeysCache.get(cls);
132     }
133 
134     /**
135      * Get a collection of names for methods marked as mandatory properties.
136      *
137      * @param cls the entity's Java class
138      * @return a collection of property names
139      */
140     public static Collection<String> getMandatoryPropertyKeys(Class<?> cls) {
141         if (!mandatoryPropertyKeysCache.containsKey(cls)) {
142             mandatoryPropertyKeysCache.put(cls, getAnnotatedPropertyKeys(cls, Mandatory.class, false));
143         }
144         return mandatoryPropertyKeysCache.get(cls);
145     }
146 
147     /**
148      * Get a collection of names for methods marked as indexed properties.
149      *
150      * @param cls the entity's Java class
151      * @return a collection of property names
152      */
153     public static Collection<String> getIndexedPropertyKeys(Class<?> cls) {
154         if (!indexedPropertyKeysCache.containsKey(cls)) {
155             indexedPropertyKeysCache.put(cls, getAnnotatedPropertyKeys(cls, Indexed.class, false));
156         }
157         return indexedPropertyKeysCache.get(cls);
158     }
159 
160     /**
161      * Get a collection of names for methods marked as unique properties.
162      *
163      * @param cls the entity's Java class
164      * @return a collection of property names
165      */
166 
167     public static Collection<String> getUniquePropertyKeys(Class<?> cls) {
168         if (!uniquePropertyKeysCache.containsKey(cls)) {
169             uniquePropertyKeysCache.put(cls, getAnnotatedPropertyKeys(cls, Unique.class, true));
170         }
171         return uniquePropertyKeysCache.get(cls);
172     }
173 
174     /**
175      * Get a collection of names for methods marked as unique properties.
176      *
177      * @param cls the entity's Java class
178      * @return a collection of property names
179      */
180 
181     public static Map<String,Set<String>> getEnumPropertyKeys(Class<?> cls) {
182         if (!enumPropertyValuesCache.containsKey(cls)) {
183             enumPropertyValuesCache.put(cls, getEnumPropertyKeysInternal(cls));
184         }
185         return enumPropertyValuesCache.get(cls);
186     }
187 
188     private static Map<String, Set<String>> getEnumPropertyKeysInternal(Class<?> cls) {
189         Map<String, Set<String>> out = Maps.newHashMap();
190         for (Method method : cls.getMethods()) {
191             Property ann = method.getAnnotation(Property.class);
192             if (ann != null) {
193                 String name = ann.value();
194                 Class<?> returnType = method.getReturnType();
195                 if (Enum.class.isAssignableFrom(returnType)) {
196                     Object[] values = returnType.getEnumConstants();
197                     Set<String> strings = Sets.newHashSet();
198                     for (Object v : values) {
199                         strings.add(v.toString());
200                     }
201                     out.put(name, strings);
202                 }
203             }
204         }
205         return out;
206     }
207 
208     private static EntityClass getEntityTypeInternal(Class<?> cls) {
209         EntityType ann = cls.getAnnotation(EntityType.class);
210         if (ann == null)
211             throw new RuntimeException(String.format(
212                     "Programming error! Bad bundle type: %s", cls.getName()));
213         return ann.value();
214     }
215 
216     private static Map<String, Direction> getDependentRelationsInternal(Class<?> cls) {
217         Map<String, Direction> out = Maps.newHashMap();
218         for (Method method : cls.getMethods()) {
219             if (method.getAnnotation(Dependent.class) != null) {
220                 Adjacency ann = method.getAnnotation(Adjacency.class);
221                 if (ann != null)
222                     out.put(ann.label(), ann.direction());
223             }
224         }
225         return out;
226     }
227 
228     private static Map<String, Method> getFetchMethodsInternal(Class<?> cls) {
229         logger.trace(" - checking for @Fetch methods: {}", cls.getCanonicalName());
230         Map<String, Method> out = Maps.newHashMap();
231         for (Method method : cls.getMethods()) {
232             Fetch fetch = method.getAnnotation(Fetch.class);
233             Dependent dep = method.getAnnotation(Dependent.class);
234             String value = fetch != null ? fetch.value() : null;
235             if ((value != null || dep != null)
236                     && method.getName().startsWith(FETCH_METHOD_PREFIX)) {
237                 out.put(value, method);
238                 logger.trace(" --- found @Fetch annotation: {}: {}", method.getName(), value);
239             }
240         }
241 
242         for (Class<?> s : cls.getInterfaces()) {
243             out.putAll(getFetchMethodsInternal(s));
244         }
245         return out;
246     }
247 
248     private static Map<String, Method> getMetaMethodsInternal(Class<?> cls) {
249         logger.trace(" - checking for @Meta methods: {}", cls.getCanonicalName());
250         Map<String, Method> out = Maps.newHashMap();
251         for (Method method : cls.getMethods()) {
252             Meta meta = method.getAnnotation(Meta.class);
253             String value = meta != null ? meta.value() : null;
254             if (value != null) {
255                 out.put(value, method);
256                 logger.trace(" --- found @Meta annotation: {}: {}", method.getName(), value);
257             }
258         }
259 
260         for (Class<?> s : cls.getInterfaces()) {
261             out.putAll(getMetaMethodsInternal(s));
262         }
263         return out;
264     }
265 
266     private static <T extends Annotation> Collection<String> getAnnotatedPropertyKeys(
267             Class<?> cls, Class<T> annotationClass, boolean includeMeta) {
268         List<String> out = Lists.newArrayList();
269         for (Method method : cls.getMethods()) {
270             T unique = method.getAnnotation(annotationClass);
271             if (unique != null) {
272                 Property prop = method.getAnnotation(Property.class);
273                 if (prop != null && (includeMeta || !prop.value().startsWith("__"))) {
274                     out.add(prop.value());
275                 }
276             }
277 
278         }
279 
280         for (Class<?> s : cls.getInterfaces()) {
281             out.addAll(getAnnotatedPropertyKeys(s, annotationClass, includeMeta));
282         }
283 
284         return ImmutableSet.copyOf(out);
285     }
286 }