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.type;
14 import java.util.HashSet;
\r
15 import java.util.List;
\r
16 import java.util.Set;
\r
17 import java.util.StringTokenizer;
\r
19 import org.simantics.databoard.accessor.error.ReferenceException;
\r
20 import org.simantics.databoard.accessor.reference.ChildReference;
\r
21 import org.simantics.databoard.accessor.reference.IndexReference;
\r
22 import org.simantics.databoard.accessor.reference.LabelReference;
\r
23 import org.simantics.databoard.accessor.reference.NameReference;
\r
24 import org.simantics.databoard.annotations.Referable;
\r
25 import org.simantics.databoard.binding.error.DatatypeConstructionException;
\r
26 import org.simantics.databoard.util.IdentityPair;
\r
28 public @Referable class RecordType extends Datatype {
30 public static final Datatype VOID_TYPE = new RecordType(false);
\r
32 public static final String KEY_REFERABLE = "referable"; // "false"/"true"
\r
34 // Key to identifier(s) index
\r
35 public static final String KEY_IDENTIFIER = "identifier"; // "0" "0,1" "n[,m]"
\r
37 public static final Component[] NO_COMPONENTS = new Component[0];
\r
39 Component[] components = NO_COMPONENTS;
\r
41 /** Indices to identifiers of this record. This field is filled on request */
\r
42 private transient int[] identifiersIndices;
\r
44 /** Identifier type */
\r
45 private transient Datatype identifierType;
48 setReferable(false);
\r
51 public RecordType(boolean referable, Component...components) {
52 this.components = components;
\r
53 setReferable(referable);
\r
56 public boolean isReferable() {
\r
57 String str = metadata.get( KEY_REFERABLE );
58 return str!=null && str.equals( Boolean.TRUE.toString() );
61 public void setReferable( boolean referable ) {
\r
64 metadata.remove(KEY_REFERABLE);
\r
66 metadata.put(KEY_REFERABLE, Boolean.toString(referable));
\r
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)
\r
86 throws DatatypeConstructionException
\r
88 int ci = src.getComponentCount();
\r
89 for (int i=0; i<ci; i++) {
\r
90 Component sc = src.components[ i ];
\r
92 int li = getComponentIndex2( sc.name );
\r
94 addComponent(sc.name, sc.type);
\r
96 Component lc = components[ li ];
\r
97 if ( sc.type instanceof RecordType && lc.type instanceof RecordType ) {
\r
98 ((RecordType)lc.type).mergeRecord( (RecordType) sc.type );
\r
99 } else if ( sc.type.equals( lc.type ) ) {}
\r
101 throw new DatatypeConstructionException("Cannot merge field \""+sc.name+"\" "+sc.type.getClass().getName()+" and "+lc.getClass().getName());
\r
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() {
\r
122 components = new Component[0];
\r
126 public void removeComponent(String name) {
\r
127 int index = getComponentIndex2(name);
\r
128 if (index<0) throw new IllegalArgumentException();
\r
129 Component[] newComponents = new Component[ components.length -1 ];
\r
130 if (index>0) System.arraycopy(components, 0, newComponents, 0, index);
\r
131 if (index<newComponents.length) System.arraycopy(components, index+1, newComponents, index, newComponents.length - index);
\r
132 components = newComponents;
\r
137 public int getComponentCount() {
\r
138 return components.length;
\r
142 protected boolean deepEquals(Object obj, Set<IdentityPair<Datatype, Datatype>> compareHistory) {
143 if ( this==obj ) return true;
\r
144 if ( !hasEqualMetadata(obj) ) return false;
\r
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
\r
207 * @param index index
\r
208 * @return componenet type or <tt>null</tt> if index was invalid
\r
211 public Datatype getComponentType(int index) {
\r
212 if (index<0||index>=components.length) return null;
\r
213 return components[index].type;
\r
217 public Datatype getComponentType(ChildReference path) throws IllegalArgumentException {
\r
218 if (path==null) return this;
\r
219 if (path instanceof IndexReference) {
\r
220 IndexReference ir = (IndexReference) path;
\r
221 return components[ir.index].type.getComponentType(path.childReference);
\r
223 if (path instanceof NameReference) {
\r
224 NameReference nr = (NameReference) path;
\r
225 return getComponent( nr.name ).type.getComponentType(path.childReference);
\r
227 if (path instanceof LabelReference) {
\r
228 LabelReference lr = (LabelReference) path;
\r
230 Integer i = new Integer(lr.label);
\r
231 return getComponent( i ).type.getComponentType(path.childReference);
\r
232 } catch (NumberFormatException nfe) {
\r
233 return getComponent( lr.label ).type.getComponentType(path.childReference);
\r
236 throw new IllegalArgumentException();
\r
239 public boolean hasComponent(String fieldName) {
\r
240 for (int i=0; i<components.length; i++)
\r
241 if (components[i].name.equals(fieldName)) return true;
\r
246 * Get component by name.
\r
248 * @param fieldName component name
\r
249 * @return component index or <code>null</code> if one does not exist
\r
251 public Integer getComponentIndex(String fieldName) {
\r
252 for (int i=0; i<components.length; i++)
\r
253 if (components[i].name.equals(fieldName)) return i;
\r
258 * Get component by name.
\r
260 * @param fieldName component name
\r
261 * @return component index or -1 if one does not exist
\r
263 public int getComponentIndex2(String fieldName) {
\r
264 for (int i=0; i<components.length; i++)
\r
265 if (components[i].name.equals(fieldName)) return i;
\r
270 * Get component Datatype by field name
\r
272 * @return datatype or <code>null</code>
\r
274 public Datatype getComponentType(String fieldName) {
\r
275 int index = getComponentIndex2(fieldName);
\r
276 if (index<0) return null;
\r
277 return components[index].type;
\r
281 * Get component by name.
\r
283 * @param fieldName component name
\r
284 * @return component or <code>null</code> if one does not exist
\r
286 public Component getComponent(String fieldName) {
\r
287 for (Component c : components)
\r
288 if (c.name.equals(fieldName)) return c;
\r
293 * Get component by index.
\r
295 * @param index component index
\r
296 * @return component or <code>null</code> if one does not exist
\r
298 public Component getComponent(int index) {
\r
299 if (index<0||index>=components.length) return null;
\r
300 return components[index];
\r
303 public Component[] getComponents() {
\r
308 * Get an array of indices that describe which fields compose the identifier
\r
313 public int[] getIdentifiers() {
\r
314 String ids = metadata.get( KEY_IDENTIFIER );
\r
316 identifiersIndices = new int[0];
\r
319 StringTokenizer st = new StringTokenizer(ids, ",");
\r
321 int[] indices = new int[ st.countTokens() ];
\r
322 for (int i=0; i<indices.length; i++) {
\r
323 String token = st.nextToken();
\r
325 indices[i] = Integer.valueOf(token);
\r
326 } catch ( NumberFormatException nfe ) {
\r
330 identifiersIndices = indices;
\r
332 return identifiersIndices;
\r
336 * Set which fields compose the identifier of this record
\r
340 public void setIdentifiers(int...indices)
\r
342 if (indices.length==0) {
\r
343 metadata.remove( KEY_IDENTIFIER );
\r
346 identifiersIndices = indices;
\r
347 StringBuilder sb = new StringBuilder();
\r
348 for (int i=0; i<indices.length; i++) {
\r
349 if (i>0) sb.append(',');
\r
350 sb.append( Integer.toString(indices[i]) );
\r
353 String str = sb.toString();
\r
354 if ( str.isEmpty() ) {
\r
355 metadata.remove( KEY_IDENTIFIER );
\r
357 metadata.put( KEY_IDENTIFIER, str );
\r
362 * Set which fields compose the identifier of this record
\r
365 public void setIdentifiers(List<Integer> indices)
\r
367 int[] indices2 = new int[indices.size()];
\r
368 for (int i=0; i<indices.size(); i++) indices2[i] = indices.get(i);
\r
369 setIdentifiers(indices2);
\r
372 public boolean isIdentifier( int fieldIndex )
\r
374 int[] ids = getIdentifiers();
\r
375 if (ids == null) return false;
\r
376 for (int index : ids)
\r
378 if (index == fieldIndex) return true;
\r
384 * Get a datatype that describes the identifier of this type.
\r
385 * If no field has Identifier annotation, the result is null.
\r
386 * If more than one field is an identifier the type is a record
\r
387 * with all composing fields.
\r
389 * @return identifier type or null
\r
391 public Datatype getIdentifierType()
\r
393 if ( identifierType != null ) return identifierType;
\r
395 int[] ids = getIdentifiers();
\r
396 if (ids.length==0) return null;
\r
398 if (ids.length==1) {
\r
399 identifierType = getComponentType(ids[0]);
\r
402 RecordType rt = new RecordType();
\r
403 for (int i : ids) {
\r
404 Component c = getComponent(i);
\r
405 rt.addComponent( c.name, c.type );
\r
407 identifierType = rt;
\r
408 return identifierType;
\r
411 @SuppressWarnings("unchecked")
\r
412 public <T extends Datatype> T getChildType( ChildReference reference ) throws ReferenceException
\r
414 if (reference==null) return (T) this;
\r
416 if (reference instanceof LabelReference) {
\r
417 LabelReference lr = (LabelReference) reference;
\r
418 String fieldName = lr.label;
\r
419 int index = getComponentIndex2(fieldName);
\r
420 if (index<0) throw new ReferenceException("RecordType doesn't have field by name \""+fieldName+"\"");
\r
421 return components[index].type.getChildType(reference.childReference);
\r
424 if (reference instanceof IndexReference) {
\r
425 IndexReference ref = (IndexReference) reference;
\r
426 int index = ref.getIndex();
\r
427 if ( index<0 || index>=components.length ) new ReferenceException("RecordType doesn't have field at index+"+index);
\r
428 return components[index].type.getChildType(reference.childReference);
\r
431 if (reference instanceof NameReference) {
\r
432 NameReference lr = (NameReference) reference;
\r
433 String fieldName = lr.name;
\r
434 int index = getComponentIndex2(fieldName);
\r
435 if (index<0) throw new ReferenceException("RecordType doesn't have field by name \""+fieldName+"\"");
\r
436 return components[index].type.getChildType(reference.childReference);
\r
439 throw new ReferenceException(reference.getClass()+" is not a subreference of RecordType");
\r