1 package org.simantics.db.layer0.variable;
3 import java.util.Arrays;
5 import org.simantics.databoard.Bindings;
6 import org.simantics.databoard.Databoard;
7 import org.simantics.databoard.annotations.Union;
8 import org.simantics.databoard.binding.Binding;
9 import org.simantics.databoard.util.Bean;
10 import org.simantics.databoard.util.URIStringUtils;
11 import org.simantics.db.ReadGraph;
12 import org.simantics.db.RequestProcessor;
13 import org.simantics.db.Resource;
14 import org.simantics.db.common.request.UniqueRead;
15 import org.simantics.db.common.utils.NameUtils;
16 import org.simantics.db.exception.AdaptionException;
17 import org.simantics.db.exception.DatabaseException;
18 import org.simantics.db.exception.ServiceException;
19 import org.simantics.db.exception.ValidationException;
20 import org.simantics.db.layer0.variable.Variables.Role;
21 import org.simantics.db.service.SerialisationSupport;
22 import org.simantics.layer0.Layer0;
25 * Relative Value Indicator is a parent-child reference inside variable-tree
29 * @author toni.kalajainen
31 public class RVI extends Bean {
33 private static final RVIPart[] NONE = {};
35 public RVIPart[] parts;
41 public RVI(Binding rviBinding) {
45 public static RVI empty(Binding rviBinding) {
46 RVI result = new RVI(rviBinding);
51 public boolean isEmpty() {
52 return parts.length == 0;
55 public Variable resolve(ReadGraph graph, Variable base) throws DatabaseException {
56 for(RVIPart part : parts) {
57 base = base.resolve(graph, part);
62 public Variable resolvePossible(ReadGraph graph, Variable base) throws DatabaseException {
63 for(RVIPart part : parts) {
64 base = base.resolvePossible(graph, part);
72 public int hashCode() {
73 return Arrays.hashCode(parts);
77 public boolean equals(Object obj) {
82 if (getClass() != obj.getClass())
84 RVI other = (RVI) obj;
85 return Arrays.equals(parts, other.parts);
89 * Returns a string representation of the all the parts of this RVI for
90 * visualization purposes. This representation of the RVI does not withstand
91 * moving the RVI-referenced resource between model composites unlike the
92 * databoard serialization of this class.
95 * Implementation takes the RVI part of a Variable resolved using this RVI
96 * and the specified Variable as a base for the resolution.
98 * @param graph transaction handle
99 * @param base base resource to use for resolving the variable
100 * @return this RVI as a String
101 * @throws DatabaseException
103 public String asString(ReadGraph graph, Variable base) throws DatabaseException {
104 String baseURI = base.getURI(graph);
105 Variable resolved = resolve(graph, base);
106 return resolved.getURI(graph).substring(baseURI.length());
109 public UniqueRead<String> asStringRequest(final Resource base) {
110 return new UniqueRead<String>() {
112 public String perform(ReadGraph graph) throws DatabaseException {
113 Variable v = Variables.getConfigurationContext(graph, base);
114 return asString(graph, v);
120 * Works like {@link #asString(ReadGraph, Variable)} but returns
121 * <code>null</code> if resolution of the string form fails because of the
124 * @param graph transaction handle
125 * @param base base resource to use for resolving the variable
126 * @return this RVI as a String
127 * @throws DatabaseException
129 public String asPossibleString(ReadGraph graph, Variable base) throws DatabaseException {
131 return asString(graph, base);
132 } catch (DatabaseException e) {
138 * Print as persistent string
141 public String toString() {
142 StringBuilder sb = new StringBuilder();
143 for (RVIPart p : parts) {
144 if (p instanceof ResourceRVIPart) {
145 sb.append(p.toString());
146 } else if (p instanceof StringRVIPart) {
147 sb.append(((StringRVIPart) p).toEscapedString());
148 } else if (p instanceof GuidRVIPart) {
149 sb.append(((GuidRVIPart) p).toEscapedString());
152 return sb.toString();
156 * Resolves this RVI into a string with a best effort method that tries to
157 * resolve the RVI parts into variables for as long as it can. After
158 * resolution is finished the remaining RVI parts are simply concatenated
159 * into the result string as strings without resolving them into variables.
160 * This is different from {@link #asString(ReadGraph, Variable)} or
161 * {@link #asPossibleString(ReadGraph, Variable)} in that it doesn't demand
162 * that the RVI resolves completely. Still at least the first RVI part must
163 * resolve, otherwise <code>null</code> is returned, since this means that
164 * the variable no longer exists at all.
167 * database read handle
169 * the base of resolution for the RVI
170 * @return The RVI of the referenced entity as a string or <code>null</code>
171 * if zero parts of this RVI can be resolved, i.e. the resources
172 * referenced by it no longer exist.
173 * @throws DatabaseException
175 public String toPossibleString(ReadGraph graph, Variable base) throws DatabaseException {
176 String baseURI = base.getURI(graph);
178 Variable resolved = null;
180 for (; i < parts.length; ++i) {
181 RVIPart p = parts[i];
182 base = base.resolvePossible(graph, p);
187 if (resolved == null)
190 StringBuilder sb = new StringBuilder();
191 String resolvedURI = resolved.getURI(graph);
192 sb.append(resolvedURI, baseURI.length(), resolvedURI.length());
193 if (i < parts.length) {
194 // The tail didn't resolve into a Variable synchronously
195 // so let's just concatenate the rest by hand into the
197 Layer0 L0 = Layer0.getInstance(graph);
198 for (; i < parts.length; ++i) {
199 RVIPart p = parts[i];
200 if (p instanceof ResourceRVIPart) {
201 Resource r = ((ResourceRVIPart) p).resource;
202 String str = graph.getPossibleRelatedValue(r, L0.HasName, Bindings.STRING);
205 sb.append(p.getRole().getIdentifier()).append(URIStringUtils.escape(str));
206 } else if (p instanceof StringRVIPart) {
207 sb.append(p.getRole().getIdentifier()).append(URIStringUtils.escape(((StringRVIPart) p).string));
208 } else if (p instanceof GuidRVIPart) {
209 sb.append(p.getRole().getIdentifier()).append(((GuidRVIPart)p).mostSignificant).append(":").append(((GuidRVIPart)p).leastSignificant);
214 return sb.toString();
218 * Print for USER. Not the real RVI
222 * @throws AdaptionException
223 * @throws ValidationException
224 * @throws ServiceException
226 public String toString(ReadGraph graph) throws DatabaseException {
227 StringBuilder sb = new StringBuilder();
228 for (RVIPart p : parts) {
229 if (p instanceof ResourceRVIPart) {
230 Resource r = ((ResourceRVIPart) p).resource;
231 String str = NameUtils.getSafeName(graph, r);
232 sb.append(p.getRole().getIdentifier()).append(str);
233 } else if (p instanceof StringRVIPart) {
234 String str = p.toString();
236 } else if (p instanceof GuidRVIPart) {
237 String str = p.toString();
241 return sb.toString();
244 // Enumeration | ResourceRVIPart | StringRVIPart | GuidRVIPart
245 @Union({ResourceRVIPart.class, StringRVIPart.class, GuidRVIPart.class})
246 public static interface RVIPart { public Role getRole(); }
248 public static class ResourceRVIPart implements RVIPart {
250 public Resource resource;
252 public Role getRole() {
255 public ResourceRVIPart() {}
256 public ResourceRVIPart(Role role, Resource resource) {
257 if (resource == null)
258 throw new NullPointerException("null resource");
260 this.resource = resource;
263 public String toString() {
264 return role.getIdentifier()+"r"+Long.toString( resource.getResourceId() );
267 public int hashCode() {
268 return resource.hashCode() * 31 + role.hashCode();
271 public boolean equals(Object obj) {
276 if (getClass() != obj.getClass())
278 ResourceRVIPart other = (ResourceRVIPart) obj;
279 return role == other.role && resource.equals(other.resource);
283 public static class StringRVIPart implements RVIPart {
285 public String string;
287 public Role getRole() {
290 public StringRVIPart() {}
291 public StringRVIPart(Role role, String string) {
293 throw new NullPointerException("null string");
295 this.string = string;
298 public String toString() {
299 return role.getIdentifier()+string;
301 public String toEscapedString() {
302 return role.getIdentifier()+URIStringUtils.escape(string);
305 public int hashCode() {
306 return string.hashCode() * 31 + role.hashCode();
309 public boolean equals(Object obj) {
314 if (getClass() != obj.getClass())
316 StringRVIPart other = (StringRVIPart) obj;
317 return role == other.role && string.equals(other.string);
321 public static class GuidRVIPart implements RVIPart {
323 public long mostSignificant;
324 public long leastSignificant;
325 public transient Resource resource;
327 public Role getRole() {
330 public GuidRVIPart() {}
331 public GuidRVIPart(Role role, Resource resource, long mostSignificant, long leastSignificant) {
333 this.resource = resource;
334 this.mostSignificant = mostSignificant;
335 this.leastSignificant = leastSignificant;
338 public String toString() {
339 return role.getIdentifier()+mostSignificant+":"+leastSignificant;
341 public String toEscapedString() {
342 String rid = resource != null ? Long.toString( resource.getResourceId() ) : "0";
343 return role.getIdentifier()+mostSignificant+":"+leastSignificant+":"+rid;
346 public int hashCode() {
347 return (longHashCode(leastSignificant) * 51 + longHashCode(mostSignificant)) * 31 + role.hashCode();
350 * TODO: remove this when switched into Java 1.8
352 private static int longHashCode(long value) {
353 return (int)(value ^ (value >>> 32));
357 public boolean equals(Object obj) {
362 if (getClass() != obj.getClass())
364 GuidRVIPart other = (GuidRVIPart) obj;
365 return role == other.role && leastSignificant == other.leastSignificant && mostSignificant == other.mostSignificant;
370 public static RVI fromResourceFormat( RequestProcessor proc, String str ) {
371 SerialisationSupport support = proc.getService(SerialisationSupport.class);
372 if (support == null) throw new RuntimeException("No serialization support in Session");
373 Databoard databoard = proc.getService(Databoard.class);
374 if (databoard == null) throw new RuntimeException("No databoard support in Session");
376 Binding rviBinding = databoard.getBindingUnchecked( RVI.class );
377 RVIBuilder rb = new RVIBuilder( rviBinding );
378 int pos = 0, len = str.length();
379 while (pos<str.length()) {
381 char c = str.charAt(pos);
384 role = Role.PROPERTY;
391 int e1 = str.indexOf('#', pos);
392 int e2 = str.indexOf('/', pos);
395 int end = e1<e2?e1:e2;
396 if (str.charAt(pos) == 'r') {
397 String x = str.substring(pos+1, end);
400 long res = (int) Long.parseLong(x);
401 Resource r = support.getResource( res );
402 rb.append( role, r );
405 } catch (NumberFormatException nfe) {
406 } catch (DatabaseException e) {
410 if (str.indexOf(":", pos) > -1) {
411 String x = str.substring(pos, end);
413 String[] parts = x.split(":");
414 if (parts.length == 3) {
416 long res = (int) Long.parseLong(parts[2]);
417 long mostSignificant = Long.parseLong(parts[0]);
418 long leastSignificant = Long.parseLong(parts[1]);
419 Resource r = support.getResource( res );
420 rb.append( role, r, mostSignificant, leastSignificant);
423 } catch (NumberFormatException nfe) {
424 } catch (DatabaseException e) {
429 String text = URIStringUtils.unescape( str.substring(pos, end) );
431 rb.append( role, text );