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