]> gerrit.simantics Code Review - simantics/platform.git/blobdiff - bundles/org.simantics.databoard/src/org/simantics/databoard/util/Bean.java
Migrated source code from Simantics SVN
[simantics/platform.git] / bundles / org.simantics.databoard / src / org / simantics / databoard / util / Bean.java
diff --git a/bundles/org.simantics.databoard/src/org/simantics/databoard/util/Bean.java b/bundles/org.simantics.databoard/src/org/simantics/databoard/util/Bean.java
new file mode 100644 (file)
index 0000000..bf47f76
--- /dev/null
@@ -0,0 +1,640 @@
+/*******************************************************************************\r
+ * Copyright (c) 2007, 2011 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.util;\r
+\r
+import java.io.File;\r
+import java.io.IOException;\r
+import java.io.InputStream;\r
+import java.io.ObjectInputStream;\r
+import java.io.ObjectOutputStream;\r
+import java.io.OutputStream;\r
+import java.util.Random;\r
+\r
+import org.simantics.databoard.Bindings;\r
+import org.simantics.databoard.Files;\r
+import org.simantics.databoard.adapter.AdaptException;\r
+import org.simantics.databoard.adapter.RuntimeAdaptException;\r
+import org.simantics.databoard.binding.Binding;\r
+import org.simantics.databoard.binding.OptionalBinding;\r
+import org.simantics.databoard.binding.RecordBinding;\r
+import org.simantics.databoard.binding.error.BindingException;\r
+import org.simantics.databoard.binding.error.RuntimeBindingException;\r
+import org.simantics.databoard.binding.util.RandomValue;\r
+import org.simantics.databoard.parser.repository.DataTypeSyntaxError;\r
+import org.simantics.databoard.parser.repository.DataValueRepository;\r
+import org.simantics.databoard.serialization.Serializer;\r
+import org.simantics.databoard.serialization.SerializerConstructionException;\r
+import org.simantics.databoard.type.Component;\r
+import org.simantics.databoard.type.Datatype;\r
+\r
+/**\r
+ * Record classes enable databoard features by sub-classing Bean.\r
+ * \r
+ * Instructions, the fields must be public, or have public get/setters.\r
+ * Sub-class gains the following services: \r
+ * \r
+ *   toString         #toString()\r
+ *   string           #print() / #parse()\r
+ *   Hash-Equals      #hashCode()  / #equals()\r
+ *   Comparable       #compareTo()\r
+ *   Serialization    #serialize()/#deserialize(), #readObject()/#writeObject(), #readFile()/#writeFile() \r
+ *   Cloning          #clone() / #readFrom()\r
+ *   Initialization   #init() / #setToDefault() / #setToRandom()\r
+ *   \r
+ * The class must be compatible with databoard's type system. \r
+ * \r
+ * See BeanExample for example.\r
+ * \r
+ * The identify of this class is composed from all the fields. The identity\r
+ * affects to the behavior how hash and equals are counted.\r
+ *  \r
+ * If only some fields compose the hash-equals-compareTo identity, use {@link Bean.Id} instead. \r
+ * In this case the identifying fields have @Identity annotation. \r
+ * \r
+ * Example:\r
+ * \r
+ * public class MyClass extends Bean.Id {\r
+ *   public @Identify String id;\r
+ *   ...\r
+ * }\r
+ * \r
+ * @author toni.kalajainen\r
+ */\r
+public class Bean implements Cloneable, /*Serializable, */Comparable<Bean> {\r
+       \r
+       transient protected RecordBinding binding;\r
+       \r
+       protected Bean() {\r
+               this.binding = Bindings.getBindingUnchecked( getClass() );\r
+       }\r
+       \r
+       protected Bean(Binding binding) {\r
+               this.binding = (RecordBinding) binding;\r
+       }\r
+               \r
+       /**\r
+        * Return datatype binding to this class.\r
+        *  \r
+        * @return record binding\r
+        */\r
+       public RecordBinding getBinding() {\r
+               return binding;\r
+       }\r
+       \r
+       /**\r
+        * Read all field values from another object. All fields are deep-cloned, \r
+        * except immutable values which are referenced.\r
+        * \r
+        * @param other\r
+        */\r
+       public void readFrom(Bean other) {      \r
+               binding.readFromUnchecked(other.binding, other, this);\r
+       }\r
+       \r
+       public void readAvailableFields(Bean other) {\r
+               if ( other.binding instanceof RecordBinding == false ) return;\r
+               Component components[] = binding.type().getComponents(); \r
+               for (int i=0; i<components.length; i++) {\r
+                       Component c = components[i];\r
+                       int ix = other.binding.getComponentIndex(c.name);\r
+                       if ( ix<0 ) continue;\r
+                       try {\r
+                               Object value = other.binding.getComponent(other, ix);\r
+                               Object value2 = Bindings.adapt(value, other.binding.getComponentBinding(ix), binding.getComponentBinding(i));\r
+                               binding.setComponent(this, i, value2);\r
+                       } catch (AdaptException e) {\r
+                               throw new RuntimeException(e);\r
+                       } catch (BindingException e) {\r
+                               throw new RuntimeBindingException(e);\r
+                       }                       \r
+               }\r
+       }\r
+       \r
+       /**\r
+        * Set default value to any null field that is not optional. \r
+        */\r
+       public void init() {\r
+               try {\r
+                       // Set value to uninitialized fields\r
+                       for (int i=0; i<binding.getComponentCount(); i++) {\r
+                               Object v = binding.getComponent(this, i);\r
+                               Binding cb = binding.componentBindings[i];\r
+                               if (v==null && cb instanceof OptionalBinding==false) {\r
+                                       v = cb.createDefault();\r
+                                       binding.setComponent(this, i, v);\r
+                               }                               \r
+                       }\r
+               } catch (BindingException e) {\r
+                       throw new RuntimeBindingException(e);\r
+               }\r
+       }\r
+       \r
+       /**\r
+        * Sets all fields to default values. \r
+        * Strings are set to "", arrays cleared or set to minimum count,\r
+        * numbers are set to 0.\r
+        */\r
+       public void setToDefault() {\r
+               for (int i=0; i<binding.componentBindings.length; i++)\r
+               {\r
+                       Binding cb = binding.componentBindings[i]; \r
+                       try {\r
+                               binding.setComponent(this, i, cb.createDefault());\r
+                       } catch (BindingException e) {\r
+                       }\r
+               }\r
+       }\r
+       \r
+       /**\r
+        * Sets all fields with random values\r
+        */\r
+       public void setToRandom(Random random) {\r
+               RandomValue rv = new RandomValue( random );\r
+               for (int i=0; i<binding.componentBindings.length; i++)\r
+               {\r
+                       Binding cb = binding.componentBindings[i]; \r
+                       try {\r
+                               binding.setComponent(this, i, cb.createRandom( rv ));\r
+                       } catch (BindingException e) {\r
+                       }\r
+               }\r
+       }\r
+       \r
+       @Override\r
+       public int hashCode() {\r
+               try {\r
+                       return binding.hashValue( this );\r
+               } catch (BindingException e) {\r
+                       return -1;\r
+               }\r
+       }\r
+       \r
+       @Override\r
+       public Bean clone() {\r
+               try {\r
+                       return (Bean) binding.clone(this);\r
+               } catch (AdaptException e) {\r
+                       throw new RuntimeAdaptException(e);\r
+               }\r
+       }\r
+       \r
+       /**\r
+        * Compare to another bean of same datatype. (Can be different binding)\r
+        */\r
+       @Override\r
+       public boolean equals(Object obj) {\r
+               if (obj==null) return false;\r
+               if (obj==this) return true;\r
+               if ( obj instanceof Bean == false ) return false;\r
+               Bean other = (Bean) obj;\r
+               if (other.binding==binding)     {\r
+                       return binding.equals(this, obj);\r
+               } else {\r
+                       try {\r
+                               return Bindings.equals(binding, this, other.binding, other);\r
+                       } catch (BindingException e) {\r
+                               throw new RuntimeBindingException(e);\r
+                       } \r
+               }\r
+       }\r
+       \r
+       public boolean equalContents(Object obj) {\r
+               if (obj==null) return false;\r
+               if (obj==this) return true;\r
+               Bean other = (Bean) obj;\r
+               if (other.binding==binding)     {\r
+                       return binding.equals(this, obj);\r
+               } else {\r
+                       try {\r
+                               return Bindings.equals(binding, this, other.binding, other);\r
+                       } catch (BindingException e) {\r
+                               throw new RuntimeBindingException(e);\r
+                       } \r
+               }\r
+       }       \r
+\r
+       @Override\r
+       public int compareTo(Bean o) {\r
+               if (o==null) return -1;\r
+               return binding.compare(this, o);\r
+       }\r
+\r
+       /**\r
+        * Print the object as string\r
+        * @return toString()\r
+        */\r
+       @Override\r
+       public String toString() {\r
+               try {\r
+                       return binding.toString(this);\r
+               } catch (BindingException e) {\r
+                       return e.getMessage();\r
+               }\r
+       }\r
+       \r
+       /**\r
+        * Print the value in string format \r
+        * \r
+        * @param out\r
+        * @throws IOException\r
+        */\r
+       public void print(Appendable out) throws IOException {\r
+               try {\r
+                       DataValueRepository rep = new DataValueRepository();\r
+                       binding.printValue(this, out, rep, false);\r
+               } catch (BindingException e) {\r
+                       throw new RuntimeBindingException(e);\r
+               }\r
+       }\r
+       \r
+       /**\r
+        * Print the value to Databoard string format\r
+        * \r
+        * @return Bean in Databoard string format\r
+        * @throws IOException\r
+        */\r
+       public String print() throws IOException {\r
+               try {\r
+                       StringBuilder sb = new StringBuilder();\r
+                       DataValueRepository rep = new DataValueRepository();\r
+                       binding.printValue(this, sb, rep, false);\r
+                       return sb.toString();\r
+                       \r
+                       /*                      StringBuilder sb = new StringBuilder();\r
+                       DataValueRepository rep = new DataValueRepository();\r
+                       rep.setTypeRepository( Datatypes.datatypeRepository );\r
+                       DataValuePrinter vp = new DataValuePrinter(sb, rep);\r
+                       vp.setFormat( PrintFormat.MULTI_LINE );\r
+                       DataTypePrinter tp = new DataTypePrinter( sb );\r
+                       tp.setLinedeed( true );\r
+                       rep.put("value", binding, this);\r
+\r
+                       for (String name : rep.getValueNames()) {\r
+                               MutableVariant value = rep.get(name);\r
+                               Datatype type = value.type();\r
+                               tp.print(type);\r
+                               vp.print(value);                        \r
+                       }       \r
+                       \r
+                       for (String name : rep.getValueNames()) {\r
+                               MutableVariant value = rep.get(name);\r
+                               Datatype type = value.type();\r
+                               sb.append( name+" : " );\r
+                               tp.print(type);\r
+                               sb.append( " = " );\r
+                               vp.print(value);                        \r
+                               sb.append("\n");\r
+                       }\r
+                       System.out.println(sb);\r
+                       return sb.toString();\r
+*/\r
+               } catch (BindingException e) {\r
+                       throw new RuntimeBindingException(e);\r
+               }\r
+       }\r
+       \r
+       /**\r
+        * Print the value to Databoard string format\r
+        * \r
+        * @return string representation in a line\r
+        * @throws IOException\r
+        */\r
+       public String printLine() throws IOException {\r
+               try {\r
+                       StringBuilder sb = new StringBuilder();\r
+                       DataValueRepository rep = new DataValueRepository();\r
+                       binding.printValue(this, sb, rep, true);\r
+                       return sb.toString();\r
+               } catch (BindingException e) {\r
+                       throw new RuntimeBindingException(e);\r
+               }\r
+       }\r
+\r
+       /**\r
+        * Read the contents from Databoard String\r
+        * @param str\r
+        * @throws DataTypeSyntaxError\r
+        */\r
+       public void parse( String str ) throws DataTypeSyntaxError {\r
+               try {\r
+                       DataValueRepository rep = new DataValueRepository();\r
+                       Object v = binding.parseValue( str, rep );\r
+                       init();\r
+                       binding.readFrom(binding, v, this);             \r
+               } catch (BindingException e) {\r
+                       throw new RuntimeBindingException( e );\r
+               }\r
+       }\r
+       \r
+       public void readObject(ObjectInputStream in) throws ClassNotFoundException, IOException {\r
+               try {\r
+                       Serializer s = Bindings.getSerializer(binding);\r
+                       s.deserialize((InputStream)in, this);\r
+               } catch (SerializerConstructionException e) {\r
+                       throw new IOException(e);\r
+               }\r
+       }\r
+\r
+    public void writeObject(ObjectOutputStream out) throws IOException {\r
+               try {\r
+                       Serializer s = Bindings.getSerializer(binding);\r
+                       s.serialize((OutputStream) out, this);\r
+               } catch (SerializerConstructionException e) {\r
+                       throw new IOException(e);\r
+               }\r
+    }\r
+    \r
+    /**\r
+     * Serialize the object to a byte array\r
+     * \r
+     * @return bean as serialized\r
+     * @throws IOException\r
+     */\r
+    public byte[] serialize() throws IOException {\r
+               try {\r
+                       Serializer s = Bindings.getSerializer(binding);\r
+                       return s.serialize( this );\r
+               } catch (SerializerConstructionException e) {\r
+                       throw new IOException(e);\r
+               }\r
+    }\r
+\r
+    /**\r
+     * Deserialize the object from a byte array\r
+     * \r
+     * @param data\r
+     * @throws IOException\r
+     */\r
+    public void deserialize( byte[] data ) throws IOException {\r
+               try {\r
+                       Serializer s = Bindings.getSerializer(binding);\r
+                       init();                 \r
+                       s.deserialize(data, this);\r
+               } catch (SerializerConstructionException e) {\r
+                       throw new IOException(e);\r
+               }       \r
+    }\r
+    \r
+    public void readFile( File file ) throws IOException {\r
+       init();\r
+       Files.readFile(file, binding, this);\r
+    }\r
+    \r
+    public void writeFile( File file ) throws IOException {\r
+       Files.writeFile(file, binding, this);\r
+    }\r
+\r
+       public void assertIsValid() throws BindingException {\r
+               binding.assertInstaceIsValid(this);\r
+       }\r
+       \r
+       public void setField(String fieldName, Binding fieldBinding, Object field) throws BindingException {\r
+               try {\r
+                       int index = binding.getComponentIndex(fieldName);\r
+                       if ( index<0 ) throw new BindingException("There is no field "+fieldName);\r
+                       Binding localFieldBinding = binding.getComponentBinding(index);\r
+                       if ( localFieldBinding instanceof OptionalBinding ) {\r
+                               OptionalBinding ob = (OptionalBinding) localFieldBinding;\r
+                               if ( field == null ) {\r
+                                       binding.setComponent(this, index, ob.createNoValue());\r
+                               } else {\r
+                                       Object newValue = Bindings.adapt(field, fieldBinding, ob.componentBinding);\r
+                                       binding.setComponent(this, index, ob.createValue(newValue));\r
+                               }\r
+                       } else {\r
+                               Object newValue = Bindings.adapt(field, fieldBinding, localFieldBinding);\r
+                               binding.setComponent(this, index, newValue);\r
+                       }\r
+               } catch (AdaptException e) {\r
+                       if ( e.getCause() !=null && e.getCause() instanceof BindingException ) throw (BindingException) e.getCause();\r
+                       throw new BindingException(e);\r
+               }\r
+       }\r
+\r
+       public void setField(int fieldIndex, Binding fieldBinding, Object field) throws BindingException {\r
+               try {\r
+                       if ( fieldIndex<0 ) throw new BindingException("There is no field #"+fieldIndex);\r
+                       Binding localFieldBinding = binding.getComponentBinding(fieldIndex);\r
+                       if ( localFieldBinding instanceof OptionalBinding ) {\r
+                               OptionalBinding ob = (OptionalBinding) localFieldBinding;\r
+                               if ( field == null ) {\r
+                                       binding.setComponent(this, fieldIndex, ob.createNoValue());\r
+                               } else {\r
+                                       Object newValue = Bindings.adapt(field, fieldBinding, ob.componentBinding);\r
+                                       binding.setComponent(this, fieldIndex, ob.createValue(newValue));\r
+                               }\r
+                       } else {\r
+                               Object newValue = Bindings.adapt(field, fieldBinding, localFieldBinding);\r
+                               binding.setComponent(this, fieldIndex, newValue);\r
+                       }\r
+               } catch (AdaptException e) {\r
+                       if ( e.getCause() !=null && e.getCause() instanceof BindingException ) throw (BindingException) e.getCause();\r
+                       throw new BindingException(e);\r
+               }\r
+       }\r
+       \r
+       public boolean hasField(String fieldName) throws BindingException {\r
+               return binding.getComponentIndex(fieldName)>=0;\r
+       }\r
+       \r
+       /**\r
+        * Get binding of a field\r
+        * \r
+        * @param fieldName\r
+        * @return binding or null of field does not exist\r
+        * @throws BindingException\r
+        */\r
+       public Binding getFieldBinding(String fieldName) throws BindingException {\r
+               int index = binding.getComponentIndex(fieldName);\r
+               if ( index<0 ) return null;\r
+               Binding r = binding.getComponentBinding(index);\r
+               if ( r!=null && r instanceof OptionalBinding ) {\r
+                       r = ((OptionalBinding)r).componentBinding;\r
+               }\r
+               return r;\r
+       }\r
+       \r
+       /**\r
+        * Get value of a field\r
+        * @param fieldName\r
+        * @return value or null if field does not exist\r
+        * @throws BindingException\r
+        */\r
+       public Object getField(String fieldName) throws BindingException {\r
+               int index = binding.type().getComponentIndex2(fieldName);\r
+               if (index<0) return null;\r
+               return binding.getComponent(this, index);\r
+       }\r
+       \r
+       /**\r
+        * Get value of a field\r
+        * @param fieldName\r
+        * @return value or null if field does not exist\r
+        * @throws BindingException\r
+        */\r
+       public Object getField(int fieldIndex) throws BindingException {\r
+               return binding.getComponent(this, fieldIndex);\r
+       }\r
+       \r
+       /**\r
+        * Get value of a field\r
+        * @param fieldName\r
+        * @param binding requested binding\r
+        * @return value or null if field does not exist\r
+        * @throws BindingException\r
+        */\r
+       public Object getField(String fieldName, Binding binding) throws BindingException {\r
+               int index = this.binding.type().getComponentIndex2(fieldName);\r
+               if (index<0) return null;               \r
+               Object obj = this.binding.getComponent(this, index);\r
+               if ( obj == null ) return null;\r
+               Binding fieldBinding = this.binding.getComponentBinding(index);\r
+               if ( fieldBinding instanceof OptionalBinding ) {\r
+                       fieldBinding = ((OptionalBinding)fieldBinding).componentBinding;\r
+               }\r
+               try {\r
+                       return Bindings.adapt(obj, fieldBinding, binding);\r
+               } catch (AdaptException e) {\r
+                       if ( e.getCause() !=null && e.getCause() instanceof BindingException ) throw (BindingException) e.getCause();\r
+                       throw new BindingException(e);\r
+               }\r
+       }\r
+\r
+       /**\r
+        * Get value of a field\r
+        * @param fieldName\r
+        * @return value or null if field does not exist\r
+        * @throws RuntimeBindingException\r
+        */\r
+       public Object getFieldUnchecked(String fieldName) throws RuntimeBindingException {\r
+               int index = binding.type().getComponentIndex2(fieldName);\r
+               if (index<0) return null;\r
+               try {\r
+                       return binding.getComponent(this, index);\r
+               } catch (BindingException e) {\r
+                       throw new RuntimeBindingException(e);\r
+               }\r
+       }\r
+       \r
+       /**\r
+        * Get value of a field\r
+        * @param fieldName\r
+        * @return value or null if field does not exist\r
+        * @throws RuntimeBindingException\r
+        */\r
+       public Object getFieldUnchecked(int fieldIndex) throws RuntimeBindingException {\r
+               try {\r
+                       return binding.getComponent(this, fieldIndex);\r
+               } catch (BindingException e) {\r
+                       throw new RuntimeBindingException(e);\r
+               }\r
+       }\r
+       \r
+       /**\r
+        * Get identifier binding. Use @Identifier annotation to indicate which \r
+        * fields compose the identifier of the record.\r
+        *  \r
+        * @return idenfitier binding. \r
+        * @throws BindingException there is no identifier\r
+        */\r
+       public Binding getIdentifierBinding() throws BindingException {\r
+               Datatype idType = binding.type().getIdentifierType();\r
+               if (idType == null) throw new BindingException("There is are no @Identifier fields in the bean");\r
+               return Bindings.getBinding( idType );\r
+       }\r
+       \r
+       /**\r
+        * Get identifier of the object. Use @Identifier annotation to indicate which\r
+        * fields compose the identifier of the record.\r
+        * \r
+        * @return identifier\r
+        * @throws BindingException\r
+        */\r
+       public Object getIdentifier() throws BindingException \r
+       {\r
+               int ids[] = binding.type().getIdentifiers();\r
+               if (ids.length == 0) throw new BindingException("There is are no @Identifier fields in the bean");\r
+               if (ids.length == 1) return binding.getComponent(this, ids[0]);\r
+               RecordBinding rb = (RecordBinding) getIdentifierBinding();\r
+               Object result = rb.createPartial();\r
+               int ix = 0;\r
+               for (int i : ids) {\r
+                       rb.setComponent(result, ix++, binding.getComponent(this, i));\r
+               }\r
+               return result;\r
+       }\r
+       \r
+       /**\r
+        * In this version of the bean, the hash/equals compares to identifiers.\r
+        * Identifier is a field with @Idenfitier annotation. \r
+        */\r
+       public static class Id extends Bean {\r
+               protected Id() {}\r
+               protected Id(Binding binding) {\r
+                       super(binding);\r
+               }\r
+\r
+               @Override\r
+               public int hashCode() {\r
+                       int hash = 0;\r
+                       try {\r
+                               for (int index : binding.type().getIdentifiers())\r
+                               {\r
+                                       Object c = binding.getComponent(this, index);\r
+                                       Binding cb = binding.getComponentBinding(index);\r
+                                       hash = 13*hash + cb.hashValue(c);\r
+                               }\r
+                       } catch (BindingException e) {\r
+                       }\r
+                       return hash;\r
+               }\r
+               \r
+               /**\r
+                * Compare to another bean of same datatype for equal identifier. (Can be different binding)\r
+                */\r
+               @Override\r
+               public boolean equals(Object obj) {\r
+                       if (obj==null) return false;\r
+                       if (obj==this) return true;\r
+                       if ( obj instanceof Bean == false ) return false;\r
+                       Bean other = (Bean) obj;\r
+                       try {\r
+                               if (other.binding==binding)     {\r
+                                       \r
+                                       for (int index : binding.type().getIdentifiers())\r
+                                       {\r
+                                               Object tc = binding.getComponent(this, index);\r
+                                               Object oc = binding.getComponent(other, index);\r
+                                               Binding cb = binding.getComponentBinding(index);\r
+                                               if ( !cb.equals(tc, oc) ) return false;\r
+                                       }\r
+                                       \r
+                               } else {\r
+\r
+                                       for (int index : binding.type().getIdentifiers())\r
+                                       {\r
+                                               Object tc = binding.getComponent(this, index);\r
+                                               Object oc = binding.getComponent(other, index);\r
+                                               Binding tcb = binding.getComponentBinding(index);\r
+                                               Binding ocb = other.binding.getComponentBinding(index);\r
+                                               if ( !Bindings.equals(tcb, tc, ocb, oc) ) return false;\r
+                                       }\r
+                               }\r
+                               return true;\r
+                       } catch (BindingException e) {\r
+                               throw new RuntimeBindingException(e);\r
+                       } \r
+               }\r
+                               \r
+       }\r
+\r
+}\r