]> gerrit.simantics Code Review - simantics/platform.git/blob - bundles/org.simantics.db.layer0/src/org/simantics/db/layer0/variable/AbstractVariable.java
Replaceable Defined Component Types
[simantics/platform.git] / bundles / org.simantics.db.layer0 / src / org / simantics / db / layer0 / variable / AbstractVariable.java
1 package org.simantics.db.layer0.variable;
2
3 import gnu.trove.map.hash.THashMap;
4
5 import java.util.Collection;
6 import java.util.Collections;
7 import java.util.HashMap;
8 import java.util.Map;
9 import java.util.Set;
10
11 import org.simantics.databoard.Bindings;
12 import org.simantics.databoard.Databoard;
13 import org.simantics.databoard.binding.Binding;
14 import org.simantics.databoard.binding.StringBinding;
15 import org.simantics.databoard.binding.error.BindingConstructionException;
16 import org.simantics.databoard.binding.error.BindingException;
17 import org.simantics.databoard.binding.mutable.Variant;
18 import org.simantics.databoard.type.Datatype;
19 import org.simantics.databoard.util.URIStringUtils;
20 import org.simantics.datatypes.literal.GUID;
21 import org.simantics.db.ReadGraph;
22 import org.simantics.db.Resource;
23 import org.simantics.db.WriteGraph;
24 import org.simantics.db.common.procedure.adapter.TransientCacheAsyncListener;
25 import org.simantics.db.common.request.PossibleIndexRoot;
26 import org.simantics.db.common.request.PropertyMapOfResource;
27 import org.simantics.db.common.utils.NameUtils;
28 import org.simantics.db.exception.AdaptionException;
29 import org.simantics.db.exception.DatabaseException;
30 import org.simantics.db.layer0.exception.InvalidVariableException;
31 import org.simantics.db.layer0.exception.MissingVariableException;
32 import org.simantics.db.layer0.exception.MissingVariableValueException;
33 import org.simantics.db.layer0.request.PossibleResource;
34 import org.simantics.db.layer0.request.PropertyInfo;
35 import org.simantics.db.layer0.request.VariableRVIRequest;
36 import org.simantics.db.layer0.variable.RVI.GuidRVIPart;
37 import org.simantics.db.layer0.variable.RVI.RVIPart;
38 import org.simantics.db.layer0.variable.RVI.ResourceRVIPart;
39 import org.simantics.db.layer0.variable.RVI.StringRVIPart;
40 import org.simantics.db.layer0.variable.Variables.Role;
41 import org.simantics.layer0.Layer0;
42 import org.simantics.scl.runtime.SCLContext;
43 import org.simantics.scl.runtime.function.Function2;
44
45 /**
46  * Abstract implementation of Variable -interface.
47  * 
48  * @author Hannu Niemistö
49  */
50 public abstract class AbstractVariable implements Variable {
51
52     @SuppressWarnings("rawtypes")
53     final public VariableNode node;
54
55     public AbstractVariable(@SuppressWarnings("rawtypes") VariableNode node) {
56         this.node = node;
57     }
58
59     /**
60      * Returns a variable that is not one of the standard properties listed
61      * in <a href="https://www.simantics.org/wiki/index.php/Org.simantics.db.layer0.variable.Variable#Standard_required_properties">specification</a>.
62      */
63     protected abstract Variable getPossibleDomainProperty(ReadGraph graph, String name) throws DatabaseException;
64     public abstract Variable getPossibleExtraProperty(ReadGraph graph, String name) throws DatabaseException;
65     public abstract Variable getPossibleChild(ReadGraph graph, String name) throws DatabaseException;
66         
67     public abstract void collectExtraProperties(ReadGraph graph, Map<String, Variable> properties) throws DatabaseException;
68     public abstract Map<String, Variable> collectDomainProperties(ReadGraph graph, Map<String, Variable> map) throws DatabaseException;
69     public abstract Collection<Variable> getChildren(ReadGraph graph) throws DatabaseException;
70     
71     public abstract <T> T getValue(ReadGraph graph) throws DatabaseException;
72     public abstract <T> T getValue(ReadGraph graph, Binding binding) throws DatabaseException;
73
74     public abstract void setValue(WriteGraph graph, Object value, Binding binding) throws DatabaseException;
75     public abstract String getName(ReadGraph graph) throws DatabaseException;
76     //public abstract Object getSerialized(ReadGraph graph) throws DatabaseException;
77     public abstract Variable getParent(ReadGraph graph) throws DatabaseException;
78     public abstract Role getRole(ReadGraph graph) throws DatabaseException;
79     public abstract Resource getRepresents(ReadGraph graph) throws DatabaseException;
80     
81     public abstract Set<String> getClassifications(ReadGraph graph) throws DatabaseException;
82      
83     @Override
84     public PropertyInfo getPropertyInfo(ReadGraph graph) throws DatabaseException {
85         throw new DatabaseException("PropertyInfo is not available");
86     }
87     
88     @Override
89     public Resource getIndexRoot(ReadGraph graph) throws DatabaseException {
90         Resource represents = getPossibleRepresents(graph);
91         if(represents != null) return graph.syncRequest(new PossibleIndexRoot(represents));
92         Variable parent = getParent(graph);
93         if(parent == null) return null;
94         return parent.getIndexRoot(graph);
95     }
96     
97     public void validate(ReadGraph graph) throws DatabaseException {
98     }
99
100     public String getIdentifier() {
101         return getClass().getSimpleName();
102     }
103     
104     
105     @Override
106     public Role getPossibleRole(ReadGraph graph) throws DatabaseException {
107         try {
108                 return getRole(graph);
109         } catch (DatabaseException e) {
110                 return null;
111         }
112     }
113     
114     protected Variable resolveChild(ReadGraph graph, Resource resource) throws DatabaseException {
115         String rName = graph.getRelatedValue(resource, Layer0.getInstance(graph).HasName, Bindings.STRING);
116         for(Variable child : browseChildren(graph)) {
117                 String name = child.getPossiblePropertyValue(graph, Variables.NAME, Bindings.STRING);
118                 if(rName.equals(name)) return child;
119         }
120         throw new DatabaseException("Could not resolve child " + resource);
121     }
122     
123     protected Variable resolveChild(ReadGraph graph, GuidRVIPart part) throws DatabaseException {
124         return StandardRVIResolver.resolveChildDefault(graph, this, part);
125     }
126     
127     protected Variable resolveProperty(ReadGraph graph, Resource resource) throws DatabaseException {
128         String rName = graph.getRelatedValue(resource, Layer0.getInstance(graph).HasName, Bindings.STRING);
129         for(Variable child : browseProperties(graph)) {
130                 String name = child.getPossiblePropertyValue(graph, Variables.NAME, Bindings.STRING);
131                 if(rName.equals(name)) return child;
132         }
133         throw new DatabaseException("Could not resolve child " + resource);
134     }
135
136     protected Variable resolveProperty(ReadGraph graph, GuidRVIPart part) throws DatabaseException {
137         return StandardRVIResolver.resolvePropertyDefault(graph, this, part);
138     }
139
140     protected Variable resolvePossibleChild(ReadGraph graph, Resource resource) throws DatabaseException {
141         String rName = graph.getRelatedValue(resource, Layer0.getInstance(graph).HasName, Bindings.STRING);
142         for(Variable child : browseChildren(graph)) {
143             String name = child.getPossiblePropertyValue(graph, Variables.NAME, Bindings.STRING);
144             if(rName.equals(name)) return child;
145         }
146         return null;
147     }
148     
149     protected Variable resolvePossibleChild(ReadGraph graph, GuidRVIPart part) throws DatabaseException {
150                 Layer0 L0 = Layer0.getInstance(graph);
151         for(Variable child : browseChildren(graph)) {
152                 GUID id = child.getPossiblePropertyValue(graph, L0.identifier, GUID.BINDING);
153                 if(id != null) {
154                         if(id.mostSignificant == part.mostSignificant && id.leastSignificant == part.leastSignificant)
155                                 return child;
156                 }
157         }
158         return null;
159     }
160
161     protected Variable resolvePossibleProperty(ReadGraph graph, Resource resource) throws DatabaseException {
162         String rName = graph.getRelatedValue(resource, Layer0.getInstance(graph).HasName, Bindings.STRING);
163         for(Variable child : browseProperties(graph)) {
164             String name = child.getPossiblePropertyValue(graph, Variables.NAME, Bindings.STRING);
165             if(rName.equals(name)) return child;
166         }
167         return null;
168     }
169
170     protected Variable resolvePossibleProperty(ReadGraph graph, GuidRVIPart part) throws DatabaseException {
171                 Layer0 L0 = Layer0.getInstance(graph);
172         for(Variable child : browseProperties(graph)) {
173                 GUID id = child.getPossiblePropertyValue(graph, L0.identifier, GUID.BINDING);
174                 if(id != null) {
175                         if(id.mostSignificant == part.mostSignificant && id.leastSignificant == part.leastSignificant)
176                                 return child;
177                 }
178         }
179         return null;
180     }
181
182     public String getLabel(ReadGraph graph) throws DatabaseException { 
183         return getName(graph);
184     }
185     
186     public String getPossibleLabel(ReadGraph graph) throws DatabaseException {
187         Resource represents = getPossibleRepresents(graph);
188         if(represents == null) return null;
189                 return graph.getPossibleRelatedValue2(represents, graph.getService(Layer0.class).HasLabel, getParent(graph), Bindings.STRING);
190     }
191
192     public RVIPart getRVIPart(ReadGraph graph) throws DatabaseException {
193         throw new UnsupportedOperationException();
194     }
195
196     @Override
197     public RVI getRVI(ReadGraph graph) throws DatabaseException {
198         Databoard databoard = graph.getService( Databoard.class );
199         Binding rviBinding = databoard.getBindingUnchecked( RVI.class );
200         if(Variables.isContext(graph, this)) {
201                 return RVI.empty( rviBinding );
202         } else {
203                 Variable parent = getParent(graph);
204                 if (parent == null)
205                         // TODO: consider using a more suitable exception here to better convey the situation.
206                         throw new MissingVariableException("no parent for variable " + this + " (URI=" + getPossibleURI(graph) + ")");
207                 RVI base = graph.syncRequest(new VariableRVIRequest(parent));
208                 RVIPart part = getRVIPart(graph);
209                 return new RVIBuilder(base).append(part).toRVI();
210         }
211     }
212     
213     protected Variable getDomainProperty(ReadGraph graph, String name) throws DatabaseException {
214         Variable property = getPossibleDomainProperty(graph, name);
215         if(property == null)
216             throw new MissingVariableException(getIdentifier() + ": Didn't find property " + name + ".");
217         return property;
218     }
219     
220     protected void addProperty(Map<String, Variable> properties, String name, Object value, Binding binding) {
221         if(value != null) {
222             properties.put(name, new ConstantPropertyVariable(this, name, value, binding));
223         }
224     }
225
226     protected Variable getNameVariable(ReadGraph graph) throws DatabaseException {
227         return new ConstantPropertyVariable(this, Variables.NAME, getName(graph), Bindings.STRING);
228     }
229
230     protected Variable getLabelVariable(ReadGraph graph) throws DatabaseException {
231         return new ConstantPropertyVariable(this, Variables.LABEL, getPossibleLabel(graph), Bindings.STRING);
232     }
233
234     final public Collection<Variable> browseProperties(ReadGraph graph) throws DatabaseException {
235         return getProperties(graph);
236     }
237
238     private Map<String, Variable> getPropertyMap(ReadGraph graph, String classification) throws DatabaseException {
239         return collectDomainProperties(graph, classification, null);
240     }
241
242     final static class PropertyMap extends THashMap<String,Variable> {
243         
244         final private Variable variable;
245         
246         PropertyMap(Variable variable) {
247                 this.variable = variable;
248         }
249
250         private Variable getTypeVariable() {
251                 
252                 return new AbstractConstantPropertyVariable(variable, Variables.TYPE, null) {
253
254                                 @SuppressWarnings("unchecked")
255                                 @Override
256                                 public <T> T getValue(ReadGraph graph) throws DatabaseException {
257                                         Resource represents = parent.getRepresents(graph);
258                                         if(represents == null) return null;
259                                         return (T)graph.getPossibleType(represents, Layer0.getInstance(graph).Entity);
260                                 }
261
262                                 @Override
263                                 public <T> T getValue(ReadGraph graph, Binding binding) throws DatabaseException {
264                                         return getValue(graph);
265                                 }
266                         
267                 };
268         }
269
270         private Variable getURIVariable() {
271                 
272                 return new AbstractConstantPropertyVariable(variable, Variables.URI, null) {
273
274                                 @SuppressWarnings("unchecked")
275                                 @Override
276                                 public <T> T getValue(ReadGraph graph) throws DatabaseException {
277                                         return (T)variable.getURI(graph);
278                                 }
279
280                                 @Override
281                                 public <T> T getValue(ReadGraph graph, Binding binding) throws DatabaseException {
282                                         return getValue(graph);
283                                 }
284                         
285                 };
286         }
287
288         public Variable get(Object key) {
289                 Variable result = super.get(key);
290                 if(result != null) return result;
291                 
292                 if(Variables.TYPE.equals(key)) return getTypeVariable();
293                 else if(Variables.URI.equals(key)) return getURIVariable();
294                 
295                         return null;
296                 }
297         
298     }
299     
300     private Map<String, Variable> getPropertyMap(ReadGraph graph) throws DatabaseException {
301         PropertyMap properties = new PropertyMap(this);
302 //        Map<String, Variable> properties = new HashMap<String, Variable>();
303 //        try {
304 //            properties.put(Variables.NAME, getNameVariable(graph));
305 //        } catch (DatabaseException e) {
306 //            // A variable that has no name doesn't exist by definition.
307 //            // Therefore it can't have any properties.
308 //            return Collections.emptyMap();
309 //        }
310 //        try {
311 //            Variable labelVariable = getLabelVariable(graph);
312 //            if(labelVariable != null) properties.put(Variables.LABEL, getLabelVariable(graph));
313 //        } catch (DatabaseException e) {
314 //            // Label not absolutely mandatory.
315 //        }
316 //        addProperty(properties, Variables.TYPE, getPossibleType(graph), null);
317 //        addProperty(properties, Variables.URI, getPossibleURI(graph), Bindings.STRING);
318         //addProperty(properties, Variables.SERIALISED, getSerialized(graph), Bindings.STRING);
319         //addProperty(properties, Variables.PARENT, getParent(graph), null);
320 //        addProperty(properties, Variables.ROLE, getRole(graph), Bindings.STRING);
321 //        addProperty(properties, Variables.REPRESENTS, getPossibleRepresents(graph), null);
322         collectExtraProperties(graph, properties);
323         collectDomainProperties(graph, properties);
324         return properties;
325     }
326     
327     @Override
328     public Collection<Variable> getProperties(ReadGraph graph) throws DatabaseException {
329         return getPropertyMap(graph).values();
330     }
331     
332     public Collection<Variable> getProperties(ReadGraph graph, String classification) throws DatabaseException {
333         Map<String,Variable> propertyMap = getPropertyMap(graph, classification);
334         if(propertyMap == null) return Collections.emptyList();
335         else return propertyMap.values();
336     }
337
338     @Override
339     public Collection<Variable> getProperties(ReadGraph graph, Resource property) throws DatabaseException {
340         return getProperties(graph, uri(graph, property));
341     }
342
343     final public Collection<Variable> browseChildren(ReadGraph graph) throws DatabaseException {
344         return getChildren(graph);
345     }
346     
347     @Override
348     public Variable getPossibleProperty(ReadGraph graph, String name)
349             throws DatabaseException {
350         if(Variables.NAME.equals(name)) {
351                 return getNameVariable(graph);
352         }
353         if(Variables.LABEL.equals(name)) {
354                 return getLabelVariable(graph);
355         }
356         if(Variables.TYPE.equals(name)) {
357             Object value = getPossibleType(graph);
358             if(value != null)
359                 return new ConstantPropertyVariable(this, name, value, null);
360         }
361         if(Variables.URI.equals(name)) {
362             // TODO: getPossibleURI or getURI?
363             Object value = getURI(graph);
364             if(value != null)
365                 return new ConstantPropertyVariable(this, name, value, Bindings.STRING);
366         }
367 //        if(Variables.SERIALISED.equals(name)) {
368 //            Object value = getSerialized(graph);
369 //            if(value != null)
370 //                return new ConstantPropertyVariable(this, name, value, Bindings.STRING);
371 //        }
372         /*if(Variables.PARENT.equals(name)) {
373             Object value = getParent(graph);
374             if(value != null)
375                 return new ConstantPropertyVariable(this, name, value, null);
376         }*/
377 //        if(Variables.ROLE.equals(name)) {
378 //            Object value = getRole(graph);
379 //            if(value != null)
380 //                return new ConstantPropertyVariable(this, name, value, null);
381 //        }
382 //        if(Variables.REPRESENTS.equals(name)) {
383 //            Object value = getRepresents(graph);
384 //            if(value != null)
385 //                return new ConstantPropertyVariable(this, name, value, null);
386 //        }
387         Variable extra = getPossibleExtraProperty(graph, name);
388         if(extra != null) return extra;
389         return getPossibleDomainProperty(graph, name);
390     }
391     
392     @Override
393     public Variable getPossibleProperty(ReadGraph graph, Resource property) throws DatabaseException {
394         return getPossibleProperty(graph, name(graph, property));
395     }
396
397     @SuppressWarnings("unchecked")
398     protected <T> T checkNull(ReadGraph graph, Object value) throws DatabaseException {
399         if(value == null)
400             throw new MissingVariableValueException(getClass().getSimpleName() + ": Didn't find value for " + getPossibleURI(graph));
401         return (T)value;
402     }
403
404     private String name(ReadGraph graph, Resource property) throws DatabaseException {
405         return graph.getRelatedValue(property, Layer0.getInstance(graph).HasName, Bindings.STRING);
406     }
407     
408     private String uri(ReadGraph graph, Resource property) throws DatabaseException {
409         return graph.getURI(property);
410     }
411
412     @Override
413     public <T> T getPropertyValue(ReadGraph graph, String name) throws DatabaseException {
414         if(Variables.LABEL.equals(name)) return checkNull(graph, getLabel(graph));
415         Variable result = getDomainProperty(graph, name);
416         if(result != null) return result.getValue(graph);
417         if(Variables.NAME.equals(name)) return checkNull(graph, getName(graph));
418         if(Variables.TYPE.equals(name)) return checkNull(graph, getPossibleType(graph));
419         if(Variables.URI.equals(name)) return checkNull(graph, getURI(graph));
420 //      if(Variables.SERIALISED.equals(name)) return checkNull(graph, getSerialized(graph));
421 //      if(Variables.ROLE.equals(name)) return checkNull(graph, getRole(graph));
422 //      if(Variables.REPRESENTS.equals(name)) return checkNull(graph, getRepresents(graph));
423         Variable extra = getPossibleExtraProperty(graph, name);
424         if(extra != null) return extra.getValue(graph);
425         return null;
426     }    
427     
428     @Override
429     public <T> T getPropertyValue(ReadGraph graph, Resource property) throws DatabaseException {
430         return getPropertyValue(graph, name(graph, property));
431     }
432     
433     @SuppressWarnings("unchecked")
434     @Override
435     public <T> T getPossiblePropertyValue(ReadGraph graph, String name)
436             throws DatabaseException {
437         
438         Variable property = getPossibleDomainProperty(graph, name);
439         if(property != null) return property.getPossibleValue(graph);
440         
441         if(Variables.NAME.equals(name)) return (T)getName(graph);
442         if(Variables.LABEL.equals(name)) return (T)getLabel(graph);
443         if(Variables.TYPE.equals(name)) return (T)getPossibleType(graph);
444         if(Variables.URI.equals(name)) return (T)getURI(graph);
445 //        if(Variables.SERIALISED.equals(name)) return (T)getSerialized(graph);
446 //        if(Variables.ROLE.equals(name)) return (T)getRole(graph);
447 //        if(Variables.REPRESENTS.equals(name)) return (T)getRepresents(graph);
448         
449         Variable extra = getPossibleExtraProperty(graph, name);
450         if(extra != null) return extra.getPossibleValue(graph);
451         
452         return null;
453         
454     }
455
456     @Override
457     public <T> T getPossiblePropertyValue(ReadGraph graph, Resource property) throws DatabaseException {
458         return getPossiblePropertyValue(graph, name(graph, property));
459     }
460
461     @SuppressWarnings("unchecked")
462     @Override
463     public <T> T getPropertyValue(ReadGraph graph, String name, Binding binding)
464             throws DatabaseException {
465         if(binding instanceof StringBinding) {
466             StringBinding sb = (StringBinding)binding;
467             try {
468                 if(Variables.NAME.equals(name)) return (T)sb.create((String)checkNull(graph, getName(graph)));
469                 if(Variables.LABEL.equals(name)) return (T)sb.create((String)checkNull(graph, getLabel(graph)));
470                 if(Variables.URI.equals(name)) return (T)sb.create((String)checkNull(graph, getURI(graph)));
471 //                if(Variables.SERIALISED.equals(name)) return (T)sb.create((String)checkNull(graph, getSerialized(graph)));
472             } catch(BindingException e) {
473                 throw new DatabaseException(e);
474             }
475         }
476         Variable property = getPossibleExtraProperty(graph, name);
477         if(property != null)
478             return property.getValue(graph, binding);
479         property = getPossibleDomainProperty(graph, name);
480         if(property == null)
481             throw new MissingVariableException("Didn't find property " + name + " for " + this + ".");
482         return property.getValue(graph, binding);
483     }
484
485     @Override
486     public <T> T getPropertyValue(ReadGraph graph, Resource property, Binding binding) throws DatabaseException {
487         return getPropertyValue(graph, name(graph, property), binding);
488     }
489
490     @SuppressWarnings("unchecked")
491     @Override
492     public <T> T getPossiblePropertyValue(ReadGraph graph, String name,
493             Binding binding) throws DatabaseException {
494         if(binding instanceof StringBinding) {
495             StringBinding sb = (StringBinding)binding;
496             try {
497                 if(Variables.NAME.equals(name)) return (T)sb.create((String)getName(graph));
498                 if(Variables.LABEL.equals(name)) return (T)sb.create((String)getLabel(graph));
499                 if(Variables.URI.equals(name)) return (T)sb.create((String)getURI(graph));
500 //                if(Variables.SERIALISED.equals(name)) return (T)sb.create((String)getSerialized(graph));
501             } catch(BindingException e) {
502                 throw new DatabaseException(e);
503             }
504         }
505         Variable property = getPossibleExtraProperty(graph, name);
506         if(property != null)
507             return property.getPossibleValue(graph, binding);
508         property = getPossibleDomainProperty(graph, name);
509         if(property == null)
510             return null;
511         return property.getPossibleValue(graph, binding);
512     }
513
514     @Override
515     public <T> T getPossiblePropertyValue(ReadGraph graph, Resource property, Binding binding) throws DatabaseException {
516         return getPossiblePropertyValue(graph, name(graph, property), binding);
517     }
518
519     @Override
520     public void setValue(WriteGraph graph, Object value) throws DatabaseException {
521         try {
522                         setValue(graph, value, Bindings.getBinding(value.getClass()));
523                 } catch (BindingConstructionException e) {
524                         throw new DatabaseException(e);
525                 }
526     }
527     
528     @Override
529     public void setPropertyValue(WriteGraph graph, String name, Object value) throws DatabaseException {
530         getProperty(graph, name).setValue(graph, value);
531     }
532
533     @Override
534     public void setPropertyValue(WriteGraph graph, Resource property, Object value) throws DatabaseException {
535         setPropertyValue(graph, name(graph, property), value);
536     }
537
538     @Override
539     public void setPropertyValue(WriteGraph graph, String name, Object value,
540             Binding binding) throws DatabaseException {
541         getProperty(graph, name).setValue(graph, value, binding);
542     }
543
544     @Override
545     public void setPropertyValue(WriteGraph graph, Resource property, Object value, Binding binding) throws DatabaseException {
546         setPropertyValue(graph, name(graph, property), value, binding);
547     }
548
549     @Override
550     public Variable getChild(ReadGraph graph, String name)
551             throws DatabaseException {
552         Variable child = getPossibleChild(graph, name);
553         if(child == null)
554             throw new MissingVariableException(getURI(graph) + ": didn't find child " + name + " for " + getIdentifier() + ".");
555         return child;
556     }    
557
558     @Override
559     public Variable getProperty(ReadGraph graph, String name)  throws DatabaseException {
560         Variable result = getPossibleProperty(graph, name);
561         if(result == null)
562             throw new MissingVariableException(getClass().getSimpleName() + ": Didn't find property " + name + " for " + getPossibleURI(graph) + ".");
563         return result;
564     }
565     
566     @Override
567     public Variable getProperty(ReadGraph graph, Resource property) throws DatabaseException {
568         return getProperty(graph, name(graph, property));
569     }
570
571     @Override
572     public Variable browse(ReadGraph graph, String suffix)
573             throws DatabaseException {
574         
575         if(suffix.isEmpty()) 
576             return this;        
577         switch(suffix.charAt(0)) {
578         case '.': {
579             Variable parent = getParent(graph); 
580             if(parent == null)
581                 throw new MissingVariableException("Didn't find " + suffix + " for " + this + " (" + getPossibleURI(graph) + ").");
582             return parent.browse(graph, suffix.substring(1));
583         }
584         case '#': {
585             int segmentEnd = getSegmentEnd(suffix);
586             Variable property = getProperty(graph, 
587                     decodeString(suffix.substring(1, segmentEnd)));
588             if(property == null) 
589                 throw new MissingVariableException("Didn't find " + suffix + " for " + this + " (" + getPossibleURI(graph) + ").");
590             return property.browse(graph, suffix.substring(segmentEnd));
591         }
592         case '/': {
593             int segmentEnd = getSegmentEnd(suffix);
594             Variable child = getChild(graph, 
595                     decodeString(suffix.substring(1, segmentEnd)));
596             if(child == null) 
597                 throw new MissingVariableException("Didn't find " + suffix + " for " + this + " (" + getPossibleURI(graph) + ").");
598             return child.browse(graph, suffix.substring(segmentEnd));
599         }
600         default:
601             throw new MissingVariableException("Didn't find " + suffix + " for " + this + " (" + getPossibleURI(graph) + ").");
602         }
603         
604     }
605
606     private static int getSegmentEnd(String suffix) {
607         int pos;
608         for(pos=1;pos<suffix.length();++pos) {
609             char c = suffix.charAt(pos);
610             if(c == '/' || c == '#')
611                 break;
612         }
613         return pos;
614     }
615     
616     @Override
617     public Variable browsePossible(ReadGraph graph, String suffix)
618             throws DatabaseException {
619         if(suffix.isEmpty()) 
620             return this;        
621         switch(suffix.charAt(0)) {
622         case '.': {
623             Variable parent = getParent(graph); 
624             if(parent == null)
625                 return null;
626             return parent.browsePossible(graph, suffix.substring(1));
627         }
628         case '#': {
629             int segmentEnd = getSegmentEnd(suffix);
630             Variable property = getPossibleProperty(graph, 
631                     decodeString(suffix.substring(1, segmentEnd)));
632             if(property == null) 
633                 return null;
634             return property.browsePossible(graph, suffix.substring(segmentEnd));
635         }
636         case '/': {
637             int segmentEnd = getSegmentEnd(suffix);
638             Variable child = getPossibleChild(graph, 
639                     decodeString(suffix.substring(1, segmentEnd)));
640             if(child == null) 
641                 return null;
642             return child.browsePossible(graph, suffix.substring(segmentEnd));
643         }
644         default:
645             return null;
646         }
647     }
648
649     @Override
650     public Variable browse(ReadGraph graph, Resource config)
651             throws DatabaseException {
652         Variable variable = browsePossible(graph, config);
653         if(variable == null)
654             throw new MissingVariableException("Didn't find a variable related to " + 
655                     NameUtils.getSafeName(graph, config) + ".");
656         return variable;
657     }
658
659     @Override
660     public Variable browsePossible(ReadGraph graph, Resource config)
661             throws DatabaseException {
662         Layer0 l0 = Layer0.getInstance(graph);
663         String name = (String)graph.getPossibleRelatedValue(config, l0.HasName, Bindings.STRING);
664         if (name == null)
665             return null;
666         return getPossibleChild(graph, name);
667     }
668
669     @Override
670     public <T> T getInterface(ReadGraph graph, Class<T> clazz)
671             throws DatabaseException {
672         return null;
673     }
674
675     @Override
676     public String getURI(ReadGraph graph) throws DatabaseException {
677         validate(graph);
678         Variable parent = getParent(graph);
679         if (parent == null)
680             throw new InvalidVariableException(this + " has no URI");
681         return parent.getURI(graph) + getRole(graph).getIdentifier() + encodeString(getName(graph));
682     }
683
684     /**
685      * For debug messages.
686      * 
687      * @param graph
688      * @return
689      * @throws DatabaseException
690      */
691     public String getPossibleURI(ReadGraph graph) throws DatabaseException {
692         Variable parent = getParent(graph);
693         if (parent == null)
694             return null;
695         if (parent instanceof AbstractVariable) {
696             String parentUri = ((AbstractVariable) parent).getPossibleURI(graph);
697             if (parentUri == null)
698                 return null;
699             return parentUri + getRole(graph).getIdentifier() + encodeString(getName(graph));
700         }
701         return null;
702     }
703
704     public <T> T getPossibleValue(ReadGraph graph) throws DatabaseException {
705         try {
706             return getValue(graph);
707         } catch(DatabaseException e) {
708             return null;
709         }
710     }
711
712     @Override
713     public Variant getVariantValue(ReadGraph graph) throws DatabaseException {
714         Binding binding = getPossibleDefaultBinding(graph);
715         if(binding != null) {
716                 Object value = getValue(graph, binding);
717                 return new Variant(binding, value);
718         } else {
719 //              System.err.println("no data type for " + getURI(graph));
720                 // TODO: hackish, consider doing something else here?
721                 Object value = getValue(graph);
722                 try {
723                                 binding = Bindings.OBJECT.getContentBinding(value);
724                         } catch (BindingException e) {
725                                 throw new DatabaseException(e);
726                         }
727                 return new Variant(binding, value);
728         }
729     }
730     
731     public Variant getPossibleVariantValue(ReadGraph graph) throws DatabaseException {
732         Binding binding = getPossibleDefaultBinding(graph);
733         if(binding != null) {
734                 Object value = getPossibleValue(graph, binding);
735                 if(value == null) return null;
736                 return new Variant(binding, value);
737         } else {
738                 Object value = getPossibleValue(graph);
739                 if(value == null) return null;
740                 try {
741                         // TODO: hackish, consider doing something else here?
742                         binding = value != null ? Bindings.getBinding(value.getClass()) : null;
743                         return new Variant(binding, value);
744                 } catch (BindingConstructionException e) {
745                         return null;
746                 }
747         }
748     }
749
750     public <T> T getPossibleValue(ReadGraph graph, Binding binding) throws DatabaseException {
751         try {
752             return getValue(graph, binding);
753         } catch(MissingVariableValueException e) {
754             return null;
755         }
756     }
757
758     public <T> T adapt(ReadGraph graph, Class<T> clazz) throws DatabaseException {
759         throw new AdaptionException(this + " does not support adaption to " + clazz);
760     }
761     
762     @Override
763     public <T> T adaptPossible(ReadGraph graph, Class<T> clazz) throws DatabaseException {
764         try {
765                 return adapt(graph, clazz);
766         } catch (AdaptionException e) {
767                 return null;
768         }
769     }
770     
771     
772     public static String encodeString(String string) throws DatabaseException {
773         if (string == null || "".equals(string)) return string;
774         return URIStringUtils.escape(string);
775     }
776     
777     public static String decodeString(String string) throws DatabaseException {
778         return URIStringUtils.unescape(string);
779     }
780     
781
782         protected Variable getPossiblePropertyFromContext(ReadGraph graph, Resource context, String name) throws DatabaseException {
783
784                 Map<String, Resource> predicates = graph.syncRequest(new PropertyMapOfResource(context));
785                 Resource property = predicates.get(name);
786                 if(property == null) return null;
787                 Resource object = graph.getSingleObject(context, property);
788                 Variable objectAdapter = graph.getPossibleContextualAdapter(object, new ModelledVariablePropertyDescriptorImpl(this, context, property), 
789                                 ModelledVariablePropertyDescriptor.class, Variable.class);
790                 if(objectAdapter != null) return objectAdapter;
791                 return graph.getPossibleContextualAdapter(property, new ModelledVariablePropertyDescriptorImpl(this, context, property), 
792                                 ModelledVariablePropertyDescriptor.class, Variable.class);
793                 
794         }
795         
796         protected Map<String, Variable> collectPropertiesFromContext(ReadGraph graph, Resource context, Map<String, Variable> properties) throws DatabaseException {
797
798                 for(Map.Entry<String, Resource> entry : graph.syncRequest(new PropertyMapOfResource(context)).entrySet()) {
799                         String name = entry.getKey();
800                         Resource property = entry.getValue();
801                         Resource object = graph.getSingleObject(context, property);
802                         Variable objectAdapter = graph.getPossibleContextualAdapter(object, new ModelledVariablePropertyDescriptorImpl(this, context, property), 
803                                         ModelledVariablePropertyDescriptor.class, Variable.class);
804                         if(objectAdapter != null) {
805                                 if(objectAdapter != null) {
806                                     if(properties == null) properties = new HashMap<String,Variable>();
807                                     properties.put(name, objectAdapter);
808                                 }
809                         } else {
810                                 Variable predicateAdapter = graph.getPossibleContextualAdapter(property, new ModelledVariablePropertyDescriptorImpl(this, context, property), 
811                                                 ModelledVariablePropertyDescriptor.class, Variable.class);
812                                 if(predicateAdapter != null) {
813                     if(properties == null) properties = new HashMap<String,Variable>();
814                                     properties.put(name, predicateAdapter);
815                                 }
816                         }
817
818                 }
819                 
820                 return properties;
821                 
822         }
823         
824         @Override
825         public Variable resolve(ReadGraph graph, RVIPart part) throws DatabaseException {
826                 if(part instanceof StringRVIPart) {
827                         StringRVIPart srp = (StringRVIPart)part;
828                         if(Role.CHILD.equals(srp.getRole())) return getChild(graph, srp.string);
829                         else if(Role.PROPERTY.equals(srp.getRole())) return getProperty(graph, srp.string);
830                 } else if(part instanceof ResourceRVIPart) {
831                         ResourceRVIPart rrp = (ResourceRVIPart)part;
832                         if(Role.CHILD.equals(rrp.getRole())) return resolveChild(graph, rrp.resource);
833                         else if(Role.PROPERTY.equals(rrp.getRole())) return resolveProperty(graph, rrp.resource);
834                 } else if(part instanceof GuidRVIPart) {
835                         GuidRVIPart grp = (GuidRVIPart)part;
836                         if(Role.CHILD.equals(grp.getRole())) return resolveChild(graph, grp);
837                         else if(Role.PROPERTY.equals(grp.getRole())) return resolveProperty(graph, grp);
838                 }
839                 throw new DatabaseException("Unrecognized RVIPart: " + part);
840         }
841
842         @Override
843         public Variable resolvePossible(ReadGraph graph, RVIPart part) throws DatabaseException {
844                 if(part instanceof StringRVIPart) {
845                         StringRVIPart srp = (StringRVIPart)part;
846                         if(Role.CHILD.equals(srp.getRole())) return getPossibleChild(graph, srp.string);
847                         else if(Role.PROPERTY.equals(srp.getRole())) return getPossibleProperty(graph, srp.string);
848                 } else if(part instanceof ResourceRVIPart) {
849                         ResourceRVIPart rrp = (ResourceRVIPart)part;
850                         if(Role.CHILD.equals(rrp.getRole())) return resolvePossibleChild(graph, rrp.resource);
851                         else if(Role.PROPERTY.equals(rrp.getRole())) return resolvePossibleProperty(graph, rrp.resource);
852                 } else if(part instanceof GuidRVIPart) {
853                         GuidRVIPart grp = (GuidRVIPart)part;
854                         if(Role.CHILD.equals(grp.getRole())) return resolvePossibleChild(graph, grp);
855                         else if(Role.PROPERTY.equals(grp.getRole())) return resolvePossibleProperty(graph, grp);
856                 }
857                 throw new DatabaseException("Unrecognized RVIPart: " + part);
858         }
859
860         @Override
861         public Datatype getDatatype(ReadGraph graph) throws DatabaseException {
862                 throw new DatabaseException("No data type.");
863         }
864
865         public Binding getDefaultBinding(ReadGraph graph) throws DatabaseException {
866                 Datatype type = getDatatype(graph);
867                 return Bindings.getBinding(type);
868         }
869         
870         public Binding getPossibleDefaultBinding(ReadGraph graph) throws DatabaseException {
871         try {
872             return getDefaultBinding(graph);
873         } catch(DatabaseException e) {
874             return null;
875         }
876         }
877
878         @Override
879         public Datatype getPossibleDatatype(ReadGraph graph) throws DatabaseException {
880         try {
881             return getDatatype(graph);
882         } catch(DatabaseException e) {
883             return null;
884         }
885         }
886         
887 //      public Binding getPossibleDefaultBinding(ReadGraph graph) throws DatabaseException {
888 //              
889 //              Datatype type = getPossibleDatatype(graph);
890 //              if(type == null) return null;
891 //              return Bindings.getBinding(type);
892 //
893 //      }
894 //
895 //      @Override
896 //      public Datatype getPossibleDatatype(ReadGraph graph) throws DatabaseException {
897 //              
898 //              Variant vt = getVariantValue(graph);
899 //              if(vt == null) return null;
900 //              Binding binding = vt.getBinding();
901 //              if(binding == null) return null;
902 //              return binding.type();
903 //
904 //      }
905
906         @Override
907         public Variable getPredicate(ReadGraph graph) throws DatabaseException {
908                 throw new DatabaseException(getClass().getSimpleName() + ": No predicate property for " + getPossibleURI(graph));
909         }
910
911         @Override
912         public Variable getPossiblePredicate(ReadGraph graph) throws DatabaseException {
913                 try {
914             return getPredicate(graph);
915         } catch(DatabaseException e) {
916             return null;
917         }
918         }
919         
920         @Override
921         public Resource getPredicateResource(ReadGraph graph) throws DatabaseException {
922                 Variable predicate = getPredicate(graph);
923                 if(predicate == null) throw new DatabaseException(getClass().getSimpleName() + ": No predicate property for " + getPossibleURI(graph));
924                 return predicate.getRepresents(graph);
925         }
926         
927         @Override
928         public Resource getPossiblePredicateResource(ReadGraph graph) throws DatabaseException {
929                 Variable predicate = getPossiblePredicate(graph);
930                 if(predicate == null) return null;
931                 else return predicate.getPossibleRepresents(graph);
932         }
933
934         @Override
935         public Resource getPossibleRepresents(ReadGraph graph) throws DatabaseException {
936         try {
937             return getRepresents(graph);
938         } catch(DatabaseException e) {
939             return null;
940         }
941         }
942
943     public Resource getType(ReadGraph graph) throws DatabaseException {
944         Resource typeFromFunction = getTypeFromPossibleTypeFunction(graph, Layer0.getInstance(graph).Entity, getPossibleTypeFunction(graph));
945         if (typeFromFunction != null)
946             return typeFromFunction;
947
948         Resource resource = getPossibleRepresents(graph);
949         if (resource == null) {
950             String uri = getPossiblePropertyValue(graph, "typeURI");
951             if (uri != null)
952                 return graph.syncRequest(new org.simantics.db.common.primitiverequest.Resource(uri),
953                         TransientCacheAsyncListener.<Resource> instance());
954             throw new DatabaseException("No type for " + getURI(graph));
955         }
956         return graph.getSingleType(resource);
957     }
958
959     @Override
960     public Resource getPossibleType(ReadGraph graph) throws DatabaseException {
961         try {
962             Resource typeFromFunction = getTypeFromPossibleTypeFunction(graph, Layer0.getInstance(graph).Entity, getPossibleTypeFunction(graph));
963             if (typeFromFunction != null)
964                 return typeFromFunction;
965         } catch (Throwable t) {
966             return null;
967         }
968
969         Resource resource = getPossibleRepresents(graph);
970         if (resource == null) {
971             String uri = getPossiblePropertyValue(graph, "typeURI");
972             if (uri != null)
973                 return graph.syncRequest(new PossibleResource(uri), TransientCacheAsyncListener.<Resource> instance());
974             return null;
975         }
976         return graph.getPossibleObject(resource, Layer0.getInstance(graph).InstanceOf);
977     }
978
979     public Resource getType(ReadGraph graph, Resource baseType) throws DatabaseException {
980         Resource typeFromFunction = getTypeFromPossibleTypeFunction(graph, baseType, getPossibleTypeFunction(graph));
981         if (typeFromFunction != null)
982             return typeFromFunction;
983
984         Resource resource = getPossibleRepresents(graph);
985         if (resource == null) {
986             String uri = getPossiblePropertyValue(graph, "typeURI");
987             if (uri != null)
988                 return graph.syncRequest(new org.simantics.db.common.primitiverequest.Resource(uri),
989                         TransientCacheAsyncListener.<Resource> instance());
990             throw new DatabaseException("No type for " + getURI(graph));
991         }
992         return graph.getSingleType(resource, baseType);
993     }
994
995     @Override
996     public Resource getPossibleType(ReadGraph graph, Resource baseType) throws DatabaseException {
997         try {
998             Resource typeFromFunction = getTypeFromPossibleTypeFunction(graph, baseType, getPossibleTypeFunction(graph));
999             if (typeFromFunction != null)
1000                 return typeFromFunction;
1001         } catch (Throwable t) {
1002             return null;
1003         }
1004
1005         Resource resource = getPossibleRepresents(graph);
1006         if(resource == null) {
1007             String uri = getPossiblePropertyValue(graph, "typeURI");
1008             if(uri != null) {
1009                 Resource type = graph.syncRequest(new PossibleResource(uri), TransientCacheAsyncListener.<Resource>instance());
1010                 if(type == null) return null;
1011                 if(graph.isInheritedFrom(type, baseType)) return type;
1012                 else return null;
1013             }
1014             return null;
1015         }
1016         return graph.getPossibleType(resource, baseType);
1017     }
1018
1019     private Resource getTypeFromPossibleTypeFunction(ReadGraph graph, Resource baseType,
1020             Function2<Variable, Resource, Resource> fn) throws DatabaseException {
1021         if (fn == null)
1022             // Type function was not defined - return nothing
1023             return null;
1024
1025         SCLContext sclContext = SCLContext.getCurrent();
1026         Object oldGraph = sclContext.put("graph", graph);
1027         try {
1028             return (Resource) fn.apply(this, baseType);
1029         } catch (Throwable t) {
1030             if (t instanceof DatabaseException)
1031                 throw (DatabaseException) t;
1032             throw new DatabaseException(t);
1033         } finally {
1034             sclContext.put("graph", oldGraph);
1035         }
1036     }
1037
1038     @SuppressWarnings("unchecked")
1039     private Function2<Variable, Resource, Resource> getPossibleTypeFunction(ReadGraph graph) throws DatabaseException {
1040         Variable custom = getPossibleProperty(graph, "typeResource");
1041         return custom != null
1042             ? (Function2<Variable, Resource, Resource>) custom.getPossibleValue(graph)
1043             : null;
1044     }
1045
1046     public Map<String, Variable> collectDomainProperties(ReadGraph graph, String classification, Map<String, Variable> map) throws DatabaseException {
1047         Map<String,Variable> all = collectDomainProperties(graph, null);
1048         for(Map.Entry<String, Variable> entry : all.entrySet()) {
1049                 Set<String> classifications = entry.getValue().getClassifications(graph);
1050                 if(classifications.contains(classification)) {
1051                         if(map == null) map = new HashMap<String,Variable>();
1052                         map.put(entry.getKey(), entry.getValue());
1053                 }
1054         }
1055         return map;
1056     }
1057     
1058     @Override
1059     public RVI getPossibleRVI(ReadGraph graph) throws DatabaseException {
1060         try {
1061             return getRVI(graph);
1062         } catch(DatabaseException e) {
1063             return null;
1064         }
1065     }
1066
1067 }