--- /dev/null
+/*******************************************************************************\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