]> gerrit.simantics Code Review - simantics/platform.git/blob - bundles/org.simantics.db.layer0/src/org/simantics/db/layer0/variable/AbstractVariable.java
Fixed multiple issues causing dangling references to discarded queries
[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 InvalidVariableException("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 MissingVariableException("Could not resolve child " + rName, 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 MissingVariableException("Could not resolve child " + rName, 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) + ")", getPossibleRepresents(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 + ".", getPossibleRepresents(graph));
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) throws DatabaseException {
464         if (binding instanceof StringBinding) {
465             StringBinding sb = (StringBinding) binding;
466             try {
467                 if (Variables.NAME.equals(name))
468                     return (T) sb.create((String) checkNull(graph, getName(graph)));
469                 if (Variables.LABEL.equals(name))
470                     return (T) sb.create((String) checkNull(graph, getLabel(graph)));
471                 if (Variables.URI.equals(name))
472                     return (T) sb.create((String) checkNull(graph, getURI(graph)));
473                 // if(Variables.SERIALISED.equals(name)) return
474                 // (T)sb.create((String)checkNull(graph, getSerialized(graph)));
475             } catch (BindingException e) {
476                 throw new org.simantics.db.exception.BindingException("Could not get value for property " + name + " with binding " + binding, e);
477             }
478         }
479         Variable property = getPossibleExtraProperty(graph, name);
480         if(property != null)
481             return property.getValue(graph, binding);
482         property = getPossibleDomainProperty(graph, name);
483         if(property == null)
484             throw new MissingVariableException("Didn't find property " + name + " for " + this + ".", getPossibleRepresents(graph));
485         return property.getValue(graph, binding);
486     }
487
488     @Override
489     public <T> T getPropertyValue(ReadGraph graph, Resource property, Binding binding) throws DatabaseException {
490         return getPropertyValue(graph, name(graph, property), binding);
491     }
492
493     @SuppressWarnings("unchecked")
494     @Override
495     public <T> T getPossiblePropertyValue(ReadGraph graph, String name,
496             Binding binding) throws DatabaseException {
497         if(binding instanceof StringBinding) {
498             StringBinding sb = (StringBinding)binding;
499             try {
500                 if(Variables.NAME.equals(name)) return (T)sb.create((String)getName(graph));
501                 if(Variables.LABEL.equals(name)) return (T)sb.create((String)getLabel(graph));
502                 if(Variables.URI.equals(name)) return (T)sb.create((String)getURI(graph));
503 //                if(Variables.SERIALISED.equals(name)) return (T)sb.create((String)getSerialized(graph));
504             } catch(BindingException e) {
505                 throw new org.simantics.db.exception.BindingException("Could not get property value for " + name + " with binding " + binding, e);
506             }
507         }
508         Variable property = getPossibleExtraProperty(graph, name);
509         if(property != null)
510             return property.getPossibleValue(graph, binding);
511         property = getPossibleDomainProperty(graph, name);
512         if(property == null)
513             return null;
514         return property.getPossibleValue(graph, binding);
515     }
516
517     @Override
518     public <T> T getPossiblePropertyValue(ReadGraph graph, Resource property, Binding binding) throws DatabaseException {
519         return getPossiblePropertyValue(graph, name(graph, property), binding);
520     }
521
522     @Override
523     public void setValue(WriteGraph graph, Object value) throws DatabaseException {
524         try {
525                         setValue(graph, value, Bindings.getBinding(value.getClass()));
526                 } catch (BindingConstructionException e) {
527                         throw new org.simantics.db.exception.BindingException("Could not set " + String.valueOf(value) + " value for " + getRepresents(graph), e);
528                 }
529     }
530     
531     @Override
532     public void setPropertyValue(WriteGraph graph, String name, Object value) throws DatabaseException {
533         getProperty(graph, name).setValue(graph, value);
534     }
535
536     @Override
537     public void setPropertyValue(WriteGraph graph, Resource property, Object value) throws DatabaseException {
538         setPropertyValue(graph, name(graph, property), value);
539     }
540
541     @Override
542     public void setPropertyValue(WriteGraph graph, String name, Object value,
543             Binding binding) throws DatabaseException {
544         getProperty(graph, name).setValue(graph, value, binding);
545     }
546
547     @Override
548     public void setPropertyValue(WriteGraph graph, Resource property, Object value, Binding binding) throws DatabaseException {
549         setPropertyValue(graph, name(graph, property), value, binding);
550     }
551
552     @Override
553     public Variable getChild(ReadGraph graph, String name)
554             throws DatabaseException {
555         Variable child = getPossibleChild(graph, name);
556         if(child == null)
557             throw new MissingVariableException(getURI(graph) + ": didn't find child " + name + " for " + getIdentifier() + ".", getPossibleRepresents(graph));
558         return child;
559     }    
560
561     @Override
562     public Variable getProperty(ReadGraph graph, String name)  throws DatabaseException {
563         Variable result = getPossibleProperty(graph, name);
564         if(result == null)
565             throw new MissingVariableException(getClass().getSimpleName() + ": Didn't find property " + name + " for " + getPossibleURI(graph) + ".", getPossibleRepresents(graph));
566         return result;
567     }
568     
569     @Override
570     public Variable getProperty(ReadGraph graph, Resource property) throws DatabaseException {
571         return getProperty(graph, name(graph, property));
572     }
573
574     @Override
575     public Variable browse(ReadGraph graph, String suffix)
576             throws DatabaseException {
577         
578         if(suffix.isEmpty()) 
579             return this;        
580         switch(suffix.charAt(0)) {
581         case '.': {
582             Variable parent = getParent(graph); 
583             if(parent == null)
584                 throw new MissingVariableException("Didn't find " + suffix + " for " + this + " (" + getPossibleURI(graph) + ").", getPossibleRepresents(graph));
585             return parent.browse(graph, suffix.substring(1));
586         }
587         case '#': {
588             int segmentEnd = getSegmentEnd(suffix);
589             Variable property = getProperty(graph, 
590                     decodeString(suffix.substring(1, segmentEnd)));
591             if(property == null) 
592                 throw new MissingVariableException("Didn't find " + suffix + " for " + this + " (" + getPossibleURI(graph) + ").", getPossibleRepresents(graph));
593             return property.browse(graph, suffix.substring(segmentEnd));
594         }
595         case '/': {
596             int segmentEnd = getSegmentEnd(suffix);
597             Variable child = getChild(graph, 
598                     decodeString(suffix.substring(1, segmentEnd)));
599             if(child == null) 
600                 throw new MissingVariableException("Didn't find " + suffix + " for " + this + " (" + getPossibleURI(graph) + ").", getPossibleRepresents(graph));
601             return child.browse(graph, suffix.substring(segmentEnd));
602         }
603         default:
604             throw new MissingVariableException("Didn't find " + suffix + " for " + this + " (" + getPossibleURI(graph) + ").", getPossibleRepresents(graph));
605         }
606         
607     }
608
609     private static int getSegmentEnd(String suffix) {
610         int pos;
611         for(pos=1;pos<suffix.length();++pos) {
612             char c = suffix.charAt(pos);
613             if(c == '/' || c == '#')
614                 break;
615         }
616         return pos;
617     }
618     
619     @Override
620     public Variable browsePossible(ReadGraph graph, String suffix)
621             throws DatabaseException {
622         if(suffix.isEmpty()) 
623             return this;        
624         switch(suffix.charAt(0)) {
625         case '.': {
626             Variable parent = getParent(graph); 
627             if(parent == null)
628                 return null;
629             return parent.browsePossible(graph, suffix.substring(1));
630         }
631         case '#': {
632             int segmentEnd = getSegmentEnd(suffix);
633             Variable property = getPossibleProperty(graph, 
634                     decodeString(suffix.substring(1, segmentEnd)));
635             if(property == null) 
636                 return null;
637             return property.browsePossible(graph, suffix.substring(segmentEnd));
638         }
639         case '/': {
640             int segmentEnd = getSegmentEnd(suffix);
641             Variable child = getPossibleChild(graph, 
642                     decodeString(suffix.substring(1, segmentEnd)));
643             if(child == null) 
644                 return null;
645             return child.browsePossible(graph, suffix.substring(segmentEnd));
646         }
647         default:
648             return null;
649         }
650     }
651
652     @Override
653     public Variable browse(ReadGraph graph, Resource config)
654             throws DatabaseException {
655         Variable variable = browsePossible(graph, config);
656         if(variable == null)
657             throw new MissingVariableException("Didn't find a variable related to " + 
658                     NameUtils.getSafeName(graph, config) + ".", config);
659         return variable;
660     }
661
662     @Override
663     public Variable browsePossible(ReadGraph graph, Resource config)
664             throws DatabaseException {
665         Layer0 l0 = Layer0.getInstance(graph);
666         String name = (String)graph.getPossibleRelatedValue(config, l0.HasName, Bindings.STRING);
667         if (name == null)
668             return null;
669         return getPossibleChild(graph, name);
670     }
671
672     @Override
673     public <T> T getInterface(ReadGraph graph, Class<T> clazz)
674             throws DatabaseException {
675         return null;
676     }
677
678     @Override
679     public String getURI(ReadGraph graph) throws DatabaseException {
680         validate(graph);
681         Variable parent = getParent(graph);
682         if (parent == null)
683             throw new InvalidVariableException(this + " has no URI");
684         return parent.getURI(graph) + getRole(graph).getIdentifier() + encodeString(getName(graph));
685     }
686
687     /**
688      * For debug messages.
689      * 
690      * @param graph
691      * @return
692      * @throws DatabaseException
693      */
694     public String getPossibleURI(ReadGraph graph) throws DatabaseException {
695         Variable parent = getParent(graph);
696         if (parent == null)
697             return null;
698         if (parent instanceof AbstractVariable) {
699             String parentUri = ((AbstractVariable) parent).getPossibleURI(graph);
700             if (parentUri == null)
701                 return null;
702             return parentUri + getRole(graph).getIdentifier() + encodeString(getName(graph));
703         }
704         return null;
705     }
706
707     public <T> T getPossibleValue(ReadGraph graph) throws DatabaseException {
708         try {
709             return getValue(graph);
710         } catch(DatabaseException e) {
711             return null;
712         }
713     }
714
715     @Override
716     public Variant getVariantValue(ReadGraph graph) throws DatabaseException {
717         Binding binding = getPossibleDefaultBinding(graph);
718         if(binding != null) {
719                 Object value = getValue(graph, binding);
720                 return new Variant(binding, value);
721         } else {
722 //              System.err.println("no data type for " + getURI(graph));
723                 // TODO: hackish, consider doing something else here?
724                 Object value = getValue(graph);
725                 try {
726                                 binding = Bindings.OBJECT.getContentBinding(value);
727                         } catch (BindingException e) {
728                                 throw new org.simantics.db.exception.BindingException("Could not bind variant value " + String.valueOf(value) + " for " + getRepresents(graph), e);
729                         }
730                 return new Variant(binding, value);
731         }
732     }
733     
734     public Variant getPossibleVariantValue(ReadGraph graph) throws DatabaseException {
735         Binding binding = getPossibleDefaultBinding(graph);
736         if(binding != null) {
737                 Object value = getPossibleValue(graph, binding);
738                 if(value == null) return null;
739                 return new Variant(binding, value);
740         } else {
741                 Object value = getPossibleValue(graph);
742                 if(value == null) return null;
743                 try {
744                         // TODO: hackish, consider doing something else here?
745                         binding = value != null ? Bindings.getBinding(value.getClass()) : null;
746                         return new Variant(binding, value);
747                 } catch (BindingConstructionException e) {
748                         return null;
749                 }
750         }
751     }
752
753     public <T> T getPossibleValue(ReadGraph graph, Binding binding) throws DatabaseException {
754         try {
755             return getValue(graph, binding);
756         } catch(MissingVariableValueException e) {
757             return null;
758         }
759     }
760
761     public <T> T adapt(ReadGraph graph, Class<T> clazz) throws DatabaseException {
762         throw new AdaptionException(this + " does not support adaption to " + clazz);
763     }
764     
765     @Override
766     public <T> T adaptPossible(ReadGraph graph, Class<T> clazz) throws DatabaseException {
767         try {
768                 return adapt(graph, clazz);
769         } catch (AdaptionException e) {
770                 return null;
771         }
772     }
773     
774     
775     public static String encodeString(String string) throws DatabaseException {
776         if (string == null || "".equals(string)) return string;
777         return URIStringUtils.escape(string);
778     }
779     
780     public static String decodeString(String string) throws DatabaseException {
781         return URIStringUtils.unescape(string);
782     }
783     
784
785         protected Variable getPossiblePropertyFromContext(ReadGraph graph, Resource context, String name) throws DatabaseException {
786
787                 Map<String, Resource> predicates = graph.syncRequest(new PropertyMapOfResource(context));
788                 Resource property = predicates.get(name);
789                 if(property == null) return null;
790                 Resource object = graph.getSingleObject(context, property);
791                 Variable objectAdapter = graph.getPossibleContextualAdapter(object, new ModelledVariablePropertyDescriptorImpl(this, context, property), 
792                                 ModelledVariablePropertyDescriptor.class, Variable.class);
793                 if(objectAdapter != null) return objectAdapter;
794                 return graph.getPossibleContextualAdapter(property, new ModelledVariablePropertyDescriptorImpl(this, context, property), 
795                                 ModelledVariablePropertyDescriptor.class, Variable.class);
796                 
797         }
798         
799         protected Map<String, Variable> collectPropertiesFromContext(ReadGraph graph, Resource context, Map<String, Variable> properties) throws DatabaseException {
800
801                 for(Map.Entry<String, Resource> entry : graph.syncRequest(new PropertyMapOfResource(context)).entrySet()) {
802                         String name = entry.getKey();
803                         Resource property = entry.getValue();
804                         Resource object = graph.getSingleObject(context, property);
805                         Variable objectAdapter = graph.getPossibleContextualAdapter(object, new ModelledVariablePropertyDescriptorImpl(this, context, property), 
806                                         ModelledVariablePropertyDescriptor.class, Variable.class);
807                         if(objectAdapter != null) {
808                                 if(objectAdapter != null) {
809                                     if(properties == null) properties = new HashMap<String,Variable>();
810                                     properties.put(name, objectAdapter);
811                                 }
812                         } else {
813                                 Variable predicateAdapter = graph.getPossibleContextualAdapter(property, new ModelledVariablePropertyDescriptorImpl(this, context, property), 
814                                                 ModelledVariablePropertyDescriptor.class, Variable.class);
815                                 if(predicateAdapter != null) {
816                     if(properties == null) properties = new HashMap<String,Variable>();
817                                     properties.put(name, predicateAdapter);
818                                 }
819                         }
820
821                 }
822                 
823                 return properties;
824                 
825         }
826         
827         @Override
828         public Variable resolve(ReadGraph graph, RVIPart part) throws DatabaseException {
829                 if(part instanceof StringRVIPart) {
830                         StringRVIPart srp = (StringRVIPart)part;
831                         if(Role.CHILD.equals(srp.getRole())) return getChild(graph, srp.string);
832                         else if(Role.PROPERTY.equals(srp.getRole())) return getProperty(graph, srp.string);
833                 } else if(part instanceof ResourceRVIPart) {
834                         ResourceRVIPart rrp = (ResourceRVIPart)part;
835                         if(Role.CHILD.equals(rrp.getRole())) return resolveChild(graph, rrp.resource);
836                         else if(Role.PROPERTY.equals(rrp.getRole())) return resolveProperty(graph, rrp.resource);
837                 } else if(part instanceof GuidRVIPart) {
838                         GuidRVIPart grp = (GuidRVIPart)part;
839                         if(Role.CHILD.equals(grp.getRole())) return resolveChild(graph, grp);
840                         else if(Role.PROPERTY.equals(grp.getRole())) return resolveProperty(graph, grp);
841                 }
842                 throw new MissingVariableException("Unrecognized RVIPart: " + part, getPossibleRepresents(graph));
843         }
844
845         @Override
846         public Variable resolvePossible(ReadGraph graph, RVIPart part) throws DatabaseException {
847                 if(part instanceof StringRVIPart) {
848                         StringRVIPart srp = (StringRVIPart)part;
849                         if(Role.CHILD.equals(srp.getRole())) return getPossibleChild(graph, srp.string);
850                         else if(Role.PROPERTY.equals(srp.getRole())) return getPossibleProperty(graph, srp.string);
851                 } else if(part instanceof ResourceRVIPart) {
852                         ResourceRVIPart rrp = (ResourceRVIPart)part;
853                         if(Role.CHILD.equals(rrp.getRole())) return resolvePossibleChild(graph, rrp.resource);
854                         else if(Role.PROPERTY.equals(rrp.getRole())) return resolvePossibleProperty(graph, rrp.resource);
855                 } else if(part instanceof GuidRVIPart) {
856                         GuidRVIPart grp = (GuidRVIPart)part;
857                         if(Role.CHILD.equals(grp.getRole())) return resolvePossibleChild(graph, grp);
858                         else if(Role.PROPERTY.equals(grp.getRole())) return resolvePossibleProperty(graph, grp);
859                 }
860                 throw new MissingVariableException("Unrecognized RVIPart: " + part, getPossibleRepresents(graph));
861         }
862
863         @Override
864         public Datatype getDatatype(ReadGraph graph) throws DatabaseException {
865                 throw new InvalidVariableException("No data type.");
866         }
867
868         public Binding getDefaultBinding(ReadGraph graph) throws DatabaseException {
869                 Datatype type = getDatatype(graph);
870                 return Bindings.getBinding(type);
871         }
872         
873         public Binding getPossibleDefaultBinding(ReadGraph graph) throws DatabaseException {
874         try {
875             return getDefaultBinding(graph);
876         } catch(DatabaseException e) {
877             return null;
878         }
879         }
880
881         @Override
882         public Datatype getPossibleDatatype(ReadGraph graph) throws DatabaseException {
883         try {
884             return getDatatype(graph);
885         } catch(DatabaseException e) {
886             return null;
887         }
888         }
889         
890 //      public Binding getPossibleDefaultBinding(ReadGraph graph) throws DatabaseException {
891 //              
892 //              Datatype type = getPossibleDatatype(graph);
893 //              if(type == null) return null;
894 //              return Bindings.getBinding(type);
895 //
896 //      }
897 //
898 //      @Override
899 //      public Datatype getPossibleDatatype(ReadGraph graph) throws DatabaseException {
900 //              
901 //              Variant vt = getVariantValue(graph);
902 //              if(vt == null) return null;
903 //              Binding binding = vt.getBinding();
904 //              if(binding == null) return null;
905 //              return binding.type();
906 //
907 //      }
908
909         @Override
910         public Variable getPredicate(ReadGraph graph) throws DatabaseException {
911                 throw new MissingVariableException(getClass().getSimpleName() + ": No predicate property for " + getPossibleURI(graph), getPossibleRepresents(graph));
912         }
913
914         @Override
915         public Variable getPossiblePredicate(ReadGraph graph) throws DatabaseException {
916                 try {
917             return getPredicate(graph);
918         } catch(DatabaseException e) {
919             return null;
920         }
921         }
922
923     @Override
924     public Resource getPredicateResource(ReadGraph graph) throws DatabaseException {
925         Variable predicate = getPredicate(graph);
926         if (predicate == null)
927             throw new MissingVariableException(getClass().getSimpleName() + ": No predicate property for " + getPossibleURI(graph), getPossibleRepresents(graph));
928         return predicate.getRepresents(graph);
929     }
930
931         @Override
932         public Resource getPossiblePredicateResource(ReadGraph graph) throws DatabaseException {
933                 Variable predicate = getPossiblePredicate(graph);
934                 if(predicate == null) return null;
935                 else return predicate.getPossibleRepresents(graph);
936         }
937
938         @Override
939         public Resource getPossibleRepresents(ReadGraph graph) throws DatabaseException {
940         try {
941             return getRepresents(graph);
942         } catch(DatabaseException e) {
943             return null;
944         }
945         }
946
947     public Resource getType(ReadGraph graph) throws DatabaseException {
948         Resource typeFromFunction = getTypeFromPossibleTypeFunction(graph, Layer0.getInstance(graph).Entity, getPossibleTypeFunction(graph));
949         if (typeFromFunction != null)
950             return typeFromFunction;
951
952         Resource resource = getPossibleRepresents(graph);
953         if (resource == null) {
954             String uri = getPossiblePropertyValue(graph, "typeURI");
955             if (uri != null)
956                 return graph.syncRequest(new org.simantics.db.common.primitiverequest.Resource(uri),
957                         TransientCacheAsyncListener.<Resource> instance());
958             throw new DatabaseException("No type for " + getURI(graph));
959         }
960         return graph.getSingleType(resource);
961     }
962
963     @Override
964     public Resource getPossibleType(ReadGraph graph) throws DatabaseException {
965         try {
966             Resource typeFromFunction = getTypeFromPossibleTypeFunction(graph, Layer0.getInstance(graph).Entity, getPossibleTypeFunction(graph));
967             if (typeFromFunction != null)
968                 return typeFromFunction;
969         } catch (DatabaseException t) {
970             return null;
971         }
972
973         Resource resource = getPossibleRepresents(graph);
974         if (resource == null) {
975             String uri = getPossiblePropertyValue(graph, "typeURI");
976             if (uri != null)
977                 return graph.syncRequest(new PossibleResource(uri), TransientCacheAsyncListener.<Resource> instance());
978             return null;
979         }
980         return graph.getPossibleObject(resource, Layer0.getInstance(graph).InstanceOf);
981     }
982
983     public Resource getType(ReadGraph graph, Resource baseType) throws DatabaseException {
984         Resource typeFromFunction = getTypeFromPossibleTypeFunction(graph, baseType, getPossibleTypeFunction(graph));
985         if (typeFromFunction != null)
986             return typeFromFunction;
987
988         Resource resource = getPossibleRepresents(graph);
989         if (resource == null) {
990             String uri = getPossiblePropertyValue(graph, "typeURI");
991             if (uri != null)
992                 return graph.syncRequest(new org.simantics.db.common.primitiverequest.Resource(uri),
993                         TransientCacheAsyncListener.<Resource> instance());
994             throw new DatabaseException("No type for " + getURI(graph));
995         }
996         return graph.getSingleType(resource, baseType);
997     }
998
999     @Override
1000     public Resource getPossibleType(ReadGraph graph, Resource baseType) throws DatabaseException {
1001         try {
1002             Resource typeFromFunction = getTypeFromPossibleTypeFunction(graph, baseType, getPossibleTypeFunction(graph));
1003             if (typeFromFunction != null)
1004                 return typeFromFunction;
1005         } catch (DatabaseException t) {
1006             return null;
1007         }
1008
1009         Resource resource = getPossibleRepresents(graph);
1010         if(resource == null) {
1011             String uri = getPossiblePropertyValue(graph, "typeURI");
1012             if(uri != null) {
1013                 Resource type = graph.syncRequest(new PossibleResource(uri), TransientCacheAsyncListener.<Resource>instance());
1014                 if(type == null) return null;
1015                 if(graph.isInheritedFrom(type, baseType)) return type;
1016                 else return null;
1017             }
1018             return null;
1019         }
1020         return graph.getPossibleType(resource, baseType);
1021     }
1022
1023     private Resource getTypeFromPossibleTypeFunction(ReadGraph graph, Resource baseType,
1024             Function2<Variable, Resource, Resource> fn) throws DatabaseException {
1025         if (fn == null)
1026             // Type function was not defined - return nothing
1027             return null;
1028
1029         SCLContext sclContext = SCLContext.getCurrent();
1030         Object oldGraph = sclContext.put("graph", graph);
1031         try {
1032             return (Resource) fn.apply(this, baseType);
1033         } catch (Throwable t) {
1034             if (t instanceof DatabaseException)
1035                 throw (DatabaseException) t;
1036             throw new DatabaseException(t);
1037         } finally {
1038             sclContext.put("graph", oldGraph);
1039         }
1040     }
1041
1042     @SuppressWarnings("unchecked")
1043     private Function2<Variable, Resource, Resource> getPossibleTypeFunction(ReadGraph graph) throws DatabaseException {
1044         Variable custom = getPossibleProperty(graph, "typeResource");
1045         return custom != null
1046             ? (Function2<Variable, Resource, Resource>) custom.getPossibleValue(graph)
1047             : null;
1048     }
1049
1050     public Map<String, Variable> collectDomainProperties(ReadGraph graph, String classification, Map<String, Variable> map) throws DatabaseException {
1051         Map<String,Variable> all = collectDomainProperties(graph, null);
1052         for(Map.Entry<String, Variable> entry : all.entrySet()) {
1053                 Set<String> classifications = entry.getValue().getClassifications(graph);
1054                 if(classifications.contains(classification)) {
1055                         if(map == null) map = new HashMap<String,Variable>();
1056                         map.put(entry.getKey(), entry.getValue());
1057                 }
1058         }
1059         return map;
1060     }
1061     
1062     @Override
1063     public RVI getPossibleRVI(ReadGraph graph) throws DatabaseException {
1064         try {
1065             return getRVI(graph);
1066         } catch(DatabaseException e) {
1067             return null;
1068         }
1069     }
1070
1071 }