Improved Bindings.getBinding(Class) caching for Datatype.class
[simantics/platform.git] / bundles / org.simantics.databoard / src / org / simantics / databoard / binding / reflection / BindingRequest.java
1 /*******************************************************************************
2  * Copyright (c) 2007, 2018 Association for Decentralized Information Management in
3  * Industry THTH ry.
4  * All rights reserved. This program and the accompanying materials
5  * are made available under the terms of the Eclipse Public License v1.0
6  * which accompanies this distribution, and is available at
7  * http://www.eclipse.org/legal/epl-v10.html
8  *
9  * Contributors:
10  *     VTT Technical Research Centre of Finland - initial API and implementation
11  *     Semantum Oy - gitlab #82
12  *******************************************************************************/
13 package org.simantics.databoard.binding.reflection;
14
15 import java.lang.annotation.Annotation;
16 import java.lang.reflect.Field;
17 import java.util.ArrayList;
18 import java.util.Arrays;
19 import java.util.Collections;
20 import java.util.List;
21 import java.util.Map;
22 import java.util.WeakHashMap;
23
24 import org.simantics.databoard.annotations.ArgumentImpl;
25 import org.simantics.databoard.annotations.Arguments;
26 import org.simantics.databoard.binding.Binding;
27 import org.simantics.databoard.primitives.MutableInteger;
28
29 public class BindingRequest {
30
31     /**
32      * A weak cache for signature strings by Class.
33      * Prevents the system from constructing new strings
34      * from Classes for every non-trivial BindingRequest. 
35      */
36     private static final Map<Class<?>, String> signatureCache = Collections.<Class<?>, String>synchronizedMap(new WeakHashMap<>());
37
38     public static final Annotation[] NO_ANNOTATIONS = {};
39
40         public static BindingRequest create( Field field )
41         {
42         Annotation[] annotations = ClassBindingFactory.getFieldAnnotations(field);
43         Class<?> fieldClass = field.getType(); 
44         return new BindingRequest(fieldClass, annotations);
45         }
46         
47         /** Requested class */
48     private Class<?> clazz;
49     private ClassLoader cl;
50     
51     /** Annotations */
52     public final Annotation[] annotations;
53     
54     public final String className; // eg. java.util.Map
55     public final String signature; // eg. Ljava/util/Map;
56     public final String descriptor; //eg. Ljava/util/Map<I;I>;
57     
58     public BindingRequest[] componentRequests;
59     public Binding[] componentBindings;
60     
61     transient int hash;
62
63     /**
64      * Create BindingRequest that creates class lazily. 
65      * 
66      * @param cl classloader
67      * @param className 
68      * @param classSignature 
69      * @param classDescriptor
70      * @param annotations 
71      */
72     public BindingRequest(ClassLoader cl, String className, String classSignature, String classDescriptor, Annotation...annotations)
73     {
74         this.className = className;
75         this.cl = cl;    
76         this.signature = classSignature;
77         this.annotations = annotations;
78         this.descriptor = classDescriptor;
79         hash = className.hashCode();
80         for (Annotation a : annotations) {
81             hash = 7*hash + a.hashCode();
82         }
83     }
84
85     /**
86      * Create BindingRequest
87      * 
88      * @param clazz
89      * @param annotations
90      */
91     public BindingRequest(Class<?> clazz)
92     {
93         this(clazz, NO_ANNOTATIONS);
94     }
95
96     /**
97      * Create BindingRequest
98      * 
99      * @param clazz
100      * @param annotations
101      */
102     public BindingRequest(Class<?> clazz, Annotation...annotations)
103     {
104         assert annotations!=null;
105         this.clazz = clazz;
106         Annotation[] classAnnotations = clazz.getAnnotations();
107         if (classAnnotations!=null && classAnnotations.length>0) {
108             this.annotations = new Annotation[classAnnotations.length + annotations.length];
109             System.arraycopy(annotations, 0, this.annotations, 0, annotations.length);
110             System.arraycopy(classAnnotations, 0, this.annotations, annotations.length, classAnnotations.length);
111         } else {
112                 this.annotations = annotations;
113         }
114         
115         className = clazz.getCanonicalName();
116         signature = getSignature(clazz);
117         List<Class<?>> args = createArgsList();
118         StringBuilder desc = new StringBuilder();
119         _buildDescriptor(desc, clazz, args, new MutableInteger(0));
120         descriptor = desc.toString();
121         hash = clazz.getName().hashCode();
122         for (Annotation a : annotations) {
123             hash = 7*hash + a.hashCode();
124         }
125     }
126     
127     private void _buildDescriptor(StringBuilder sb, Class<?> c, List<Class<?>> classes, MutableInteger pos)
128     {
129         int genericCount = c.getTypeParameters().length;
130         int genericsLeft = classes.size()-pos.value;
131         if ( genericCount>0 && genericsLeft >= genericCount ) {
132                 sb.append('L');
133                 sb.append(c.getName().replaceAll("\\.", "/"));
134                 sb.append('<');
135                 for (int i=0; i<genericCount; i++) 
136                 {
137                         Class<?> gc = classes.get( pos.value++ );
138                         _buildDescriptor(sb, gc, classes, pos);
139                 }
140                 sb.append('>');                 
141                 sb.append(';');
142         } else {
143                 sb.append( getSignature(c) );
144         }
145     }
146
147     public BindingRequest(Class<?> clazz, List<Annotation> annotations)
148     {
149         this(clazz, annotations.toArray(new Annotation[annotations.size()]));
150     }
151     
152     public BindingRequest(Class<?> clazz, Class<?>[] parameters)
153     {
154         this(clazz, new ArgumentImpl(parameters));
155     }
156     
157     public boolean hasAnnotation(Class<?> annotationClass) 
158     {
159         for (Annotation a : annotations)
160             if (annotationClass.equals(a.annotationType())) return true;
161         return false;
162     }
163     
164     @SuppressWarnings("unchecked")
165     public <A extends Annotation> A getAnnotation(Class<A> annotationClass) 
166     {
167         for (Annotation a : annotations)
168         {
169             if (annotationClass.equals(a.annotationType()))
170                 return (A) a;
171         }
172         return null;
173     }
174     @Override
175     public int hashCode() {
176         return hash;
177     }
178     
179     @Override
180     public boolean equals(Object obj) {
181         if (obj==null) return false;
182         if (obj instanceof BindingRequest==false) return false;
183         BindingRequest other = (BindingRequest) obj;
184         return other.descriptor.equals(descriptor) &&
185             Arrays.deepEquals(annotations, other.annotations);
186     }
187     
188     public Class<?> getClazz()
189     {
190         if ( clazz==null ) {
191                 try {
192                                 clazz = cl.loadClass( className );
193                         } catch (ClassNotFoundException e) {
194                                 throw new RuntimeException( e );
195                         }
196         }
197         return clazz;
198     }
199         
200     /**
201      * Return a version of annotations list, where given set of annotations and
202      * a number of class arguments were dropped. 
203      * 
204      * @param argumentsToDrop the number of class arguments to drop
205      * @param annotationsToDrop annotation to drop
206      * @return request without argument annotation
207      */
208     public Annotation[] dropAnnotations(int argumentsToDrop, Annotation...annotationsToDrop)
209     {
210         ArrayList<Annotation> result = new ArrayList<Annotation>( annotations.length );
211         nextA:
212         for (Annotation a : annotations) {
213                 for (Annotation b : annotationsToDrop) 
214                         if (a==b) continue nextA;
215                 if (a instanceof Arguments && argumentsToDrop>0) {
216                         Arguments c = ArgumentImpl.dropArguments((Arguments) a, argumentsToDrop);
217                         if (c!=null) result.add(c);
218                 } else result.add(a);
219         }
220         Annotation[] newAnnotations = result.toArray( new Annotation[result.size()] );
221         return newAnnotations;
222     }
223     
224     
225     @Override
226     public String toString() {
227         StringBuilder sb = new StringBuilder();
228         sb.append(clazz.getName());
229         if ( annotations!=null && annotations.length>0 ) {
230             sb.append('(');
231             for (int i=0; i<annotations.length; i++) {
232                 Annotation a = annotations[i];
233                 if (i>0) sb.append(", ");
234                 sb.append(a);
235             }           
236             sb.append(')');
237         }
238         
239         return sb.toString();
240     }
241     
242     /**
243      * Get signature, e.g. Ljava/util/Map;
244      * 
245      * @return singature string
246      */
247     public static String getSignature(Class<?> clazz) {
248                 if (clazz==void.class) return "V";
249                 if (clazz==boolean.class) return "Z";
250                 if (clazz==char.class) return "C";
251                 if (clazz==byte.class) return "B";
252                 if (clazz==short.class) return "S";
253                 if (clazz==int.class) return "I";
254                 if (clazz==float.class) return "F";
255                 if (clazz==long.class) return "J";
256                 if (clazz==double.class) return "D";
257                 String cached = signatureCache.get(clazz);
258                 if (cached == null) {
259                         cached = clazz.isArray()
260                                         ? clazz.getName().replace('.', '/')
261                                         : "L"+clazz.getName().replace('.', '/')+";";
262                         signatureCache.put(clazz, cached);
263                         //System.out.println("BindingRequest.getSignature: cache miss for " + clazz + " = " + cached);
264                 } else {
265                         //System.out.println("BindingRequest.getSignature: cache hit for " + clazz + " = " + cached);
266                 }
267                 return cached;
268     }
269     
270     @SuppressWarnings("unchecked")
271         List<Class<?>> createArgsList()
272     {
273         if (annotations==null || !hasAnnotation(Arguments.class)) return Collections.EMPTY_LIST;
274         List<Class<?>> result = new ArrayList<Class<?>>();
275         for (Annotation a : annotations) {
276                 if ( a instanceof Arguments ) {
277                         Arguments args = (Arguments) a;
278                         for (Class<?> clazz : args.value()) result.add( clazz );
279                 }
280         }
281         return result;
282     }
283         
284 }