1 /*******************************************************************************
2 * Copyright (c) 2010 Association for Decentralized Information Management in
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
10 * VTT Technical Research Centre of Finland - initial API and implementation
11 *******************************************************************************/
12 package org.simantics.databoard.type;
14 import java.util.HashSet;
15 import java.util.List;
17 import java.util.StringTokenizer;
19 import org.simantics.databoard.accessor.error.ReferenceException;
20 import org.simantics.databoard.accessor.reference.ChildReference;
21 import org.simantics.databoard.accessor.reference.IndexReference;
22 import org.simantics.databoard.accessor.reference.LabelReference;
23 import org.simantics.databoard.accessor.reference.NameReference;
24 import org.simantics.databoard.annotations.Referable;
25 import org.simantics.databoard.binding.error.DatatypeConstructionException;
26 import org.simantics.databoard.util.IdentityPair;
28 public @Referable class RecordType extends Datatype {
30 public static final Datatype VOID_TYPE = new RecordType(false);
32 public static final String KEY_REFERABLE = "referable"; // "false"/"true"
34 // Key to identifier(s) index
35 public static final String KEY_IDENTIFIER = "identifier"; // "0" "0,1" "n[,m]"
37 public static final Component[] NO_COMPONENTS = new Component[0];
39 Component[] components = NO_COMPONENTS;
41 /** Indices to identifiers of this record. This field is filled on request */
42 private transient int[] identifiersIndices;
44 /** Identifier type */
45 private transient Datatype identifierType;
51 public RecordType(boolean referable, Component...components) {
52 this.components = components;
53 setReferable(referable);
56 public boolean isReferable() {
57 String str = metadata.get( KEY_REFERABLE );
58 return str!=null && str.equals( Boolean.TRUE.toString() );
61 public void setReferable( boolean referable ) {
64 metadata.remove(KEY_REFERABLE);
66 metadata.put(KEY_REFERABLE, Boolean.toString(referable));
71 protected void collectSubtypes(Set<Datatype> subtypes,
72 Set<Datatype> recursiveSubtypes) {
73 if(!subtypes.add(this)) {
74 recursiveSubtypes.add(this);
77 for(Component component : components)
78 component.type.collectSubtypes(subtypes, recursiveSubtypes);
81 public void setComponents(Component[] components) {
82 this.components = components;
85 public void mergeRecord(RecordType src)
86 throws DatatypeConstructionException
88 int ci = src.getComponentCount();
89 for (int i=0; i<ci; i++) {
90 Component sc = src.components[ i ];
92 int li = getComponentIndex2( sc.name );
94 addComponent(sc.name, sc.type);
96 Component lc = components[ li ];
97 if ( sc.type instanceof RecordType && lc.type instanceof RecordType ) {
98 ((RecordType)lc.type).mergeRecord( (RecordType) sc.type );
99 } else if ( sc.type.equals( lc.type ) ) {}
101 throw new DatatypeConstructionException("Cannot merge field \""+sc.name+"\" "+sc.type.getClass().getName()+" and "+lc.getClass().getName());
108 public void addComponent(String name, Datatype type)
110 Component c = new Component(name, type);
111 if (components == null) {
112 components = new Component[] { c };
114 Component[] newComponents = new Component[ components.length +1 ];
115 System.arraycopy(components, 0, newComponents, 0, components.length);
116 newComponents[ components.length ] = c;
117 components = newComponents;
121 public void clear() {
122 components = new Component[0];
126 public void removeComponent(String name) {
127 int index = getComponentIndex2(name);
128 if (index<0) throw new IllegalArgumentException();
129 Component[] newComponents = new Component[ components.length -1 ];
130 if (index>0) System.arraycopy(components, 0, newComponents, 0, index);
131 if (index<newComponents.length) System.arraycopy(components, index+1, newComponents, index, newComponents.length - index);
132 components = newComponents;
137 public int getComponentCount() {
138 return components.length;
142 protected boolean deepEquals(Object obj, Set<IdentityPair<Datatype, Datatype>> compareHistory) {
143 if ( this==obj ) return true;
144 if ( !hasEqualMetadata(obj) ) return false;
145 if (obj instanceof RecordType == false) return false;
146 RecordType other = (RecordType) obj;
148 if (components.length!= other.components.length) return false;
150 for (int i = 0; i<components.length; i++) {
151 Component lc = components[i];
152 Component rc = other.components[i];
153 if (!lc.name.equals(rc.name)) return false;
158 if (compareHistory==null) compareHistory = new HashSet<IdentityPair<Datatype, Datatype>>(1);
160 IdentityPair<Datatype, Datatype> pair = new IdentityPair<Datatype, Datatype>(this, other);
161 if (compareHistory.contains(pair)) return true;
162 compareHistory.add(pair);
164 for (int i = 0; i<components.length; i++) {
165 Component lc = components[i];
166 Component rc = other.components[i];
167 if (!lc.type.deepEquals(rc.type, compareHistory)) return false;
173 public int hashCode() {
174 int hash = super.hashCode();
175 for (Component c : components)
176 hash = hash*13 + 7 * c.name.hashCode() /*+ 3*c.type.hashCode()*/;
181 public void accept(Visitor1 v, Object obj) {
186 public <T> T accept(Visitor<T> v) {
187 return v.visit(this);
191 * Return true if the record is a tuple.
192 * Tuple is a record with all components are named as a number, the index number of the field.
193 * Empty record is a tuple
195 * @return true if the record type is a tuple.
197 public boolean isTupleType() {
198 if (components.length==0) return false;
199 for (int i=0; i<getComponentCount(); i++) {
200 if (!getComponent(i).name.equals(Integer.toString(i))) return false;
206 * Get component type by index
208 * @return componenet type or <tt>null</tt> if index was invalid
211 public Datatype getComponentType(int index) {
212 if (index<0||index>=components.length) return null;
213 return components[index].type;
217 public Datatype getComponentType(ChildReference path) throws IllegalArgumentException {
218 if (path==null) return this;
219 if (path instanceof IndexReference) {
220 IndexReference ir = (IndexReference) path;
221 return components[ir.index].type.getComponentType(path.childReference);
223 if (path instanceof NameReference) {
224 NameReference nr = (NameReference) path;
225 return getComponent( nr.name ).type.getComponentType(path.childReference);
227 if (path instanceof LabelReference) {
228 LabelReference lr = (LabelReference) path;
230 Integer i = new Integer(lr.label);
231 return getComponent( i ).type.getComponentType(path.childReference);
232 } catch (NumberFormatException nfe) {
233 return getComponent( lr.label ).type.getComponentType(path.childReference);
236 throw new IllegalArgumentException();
239 public boolean hasComponent(String fieldName) {
240 for (int i=0; i<components.length; i++)
241 if (components[i].name.equals(fieldName)) return true;
246 * Get component by name.
248 * @param fieldName component name
249 * @return component index or <code>null</code> if one does not exist
251 public Integer getComponentIndex(String fieldName) {
252 for (int i=0; i<components.length; i++)
253 if (components[i].name.equals(fieldName)) return i;
258 * Get component by name.
260 * @param fieldName component name
261 * @return component index or -1 if one does not exist
263 public int getComponentIndex2(String fieldName) {
264 for (int i=0; i<components.length; i++)
265 if (components[i].name.equals(fieldName)) return i;
270 * Get component Datatype by field name
272 * @return datatype or <code>null</code>
274 public Datatype getComponentType(String fieldName) {
275 int index = getComponentIndex2(fieldName);
276 if (index<0) return null;
277 return components[index].type;
281 * Get component by name.
283 * @param fieldName component name
284 * @return component or <code>null</code> if one does not exist
286 public Component getComponent(String fieldName) {
287 for (Component c : components)
288 if (c.name.equals(fieldName)) return c;
293 * Get component by index.
295 * @param index component index
296 * @return component or <code>null</code> if one does not exist
298 public Component getComponent(int index) {
299 if (index<0||index>=components.length) return null;
300 return components[index];
303 public Component[] getComponents() {
308 * Get an array of indices that describe which fields compose the identifier
313 public int[] getIdentifiers() {
314 String ids = metadata.get( KEY_IDENTIFIER );
316 identifiersIndices = new int[0];
319 StringTokenizer st = new StringTokenizer(ids, ",");
321 int[] indices = new int[ st.countTokens() ];
322 for (int i=0; i<indices.length; i++) {
323 String token = st.nextToken();
325 indices[i] = Integer.valueOf(token);
326 } catch ( NumberFormatException nfe ) {
330 identifiersIndices = indices;
332 return identifiersIndices;
336 * Set which fields compose the identifier of this record
340 public void setIdentifiers(int...indices)
342 if (indices.length==0) {
343 metadata.remove( KEY_IDENTIFIER );
346 identifiersIndices = indices;
347 StringBuilder sb = new StringBuilder();
348 for (int i=0; i<indices.length; i++) {
349 if (i>0) sb.append(',');
350 sb.append( Integer.toString(indices[i]) );
353 String str = sb.toString();
354 if ( str.isEmpty() ) {
355 metadata.remove( KEY_IDENTIFIER );
357 metadata.put( KEY_IDENTIFIER, str );
362 * Set which fields compose the identifier of this record
365 public void setIdentifiers(List<Integer> indices)
367 int[] indices2 = new int[indices.size()];
368 for (int i=0; i<indices.size(); i++) indices2[i] = indices.get(i);
369 setIdentifiers(indices2);
372 public boolean isIdentifier( int fieldIndex )
374 int[] ids = getIdentifiers();
375 if (ids == null) return false;
376 for (int index : ids)
378 if (index == fieldIndex) return true;
384 * Get a datatype that describes the identifier of this type.
385 * If no field has Identifier annotation, the result is null.
386 * If more than one field is an identifier the type is a record
387 * with all composing fields.
389 * @return identifier type or null
391 public Datatype getIdentifierType()
393 if ( identifierType != null ) return identifierType;
395 int[] ids = getIdentifiers();
396 if (ids.length==0) return null;
399 identifierType = getComponentType(ids[0]);
402 RecordType rt = new RecordType();
404 Component c = getComponent(i);
405 rt.addComponent( c.name, c.type );
408 return identifierType;
411 @SuppressWarnings("unchecked")
412 public <T extends Datatype> T getChildType( ChildReference reference ) throws ReferenceException
414 if (reference==null) return (T) this;
416 if (reference instanceof LabelReference) {
417 LabelReference lr = (LabelReference) reference;
418 String fieldName = lr.label;
419 int index = getComponentIndex2(fieldName);
420 if (index<0) throw new ReferenceException("RecordType doesn't have field by name \""+fieldName+"\"");
421 return components[index].type.getChildType(reference.childReference);
424 if (reference instanceof IndexReference) {
425 IndexReference ref = (IndexReference) reference;
426 int index = ref.getIndex();
427 if ( index<0 || index>=components.length ) new ReferenceException("RecordType doesn't have field at index+"+index);
428 return components[index].type.getChildType(reference.childReference);
431 if (reference instanceof NameReference) {
432 NameReference lr = (NameReference) reference;
433 String fieldName = lr.name;
434 int index = getComponentIndex2(fieldName);
435 if (index<0) throw new ReferenceException("RecordType doesn't have field by name \""+fieldName+"\"");
436 return components[index].type.getChildType(reference.childReference);
439 throw new ReferenceException(reference.getClass()+" is not a subreference of RecordType");