1 /*******************************************************************************
\r
2 * Copyright (c) 2010 Association for Decentralized Information Management in
\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
10 * VTT Technical Research Centre of Finland - initial API and implementation
\r
11 *******************************************************************************/
\r
12 package org.simantics.databoard.accessor.binary;
14 import java.io.IOException;
\r
15 import java.lang.ref.SoftReference;
\r
16 import java.util.ArrayList;
\r
17 import java.util.List;
\r
18 import java.util.concurrent.Executor;
\r
20 import org.simantics.databoard.Bindings;
\r
21 import org.simantics.databoard.accessor.Accessor;
\r
22 import org.simantics.databoard.accessor.RecordAccessor;
\r
23 import org.simantics.databoard.accessor.error.AccessorConstructionException;
\r
24 import org.simantics.databoard.accessor.error.AccessorException;
\r
25 import org.simantics.databoard.accessor.error.ReferenceException;
\r
26 import org.simantics.databoard.accessor.event.Event;
\r
27 import org.simantics.databoard.accessor.event.ValueAssigned;
\r
28 import org.simantics.databoard.accessor.file.FileRecordAccessor;
\r
29 import org.simantics.databoard.accessor.impl.AccessorParams;
\r
30 import org.simantics.databoard.accessor.impl.ListenerEntry;
\r
31 import org.simantics.databoard.accessor.interestset.InterestSet;
\r
32 import org.simantics.databoard.accessor.interestset.RecordInterestSet;
\r
33 import org.simantics.databoard.accessor.reference.ChildReference;
\r
34 import org.simantics.databoard.accessor.reference.IndexReference;
\r
35 import org.simantics.databoard.accessor.reference.LabelReference;
\r
36 import org.simantics.databoard.accessor.reference.NameReference;
\r
37 import org.simantics.databoard.adapter.AdaptException;
\r
38 import org.simantics.databoard.binding.Binding;
\r
39 import org.simantics.databoard.binding.RecordBinding;
\r
40 import org.simantics.databoard.binding.error.BindingConstructionException;
\r
41 import org.simantics.databoard.binding.error.BindingException;
\r
42 import org.simantics.databoard.binding.mutable.MutableVariant;
\r
43 import org.simantics.databoard.serialization.RuntimeSerializerConstructionException;
\r
44 import org.simantics.databoard.serialization.Serializer;
\r
45 import org.simantics.databoard.serialization.SerializerConstructionException;
\r
46 import org.simantics.databoard.type.RecordType;
\r
47 import org.simantics.databoard.util.binary.Blob;
\r
48 import org.simantics.databoard.util.binary.RandomAccessBinary.ByteSide;
\r
51 * Accessor to a Binary Record.
52 * Get and set field operations scan the file.
53 * File operations are most efficient if field-specific subaccessors are acquired
54 * and used instead of {@link #setFieldValue(int, Binding, Object)} and
55 * {@link #getFieldValue(int, Binding)}.
57 * Note, To increase the random access performance of the record, create sub-accessors of
60 * @author Toni Kalajainen <toni.kalajainen@vtt.fi>
62 public class BinaryRecord extends BinaryObject implements RecordAccessor, FileRecordAccessor {
64 /** Accessors to children */
65 java.lang.ref.Reference<BinaryObject>[] children;
67 RecordBinding binding;
69 @SuppressWarnings("unchecked")
70 public BinaryRecord(BinaryObject parent, Blob blob, RecordType type, AccessorParams params) throws AccessorConstructionException {
71 super(parent, blob, type, params);
72 if (type.isReferable()) throw new AccessorConstructionException("Refereable record are not supported");
75 binding = (RecordBinding) params.bindingScheme.getBinding(type);
\r
76 } catch (BindingConstructionException e) {
\r
77 throw new AccessorConstructionException(e);
\r
79 int count = type.getComponentCount();
80 children = new java.lang.ref.Reference[count];
84 public RecordType type() {
85 return (RecordType) type;
90 return type().getComponentCount();
94 * Get start position of a field
98 * @throws AccessorException
100 long getStartPosition(int fieldIndex) throws AccessorException {
\r
104 // sa, saIndex = getFloorExistingAccessor
105 BinaryObject sa = getExistingAccessor(fieldIndex);
106 if (sa!=null) return sa.b.getStartPositionInSourceBinary();
108 for (int i=fieldIndex; i>=0; i--) {
109 sa = getExistingAccessor(i);
116 long pos = saIndex==-1 ? 0L : sa.b.getStartPositionInSourceBinary();
118 if (saIndex<0) saIndex = 0;
119 for (int i=saIndex; i<fieldIndex; i++) {
120 Binding cb = binding.getComponentBinding(i);
121 Serializer cs = params.serializerScheme.getSerializer( cb );
125 } catch (IOException ioe) {
126 throw new AccessorException( ioe );
127 } catch (SerializerConstructionException e) {
\r
128 throw new AccessorException( e );
\r
134 @SuppressWarnings("unchecked")
136 public <T extends Accessor> T getFieldAccessor(String fieldName) throws AccessorConstructionException
138 int fieldIndex = type().getComponentIndex(fieldName);
139 if (fieldIndex<0) throw new AccessorConstructionException("Field "+fieldName+" does not exist");
140 return (T) getFieldAccessor(fieldIndex);
143 @SuppressWarnings("unchecked")
145 public <T extends Accessor> T getFieldAccessor(int index) throws AccessorConstructionException
147 if (index<0 || index>=count()) throw new ReferenceException("Field index ("+index+") out of bounds ("+count()+")");
151 // Get existing or create new
152 java.lang.ref.Reference<BinaryObject> ref = children[index];
153 BinaryObject sa = (ref!=null)?(BinaryObject)ref.get():null;
156 // Instantiate new accessor
157 Binding cb = binding.getComponentBinding(index);
158 Serializer cs = params.serializerScheme.getSerializer( cb );
159 long pos = getStartPosition(index);
162 long len = b.position() - pos;
164 sa = createSubAccessor(cb.type(), pos, len, params);
165 children[index] = new SoftReference<BinaryObject>(sa);
167 // Add component interest sets
168 ListenerEntry le = listeners;
170 RecordInterestSet is = le.getInterestSet();
171 InterestSet cis = is.getComponentInterest(index);
174 ChildReference childPath = ChildReference.concatenate( le.path, new IndexReference(index) );
175 sa.addListener(le.listener, cis, childPath, le.executor);
176 } catch (AccessorException e) {
177 throw new AccessorConstructionException(e);
185 } catch (IOException e) {
186 throw new AccessorConstructionException(e);
187 } catch (AccessorException e) {
188 throw new AccessorConstructionException(e);
189 } catch (SerializerConstructionException e) {
\r
190 throw new AccessorConstructionException(e);
\r
197 * Get existing sub accessor
199 * @return sub-accessor or <code>null</code>
201 BinaryObject getExistingAccessor(int index)
203 if (index<0 || index>=count()) throw new RuntimeException("Field index ("+index+") out of bounds ("+count()+")");
205 // Get existing or create new
206 java.lang.ref.Reference<BinaryObject> ref = children[index];
207 BinaryObject accessor = (ref!=null)?(BinaryObject)ref.get():null;
212 public Object getFieldValue(String fieldName, Binding fieldBinding)
\r
213 throws AccessorException {
\r
214 int fieldIndex = type().getComponentIndex(fieldName);
\r
215 if (fieldIndex<0) throw new AccessorException("Field "+fieldName+" does not exist");
\r
216 return getFieldValue(fieldIndex, fieldBinding);
\r
220 public Object getFieldValue(int index, Binding fieldBinding)
221 throws AccessorException {
\r
225 b.position( getStartPosition(index) );
226 List<Object> ids = new ArrayList<Object>(0);
227 return params.serializerScheme.getSerializer( fieldBinding ).deserialize(b, ids);
228 } catch (IOException e) {
229 throw new AccessorException(e);
230 } catch (RuntimeSerializerConstructionException e) {
231 throw new AccessorException(e);
232 } catch (SerializerConstructionException e) {
\r
233 throw new AccessorException(e);
\r
240 public void setFieldValue(String fieldName, Binding fieldBinding,
\r
241 Object value) throws AccessorException {
\r
245 setFieldValueNoflush(fieldName, fieldBinding, value);
\r
253 public void setFieldValueNoflush(String fieldName, Binding fieldBinding,
\r
254 Object value) throws AccessorException {
\r
258 int fieldIndex = type().getComponentIndex(fieldName);
\r
259 if (fieldIndex<0) throw new AccessorException("Field "+fieldName+" does not exist");
\r
260 setFieldValue(fieldIndex, fieldBinding, value);
\r
267 public void setFieldValue(int index, Binding fieldBinding,
268 Object value) throws AccessorException {
\r
272 setFieldValueNoflush(index, fieldBinding, value);
280 public void setFieldValueNoflush(int index, Binding cb, Object cv)
281 throws AccessorException {
286 BinaryObject sa = getExistingAccessor(index);
288 // Recursive write using existing sub accessor
289 sa.setValueNoflush(cb, cv);
294 long pos = getStartPosition(index);
295 Serializer cs = params.serializerScheme.getSerializer( cb );
297 // The size might need adjusting
298 if (cs.getConstantSize() == null) {
302 long oldLen = b.position() - pos;
305 long newLen = cs.getSize(cv, null);
307 b.position(pos+oldLen);
308 b.insertBytes(newLen - oldLen, ByteSide.Left);
310 b.position(pos+newLen);
311 b.removeBytes(oldLen - newLen, ByteSide.Left);
317 cs.serialize(b, null, cv);
320 ListenerEntry le = listeners;
322 RecordInterestSet is = le.getInterestSet();
323 if (is.inNotificationsOf(index)) {
324 MutableVariant newValue = is.inValuesOf(index) ? new MutableVariant(cb, cv) : null;
325 if (is.inValuesOf(index)) newValue = new MutableVariant(cb, cb.isImmutable() ? cv : cb.clone(cv));
\r
327 Event e = new ValueAssigned(new IndexReference(index), newValue);
332 } catch (IOException e) {
333 throw new AccessorException(e);
334 } catch (AdaptException e) {
335 throw new AccessorException(e);
336 } catch (SerializerConstructionException e) {
\r
337 throw new AccessorException(e);
\r
344 public void addListener(Listener listener, InterestSet interestSet,
345 ChildReference path, Executor executor) throws AccessorException {
346 RecordInterestSet is = (RecordInterestSet) interestSet;
347 super.addListener(listener, interestSet, path, executor);
348 // Apply component interest set to existing sub-accessors
349 if (is.componentInterests!=null) {
350 for (int i=0; i<count(); i++) {
351 InterestSet cis = is.getComponentInterest(i);
352 if (cis==null) continue;
353 BinaryObject sa = getExistingAccessor(i);
354 if (sa==null) continue;
355 ChildReference childPath = ChildReference.concatenate( path, new IndexReference(i));
356 sa.addListener(listener, cis, childPath, executor);
362 public void removeListener(Listener listener) throws AccessorException {
363 ListenerEntry e = detachListener(listener);
365 RecordInterestSet is = (RecordInterestSet) e.interestSet;
367 // Apply component interest set to existing sub-accessors
368 if (is.componentInterests!=null) {
369 for (int i=0; i<count(); i++) {
370 InterestSet cis = is.getComponentInterest(i);
371 if (cis==null) continue;
372 BinaryObject sa = getExistingAccessor(i);
373 if (sa==null) continue;
374 sa.removeListener(listener);
379 @SuppressWarnings("unchecked")
381 public <T extends Accessor> T getComponent(ChildReference reference) throws AccessorConstructionException {
382 if (reference==null) return (T) this;
384 if (reference instanceof LabelReference) {
\r
385 LabelReference lr = (LabelReference) reference;
\r
386 String fieldName = lr.label;
\r
387 Integer index = type().getComponentIndex(fieldName);
\r
388 if (index==null) throw new ReferenceException("RecordType doesn't have field by name \""+fieldName+"\"");
\r
389 BinaryObject sa = getFieldAccessor(index);
\r
390 if (reference.getChildReference() != null) sa = sa.getComponent(reference.getChildReference());
\r
394 if (reference instanceof IndexReference) {
395 IndexReference ref = (IndexReference) reference;
396 int index = ref.getIndex();
397 BinaryObject sa = getFieldAccessor(index);
\r
398 if (reference.getChildReference() != null) sa = sa.getComponent(reference.getChildReference());
\r
402 if (reference instanceof NameReference) {
403 NameReference ref = (NameReference) reference;
404 String fieldName = ref.getName();
405 Integer index = type().getComponentIndex(fieldName);
406 if (index==null) throw new ReferenceException("RecordType doesn't have field by name \""+fieldName+"\"");
407 BinaryObject sa = getFieldAccessor(index);
\r
408 if (reference.getChildReference() != null) sa = sa.getComponent(reference.getChildReference());
\r
412 throw new ReferenceException(reference.getClass()+" is not a subreference of RecordType");
417 public void setValueNoflush(Binding binding, Object newValue) throws AccessorException {
\r
421 RecordBinding rb = (RecordBinding) binding;
422 Serializer rs = params.serializerScheme.getSerializer( rb );
425 long newSize = rs.getSize(newValue, null);
426 b.setLength(newSize);
429 for (int index=0; index<count(); index++) {
430 long pos = b.position();
431 Binding cb = rb.getComponentBinding(index);
432 Serializer cs = params.serializerScheme.getSerializer( cb );
433 Object cv = rb.getComponent(newValue, index);
435 long len = b.position() - pos;
436 BinaryObject sa = getExistingAccessor(index);
438 sa.b.setPositionInSource(pos, len);
441 // Notify field specific listeners
442 if (listeners!=null) {
443 ListenerEntry le = listeners;
445 RecordInterestSet is = le.getInterestSet();
446 if (is.inNotificationsOf(index) /*&& !is.inNotifications()*/) {
447 Event e = new ValueAssigned( new IndexReference( index ), cb, is.inValuesOf(index) ? cv : null);
455 // Notify record specific listeners
\r
457 if (listeners!=null) {
\r
458 ListenerEntry le = listeners;
\r
459 while (le!=null) {
\r
460 RecordInterestSet is = le.getInterestSet();
\r
461 if (is.inNotifications() && !is.inComponentNotifications()) {
\r
462 Event e = new ValueAssigned( binding, is.inValues() ? newValue : null);
\r
470 } catch (BindingException e) {
471 throw new AccessorException(e);
472 } catch (IOException e) {
473 throw new AccessorException(e);
474 } catch (SerializerConstructionException e) {
\r
475 throw new AccessorException(e);
\r
482 Event applyLocal(Event e, boolean makeRollback) throws AccessorException {
483 if (e instanceof ValueAssigned) {
\r
484 ValueAssigned va = (ValueAssigned) e;
\r
485 Event rollback = null;
\r
486 if (makeRollback) {
\r
487 Binding binding = Bindings.getMutableBinding(type());
\r
488 rollback = new ValueAssigned(binding, getValue(binding));
\r
490 setValueNoflush(va.newValue.getBinding(), va.newValue.getValue());
\r
493 throw new AccessorException("Cannot apply "+e.getClass().getName()+" to Record Type");
\r