package org.simantics.db.layer0.variable; import java.util.Arrays; import org.simantics.databoard.Bindings; import org.simantics.databoard.Databoard; import org.simantics.databoard.annotations.Union; import org.simantics.databoard.binding.Binding; import org.simantics.databoard.util.Bean; import org.simantics.databoard.util.URIStringUtils; import org.simantics.db.ReadGraph; import org.simantics.db.RequestProcessor; import org.simantics.db.Resource; import org.simantics.db.common.request.UniqueRead; import org.simantics.db.common.utils.NameUtils; import org.simantics.db.exception.AdaptionException; import org.simantics.db.exception.DatabaseException; import org.simantics.db.exception.ServiceException; import org.simantics.db.exception.ValidationException; import org.simantics.db.layer0.variable.Variables.Role; import org.simantics.db.service.SerialisationSupport; import org.simantics.layer0.Layer0; /** * Relative Value Indicator is a parent-child reference inside variable-tree * address space. * * @See RVIBuilder * @author toni.kalajainen */ public class RVI extends Bean { private static final RVIPart[] NONE = {}; public RVIPart[] parts; public RVI() { super(); } public RVI(Binding rviBinding) { super(rviBinding); } public static RVI empty(Binding rviBinding) { RVI result = new RVI(rviBinding); result.parts = NONE; return result; } public boolean isEmpty() { return parts.length == 0; } public Variable resolve(ReadGraph graph, Variable base) throws DatabaseException { for(RVIPart part : parts) { base = base.resolve(graph, part); } return base; } public Variable resolvePossible(ReadGraph graph, Variable base) throws DatabaseException { for(RVIPart part : parts) { base = base.resolvePossible(graph, part); if (base == null) return null; } return base; } @Override public int hashCode() { return Arrays.hashCode(parts); } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; RVI other = (RVI) obj; return Arrays.equals(parts, other.parts); } /** * Returns a string representation of the all the parts of this RVI for * visualization purposes. This representation of the RVI does not withstand * moving the RVI-referenced resource between model composites unlike the * databoard serialization of this class. * *

* Implementation takes the RVI part of a Variable resolved using this RVI * and the specified Variable as a base for the resolution. * * @param graph transaction handle * @param base base resource to use for resolving the variable * @return this RVI as a String * @throws DatabaseException */ public String asString(ReadGraph graph, Variable base) throws DatabaseException { String baseURI = base.getURI(graph); Variable resolved = resolve(graph, base); return resolved.getURI(graph).substring(baseURI.length()); } public UniqueRead asStringRequest(final Resource base) { return new UniqueRead() { @Override public String perform(ReadGraph graph) throws DatabaseException { Variable v = Variables.getConfigurationContext(graph, base); return asString(graph, v); } }; } /** * Works like {@link #asString(ReadGraph, Variable)} but returns * null if resolution of the string form fails because of the * database contents. * * @param graph transaction handle * @param base base resource to use for resolving the variable * @return this RVI as a String * @throws DatabaseException */ public String asPossibleString(ReadGraph graph, Variable base) throws DatabaseException { try { return asString(graph, base); } catch (DatabaseException e) { return null; } } /** * Print as persistent string */ @Override public String toString() { StringBuilder sb = new StringBuilder(); for (RVIPart p : parts) { if (p instanceof ResourceRVIPart) { sb.append(p.toString()); } else if (p instanceof StringRVIPart) { sb.append(((StringRVIPart) p).toEscapedString()); } else if (p instanceof GuidRVIPart) { sb.append(((GuidRVIPart) p).toEscapedString()); } } return sb.toString(); } /** * Resolves this RVI into a string with a best effort method that tries to * resolve the RVI parts into variables for as long as it can. After * resolution is finished the remaining RVI parts are simply concatenated * into the result string as strings without resolving them into variables. * This is different from {@link #asString(ReadGraph, Variable)} or * {@link #asPossibleString(ReadGraph, Variable)} in that it doesn't demand * that the RVI resolves completely. Still at least the first RVI part must * resolve, otherwise null is returned, since this means that * the variable no longer exists at all. * * @param graph * database read handle * @param base * the base of resolution for the RVI * @return The RVI of the referenced entity as a string or null * if zero parts of this RVI can be resolved, i.e. the resources * referenced by it no longer exist. * @throws DatabaseException */ public String toPossibleString(ReadGraph graph, Variable base) throws DatabaseException { String baseURI = base.getURI(graph); Variable resolved = null; int i = 0; for (; i < parts.length; ++i) { RVIPart p = parts[i]; base = base.resolvePossible(graph, p); if (base == null) break; resolved = base; } if (resolved == null) return null; StringBuilder sb = new StringBuilder(); String resolvedURI = resolved.getURI(graph); sb.append(resolvedURI, baseURI.length(), resolvedURI.length()); if (i < parts.length) { // The tail didn't resolve into a Variable synchronously // so let's just concatenate the rest by hand into the // result string. Layer0 L0 = Layer0.getInstance(graph); for (; i < parts.length; ++i) { RVIPart p = parts[i]; if (p instanceof ResourceRVIPart) { Resource r = ((ResourceRVIPart) p).resource; String str = graph.getPossibleRelatedValue(r, L0.HasName, Bindings.STRING); if (str == null) return null; sb.append(p.getRole().getIdentifier()).append(URIStringUtils.escape(str)); } else if (p instanceof StringRVIPart) { sb.append(p.getRole().getIdentifier()).append(URIStringUtils.escape(((StringRVIPart) p).string)); } else if (p instanceof GuidRVIPart) { sb.append(p.getRole().getIdentifier()).append(((GuidRVIPart)p).mostSignificant).append(":").append(((GuidRVIPart)p).leastSignificant); } } } return sb.toString(); } /** * Print for USER. Not the real RVI * * @param graph * @return * @throws AdaptionException * @throws ValidationException * @throws ServiceException */ public String toString(ReadGraph graph) throws DatabaseException { StringBuilder sb = new StringBuilder(); for (RVIPart p : parts) { if (p instanceof ResourceRVIPart) { Resource r = ((ResourceRVIPart) p).resource; String str = NameUtils.getSafeName(graph, r); sb.append(p.getRole().getIdentifier()).append(str); } else if (p instanceof StringRVIPart) { String str = p.toString(); sb.append(str); } else if (p instanceof GuidRVIPart) { String str = p.toString(); sb.append(str); } } return sb.toString(); } // Enumeration | ResourceRVIPart | StringRVIPart | GuidRVIPart @Union({ResourceRVIPart.class, StringRVIPart.class, GuidRVIPart.class}) public static interface RVIPart { public Role getRole(); } public static class ResourceRVIPart implements RVIPart { public Role role; public Resource resource; @Override public Role getRole() { return role; } public ResourceRVIPart() {} public ResourceRVIPart(Role role, Resource resource) { if (resource == null) throw new NullPointerException("null resource"); this.role = role; this.resource = resource; } @Override public String toString() { return role.getIdentifier()+"r"+Long.toString( resource.getResourceId() ); } @Override public int hashCode() { return resource.hashCode() * 31 + role.hashCode(); } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; ResourceRVIPart other = (ResourceRVIPart) obj; return role == other.role && resource.equals(other.resource); } } public static class StringRVIPart implements RVIPart { public Role role; public String string; @Override public Role getRole() { return role; } public StringRVIPart() {} public StringRVIPart(Role role, String string) { if (string == null) throw new NullPointerException("null string"); this.role = role; this.string = string; } @Override public String toString() { return role.getIdentifier()+string; } public String toEscapedString() { return role.getIdentifier()+URIStringUtils.escape(string); } @Override public int hashCode() { return string.hashCode() * 31 + role.hashCode(); } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; StringRVIPart other = (StringRVIPart) obj; return role == other.role && string.equals(other.string); } } public static class GuidRVIPart implements RVIPart { public Role role; public long mostSignificant; public long leastSignificant; public transient Resource resource; @Override public Role getRole() { return role; } public GuidRVIPart() {} public GuidRVIPart(Role role, Resource resource, long mostSignificant, long leastSignificant) { this.role = role; this.resource = resource; this.mostSignificant = mostSignificant; this.leastSignificant = leastSignificant; } @Override public String toString() { return role.getIdentifier()+mostSignificant+":"+leastSignificant; } public String toEscapedString() { String rid = resource != null ? Long.toString( resource.getResourceId() ) : "0"; return role.getIdentifier()+mostSignificant+":"+leastSignificant+":"+rid; } @Override public int hashCode() { return (longHashCode(leastSignificant) * 51 + longHashCode(mostSignificant)) * 31 + role.hashCode(); } /* * TODO: remove this when switched into Java 1.8 */ private static int longHashCode(long value) { return (int)(value ^ (value >>> 32)); } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; GuidRVIPart other = (GuidRVIPart) obj; return role == other.role && leastSignificant == other.leastSignificant && mostSignificant == other.mostSignificant; } } public static RVI fromResourceFormat( RequestProcessor proc, String str ) { SerialisationSupport support = proc.getService(SerialisationSupport.class); if (support == null) throw new RuntimeException("No serialization support in Session"); Databoard databoard = proc.getService(Databoard.class); if (databoard == null) throw new RuntimeException("No databoard support in Session"); Binding rviBinding = databoard.getBindingUnchecked( RVI.class ); RVIBuilder rb = new RVIBuilder( rviBinding ); int pos = 0, len = str.length(); while (pos -1) { String x = str.substring(pos, end); if (!x.isEmpty()) { String[] parts = x.split(":"); if (parts.length == 3) { try { long res = (int) Long.parseLong(parts[2]); long mostSignificant = Long.parseLong(parts[0]); long leastSignificant = Long.parseLong(parts[1]); Resource r = support.getResource( res ); rb.append( role, r, mostSignificant, leastSignificant); pos = end; continue; } catch (NumberFormatException nfe) { } catch (DatabaseException e) { } } } } String text = URIStringUtils.unescape( str.substring(pos, end) ); pos = end; rb.append( role, text ); } return rb.toRVI(); } }