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