]> gerrit.simantics Code Review - simantics/platform.git/blob - bundles/org.simantics.databoard/src/org/simantics/databoard/binding/reflection/BindingRequest.java
Fixing several binding-related bugs
[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, gitlab #313
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      * Cloning constructor with replacement annotations.
65      * 
66      * @param other the request to clone
67      * @param annotations the annotations to use while cloning
68      */
69     private BindingRequest(BindingRequest other, Annotation...annotations)
70     {
71         this.clazz = other.clazz;
72         this.cl = other.cl;
73         this.annotations = annotations;
74         this.className = other.className;
75         this.signature = other.signature;
76         this.descriptor = other.descriptor;
77         hash = calcHash(clazz.getName());
78     }
79
80     /**
81      * Create BindingRequest that creates class lazily. 
82      * 
83      * @param cl classloader
84      * @param className 
85      * @param classSignature 
86      * @param classDescriptor
87      * @param annotations 
88      */
89     public BindingRequest(ClassLoader cl, String className, String classSignature, String classDescriptor, Annotation...annotations)
90     {
91         this.className = className;
92         this.cl = cl;    
93         this.signature = classSignature;
94         this.annotations = annotations;
95         this.descriptor = classDescriptor;
96         hash = calcHash(className);
97     }
98
99     /**
100      * Create BindingRequest
101      * 
102      * @param clazz
103      * @param annotations
104      */
105     public BindingRequest(Class<?> clazz)
106     {
107         this(clazz, NO_ANNOTATIONS);
108     }
109
110     /**
111      * Create BindingRequest
112      * 
113      * @param clazz
114      * @param annotations
115      */
116     public BindingRequest(Class<?> clazz, Annotation...annotations)
117     {
118         assert annotations!=null;
119         this.clazz = clazz;
120         Annotation[] classAnnotations = clazz.getAnnotations();
121         if (classAnnotations!=null && classAnnotations.length>0) {
122             this.annotations = new Annotation[classAnnotations.length + annotations.length];
123             System.arraycopy(annotations, 0, this.annotations, 0, annotations.length);
124             System.arraycopy(classAnnotations, 0, this.annotations, annotations.length, classAnnotations.length);
125         } else {
126                 this.annotations = annotations;
127         }
128         
129         className = clazz.getCanonicalName();
130         signature = getSignature(clazz);
131         descriptor = _buildDescriptor(new StringBuilder(), clazz, createArgsList(), new MutableInteger(0)).toString();
132         hash = calcHash(clazz.getName());
133     }
134
135     public BindingRequest withAnnotations(Annotation... newAnnotations) {
136         return new BindingRequest(this, newAnnotations);
137     }
138
139     private int calcHash(String className) {
140         int hash = className.hashCode();
141         for (Annotation a : this.annotations) {
142             hash += a.hashCode();
143         }
144         return hash;
145     }
146
147     private StringBuilder _buildDescriptor(StringBuilder sb, Class<?> c, List<Class<?>> classes, MutableInteger pos)
148     {
149         int genericCount = c.getTypeParameters().length;
150         int genericsLeft = classes.size()-pos.value;
151         if ( genericCount>0 && genericsLeft >= genericCount ) {
152                 sb.append('L');
153                 sb.append(c.getName().replaceAll("\\.", "/"));
154                 sb.append('<');
155                 for (int i=0; i<genericCount; i++) 
156                 {
157                         Class<?> gc = classes.get( pos.value++ );
158                         _buildDescriptor(sb, gc, classes, pos);
159                 }
160                 sb.append('>');                 
161                 sb.append(';');
162         } else {
163                 sb.append( getSignature(c) );
164         }
165         return sb;
166     }
167
168     public BindingRequest(Class<?> clazz, List<Annotation> annotations)
169     {
170         this(clazz, annotations.toArray(new Annotation[annotations.size()]));
171     }
172     
173     public BindingRequest(Class<?> clazz, Class<?>[] parameters)
174     {
175         this(clazz, new ArgumentImpl(parameters));
176     }
177     
178     public boolean hasAnnotation(Class<?> annotationClass) 
179     {
180         for (Annotation a : annotations)
181             if (annotationClass.equals(a.annotationType())) return true;
182         return false;
183     }
184     
185     @SuppressWarnings("unchecked")
186     public <A extends Annotation> A getAnnotation(Class<A> annotationClass) 
187     {
188         for (Annotation a : annotations)
189         {
190             if (annotationClass.equals(a.annotationType()))
191                 return (A) a;
192         }
193         return null;
194     }
195     @Override
196     public int hashCode() {
197         return hash;
198     }
199     
200     @Override
201     public boolean equals(Object obj) {
202         if (obj==null) return false;
203         if (obj instanceof BindingRequest==false) return false;
204         BindingRequest other = (BindingRequest) obj;
205         return other.descriptor.equals(descriptor) &&
206             Arrays.deepEquals(annotations, other.annotations);
207     }
208     
209     public Class<?> getClazz()
210     {
211         if ( clazz==null ) {
212                 try {
213                                 clazz = cl.loadClass( className );
214                         } catch (ClassNotFoundException e) {
215                                 throw new RuntimeException( e );
216                         }
217         }
218         return clazz;
219     }
220         
221     /**
222      * Return a version of annotations list, where given set of annotations and
223      * a number of class arguments were dropped. 
224      * 
225      * @param argumentsToDrop the number of class arguments to drop
226      * @param annotationsToDrop annotation to drop
227      * @return request without argument annotation
228      */
229     public Annotation[] dropAnnotations(int argumentsToDrop, Annotation...annotationsToDrop)
230     {
231         ArrayList<Annotation> result = new ArrayList<Annotation>( annotations.length );
232         nextA:
233         for (Annotation a : annotations) {
234                 for (Annotation b : annotationsToDrop) 
235                         if (a==b) continue nextA;
236                 if (a instanceof Arguments && argumentsToDrop>0) {
237                         Arguments c = ArgumentImpl.dropArguments((Arguments) a, argumentsToDrop);
238                         if (c!=null) result.add(c);
239                 } else result.add(a);
240         }
241         Annotation[] newAnnotations = result.toArray( new Annotation[result.size()] );
242         return newAnnotations;
243     }
244     
245     
246     @Override
247     public String toString() {
248         StringBuilder sb = new StringBuilder();
249         sb.append(clazz.getName());
250         if ( annotations!=null && annotations.length>0 ) {
251             sb.append('(');
252             for (int i=0; i<annotations.length; i++) {
253                 Annotation a = annotations[i];
254                 if (i>0) sb.append(", ");
255                 sb.append(a);
256             }           
257             sb.append(')');
258         }
259         
260         return sb.toString();
261     }
262     
263     /**
264      * Get signature, e.g. Ljava/util/Map;
265      * 
266      * @return singature string
267      */
268     public static String getSignature(Class<?> clazz) {
269                 if (clazz==void.class) return "V";
270                 if (clazz==boolean.class) return "Z";
271                 if (clazz==char.class) return "C";
272                 if (clazz==byte.class) return "B";
273                 if (clazz==short.class) return "S";
274                 if (clazz==int.class) return "I";
275                 if (clazz==float.class) return "F";
276                 if (clazz==long.class) return "J";
277                 if (clazz==double.class) return "D";
278                 String cached = signatureCache.get(clazz);
279                 if (cached == null) {
280                         cached = clazz.isArray()
281                                         ? clazz.getName().replace('.', '/')
282                                         : "L"+clazz.getName().replace('.', '/')+";";
283                         signatureCache.put(clazz, cached);
284                         //System.out.println("BindingRequest.getSignature: cache miss for " + clazz + " = " + cached);
285                 } else {
286                         //System.out.println("BindingRequest.getSignature: cache hit for " + clazz + " = " + cached);
287                 }
288                 return cached;
289     }
290     
291     @SuppressWarnings("unchecked")
292         List<Class<?>> createArgsList()
293     {
294         if (annotations==null || !hasAnnotation(Arguments.class)) return Collections.EMPTY_LIST;
295         List<Class<?>> result = new ArrayList<Class<?>>();
296         for (Annotation a : annotations) {
297                 if ( a instanceof Arguments ) {
298                         Arguments args = (Arguments) a;
299                         for (Class<?> clazz : args.value()) result.add( clazz );
300                 }
301         }
302         return result;
303     }
304         
305 }