/* * Copyright Aduna (http://www.aduna-software.com/) (c) 2008. * * Licensed under the Aduna BSD-style license. */ package org.openrdf.model.impl; import static java.util.Arrays.asList; import static java.util.Collections.unmodifiableSet; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.Serializable; import java.util.AbstractSet; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.Map; import java.util.NoSuchElementException; import java.util.Set; import info.aduna.collections.iterators.FilterIterator; import org.openrdf.OpenRDFUtil; import org.openrdf.model.Literal; import org.openrdf.model.Model; import org.openrdf.model.Resource; import org.openrdf.model.Statement; import org.openrdf.model.URI; import org.openrdf.model.Value; import org.openrdf.model.util.ModelUtil; import org.openrdf.model.util.ModelException; /** * @author James Leigh */ @SuppressWarnings("unchecked") public class LinkedHashModel extends AbstractSet implements Model { private static final long serialVersionUID = -9161104123818983614L; static final Resource[] NULL_CTX = new Resource[] { null }; Map namespaces = new LinkedHashMap(); transient Map values; transient Set statements; private String baseURI; private URI resourceOfInterest; public LinkedHashModel() { super(); values = new HashMap(); statements = new LinkedHashSet(); } public LinkedHashModel(Collection c) { super(); values = new HashMap(c.size() * 2); statements = new LinkedHashSet(c.size()); addAll(c); } public LinkedHashModel(int size) { super(); values = new HashMap(size * 2); statements = new LinkedHashSet(size); } public LinkedHashModel(Map namespaces, Collection c) { this(c); this.namespaces.putAll(namespaces); } public LinkedHashModel(Map namespaces) { this(); this.namespaces.putAll(namespaces); } public LinkedHashModel(Map namespaces, int size) { this(size); this.namespaces.putAll(namespaces); } public String getNamespace(String prefix) { return namespaces.get(prefix); } public Map getNamespaces() { return namespaces; } public String setNamespace(String prefix, String name) { return namespaces.put(prefix, name); } public void removeNamespace(String prefix) { namespaces.remove(prefix); } public String getBaseURI() { return baseURI; } public void setBaseURI(String baseURI) { this.baseURI = baseURI; } public URI getResourceOfInterest() { return resourceOfInterest; } public void setResourceOfInterest(URI resourceOfInterest) { this.resourceOfInterest = resourceOfInterest; } public int size() { return statements.size(); } @Override public boolean add(Statement st) { return add(st.getSubject(), st.getPredicate(), st.getObject(), st.getContext()); } public boolean add(Resource subj, URI pred, Value obj, Resource... contexts) { Resource[] ctxs = OpenRDFUtil.notNull(contexts); if (ctxs.length == 0) { ctxs = NULL_CTX; } boolean changed = false; for (Resource ctx : ctxs) { ModelNode s = asGraphNode(subj); ModelNode p = asGraphNode(pred); ModelNode o = asGraphNode(obj); ModelNode c = asGraphNode(ctx); ModelStatement st = new ModelStatement(s, p, o, c); changed |= addModelStatement(st); } return changed; } public void addAll(Model other) { addAll((Set)other); namespaces.putAll(other.getNamespaces()); if (baseURI == null) { baseURI = other.getBaseURI(); } if (resourceOfInterest == null) { resourceOfInterest = other.getResourceOfInterest(); } } @Override public void clear() { values.clear(); statements.clear(); } @Override public boolean remove(Object o) { if (o instanceof Statement) { Iterator iter = find((Statement)o); if (iter.hasNext()) { iter.next(); iter.remove(); return true; } } return false; } @Override public boolean contains(Object o) { if (o instanceof Statement) { return find((Statement)o).hasNext(); } return false; } public Iterator iterator() { return match(null, null, null); } public boolean contains(Resource subj, URI pred, Value obj, Resource... contexts) { return match(subj, pred, obj, contexts).hasNext(); } public boolean remove(Resource subj, URI pred, Value obj, Resource... contexts) { Iterator iter = match(subj, pred, obj, contexts); if (!iter.hasNext()) return false; while (iter.hasNext()) { iter.next(); iter.remove(); } return true; } public boolean clear(Resource... contexts) { return remove(null, null, null, contexts); } public Model filter(Resource subj, URI pred, Value obj, Resource... contexts) { return new FilteredModel(subj, pred, obj, contexts); } public Set subjects() { return subjects(null, null); } public Set predicates() { return predicates(null, null); } public Set objects() { return objects(null, null); } public Set contexts() { return contexts(null, null, null); } public Value objectValue() throws ModelException { Iterator iter = objects().iterator(); if (iter.hasNext()) { Value obj = iter.next(); if (iter.hasNext()) throw new ModelException(); return obj; } return null; } public Literal objectLiteral() throws ModelException { Value obj = objectValue(); if (obj == null) return null; if (obj instanceof Literal) return (Literal)obj; throw new ModelException(); } public Resource objectResource() throws ModelException { Value obj = objectValue(); if (obj == null) return null; if (obj instanceof Resource) return (Resource)obj; throw new ModelException(); } public URI objectURI() throws ModelException { Value obj = objectValue(); if (obj == null) return null; if (obj instanceof URI) return (URI)obj; throw new ModelException(); } public String objectString() throws ModelException { Value obj = objectValue(); if (obj == null) return null; return obj.stringValue(); } public int hashCode() { return size(); } public boolean equals(Object o) { if (this == o) return true; if (o instanceof Model) { Model model = (Model)o; return ModelUtil.equals(this, model); } return false; } Set contexts(final Resource subj, final URI pred, final Value obj) { return new ValueSet() { @Override public boolean contains(Object o) { if (o instanceof Resource || o == null) { return LinkedHashModel.this.contains(subj, pred, obj, (Resource)o); } return false; } @Override public boolean remove(Object o) { if (o instanceof Resource || o == null) return LinkedHashModel.this.remove(subj, pred, obj, (Resource)o); return false; } @Override public boolean add(Resource ctx) { if (subj == null || pred == null || obj == null) throw new UnsupportedOperationException("Incomplete statement"); if (contains(ctx)) return false; return LinkedHashModel.this.add(subj, pred, obj, ctx); } @Override public void clear() { LinkedHashModel.this.remove(subj, pred, obj); } @Override protected ModelIterator statementIterator() { return match(subj, pred, obj); } @Override protected ModelNode node(ModelStatement st) { return st.ctx; } @Override protected Set set(ModelNode node) { return node.contexts; } }; } Set objects(final Resource subj, final URI pred, final Resource... contexts) { return new ValueSet() { @Override public boolean contains(Object o) { if (o instanceof Value) { return LinkedHashModel.this.contains(subj, pred, (Value)o, contexts); } return false; } @Override public boolean remove(Object o) { if (o instanceof Value) return LinkedHashModel.this.remove(subj, pred, (Value)o, contexts); return false; } @Override public boolean add(Value obj) { if (subj == null || pred == null) throw new UnsupportedOperationException("Incomplete statement"); if (contains(obj)) return false; return LinkedHashModel.this.add(subj, pred, obj, contexts); } @Override public void clear() { LinkedHashModel.this.remove(subj, pred, null, contexts); } @Override protected ModelIterator statementIterator() { return match(subj, pred, null, contexts); } @Override protected ModelNode node(ModelStatement st) { return st.obj; } @Override protected Set set(ModelNode node) { return node.objects; } }; } Set predicates(final Resource subj, final Value obj, final Resource... contexts) { return new ValueSet() { @Override public boolean contains(Object o) { if (o instanceof URI) { return LinkedHashModel.this.contains(subj, (URI)o, obj, contexts); } return false; } @Override public boolean remove(Object o) { if (o instanceof URI) return LinkedHashModel.this.remove(subj, (URI)o, obj, contexts); return false; } @Override public boolean add(URI pred) { if (subj == null || obj == null) throw new UnsupportedOperationException("Incomplete statement"); if (contains(pred)) return false; return LinkedHashModel.this.add(subj, pred, obj, contexts); } @Override public void clear() { LinkedHashModel.this.remove(subj, null, obj, contexts); } @Override protected ModelIterator statementIterator() { return match(subj, null, obj, contexts); } @Override protected ModelNode node(ModelStatement st) { return st.pred; } @Override protected Set set(ModelNode node) { return node.predicates; } }; } Set subjects(final URI pred, final Value obj, final Resource... contexts) { return new ValueSet() { @Override public boolean contains(Object o) { if (o instanceof Resource) { return LinkedHashModel.this.contains((Resource)o, pred, obj, contexts); } return false; } @Override public boolean remove(Object o) { if (o instanceof Resource) return LinkedHashModel.this.remove((Resource)o, pred, obj, contexts); return false; } @Override public boolean add(Resource subj) { if (pred == null || obj == null) throw new UnsupportedOperationException("Incomplete statement"); if (contains(subj)) return false; return LinkedHashModel.this.add(subj, pred, obj, contexts); } @Override public void clear() { LinkedHashModel.this.remove(null, pred, obj, contexts); } @Override protected ModelIterator statementIterator() { return match(null, pred, obj, contexts); } @Override protected ModelNode node(ModelStatement st) { return st.subj; } @Override protected Set set(ModelNode node) { return node.subjects; } }; } ModelIterator match(Resource subj, URI pred, Value obj, Resource... contexts) { assert contexts != null; Set s = null; Set p = null; Set o = null; if (subj != null) { s = asGraphNode(subj).subjects; } if (pred != null) { p = asGraphNode(pred).predicates; } if (obj != null) { o = asGraphNode(obj).objects; } Set set; contexts = OpenRDFUtil.notNull(contexts); if (contexts.length == 1) { Set c = asGraphNode(contexts[0]).contexts; set = smallest(statements, s, p, o, c); } else { set = smallest(statements, s, p, o); } Iterator it = set.iterator(); Iterator iter; iter = new PatternIterator(it, subj, pred, obj, contexts); return new ModelIterator(iter, set); } boolean matches(Statement st, Resource subj, URI pred, Value obj, Resource... contexts) { if (subj != null && !subj.equals(st.getSubject())) { return false; } if (pred != null && !pred.equals(st.getPredicate())) { return false; } if (obj != null && !obj.equals(st.getObject())) { return false; } return matches(st.getContext(), contexts); } boolean matches(Resource[] stContext, Resource... contexts) { if (stContext != null && stContext.length > 0) { for (Resource c : stContext) { if (!matches(c, contexts)) return false; } } return true; } boolean matches(Resource stContext, Resource... contexts) { if (contexts != null && contexts.length == 0) { // Any context matches return true; } else { // Accept if one of the contexts from the pattern matches for (Resource context : OpenRDFUtil.notNull(contexts)) { if (context == null && stContext == null) { return true; } if (context != null && context.equals(stContext)) { return true; } } return false; } } Model emptyModel = new EmptyModel(); Model emptyModel() { return emptyModel; } class EmptyModel extends AbstractSet implements Model { private static final long serialVersionUID = 3123007631452759092L; private Set emptySet = Collections.emptySet(); private String baseURI; public String getNamespace(String prefix) { return namespaces.get(prefix); } public Map getNamespaces() { return namespaces; } public String setNamespace(String prefix, String name) { return namespaces.put(prefix, name); } public void removeNamespace(String prefix) { namespaces.remove(prefix); } public String getBaseURI() { return baseURI; } public void setBaseURI(String baseURI) { this.baseURI = baseURI; } public URI getResourceOfInterest() { return null; } public void setResourceOfInterest(URI resourceOfInterest) {} @Override public Iterator iterator() { return emptySet.iterator(); } @Override public int size() { return 0; } @Override public boolean add(Statement e) { throw new UnsupportedOperationException("All statements are filtered out of view"); } public boolean add(Resource subj, URI pred, Value obj, Resource... contexts) { throw new UnsupportedOperationException("All statements are filtered out of view"); } public void addAll(Model other) { throw new UnsupportedOperationException("All statements are filtered out of view"); } public boolean clear(Resource... context) { return false; } public boolean contains(Resource subj, URI pred, Value obj, Resource... contexts) { return false; } public Set contexts() { return Collections.emptySet(); } public Model filter(Resource subj, URI pred, Value obj, Resource... contexts) { return emptyModel; } public Set objects() { return Collections.emptySet(); } public Set predicates() { return Collections.emptySet(); } public boolean remove(Resource subj, URI pred, Value obj, Resource... contexts) { return false; } public Set subjects() { return Collections.emptySet(); } public Literal objectLiteral() { return null; } public Value objectValue() { return null; } public Resource objectResource() { return null; } public URI objectURI() { return null; } public String objectString() throws ModelException { return null; } public int hashCode() { return size(); } public boolean equals(Object o) { if (this == o) return true; if (o instanceof Model) { Model model = (Model)o; return model.isEmpty(); } return false; } } class FilteredModel extends AbstractSet implements Model { private static final long serialVersionUID = -2353344619836326934L; private Resource subj; private URI pred; private Value obj; private Resource[] contexts; private String baseURI; private URI resourceOfInterest; public FilteredModel(Resource subj, URI pred, Value obj, Resource... contexts) { this.subj = subj; this.pred = pred; this.obj = obj; this.contexts = OpenRDFUtil.notNull(contexts); } public String getNamespace(String prefix) { return namespaces.get(prefix); } public Map getNamespaces() { return namespaces; } public String setNamespace(String prefix, String name) { return namespaces.put(prefix, name); } public void removeNamespace(String prefix) { namespaces.remove(prefix); } public String getBaseURI() { return baseURI; } public void setBaseURI(String baseURI) { this.baseURI = baseURI; } public URI getResourceOfInterest() { return resourceOfInterest; } public void setResourceOfInterest(URI resourceOfInterest) { this.resourceOfInterest = resourceOfInterest; } @Override public Iterator iterator() { final ModelIterator iter = statementIterator(); return new Iterator() { private ModelStatement current; private ModelStatement next; public boolean hasNext() { if (next == null && iter.hasNext()) { next = iter.next(); } return next != null; } public ModelStatement next() { if (next == null) { next = iter.next(); } current = next; next = null; return current; } public void remove() { iter.remove(); } }; } @Override public int size() { int size = 0; Iterator iter = statementIterator(); while (iter.hasNext()) { size++; iter.next(); } return size; } @Override public boolean contains(Object o) { if (o instanceof Statement) { Statement st = (Statement)o; if (accept(st)) return LinkedHashModel.this.contains(o); } return false; } @Override public boolean add(Statement st) { if (accept(st)) return LinkedHashModel.this.add(st); throw new IllegalArgumentException("Statement is filtered out of view: " + st); } public boolean add(Resource s, URI p, Value o, Resource... c) { if (!accept(s, p, o, c)) throw new IllegalArgumentException("Statement is filtered out of view"); if (s == null) { s = subj; } if (p == null) { p = pred; } if (o == null) { o = obj; } if (c != null && c.length == 0) { c = contexts; } return LinkedHashModel.this.add(s, p, o, c); } public void addAll(Model other) { addAll((Set)other); namespaces.putAll(other.getNamespaces()); if (baseURI == null) { baseURI = other.getBaseURI(); } if (resourceOfInterest == null) { resourceOfInterest = other.getResourceOfInterest(); } } @Override public void clear() { LinkedHashModel.this.remove(subj, pred, obj, contexts); } public boolean clear(Resource... c) { c = OpenRDFUtil.notNull(c); if (c.length == 0) { return remove(subj, pred, obj, contexts); } else if (matches(c, contexts)) { return LinkedHashModel.this.remove(subj, pred, obj, c); } else { return false; } } public boolean remove(Resource s, URI p, Value o, Resource... c) { if (!accept(s, p, o, c)) return false; if (s == null) { s = subj; } if (p == null) { p = pred; } if (o == null) { o = obj; } if (c != null && c.length == 0) { c = contexts; } return LinkedHashModel.this.remove(s, p, o, c); } public boolean contains(Resource s, URI p, Value o, Resource... c) { if (!accept(s, p, o, c)) return false; if (s == null) { s = subj; } if (p == null) { p = pred; } if (o == null) { o = obj; } if (c != null && c.length == 0) { c = contexts; } return LinkedHashModel.this.contains(s, p, o, c); } public Model filter(Resource s, URI p, Value o, Resource... c) { if (!accept(s, p, o, c)) return emptyModel(); if (s == null) { s = subj; } if (p == null) { p = pred; } if (o == null) { o = obj; } if (c != null && c.length == 0) { c = contexts; } return LinkedHashModel.this.filter(s, p, o, c); } public Set contexts() { if (contexts != null && contexts.length > 0) { return unmodifiableSet(new LinkedHashSet(asList(contexts))); } return LinkedHashModel.this.contexts(subj, pred, obj); } public Set objects() { if (obj != null) { return Collections.singleton(obj); } return LinkedHashModel.this.objects(subj, pred, contexts); } public Set predicates() { if (pred != null) { return Collections.singleton(pred); } return LinkedHashModel.this.predicates(subj, obj, contexts); } public Set subjects() { if (subj != null) { return Collections.singleton(subj); } return LinkedHashModel.this.subjects(pred, obj, contexts); } public Value objectValue() throws ModelException { Iterator iter = objects().iterator(); if (iter.hasNext()) { Value obj = iter.next(); if (iter.hasNext()) throw new ModelException(); return obj; } return null; } public Literal objectLiteral() throws ModelException { Value obj = objectValue(); if (obj == null) return null; if (obj instanceof Literal) return (Literal)obj; throw new ModelException(); } public Resource objectResource() throws ModelException { Value obj = objectValue(); if (obj == null) return null; if (obj instanceof Resource) return (Resource)obj; throw new ModelException(); } public URI objectURI() throws ModelException { Value obj = objectValue(); if (obj == null) return null; if (obj instanceof URI) return (URI)obj; throw new ModelException(); } public String objectString() throws ModelException { Value obj = objectValue(); if (obj == null) return null; return obj.stringValue(); } public int hashCode() { return size(); } public boolean equals(Object o) { if (this == o) return true; if (o instanceof Model) { Model model = (Model)o; return ModelUtil.equals(this, model); } return false; } private ModelIterator statementIterator() { return match(subj, pred, obj, contexts); } private boolean accept(Statement st) { return matches(st, subj, pred, obj, contexts); } private boolean accept(Resource s, URI p, Value o, Resource... c) { if (subj != null && !subj.equals(s)) { return false; } if (pred != null && !pred.equals(p)) { return false; } if (obj != null && !obj.equals(o)) { return false; } if (!matches(OpenRDFUtil.notNull(c), contexts)) { return false; } return true; } } abstract class ValueSet extends AbstractSet { @Override public Iterator iterator() { final Set set = new LinkedHashSet(); final ModelIterator iter = statementIterator(); return new Iterator() { private ModelStatement current; private ModelStatement next; public boolean hasNext() { if (next == null) { next = findNext(); } return next != null; } public V next() { if (next == null) { next = findNext(); if (next == null) throw new NoSuchElementException(); } current = next; next = null; V value = convert(current); set.add(value); return value; } public void remove() { if (current == null) throw new IllegalStateException(); removeAll(set(node(current)), iter.getOwner()); current = null; } private ModelStatement findNext() { while (iter.hasNext()) { ModelStatement st = iter.next(); if (accept(st)) return st; } return null; } private boolean accept(ModelStatement st) { return !set.contains(convert(st)); } private V convert(ModelStatement st) { return node(st).getValue(); } }; } @Override public int size() { Set set = new LinkedHashSet(); Iterator iter = statementIterator(); while (iter.hasNext()) { set.add(node(iter.next()).getValue()); } return set.size(); } @Override public boolean remove(Object o) { if (values.containsKey(o)) { return removeAll(set(values.get(o)), null); } return false; } protected abstract ModelIterator statementIterator(); protected abstract ModelNode node(ModelStatement st); protected abstract Set set(ModelNode node); boolean removeAll(Set remove, Set owner) { if (remove.isEmpty()) return false; for (ModelStatement st : remove) { ModelNode subj = st.subj; Set subjects = subj.subjects; if (subjects == owner) { subj.subjects = new LinkedHashSet(owner); subj.subjects.removeAll(remove); } else if (subjects != remove) { subjects.remove(st); } ModelNode pred = st.pred; Set predicates = pred.predicates; if (predicates == owner) { pred.predicates = new LinkedHashSet(owner); pred.predicates.removeAll(remove); } else if (predicates != remove) { predicates.remove(st); } ModelNode obj = st.obj; Set objects = obj.objects; if (objects == owner) { obj.objects = new LinkedHashSet(owner); obj.objects.removeAll(remove); } else if (objects != remove) { objects.remove(st); } ModelNode ctx = st.ctx; Set contexts = ctx.contexts; if (contexts == owner) { ctx.contexts = new LinkedHashSet(owner); ctx.contexts.removeAll(remove); } else if (contexts != remove) { contexts.remove(st); } if (statements == owner) { statements = new LinkedHashSet(statements); statements.removeAll(remove); } else if (statements != remove && statements != owner) { statements.remove(st); } } remove.clear(); return true; } } class ModelIterator implements Iterator { private Iterator iter; private Set owner; private ModelStatement last; public ModelIterator(Iterator iter, Set owner) { this.iter = iter; this.owner = owner; } public Set getOwner() { return owner; } public boolean hasNext() { return iter.hasNext(); } public ModelStatement next() { return last = iter.next(); } public void remove() { if (last == null) throw new IllegalStateException(); removeIfNotOwner(statements); removeIfNotOwner(last.subj.subjects); removeIfNotOwner(last.pred.predicates); removeIfNotOwner(last.obj.objects); removeIfNotOwner(last.ctx.contexts); iter.remove(); // remove from owner } private void removeIfNotOwner(Set subjects) { if (subjects != owner) { subjects.remove(last); } } } class ModelNode implements Serializable { private static final long serialVersionUID = -1205676084606998540L; Set subjects = new LinkedHashSet(); Set predicates = new LinkedHashSet(); Set objects = new LinkedHashSet(); Set contexts = new LinkedHashSet(); private V value; public ModelNode(V value) { this.value = value; } public V getValue() { return value; } public boolean isNull() { return value == null; } } class ModelStatement extends StatementBase { private static final long serialVersionUID = 2200404772364346279L; ModelNode subj; ModelNode pred; ModelNode obj; ModelNode ctx; public ModelStatement(ModelNode subj, ModelNode pred, ModelNode obj, ModelNode ctx) { assert subj != null; assert pred != null; assert obj != null; assert ctx != null; this.subj = subj; this.pred = pred; this.obj = obj; this.ctx = ctx; } public Resource getSubject() { return subj.getValue(); } public URI getPredicate() { return pred.getValue(); } public Value getObject() { return obj.getValue(); } public Resource getContext() { return ctx.getValue(); } } class PatternIterator extends FilterIterator { private Resource subj; private URI pred; private Value obj; private Resource[] contexts; public PatternIterator(Iterator iter, Resource subj, URI pred, Value obj, Resource... contexts) { super(iter); this.subj = subj; this.pred = pred; this.obj = obj; this.contexts = OpenRDFUtil.notNull(contexts); } @Override protected boolean accept(S st) { return matches(st, subj, pred, obj, contexts); } } private void writeObject(ObjectOutputStream s) throws IOException { // Write out any hidden serialization magic s.defaultWriteObject(); // Write in size s.writeInt(statements.size()); // Write in all elements for (ModelStatement st : statements) { Resource subj = st.getSubject(); URI pred = st.getPredicate(); Value obj = st.getObject(); Resource ctx = st.getContext(); s.writeObject(new StatementImpl(subj, pred, obj, ctx)); } } private void readObject(ObjectInputStream s) throws IOException, ClassNotFoundException { // Read in any hidden serialization magic s.defaultReadObject(); // Read in size int size = s.readInt(); values = new HashMap(size * 2); statements = new LinkedHashSet(size); // Read in all elements for (int i = 0; i < size; i++) { Statement st = (Statement)s.readObject(); add(st); } } private Iterator find(Statement st) { Resource subj = st.getSubject(); URI pred = st.getPredicate(); Value obj = st.getObject(); Resource ctx = st.getContext(); return match(subj, pred, obj, ctx); } private boolean addModelStatement(ModelStatement st) { Set subj = st.subj.subjects; Set pred = st.pred.predicates; Set obj = st.obj.objects; Set ctx = st.ctx.contexts; if (smallest(subj, pred, obj, ctx).contains(st)) return false; statements.add(st); subj.add(st); pred.add(st); obj.add(st); ctx.add(st); return true; } private Set smallest(Set... sets) { int minSize = Integer.MAX_VALUE; Set minSet = null; for (Set set : sets) { if (set != null && set.size() < minSize) { minSet = set; } } return minSet; } private ModelNode asGraphNode(V value) { if (values.containsKey(value)) return values.get(value); ModelNode node = new ModelNode(value); values.put(value, node); return node; } }