]> gerrit.simantics Code Review - simantics/platform.git/blobdiff - 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
diff --git a/bundles/org.simantics.databoard/src/org/simantics/databoard/type/RecordType.java b/bundles/org.simantics.databoard/src/org/simantics/databoard/type/RecordType.java
new file mode 100644 (file)
index 0000000..1fc4c7a
--- /dev/null
@@ -0,0 +1,442 @@
+/*******************************************************************************\r
+ *  Copyright (c) 2010 Association for Decentralized Information Management in\r
+ *  Industry THTH ry.\r
+ *  All rights reserved. This program and the accompanying materials\r
+ *  are made available under the terms of the Eclipse Public License v1.0\r
+ *  which accompanies this distribution, and is available at\r
+ *  http://www.eclipse.org/legal/epl-v10.html\r
+ *\r
+ *  Contributors:\r
+ *      VTT Technical Research Centre of Finland - initial API and implementation\r
+ *******************************************************************************/\r
+package org.simantics.databoard.type;
+
+import java.util.HashSet;\r
+import java.util.List;\r
+import java.util.Set;\r
+import java.util.StringTokenizer;\r
+\r
+import org.simantics.databoard.accessor.error.ReferenceException;\r
+import org.simantics.databoard.accessor.reference.ChildReference;\r
+import org.simantics.databoard.accessor.reference.IndexReference;\r
+import org.simantics.databoard.accessor.reference.LabelReference;\r
+import org.simantics.databoard.accessor.reference.NameReference;\r
+import org.simantics.databoard.annotations.Referable;\r
+import org.simantics.databoard.binding.error.DatatypeConstructionException;\r
+import org.simantics.databoard.util.IdentityPair;\r
+
+public @Referable class RecordType extends Datatype {
+       \r
+       public static final Datatype VOID_TYPE = new RecordType(false);\r
+       \r
+       public static final String KEY_REFERABLE = "referable"; // "false"/"true"\r
+       \r
+       // Key to identifier(s) index\r
+       public static final String KEY_IDENTIFIER = "identifier"; // "0" "0,1" "n[,m]"\r
+       \r
+       public static final Component[] NO_COMPONENTS = new Component[0];\r
+       
+    Component[] components = NO_COMPONENTS;\r
+    \r
+    /** Indices to identifiers of this record. This field is filled on request */\r
+    private transient int[] identifiersIndices;\r
+    \r
+    /** Identifier type */\r
+    private transient Datatype identifierType;
+    
+    public RecordType() {      
+       setReferable(false);\r
+    }
+    
+    public RecordType(boolean referable, Component...components) {
+       this.components = components;\r
+       setReferable(referable);\r
+    }
+    
+    public boolean isReferable() {\r
+       String str = metadata.get( KEY_REFERABLE );
+       return str!=null && str.equals( Boolean.TRUE.toString() );
+    }\r
+    \r
+    public void setReferable( boolean referable ) {\r
+       if ( !referable ) \r
+       {\r
+               metadata.remove(KEY_REFERABLE);  \r
+       } else {\r
+               metadata.put(KEY_REFERABLE, Boolean.toString(referable));\r
+       }\r
+    }
+
+    @Override
+    protected void collectSubtypes(Set<Datatype> subtypes,
+               Set<Datatype> recursiveSubtypes) {
+       if(!subtypes.add(this)) {
+               recursiveSubtypes.add(this);
+               return;
+       }
+       for(Component component : components)
+               component.type.collectSubtypes(subtypes, recursiveSubtypes);
+    }
+    
+    public void setComponents(Component[] components) {
+       this.components = components;
+    }
+   \r
+    public void mergeRecord(RecordType src)\r
+    throws DatatypeConstructionException\r
+    {\r
+       int ci = src.getComponentCount();\r
+       for (int i=0; i<ci; i++) {\r
+               Component sc = src.components[ i ];\r
+               \r
+               int li = getComponentIndex2( sc.name );\r
+               if ( li<0 ) {\r
+                       addComponent(sc.name, sc.type);\r
+               } else {\r
+                               Component lc = components[ li ];\r
+                       if ( sc.type instanceof RecordType && lc.type instanceof RecordType ) {\r
+                               ((RecordType)lc.type).mergeRecord( (RecordType) sc.type );\r
+                       } else if ( sc.type.equals( lc.type ) ) {} \r
+                       else {\r
+                               throw new DatatypeConstructionException("Cannot merge field \""+sc.name+"\" "+sc.type.getClass().getName()+" and "+lc.getClass().getName());\r
+                       }\r
+               }\r
+               \r
+       }\r
+    }\r
+    \r
+    public void addComponent(String name, Datatype type)
+    {
+       Component c = new Component(name, type);
+       if (components == null) {
+               components = new Component[] { c };
+       } else {
+               Component[] newComponents = new Component[ components.length +1 ];
+               System.arraycopy(components, 0, newComponents, 0, components.length);
+               newComponents[ components.length ] = c;
+               components = newComponents;     
+       }       
+    }\r
+    \r
+    public void clear() {\r
+       components = new Component[0];\r
+       metadata.clear();\r
+    }\r
+    \r
+    public void removeComponent(String name) {\r
+       int index = getComponentIndex2(name);\r
+       if (index<0) throw new IllegalArgumentException();\r
+               Component[] newComponents = new Component[ components.length -1 ];\r
+               if (index>0) System.arraycopy(components, 0, newComponents, 0, index);\r
+               if (index<newComponents.length) System.arraycopy(components, index+1, newComponents, index, newComponents.length - index);\r
+               components = newComponents;     \r
+               // xxx untested\r
+    }
+    \r
+    @Override\r
+    public int getComponentCount() {\r
+       return components.length;\r
+    }\r
+    \r
+    @Override
+    protected boolean deepEquals(Object obj, Set<IdentityPair<Datatype, Datatype>> compareHistory) {
+               if ( this==obj ) return true;\r
+               if ( !hasEqualMetadata(obj) ) return false;\r
+               if (obj instanceof RecordType == false) return false;
+               RecordType other = (RecordType) obj;
+               
+               if (components.length!= other.components.length) return false;
+               // Verify names
+               for (int i = 0; i<components.length; i++) {
+                       Component lc = components[i];
+                       Component rc = other.components[i];
+                       if (!lc.name.equals(rc.name)) return false;
+                       
+               }
+
+               // Verify types
+               if (compareHistory==null) compareHistory = new HashSet<IdentityPair<Datatype, Datatype>>(1);
+
+               IdentityPair<Datatype, Datatype> pair = new IdentityPair<Datatype, Datatype>(this, other);
+               if (compareHistory.contains(pair)) return true;
+               compareHistory.add(pair);
+               
+               for (int i = 0; i<components.length; i++) {
+                       Component lc = components[i];
+                       Component rc = other.components[i];
+                       if (!lc.type.deepEquals(rc.type, compareHistory)) return false;
+               }
+               return true;
+       }
+       
+       @Override
+       public int hashCode() {
+               int hash = super.hashCode();            
+               for (Component c : components) 
+                       hash = hash*13 + 7 * c.name.hashCode() /*+ 3*c.type.hashCode()*/;
+               return hash;
+       }    \r
+       
+       @Override
+       public void accept(Visitor1 v, Object obj) {
+           v.visit(this, obj);        
+       }
+
+       @Override
+       public <T> T accept(Visitor<T> v) {
+           return v.visit(this);
+       }
+       
+       /**
+        * Return true if the record is a tuple.
+        * Tuple is a record with all components are named as a number, the index number of the field.
+        * Empty record is a tuple  
+        * 
+        * @return true if the record type is a tuple.
+        */
+       public boolean isTupleType() {
+               if (components.length==0) return false;
+               for (int i=0; i<getComponentCount(); i++) {
+                       if (!getComponent(i).name.equals(Integer.toString(i))) return false;
+               }
+               return true;
+       }\r
+\r
+    /**\r
+     * Get component type by index\r
+     * @param index index\r
+     * @return componenet type or <tt>null</tt> if index was invalid\r
+     */\r
+    @Override\r
+    public Datatype getComponentType(int index) {\r
+       if (index<0||index>=components.length) return null;\r
+       return components[index].type;\r
+    }\r
+    \r
+       @Override\r
+       public Datatype getComponentType(ChildReference path) throws IllegalArgumentException {\r
+               if (path==null) return this;\r
+               if (path instanceof IndexReference) {\r
+                       IndexReference ir = (IndexReference) path;\r
+                       return components[ir.index].type.getComponentType(path.childReference);\r
+               }\r
+               if (path instanceof NameReference) {\r
+                       NameReference nr = (NameReference) path;\r
+                       return getComponent( nr.name ).type.getComponentType(path.childReference);\r
+               }\r
+               if (path instanceof LabelReference) {\r
+                       LabelReference lr = (LabelReference) path;                      \r
+                       try {\r
+                               Integer i = new Integer(lr.label);\r
+                               return getComponent( i ).type.getComponentType(path.childReference);\r
+                       } catch (NumberFormatException nfe) {\r
+                               return getComponent( lr.label ).type.getComponentType(path.childReference);\r
+                       }\r
+               }\r
+               throw new IllegalArgumentException();\r
+       }\r
+    \r
+       public boolean hasComponent(String fieldName) {\r
+        for (int i=0; i<components.length; i++)\r
+            if (components[i].name.equals(fieldName)) return true;\r
+        return false;\r
+       }\r
+       \r
+    /**\r
+     * Get component by name.\r
+     * \r
+     * @param fieldName component name\r
+     * @return component index or <code>null</code> if one does not exist\r
+     */\r
+    public Integer getComponentIndex(String fieldName) {\r
+        for (int i=0; i<components.length; i++)\r
+            if (components[i].name.equals(fieldName)) return i;\r
+        return null;\r
+    }\r
+\r
+    /**\r
+     * Get component by name.\r
+     * \r
+     * @param fieldName component name\r
+     * @return component index or -1 if one does not exist\r
+     */\r
+    public int getComponentIndex2(String fieldName) {\r
+        for (int i=0; i<components.length; i++)\r
+            if (components[i].name.equals(fieldName)) return i;\r
+        return -1;\r
+    }\r
+    \r
+    /**\r
+     * Get component Datatype by field name\r
+     * @param fieldName\r
+     * @return datatype or <code>null</code>\r
+     */\r
+    public Datatype getComponentType(String fieldName) {\r
+       int index = getComponentIndex2(fieldName);\r
+       if (index<0) return null;\r
+       return components[index].type;\r
+    }\r
+    \r
+    /**\r
+     * Get component by name.\r
+     * \r
+     * @param fieldName component name\r
+     * @return component or <code>null</code> if one does not exist\r
+     */\r
+    public Component getComponent(String fieldName) {\r
+        for (Component c : components)\r
+            if (c.name.equals(fieldName)) return c;\r
+        return null;\r
+    }\r
+\r
+    /**\r
+     * Get component by index.\r
+     * \r
+     * @param index component index\r
+     * @return component or <code>null</code> if one does not exist\r
+     */\r
+    public Component getComponent(int index) {\r
+       if (index<0||index>=components.length) return null;\r
+       return components[index];\r
+    }\r
+       \r
+       public Component[] getComponents() {\r
+               return components;\r
+       }\r
+\r
+       /**\r
+        * Get an array of indices that describe which fields compose the identifier \r
+        * of this record \r
+        * \r
+        * @return indices\r
+        */\r
+       public int[] getIdentifiers() {\r
+               String ids = metadata.get( KEY_IDENTIFIER );\r
+               if (ids == null) {\r
+                       identifiersIndices = new int[0];\r
+               } else {\r
+                       // Parse\r
+                       StringTokenizer st = new StringTokenizer(ids, ",");\r
+                       \r
+                       int[] indices = new int[ st.countTokens() ];\r
+                       for (int i=0; i<indices.length; i++) {\r
+                               String token = st.nextToken();\r
+                               try {\r
+                                       indices[i] = Integer.valueOf(token);\r
+                               } catch ( NumberFormatException nfe ) {\r
+                                       indices[i] = -1;\r
+                               }\r
+                       }\r
+                       identifiersIndices = indices;\r
+               }\r
+               return identifiersIndices;\r
+       }\r
+       \r
+       /**\r
+        * Set which fields compose the identifier of this record \r
+        * \r
+        * @param indices\r
+        */\r
+       public void setIdentifiers(int...indices)\r
+       {\r
+               if (indices.length==0) {\r
+                       metadata.remove( KEY_IDENTIFIER );\r
+                       return;\r
+               }\r
+               identifiersIndices = indices;\r
+               StringBuilder sb = new StringBuilder();\r
+               for (int i=0; i<indices.length; i++) {\r
+                       if (i>0) sb.append(',');\r
+                       sb.append( Integer.toString(indices[i]) );\r
+               }\r
+               \r
+               String str = sb.toString();\r
+               if ( str.isEmpty() ) {\r
+                       metadata.remove( KEY_IDENTIFIER ); \r
+               } else {\r
+                       metadata.put( KEY_IDENTIFIER, str );\r
+               }\r
+       }\r
+\r
+       /**\r
+        * Set which fields compose the identifier of this record\r
+        * @param indices\r
+        */\r
+       public void setIdentifiers(List<Integer> indices)\r
+       {\r
+               int[] indices2 = new int[indices.size()];\r
+               for (int i=0; i<indices.size(); i++) indices2[i] = indices.get(i);\r
+               setIdentifiers(indices2);\r
+       }\r
+       \r
+       public boolean isIdentifier( int fieldIndex )\r
+       {\r
+               int[] ids = getIdentifiers();\r
+               if (ids == null) return false;\r
+               for (int index : ids)\r
+               {\r
+                       if (index == fieldIndex) return true;\r
+               }\r
+               return false;\r
+       }\r
+       \r
+       /**\r
+        * Get a datatype that describes the identifier of this type.\r
+        * If no field has Identifier annotation, the result is null.\r
+        * If more than one field is an identifier the type is a record\r
+        * with all composing fields.\r
+        * \r
+        * @return identifier type or null\r
+        */\r
+       public Datatype getIdentifierType() \r
+       {               \r
+               if ( identifierType != null ) return identifierType;\r
+               \r
+               int[] ids = getIdentifiers();\r
+               if (ids.length==0) return null;\r
+               \r
+               if (ids.length==1) {\r
+                       identifierType = getComponentType(ids[0]);\r
+               }\r
+               \r
+               RecordType rt = new RecordType();\r
+               for (int i : ids) {\r
+                       Component c = getComponent(i);\r
+                       rt.addComponent( c.name, c.type );\r
+               }\r
+               identifierType = rt;            \r
+               return identifierType;\r
+       }\r
+\r
+       @SuppressWarnings("unchecked")\r
+       public <T extends Datatype> T getChildType( ChildReference reference ) throws ReferenceException\r
+       {\r
+               if (reference==null) return (T) this;\r
+               \r
+               if (reference instanceof LabelReference) {\r
+                       LabelReference lr = (LabelReference) reference;\r
+                       String fieldName = lr.label;\r
+                       int index = getComponentIndex2(fieldName);\r
+                       if (index<0) throw new ReferenceException("RecordType doesn't have field by name \""+fieldName+"\"");\r
+                       return components[index].type.getChildType(reference.childReference);\r
+               }               \r
+               \r
+               if (reference instanceof IndexReference) {\r
+                       IndexReference ref = (IndexReference) reference;\r
+                       int index = ref.getIndex();\r
+                       if ( index<0 || index>=components.length ) new ReferenceException("RecordType doesn't have field at index+"+index);\r
+                       return components[index].type.getChildType(reference.childReference);\r
+               } \r
+               \r
+               if (reference instanceof NameReference) {\r
+                       NameReference lr = (NameReference) reference;\r
+                       String fieldName = lr.name;\r
+                       int index = getComponentIndex2(fieldName);\r
+                       if (index<0) throw new ReferenceException("RecordType doesn't have field by name \""+fieldName+"\"");\r
+                       return components[index].type.getChildType(reference.childReference);\r
+               } \r
+               \r
+               throw new ReferenceException(reference.getClass()+" is not a subreference of RecordType");\r
+               \r
+       }
+}