]> gerrit.simantics Code Review - simantics/platform.git/blob - bundles/org.simantics.databoard/src/org/simantics/databoard/type/RecordType.java
Migrated source code from Simantics SVN
[simantics/platform.git] / bundles / org.simantics.databoard / src / org / simantics / databoard / type / RecordType.java
1 /*******************************************************************************\r
2  *  Copyright (c) 2010 Association for Decentralized Information Management in\r
3  *  Industry THTH ry.\r
4  *  All rights reserved. This program and the accompanying materials\r
5  *  are made available under the terms of the Eclipse Public License v1.0\r
6  *  which accompanies this distribution, and is available at\r
7  *  http://www.eclipse.org/legal/epl-v10.html\r
8  *\r
9  *  Contributors:\r
10  *      VTT Technical Research Centre of Finland - initial API and implementation\r
11  *******************************************************************************/\r
12 package org.simantics.databoard.type;
13
14 import java.util.HashSet;\r
15 import java.util.List;\r
16 import java.util.Set;\r
17 import java.util.StringTokenizer;\r
18 \r
19 import org.simantics.databoard.accessor.error.ReferenceException;\r
20 import org.simantics.databoard.accessor.reference.ChildReference;\r
21 import org.simantics.databoard.accessor.reference.IndexReference;\r
22 import org.simantics.databoard.accessor.reference.LabelReference;\r
23 import org.simantics.databoard.accessor.reference.NameReference;\r
24 import org.simantics.databoard.annotations.Referable;\r
25 import org.simantics.databoard.binding.error.DatatypeConstructionException;\r
26 import org.simantics.databoard.util.IdentityPair;\r
27
28 public @Referable class RecordType extends Datatype {
29         \r
30         public static final Datatype VOID_TYPE = new RecordType(false);\r
31         \r
32         public static final String KEY_REFERABLE = "referable"; // "false"/"true"\r
33         \r
34         // Key to identifier(s) index\r
35         public static final String KEY_IDENTIFIER = "identifier"; // "0" "0,1" "n[,m]"\r
36         \r
37         public static final Component[] NO_COMPONENTS = new Component[0];\r
38         
39     Component[] components = NO_COMPONENTS;\r
40     \r
41     /** Indices to identifiers of this record. This field is filled on request */\r
42     private transient int[] identifiersIndices;\r
43     \r
44     /** Identifier type */\r
45     private transient Datatype identifierType;
46     
47     public RecordType() {       
48         setReferable(false);\r
49     }
50     
51     public RecordType(boolean referable, Component...components) {
52         this.components = components;\r
53         setReferable(referable);\r
54     }
55     
56     public boolean isReferable() {\r
57         String str = metadata.get( KEY_REFERABLE );
58         return str!=null && str.equals( Boolean.TRUE.toString() );
59     }\r
60     \r
61     public void setReferable( boolean referable ) {\r
62         if ( !referable ) \r
63         {\r
64                 metadata.remove(KEY_REFERABLE);  \r
65         } else {\r
66                 metadata.put(KEY_REFERABLE, Boolean.toString(referable));\r
67         }\r
68     }
69
70     @Override
71     protected void collectSubtypes(Set<Datatype> subtypes,
72                 Set<Datatype> recursiveSubtypes) {
73         if(!subtypes.add(this)) {
74                 recursiveSubtypes.add(this);
75                 return;
76         }
77         for(Component component : components)
78                 component.type.collectSubtypes(subtypes, recursiveSubtypes);
79     }
80     
81     public void setComponents(Component[] components) {
82         this.components = components;
83     }
84    \r
85     public void mergeRecord(RecordType src)\r
86     throws DatatypeConstructionException\r
87     {\r
88         int ci = src.getComponentCount();\r
89         for (int i=0; i<ci; i++) {\r
90                 Component sc = src.components[ i ];\r
91                 \r
92                 int li = getComponentIndex2( sc.name );\r
93                 if ( li<0 ) {\r
94                         addComponent(sc.name, sc.type);\r
95                 } else {\r
96                                 Component lc = components[ li ];\r
97                         if ( sc.type instanceof RecordType && lc.type instanceof RecordType ) {\r
98                                 ((RecordType)lc.type).mergeRecord( (RecordType) sc.type );\r
99                         } else if ( sc.type.equals( lc.type ) ) {} \r
100                         else {\r
101                                 throw new DatatypeConstructionException("Cannot merge field \""+sc.name+"\" "+sc.type.getClass().getName()+" and "+lc.getClass().getName());\r
102                         }\r
103                 }\r
104                 \r
105         }\r
106     }\r
107     \r
108     public void addComponent(String name, Datatype type)
109     {
110         Component c = new Component(name, type);
111         if (components == null) {
112                 components = new Component[] { c };
113         } else {
114                 Component[] newComponents = new Component[ components.length +1 ];
115                 System.arraycopy(components, 0, newComponents, 0, components.length);
116                 newComponents[ components.length ] = c;
117                 components = newComponents;     
118         }       
119     }\r
120     \r
121     public void clear() {\r
122         components = new Component[0];\r
123         metadata.clear();\r
124     }\r
125     \r
126     public void removeComponent(String name) {\r
127         int index = getComponentIndex2(name);\r
128         if (index<0) throw new IllegalArgumentException();\r
129                 Component[] newComponents = new Component[ components.length -1 ];\r
130                 if (index>0) System.arraycopy(components, 0, newComponents, 0, index);\r
131                 if (index<newComponents.length) System.arraycopy(components, index+1, newComponents, index, newComponents.length - index);\r
132                 components = newComponents;     \r
133                 // xxx untested\r
134     }
135     \r
136     @Override\r
137     public int getComponentCount() {\r
138         return components.length;\r
139     }\r
140     \r
141     @Override
142     protected boolean deepEquals(Object obj, Set<IdentityPair<Datatype, Datatype>> compareHistory) {
143                 if ( this==obj ) return true;\r
144                 if ( !hasEqualMetadata(obj) ) return false;\r
145                 if (obj instanceof RecordType == false) return false;
146                 RecordType other = (RecordType) obj;
147                 
148                 if (components.length!= other.components.length) return false;
149                 // Verify names
150                 for (int i = 0; i<components.length; i++) {
151                         Component lc = components[i];
152                         Component rc = other.components[i];
153                         if (!lc.name.equals(rc.name)) return false;
154                         
155                 }
156
157                 // Verify types
158                 if (compareHistory==null) compareHistory = new HashSet<IdentityPair<Datatype, Datatype>>(1);
159
160                 IdentityPair<Datatype, Datatype> pair = new IdentityPair<Datatype, Datatype>(this, other);
161                 if (compareHistory.contains(pair)) return true;
162                 compareHistory.add(pair);
163                 
164                 for (int i = 0; i<components.length; i++) {
165                         Component lc = components[i];
166                         Component rc = other.components[i];
167                         if (!lc.type.deepEquals(rc.type, compareHistory)) return false;
168                 }
169                 return true;
170         }
171         
172         @Override
173         public int hashCode() {
174                 int hash = super.hashCode();            
175                 for (Component c : components) 
176                         hash = hash*13 + 7 * c.name.hashCode() /*+ 3*c.type.hashCode()*/;
177                 return hash;
178         }    \r
179         
180         @Override
181         public void accept(Visitor1 v, Object obj) {
182             v.visit(this, obj);        
183         }
184
185         @Override
186         public <T> T accept(Visitor<T> v) {
187             return v.visit(this);
188         }
189         
190         /**
191          * Return true if the record is a tuple.
192          * Tuple is a record with all components are named as a number, the index number of the field.
193          * Empty record is a tuple  
194          * 
195          * @return true if the record type is a tuple.
196          */
197         public boolean isTupleType() {
198                 if (components.length==0) return false;
199                 for (int i=0; i<getComponentCount(); i++) {
200                         if (!getComponent(i).name.equals(Integer.toString(i))) return false;
201                 }
202                 return true;
203         }\r
204 \r
205     /**\r
206      * Get component type by index\r
207      * @param index index\r
208      * @return componenet type or <tt>null</tt> if index was invalid\r
209      */\r
210     @Override\r
211     public Datatype getComponentType(int index) {\r
212         if (index<0||index>=components.length) return null;\r
213         return components[index].type;\r
214     }\r
215     \r
216         @Override\r
217         public Datatype getComponentType(ChildReference path) throws IllegalArgumentException {\r
218                 if (path==null) return this;\r
219                 if (path instanceof IndexReference) {\r
220                         IndexReference ir = (IndexReference) path;\r
221                         return components[ir.index].type.getComponentType(path.childReference);\r
222                 }\r
223                 if (path instanceof NameReference) {\r
224                         NameReference nr = (NameReference) path;\r
225                         return getComponent( nr.name ).type.getComponentType(path.childReference);\r
226                 }\r
227                 if (path instanceof LabelReference) {\r
228                         LabelReference lr = (LabelReference) path;                      \r
229                         try {\r
230                                 Integer i = new Integer(lr.label);\r
231                                 return getComponent( i ).type.getComponentType(path.childReference);\r
232                         } catch (NumberFormatException nfe) {\r
233                                 return getComponent( lr.label ).type.getComponentType(path.childReference);\r
234                         }\r
235                 }\r
236                 throw new IllegalArgumentException();\r
237         }\r
238     \r
239         public boolean hasComponent(String fieldName) {\r
240         for (int i=0; i<components.length; i++)\r
241             if (components[i].name.equals(fieldName)) return true;\r
242         return false;\r
243         }\r
244         \r
245     /**\r
246      * Get component by name.\r
247      * \r
248      * @param fieldName component name\r
249      * @return component index or <code>null</code> if one does not exist\r
250      */\r
251     public Integer getComponentIndex(String fieldName) {\r
252         for (int i=0; i<components.length; i++)\r
253             if (components[i].name.equals(fieldName)) return i;\r
254         return null;\r
255     }\r
256 \r
257     /**\r
258      * Get component by name.\r
259      * \r
260      * @param fieldName component name\r
261      * @return component index or -1 if one does not exist\r
262      */\r
263     public int getComponentIndex2(String fieldName) {\r
264         for (int i=0; i<components.length; i++)\r
265             if (components[i].name.equals(fieldName)) return i;\r
266         return -1;\r
267     }\r
268     \r
269     /**\r
270      * Get component Datatype by field name\r
271      * @param fieldName\r
272      * @return datatype or <code>null</code>\r
273      */\r
274     public Datatype getComponentType(String fieldName) {\r
275         int index = getComponentIndex2(fieldName);\r
276         if (index<0) return null;\r
277         return components[index].type;\r
278     }\r
279     \r
280     /**\r
281      * Get component by name.\r
282      * \r
283      * @param fieldName component name\r
284      * @return component or <code>null</code> if one does not exist\r
285      */\r
286     public Component getComponent(String fieldName) {\r
287         for (Component c : components)\r
288             if (c.name.equals(fieldName)) return c;\r
289         return null;\r
290     }\r
291 \r
292     /**\r
293      * Get component by index.\r
294      * \r
295      * @param index component index\r
296      * @return component or <code>null</code> if one does not exist\r
297      */\r
298     public Component getComponent(int index) {\r
299         if (index<0||index>=components.length) return null;\r
300         return components[index];\r
301     }\r
302         \r
303         public Component[] getComponents() {\r
304                 return components;\r
305         }\r
306 \r
307         /**\r
308          * Get an array of indices that describe which fields compose the identifier \r
309          * of this record \r
310          * \r
311          * @return indices\r
312          */\r
313         public int[] getIdentifiers() {\r
314                 String ids = metadata.get( KEY_IDENTIFIER );\r
315                 if (ids == null) {\r
316                         identifiersIndices = new int[0];\r
317                 } else {\r
318                         // Parse\r
319                         StringTokenizer st = new StringTokenizer(ids, ",");\r
320                         \r
321                         int[] indices = new int[ st.countTokens() ];\r
322                         for (int i=0; i<indices.length; i++) {\r
323                                 String token = st.nextToken();\r
324                                 try {\r
325                                         indices[i] = Integer.valueOf(token);\r
326                                 } catch ( NumberFormatException nfe ) {\r
327                                         indices[i] = -1;\r
328                                 }\r
329                         }\r
330                         identifiersIndices = indices;\r
331                 }\r
332                 return identifiersIndices;\r
333         }\r
334         \r
335         /**\r
336          * Set which fields compose the identifier of this record \r
337          * \r
338          * @param indices\r
339          */\r
340         public void setIdentifiers(int...indices)\r
341         {\r
342                 if (indices.length==0) {\r
343                         metadata.remove( KEY_IDENTIFIER );\r
344                         return;\r
345                 }\r
346                 identifiersIndices = indices;\r
347                 StringBuilder sb = new StringBuilder();\r
348                 for (int i=0; i<indices.length; i++) {\r
349                         if (i>0) sb.append(',');\r
350                         sb.append( Integer.toString(indices[i]) );\r
351                 }\r
352                 \r
353                 String str = sb.toString();\r
354                 if ( str.isEmpty() ) {\r
355                         metadata.remove( KEY_IDENTIFIER ); \r
356                 } else {\r
357                         metadata.put( KEY_IDENTIFIER, str );\r
358                 }\r
359         }\r
360 \r
361         /**\r
362          * Set which fields compose the identifier of this record\r
363          * @param indices\r
364          */\r
365         public void setIdentifiers(List<Integer> indices)\r
366         {\r
367                 int[] indices2 = new int[indices.size()];\r
368                 for (int i=0; i<indices.size(); i++) indices2[i] = indices.get(i);\r
369                 setIdentifiers(indices2);\r
370         }\r
371         \r
372         public boolean isIdentifier( int fieldIndex )\r
373         {\r
374                 int[] ids = getIdentifiers();\r
375                 if (ids == null) return false;\r
376                 for (int index : ids)\r
377                 {\r
378                         if (index == fieldIndex) return true;\r
379                 }\r
380                 return false;\r
381         }\r
382         \r
383         /**\r
384          * Get a datatype that describes the identifier of this type.\r
385          * If no field has Identifier annotation, the result is null.\r
386          * If more than one field is an identifier the type is a record\r
387          * with all composing fields.\r
388          * \r
389          * @return identifier type or null\r
390          */\r
391         public Datatype getIdentifierType() \r
392         {               \r
393                 if ( identifierType != null ) return identifierType;\r
394                 \r
395                 int[] ids = getIdentifiers();\r
396                 if (ids.length==0) return null;\r
397                 \r
398                 if (ids.length==1) {\r
399                         identifierType = getComponentType(ids[0]);\r
400                 }\r
401                 \r
402                 RecordType rt = new RecordType();\r
403                 for (int i : ids) {\r
404                         Component c = getComponent(i);\r
405                         rt.addComponent( c.name, c.type );\r
406                 }\r
407                 identifierType = rt;            \r
408                 return identifierType;\r
409         }\r
410 \r
411         @SuppressWarnings("unchecked")\r
412         public <T extends Datatype> T getChildType( ChildReference reference ) throws ReferenceException\r
413         {\r
414                 if (reference==null) return (T) this;\r
415                 \r
416                 if (reference instanceof LabelReference) {\r
417                         LabelReference lr = (LabelReference) reference;\r
418                         String fieldName = lr.label;\r
419                         int index = getComponentIndex2(fieldName);\r
420                         if (index<0) throw new ReferenceException("RecordType doesn't have field by name \""+fieldName+"\"");\r
421                         return components[index].type.getChildType(reference.childReference);\r
422                 }               \r
423                 \r
424                 if (reference instanceof IndexReference) {\r
425                         IndexReference ref = (IndexReference) reference;\r
426                         int index = ref.getIndex();\r
427                         if ( index<0 || index>=components.length ) new ReferenceException("RecordType doesn't have field at index+"+index);\r
428                         return components[index].type.getChildType(reference.childReference);\r
429                 } \r
430                 \r
431                 if (reference instanceof NameReference) {\r
432                         NameReference lr = (NameReference) reference;\r
433                         String fieldName = lr.name;\r
434                         int index = getComponentIndex2(fieldName);\r
435                         if (index<0) throw new ReferenceException("RecordType doesn't have field by name \""+fieldName+"\"");\r
436                         return components[index].type.getChildType(reference.childReference);\r
437                 } \r
438                 \r
439                 throw new ReferenceException(reference.getClass()+" is not a subreference of RecordType");\r
440                 \r
441         }
442 }