]> gerrit.simantics Code Review - simantics/platform.git/blob - bundles/org.simantics.databoard/src/org/simantics/databoard/util/Bean.java
Improve Databoard's dynamically typed data capabilities.
[simantics/platform.git] / bundles / org.simantics.databoard / src / org / simantics / databoard / util / Bean.java
1 /*******************************************************************************\r
2  * Copyright (c) 2007, 2011 Association for Decentralized Information Management in\r
3  * Industry THTH ry.\r
4  * All rights reserved. This program and the accompanying materials\r
5  * are made available under the terms of the Eclipse Public License v1.0\r
6  * which accompanies this distribution, and is available at\r
7  * http://www.eclipse.org/legal/epl-v10.html\r
8  *\r
9  * Contributors:\r
10  *     VTT Technical Research Centre of Finland - initial API and implementation\r
11  *******************************************************************************/\r
12 package org.simantics.databoard.util;\r
13 \r
14 import java.io.File;\r
15 import java.io.IOException;\r
16 import java.io.InputStream;\r
17 import java.io.ObjectInputStream;\r
18 import java.io.ObjectOutputStream;\r
19 import java.io.OutputStream;\r
20 import java.util.Random;\r
21 \r
22 import org.simantics.databoard.Bindings;\r
23 import org.simantics.databoard.Files;\r
24 import org.simantics.databoard.adapter.AdaptException;\r
25 import org.simantics.databoard.adapter.RuntimeAdaptException;\r
26 import org.simantics.databoard.binding.Binding;\r
27 import org.simantics.databoard.binding.OptionalBinding;\r
28 import org.simantics.databoard.binding.RecordBinding;\r
29 import org.simantics.databoard.binding.error.BindingException;\r
30 import org.simantics.databoard.binding.error.RuntimeBindingException;\r
31 import org.simantics.databoard.binding.util.RandomValue;\r
32 import org.simantics.databoard.parser.repository.DataTypeSyntaxError;\r
33 import org.simantics.databoard.parser.repository.DataValueRepository;\r
34 import org.simantics.databoard.serialization.Serializer;\r
35 import org.simantics.databoard.serialization.SerializerConstructionException;\r
36 import org.simantics.databoard.type.Component;\r
37 import org.simantics.databoard.type.Datatype;\r
38 \r
39 /**\r
40  * Record classes enable databoard features by sub-classing Bean.\r
41  * \r
42  * Instructions, the fields must be public, or have public get/setters.\r
43  * Sub-class gains the following services: \r
44  * \r
45  *   toString         #toString()\r
46  *   string           #print() / #parse()\r
47  *   Hash-Equals      #hashCode()  / #equals()\r
48  *   Comparable       #compareTo()\r
49  *   Serialization    #serialize()/#deserialize(), #readObject()/#writeObject(), #readFile()/#writeFile() \r
50  *   Cloning          #clone() / #readFrom()\r
51  *   Initialization   #init() / #setToDefault() / #setToRandom()\r
52  *   \r
53  * The class must be compatible with databoard's type system. \r
54  * \r
55  * See BeanExample for example.\r
56  * \r
57  * The identify of this class is composed from all the fields. The identity\r
58  * affects to the behavior how hash and equals are counted.\r
59  *  \r
60  * If only some fields compose the hash-equals-compareTo identity, use {@link Bean.Id} instead. \r
61  * In this case the identifying fields have @Identity annotation. \r
62  * \r
63  * Example:\r
64  * \r
65  * public class MyClass extends Bean.Id {\r
66  *   public @Identify String id;\r
67  *   ...\r
68  * }\r
69  * \r
70  * @author toni.kalajainen\r
71  */\r
72 public class Bean implements Cloneable, /*Serializable, */Comparable<Bean> {\r
73         \r
74         transient protected RecordBinding binding;\r
75         \r
76         protected Bean() {\r
77                 this.binding = Bindings.getBindingUnchecked( getClass() );\r
78         }\r
79         \r
80         protected Bean(Binding binding) {\r
81                 this.binding = (RecordBinding) binding;\r
82         }\r
83                 \r
84         /**\r
85          * Return datatype binding to this class.\r
86          *  \r
87          * @return record binding\r
88          */\r
89         public RecordBinding getBinding() {\r
90                 return binding;\r
91         }\r
92         \r
93         /**\r
94          * Read all field values from another object. All fields are deep-cloned, \r
95          * except immutable values which are referenced.\r
96          * \r
97          * @param other\r
98          */\r
99         public void readFrom(Bean other) {      \r
100                 binding.readFromUnchecked(other.binding, other, this);\r
101         }\r
102         \r
103         public void readAvailableFields(Bean other) {\r
104                 if ( other.binding instanceof RecordBinding == false ) return;\r
105                 Component components[] = binding.type().getComponents(); \r
106                 for (int i=0; i<components.length; i++) {\r
107                         Component c = components[i];\r
108                         int ix = other.binding.getComponentIndex(c.name);\r
109                         if ( ix<0 ) continue;\r
110                         try {\r
111                                 Object value = other.binding.getComponent(other, ix);\r
112                                 Object value2 = Bindings.adapt(value, other.binding.getComponentBinding(ix), binding.getComponentBinding(i));\r
113                                 binding.setComponent(this, i, value2);\r
114                         } catch (AdaptException e) {\r
115                                 throw new RuntimeException(e);\r
116                         } catch (BindingException e) {\r
117                                 throw new RuntimeBindingException(e);\r
118                         }                       \r
119                 }\r
120         }\r
121         \r
122         /**\r
123          * Set default value to any null field that is not optional. \r
124          */\r
125         public void init() {\r
126                 try {\r
127                         // Set value to uninitialized fields\r
128                         for (int i=0; i<binding.getComponentCount(); i++) {\r
129                                 Object v = binding.getComponent(this, i);\r
130                                 Binding cb = binding.componentBindings[i];\r
131                                 if (v==null && cb instanceof OptionalBinding==false) {\r
132                                         v = cb.createDefault();\r
133                                         binding.setComponent(this, i, v);\r
134                                 }                               \r
135                         }\r
136                 } catch (BindingException e) {\r
137                         throw new RuntimeBindingException(e);\r
138                 }\r
139         }\r
140         \r
141         /**\r
142          * Sets all fields to default values. \r
143          * Strings are set to "", arrays cleared or set to minimum count,\r
144          * numbers are set to 0.\r
145          */\r
146         public void setToDefault() {\r
147                 for (int i=0; i<binding.componentBindings.length; i++)\r
148                 {\r
149                         Binding cb = binding.componentBindings[i]; \r
150                         try {\r
151                                 binding.setComponent(this, i, cb.createDefault());\r
152                         } catch (BindingException e) {\r
153                         }\r
154                 }\r
155         }\r
156         \r
157         /**\r
158          * Sets all fields with random values\r
159          */\r
160         public void setToRandom(Random random) {\r
161                 RandomValue rv = new RandomValue( random );\r
162                 for (int i=0; i<binding.componentBindings.length; i++)\r
163                 {\r
164                         Binding cb = binding.componentBindings[i]; \r
165                         try {\r
166                                 binding.setComponent(this, i, cb.createRandom( rv ));\r
167                         } catch (BindingException e) {\r
168                         }\r
169                 }\r
170         }\r
171         \r
172         @Override\r
173         public int hashCode() {\r
174                 try {\r
175                         return binding.hashValue( this );\r
176                 } catch (BindingException e) {\r
177                         return -1;\r
178                 }\r
179         }\r
180         \r
181         @Override\r
182         public Bean clone() {\r
183                 try {\r
184                         return (Bean) binding.clone(this);\r
185                 } catch (AdaptException e) {\r
186                         throw new RuntimeAdaptException(e);\r
187                 }\r
188         }\r
189         \r
190         /**\r
191          * Compare to another bean of same datatype. (Can be different binding)\r
192          */\r
193         @Override\r
194         public boolean equals(Object obj) {\r
195                 if (obj==null) return false;\r
196                 if (obj==this) return true;\r
197                 if ( obj instanceof Bean == false ) return false;\r
198                 Bean other = (Bean) obj;\r
199                 if (other.binding==binding)     {\r
200                         return binding.equals(this, obj);\r
201                 } else {\r
202                         try {\r
203                                 return Bindings.equals(binding, this, other.binding, other);\r
204                         } catch (BindingException e) {\r
205                                 throw new RuntimeBindingException(e);\r
206                         } \r
207                 }\r
208         }\r
209         \r
210         public boolean equalContents(Object obj) {\r
211                 if (obj==null) return false;\r
212                 if (obj==this) return true;\r
213                 Bean other = (Bean) obj;\r
214                 if (other.binding==binding)     {\r
215                         return binding.equals(this, obj);\r
216                 } else {\r
217                         try {\r
218                                 return Bindings.equals(binding, this, other.binding, other);\r
219                         } catch (BindingException e) {\r
220                                 throw new RuntimeBindingException(e);\r
221                         } \r
222                 }\r
223         }       \r
224 \r
225         @Override\r
226         public int compareTo(Bean o) {\r
227                 if (o==null) return -1;\r
228                 return binding.compare(this, o);\r
229         }\r
230 \r
231         /**\r
232          * Print the object as string\r
233          * @return toString()\r
234          */\r
235         @Override\r
236         public String toString() {\r
237                 try {\r
238                         return binding.toString(this);\r
239                 } catch (BindingException e) {\r
240                         return e.getMessage();\r
241                 }\r
242         }\r
243         \r
244         /**\r
245          * Print the value in string format \r
246          * \r
247          * @param out\r
248          * @throws IOException\r
249          */\r
250         public void print(Appendable out) throws IOException {\r
251                 try {\r
252                         DataValueRepository rep = new DataValueRepository();\r
253                         binding.printValue(this, out, rep, false);\r
254                 } catch (BindingException e) {\r
255                         throw new RuntimeBindingException(e);\r
256                 }\r
257         }\r
258         \r
259         /**\r
260          * Print the value to Databoard string format\r
261          * \r
262          * @return Bean in Databoard string format\r
263          * @throws IOException\r
264          */\r
265         public String print() throws IOException {\r
266                 try {\r
267                         StringBuilder sb = new StringBuilder();\r
268                         DataValueRepository rep = new DataValueRepository();\r
269                         binding.printValue(this, sb, rep, false);\r
270                         return sb.toString();\r
271                         \r
272                         /*                      StringBuilder sb = new StringBuilder();\r
273                         DataValueRepository rep = new DataValueRepository();\r
274                         rep.setTypeRepository( Datatypes.datatypeRepository );\r
275                         DataValuePrinter vp = new DataValuePrinter(sb, rep);\r
276                         vp.setFormat( PrintFormat.MULTI_LINE );\r
277                         DataTypePrinter tp = new DataTypePrinter( sb );\r
278                         tp.setLinedeed( true );\r
279                         rep.put("value", binding, this);\r
280 \r
281                         for (String name : rep.getValueNames()) {\r
282                                 MutableVariant value = rep.get(name);\r
283                                 Datatype type = value.type();\r
284                                 tp.print(type);\r
285                                 vp.print(value);                        \r
286                         }       \r
287                         \r
288                         for (String name : rep.getValueNames()) {\r
289                                 MutableVariant value = rep.get(name);\r
290                                 Datatype type = value.type();\r
291                                 sb.append( name+" : " );\r
292                                 tp.print(type);\r
293                                 sb.append( " = " );\r
294                                 vp.print(value);                        \r
295                                 sb.append("\n");\r
296                         }\r
297                         System.out.println(sb);\r
298                         return sb.toString();\r
299 */\r
300                 } catch (BindingException e) {\r
301                         throw new RuntimeBindingException(e);\r
302                 }\r
303         }\r
304         \r
305         /**\r
306          * Print the value to Databoard string format\r
307          * \r
308          * @return string representation in a line\r
309          * @throws IOException\r
310          */\r
311         public String printLine() throws IOException {\r
312                 try {\r
313                         StringBuilder sb = new StringBuilder();\r
314                         DataValueRepository rep = new DataValueRepository();\r
315                         binding.printValue(this, sb, rep, true);\r
316                         return sb.toString();\r
317                 } catch (BindingException e) {\r
318                         throw new RuntimeBindingException(e);\r
319                 }\r
320         }\r
321 \r
322         /**\r
323          * Read the contents from Databoard String\r
324          * @param str\r
325          * @throws DataTypeSyntaxError\r
326          */\r
327         public void parse( String str ) throws DataTypeSyntaxError {\r
328                 try {\r
329                         DataValueRepository rep = new DataValueRepository();\r
330                         Object v = binding.parseValue( str, rep );\r
331                         init();\r
332                         binding.readFrom(binding, v, this);             \r
333                 } catch (BindingException e) {\r
334                         throw new RuntimeBindingException( e );\r
335                 }\r
336         }\r
337         \r
338         public void readObject(ObjectInputStream in) throws ClassNotFoundException, IOException {\r
339                 try {\r
340                         Serializer s = Bindings.getSerializer(binding);\r
341                         s.deserialize((InputStream)in, this);\r
342                 } catch (SerializerConstructionException e) {\r
343                         throw new IOException(e);\r
344                 }\r
345         }\r
346 \r
347     public void writeObject(ObjectOutputStream out) throws IOException {\r
348                 try {\r
349                         Serializer s = Bindings.getSerializer(binding);\r
350                         s.serialize((OutputStream) out, this);\r
351                 } catch (SerializerConstructionException e) {\r
352                         throw new IOException(e);\r
353                 }\r
354     }\r
355     \r
356     /**\r
357      * Serialize the object to a byte array\r
358      * \r
359      * @return bean as serialized\r
360      * @throws IOException\r
361      */\r
362     public byte[] serialize() throws IOException {\r
363                 try {\r
364                         Serializer s = Bindings.getSerializer(binding);\r
365                         return s.serialize( this );\r
366                 } catch (SerializerConstructionException e) {\r
367                         throw new IOException(e);\r
368                 }\r
369     }\r
370 \r
371     /**\r
372      * Deserialize the object from a byte array\r
373      * \r
374      * @param data\r
375      * @throws IOException\r
376      */\r
377     public void deserialize( byte[] data ) throws IOException {\r
378                 try {\r
379                         Serializer s = Bindings.getSerializer(binding);\r
380                         init();                 \r
381                         s.deserialize(data, this);\r
382                 } catch (SerializerConstructionException e) {\r
383                         throw new IOException(e);\r
384                 }       \r
385     }\r
386     \r
387     public void readFile( File file ) throws IOException {\r
388         init();\r
389         Files.readFile(file, binding, this);\r
390     }\r
391     \r
392     public void writeFile( File file ) throws IOException {\r
393         Files.writeFile(file, binding, this);\r
394     }\r
395 \r
396         public void assertIsValid() throws BindingException {\r
397                 binding.assertInstaceIsValid(this);\r
398         }\r
399         \r
400         public void setField(String fieldName, Binding fieldBinding, Object field) throws BindingException {\r
401                 try {\r
402                         int index = binding.getComponentIndex(fieldName);\r
403                         if ( index<0 ) throw new BindingException("There is no field "+fieldName);\r
404                         Binding localFieldBinding = binding.getComponentBinding(index);\r
405                         if ( localFieldBinding instanceof OptionalBinding ) {\r
406                                 OptionalBinding ob = (OptionalBinding) localFieldBinding;\r
407                                 if ( field == null ) {\r
408                                         binding.setComponent(this, index, ob.createNoValue());\r
409                                 } else {\r
410                                         Object newValue = Bindings.adapt(field, fieldBinding, ob.componentBinding);\r
411                                         binding.setComponent(this, index, ob.createValue(newValue));\r
412                                 }\r
413                         } else {\r
414                                 Object newValue = Bindings.adapt(field, fieldBinding, localFieldBinding);\r
415                                 binding.setComponent(this, index, newValue);\r
416                         }\r
417                 } catch (AdaptException e) {\r
418                         if ( e.getCause() !=null && e.getCause() instanceof BindingException ) throw (BindingException) e.getCause();\r
419                         throw new BindingException(e);\r
420                 }\r
421         }\r
422 \r
423         public void setField(int fieldIndex, Binding fieldBinding, Object field) throws BindingException {\r
424                 try {\r
425                         if ( fieldIndex<0 ) throw new BindingException("There is no field #"+fieldIndex);\r
426                         Binding localFieldBinding = binding.getComponentBinding(fieldIndex);\r
427                         if ( localFieldBinding instanceof OptionalBinding ) {\r
428                                 OptionalBinding ob = (OptionalBinding) localFieldBinding;\r
429                                 if ( field == null ) {\r
430                                         binding.setComponent(this, fieldIndex, ob.createNoValue());\r
431                                 } else {\r
432                                         Object newValue = Bindings.adapt(field, fieldBinding, ob.componentBinding);\r
433                                         binding.setComponent(this, fieldIndex, ob.createValue(newValue));\r
434                                 }\r
435                         } else {\r
436                                 Object newValue = Bindings.adapt(field, fieldBinding, localFieldBinding);\r
437                                 binding.setComponent(this, fieldIndex, newValue);\r
438                         }\r
439                 } catch (AdaptException e) {\r
440                         if ( e.getCause() !=null && e.getCause() instanceof BindingException ) throw (BindingException) e.getCause();\r
441                         throw new BindingException(e);\r
442                 }\r
443         }\r
444         \r
445         public boolean hasField(String fieldName) throws BindingException {\r
446                 return binding.getComponentIndex(fieldName)>=0;\r
447         }\r
448         \r
449         /**\r
450          * Get binding of a field\r
451          * \r
452          * @param fieldName\r
453          * @return binding or null of field does not exist\r
454          * @throws BindingException\r
455          */\r
456         public Binding getFieldBinding(String fieldName) throws BindingException {\r
457                 int index = binding.getComponentIndex(fieldName);\r
458                 if ( index<0 ) return null;\r
459                 Binding r = binding.getComponentBinding(index);\r
460                 if ( r!=null && r instanceof OptionalBinding ) {\r
461                         r = ((OptionalBinding)r).componentBinding;\r
462                 }\r
463                 return r;\r
464         }\r
465         \r
466         /**\r
467          * Get value of a field\r
468          * @param fieldName\r
469          * @return value or null if field does not exist\r
470          * @throws BindingException\r
471          */\r
472         public Object getField(String fieldName) throws BindingException {\r
473                 int index = binding.type().getComponentIndex2(fieldName);\r
474                 if (index<0) return null;\r
475                 return binding.getComponent(this, index);\r
476         }\r
477         \r
478         /**\r
479          * Get value of a field\r
480          * @param fieldName\r
481          * @return value or null if field does not exist\r
482          * @throws BindingException\r
483          */\r
484         public Object getField(int fieldIndex) throws BindingException {\r
485                 return binding.getComponent(this, fieldIndex);\r
486         }\r
487         \r
488         /**\r
489          * Get value of a field\r
490          * @param fieldName\r
491          * @param binding requested binding\r
492          * @return value or null if field does not exist\r
493          * @throws BindingException\r
494          */\r
495         public Object getField(String fieldName, Binding binding) throws BindingException {\r
496                 int index = this.binding.type().getComponentIndex2(fieldName);\r
497                 if (index<0) return null;               \r
498                 Object obj = this.binding.getComponent(this, index);\r
499                 if ( obj == null ) return null;\r
500                 Binding fieldBinding = this.binding.getComponentBinding(index);\r
501                 if ( fieldBinding instanceof OptionalBinding ) {\r
502                         fieldBinding = ((OptionalBinding)fieldBinding).componentBinding;\r
503                 }\r
504                 try {\r
505                         return Bindings.adapt(obj, fieldBinding, binding);\r
506                 } catch (AdaptException e) {\r
507                         if ( e.getCause() !=null && e.getCause() instanceof BindingException ) throw (BindingException) e.getCause();\r
508                         throw new BindingException(e);\r
509                 }\r
510         }\r
511 \r
512         /**\r
513          * Get value of a field\r
514          * @param fieldName\r
515          * @return value or null if field does not exist\r
516          * @throws RuntimeBindingException\r
517          */\r
518         public Object getFieldUnchecked(String fieldName) throws RuntimeBindingException {\r
519                 int index = binding.type().getComponentIndex2(fieldName);\r
520                 if (index<0) return null;\r
521                 try {\r
522                         return binding.getComponent(this, index);\r
523                 } catch (BindingException e) {\r
524                         throw new RuntimeBindingException(e);\r
525                 }\r
526         }\r
527         \r
528         /**\r
529          * Get value of a field\r
530          * @param fieldName\r
531          * @return value or null if field does not exist\r
532          * @throws RuntimeBindingException\r
533          */\r
534         public Object getFieldUnchecked(int fieldIndex) throws RuntimeBindingException {\r
535                 try {\r
536                         return binding.getComponent(this, fieldIndex);\r
537                 } catch (BindingException e) {\r
538                         throw new RuntimeBindingException(e);\r
539                 }\r
540         }\r
541         \r
542         /**\r
543          * Get identifier binding. Use @Identifier annotation to indicate which \r
544          * fields compose the identifier of the record.\r
545          *  \r
546          * @return idenfitier binding. \r
547          * @throws BindingException there is no identifier\r
548          */\r
549         public Binding getIdentifierBinding() throws BindingException {\r
550                 Datatype idType = binding.type().getIdentifierType();\r
551                 if (idType == null) throw new BindingException("There is are no @Identifier fields in the bean");\r
552                 return Bindings.getBinding( idType );\r
553         }\r
554         \r
555         /**\r
556          * Get identifier of the object. Use @Identifier annotation to indicate which\r
557          * fields compose the identifier of the record.\r
558          * \r
559          * @return identifier\r
560          * @throws BindingException\r
561          */\r
562         public Object getIdentifier() throws BindingException \r
563         {\r
564                 int ids[] = binding.type().getIdentifiers();\r
565                 if (ids.length == 0) throw new BindingException("There is are no @Identifier fields in the bean");\r
566                 if (ids.length == 1) return binding.getComponent(this, ids[0]);\r
567                 RecordBinding rb = (RecordBinding) getIdentifierBinding();\r
568                 Object result = rb.createPartial();\r
569                 int ix = 0;\r
570                 for (int i : ids) {\r
571                         rb.setComponent(result, ix++, binding.getComponent(this, i));\r
572                 }\r
573                 return result;\r
574         }\r
575         \r
576         /**\r
577          * In this version of the bean, the hash/equals compares to identifiers.\r
578          * Identifier is a field with @Idenfitier annotation. \r
579          */\r
580         public static class Id extends Bean {\r
581                 protected Id() {}\r
582                 protected Id(Binding binding) {\r
583                         super(binding);\r
584                 }\r
585 \r
586                 @Override\r
587                 public int hashCode() {\r
588                         int hash = 0;\r
589                         try {\r
590                                 for (int index : binding.type().getIdentifiers())\r
591                                 {\r
592                                         Object c = binding.getComponent(this, index);\r
593                                         Binding cb = binding.getComponentBinding(index);\r
594                                         hash = 13*hash + cb.hashValue(c);\r
595                                 }\r
596                         } catch (BindingException e) {\r
597                         }\r
598                         return hash;\r
599                 }\r
600                 \r
601                 /**\r
602                  * Compare to another bean of same datatype for equal identifier. (Can be different binding)\r
603                  */\r
604                 @Override\r
605                 public boolean equals(Object obj) {\r
606                         if (obj==null) return false;\r
607                         if (obj==this) return true;\r
608                         if ( obj instanceof Bean == false ) return false;\r
609                         Bean other = (Bean) obj;\r
610                         try {\r
611                                 if (other.binding==binding)     {\r
612                                         \r
613                                         for (int index : binding.type().getIdentifiers())\r
614                                         {\r
615                                                 Object tc = binding.getComponent(this, index);\r
616                                                 Object oc = binding.getComponent(other, index);\r
617                                                 Binding cb = binding.getComponentBinding(index);\r
618                                                 if ( !cb.equals(tc, oc) ) return false;\r
619                                         }\r
620                                         \r
621                                 } else {\r
622 \r
623                                         for (int index : binding.type().getIdentifiers())\r
624                                         {\r
625                                                 Object tc = binding.getComponent(this, index);\r
626                                                 Object oc = binding.getComponent(other, index);\r
627                                                 Binding tcb = binding.getComponentBinding(index);\r
628                                                 Binding ocb = other.binding.getComponentBinding(index);\r
629                                                 if ( !Bindings.equals(tcb, tc, ocb, oc) ) return false;\r
630                                         }\r
631                                 }\r
632                                 return true;\r
633                         } catch (BindingException e) {\r
634                                 throw new RuntimeBindingException(e);\r
635                         } \r
636                 }\r
637                                 \r
638         }\r
639 \r
640 }\r