diff --git a/src/main/java/com/dc2f/dstore/hierachynodestore/HierarchicalNodeStore.java b/src/main/java/com/dc2f/dstore/hierachynodestore/HierarchicalNodeStore.java index e5e002c..220c5ca 100644 --- a/src/main/java/com/dc2f/dstore/hierachynodestore/HierarchicalNodeStore.java +++ b/src/main/java/com/dc2f/dstore/hierachynodestore/HierarchicalNodeStore.java @@ -2,6 +2,8 @@ import java.util.Collections; +import javax.annotation.Nonnull; + import com.dc2f.dstore.hierachynodestore.impl.WorkingTreeImpl; import com.dc2f.dstore.storage.Property; import com.dc2f.dstore.storage.StorageBackend; @@ -15,9 +17,9 @@ public class HierarchicalNodeStore { private StorageBackend storageBackend; - private final static String ROOT_COMMIT_ID = "rootCommitId"; - private final static String ROOT_NODE_NAME = ""; - private final static String MASTER_BRANCH_NAME = "master"; + private final static @Nonnull String ROOT_COMMIT_ID = "rootCommitId"; + private final static @Nonnull String ROOT_NODE_NAME = ""; + private final static @Nonnull String MASTER_BRANCH_NAME = "master"; public HierarchicalNodeStore(StorageBackend storageBackend) { diff --git a/src/main/java/com/dc2f/dstore/hierachynodestore/WorkingTree.java b/src/main/java/com/dc2f/dstore/hierachynodestore/WorkingTree.java index 8bcf330..2661a5a 100644 --- a/src/main/java/com/dc2f/dstore/hierachynodestore/WorkingTree.java +++ b/src/main/java/com/dc2f/dstore/hierachynodestore/WorkingTree.java @@ -1,8 +1,16 @@ package com.dc2f.dstore.hierachynodestore; +import javax.annotation.Nonnull; + +import com.dc2f.dstore.hierachynodestore.nodetype.NodeTypeAccessor; + public interface WorkingTree { - WorkingTreeNode getRootNode(); + public static final @Nonnull String NAME_NODETYPE = ":nodetype"; + + @Nonnull WorkingTreeNode getRootNode(); - Commit commit(String message); + @Nonnull Commit commit(String message); + + @Nonnull NodeTypeAccessor getNodeTypeAccessor(); } diff --git a/src/main/java/com/dc2f/dstore/hierachynodestore/WorkingTreeNode.java b/src/main/java/com/dc2f/dstore/hierachynodestore/WorkingTreeNode.java index 08f8a17..1acf171 100644 --- a/src/main/java/com/dc2f/dstore/hierachynodestore/WorkingTreeNode.java +++ b/src/main/java/com/dc2f/dstore/hierachynodestore/WorkingTreeNode.java @@ -16,7 +16,10 @@ public interface WorkingTreeNode { * @return the created child for this node */ @Nonnull - WorkingTreeNode addChild(String childName); + WorkingTreeNode addChild(@Nonnull String childName); + + @Nonnull + WorkingTreeNode addChild(); @Nonnull @@ -24,14 +27,21 @@ public interface WorkingTreeNode { int getChildrenCount(); @Nullable - Property getProperty(String name); + Property getProperty(@Nonnull String name); void setProperty(@Nonnull String name, @Nonnull Property value); @Nonnull Map getProperties(); - Iterable getChildrenByProperty(String propertyName, Object value); + Iterable getChildrenByProperty(@Nonnull String propertyName, Object value); + + /** + * TODO: does it make sense to pass in a String here, instead of a NodeTypeDefinition object? + * @param nodeType fully qualified node type name + * @return all direct children with the given node type. + */ + Iterable getChildrenByNodeType(@Nonnull String nodeTypeName); /** * @return the storage id of the node. might return null, if it was not yet committed. diff --git a/src/main/java/com/dc2f/dstore/hierachynodestore/exception/NodeStoreException.java b/src/main/java/com/dc2f/dstore/hierachynodestore/exception/NodeStoreException.java new file mode 100644 index 0000000..cd1a181 --- /dev/null +++ b/src/main/java/com/dc2f/dstore/hierachynodestore/exception/NodeStoreException.java @@ -0,0 +1,16 @@ +package com.dc2f.dstore.hierachynodestore.exception; + +/** + * generic exception from your node store. + */ +public class NodeStoreException extends RuntimeException { + private static final long serialVersionUID = 1L; + + + public NodeStoreException(String message) { + this(message, null); + } + public NodeStoreException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/src/main/java/com/dc2f/dstore/hierachynodestore/impl/WorkingTreeImpl.java b/src/main/java/com/dc2f/dstore/hierachynodestore/impl/WorkingTreeImpl.java index 1e94308..5e530e7 100644 --- a/src/main/java/com/dc2f/dstore/hierachynodestore/impl/WorkingTreeImpl.java +++ b/src/main/java/com/dc2f/dstore/hierachynodestore/impl/WorkingTreeImpl.java @@ -7,10 +7,15 @@ import java.util.Map; import java.util.Set; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + import com.dc2f.dstore.hierachynodestore.Commit; import com.dc2f.dstore.hierachynodestore.HierarchicalNodeStore; import com.dc2f.dstore.hierachynodestore.WorkingTree; import com.dc2f.dstore.hierachynodestore.WorkingTreeNode; +import com.dc2f.dstore.hierachynodestore.impl.nodetype.NodeTypeAccessorImpl; +import com.dc2f.dstore.hierachynodestore.nodetype.NodeTypeAccessor; import com.dc2f.dstore.storage.MutableStoredFlatNode; import com.dc2f.dstore.storage.Property; import com.dc2f.dstore.storage.StorageBackend; @@ -20,14 +25,19 @@ public class WorkingTreeImpl implements WorkingTree { - private HierarchicalNodeStore hierarchicalNodeStore; + /** + * Reference back to the hierarchical node store in case we need it(?) + */ + @SuppressWarnings("unused") + private @Nonnull HierarchicalNodeStore hierarchicalNodeStore; private StoredCommit headCommit; private String branchName; + private @Nullable NodeTypeAccessor nodeTypeAccessor; StorageBackend storageBackend; Map loadedNodes = new HashMap<>(); private List changedNodes = new ArrayList<>(); - public WorkingTreeImpl(HierarchicalNodeStore hierarchicalNodeStore, + public WorkingTreeImpl(@Nonnull HierarchicalNodeStore hierarchicalNodeStore, StorageBackend storageBackend, StoredCommit headCommit, String branchName) { this.hierarchicalNodeStore = hierarchicalNodeStore; this.storageBackend = storageBackend; @@ -36,13 +46,13 @@ public WorkingTreeImpl(HierarchicalNodeStore hierarchicalNodeStore, } @Override - public WorkingTreeNode getRootNode() { + public @Nonnull WorkingTreeNode getRootNode() { StorageId rootNodeId = headCommit.getRootNode(); return getNodeByStorageId(rootNodeId, null); // return new WorkingTreeNodeImpl(this, storageBackend.readNode(rootNodeId)); } - public WorkingTreeNode getNodeByStorageId(StorageId nodeStorageId, WorkingTreeNodeImpl parentNode) { + public @Nonnull WorkingTreeNode getNodeByStorageId(StorageId nodeStorageId, WorkingTreeNodeImpl parentNode) { // TODO shouldn't we add caching right here? WorkingTreeNodeImpl ret = loadedNodes.get(nodeStorageId); if (ret == null) { @@ -61,7 +71,7 @@ public void notifyNodeChanged(WorkingTreeNodeImpl workingTreeNodeImpl) { } @Override - public Commit commit(String message) { + public @Nonnull Commit commit(String message) { if (message == null) { message = ""; } @@ -191,5 +201,15 @@ private Set findNodesToUpdate() { return toUpdate; } + @Override + @Nonnull + public NodeTypeAccessor getNodeTypeAccessor() { + NodeTypeAccessor ret = nodeTypeAccessor; + if (ret == null) { + nodeTypeAccessor = ret = new NodeTypeAccessorImpl(this); + } + return ret; + } + } diff --git a/src/main/java/com/dc2f/dstore/hierachynodestore/impl/WorkingTreeNodeImpl.java b/src/main/java/com/dc2f/dstore/hierachynodestore/impl/WorkingTreeNodeImpl.java index 2cf36da..4897d4a 100644 --- a/src/main/java/com/dc2f/dstore/hierachynodestore/impl/WorkingTreeNodeImpl.java +++ b/src/main/java/com/dc2f/dstore/hierachynodestore/impl/WorkingTreeNodeImpl.java @@ -11,6 +11,7 @@ import javax.annotation.Nullable; import com.dc2f.dstore.hierachynodestore.ChildQueryAdapter; +import com.dc2f.dstore.hierachynodestore.WorkingTree; import com.dc2f.dstore.hierachynodestore.WorkingTreeNode; import com.dc2f.dstore.storage.Property; import com.dc2f.dstore.storage.StorageId; @@ -58,7 +59,7 @@ public WorkingTreeNodeImpl(@Nonnull WorkingTreeImpl workingTreeImpl, } @Override - public Iterable getChildrenByProperty(String propertyName, Object value) { + public Iterable getChildrenByProperty(@Nonnull String propertyName, Object value) { ChildQueryAdapter queryAdapter = workingTreeImpl.storageBackend.getAdapter(ChildQueryAdapter.class); StorageId storageId = this.getStorageId(); @@ -123,10 +124,24 @@ public Iterable getChildrenByProperty(String propertyName, Obje // return ret; // } + @Override + public Iterable getChildrenByNodeType( + @Nonnull String nodeTypeName) { + return getChildrenByProperty(WorkingTree.NAME_NODETYPE, nodeTypeName); + } + @Override @Nonnull - public WorkingTreeNode addChild(String childName) { - WorkingTreeNodeImpl child = new WorkingTreeNodeImpl(workingTreeImpl, null, this); + public WorkingTreeNode addChild(@Nonnull String childName) { + WorkingTreeNode child = addChild(); child.setProperty(Property.PROPERTY_NAME, new Property(childName)); + + return child; + } + + @Override + @Nonnull + public WorkingTreeNode addChild() { + WorkingTreeNodeImpl child = new WorkingTreeNodeImpl(workingTreeImpl, null, this); child.isNew = true; workingTreeImpl.loadedNodes.put(child.getStorageId(), child); if (createdChildren == null) { @@ -228,7 +243,7 @@ public int getChildrenCount() { } @Nullable - public Property getProperty(String propertyName) { + public Property getProperty(@Nonnull String propertyName) { return loadProperties().get(propertyName); // Map properties = workingTreeImpl.storageBackend.readProperties(storedNode.getProperties()); // return properties.get(propertyName); diff --git a/src/main/java/com/dc2f/dstore/hierachynodestore/impl/nodetype/NodeTypeAccessorImpl.java b/src/main/java/com/dc2f/dstore/hierachynodestore/impl/nodetype/NodeTypeAccessorImpl.java new file mode 100644 index 0000000..a338ce5 --- /dev/null +++ b/src/main/java/com/dc2f/dstore/hierachynodestore/impl/nodetype/NodeTypeAccessorImpl.java @@ -0,0 +1,96 @@ +package com.dc2f.dstore.hierachynodestore.impl.nodetype; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.NoSuchElementException; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +import com.dc2f.dstore.hierachynodestore.WorkingTree; +import com.dc2f.dstore.hierachynodestore.WorkingTreeNode; +import com.dc2f.dstore.hierachynodestore.exception.NodeStoreException; +import com.dc2f.dstore.hierachynodestore.impl.WorkingTreeImpl; +import com.dc2f.dstore.hierachynodestore.nodetype.NodeTypeAccessor; +import com.dc2f.dstore.hierachynodestore.nodetype.NodeTypeDefinition; +import com.dc2f.dstore.storage.Property; +import com.google.common.collect.Iterables; + +import static com.dc2f.utils.NullUtils.assertNotNull; + +// FIXME: We currently create a new NodeTypeDefinitionImpl instance for each accessor call, maybe we should cache it internally? +public class NodeTypeAccessorImpl implements NodeTypeAccessor { + private @Nonnull WorkingTreeImpl workingTreeImpl; + private @Nullable WorkingTreeNode rootNode; + + public NodeTypeAccessorImpl(@Nonnull WorkingTreeImpl workingTreeImpl) { + this.workingTreeImpl = workingTreeImpl; + } + + @Override + public @Nonnull Collection listNodeTypeDefinitions() { + WorkingTreeNode child = getNodeTypeRootNode(); + + List nodeTypeDefs = new ArrayList<>(); + for (WorkingTreeNode nodeTypeChild : child.getChildrenByNodeType(NodeTypeDefinition.NODETYPE_NAME_NODETYPEDEFINTION)) { + nodeTypeDefs.add(new NodeTypeDefinitionImpl(assertNotNull(nodeTypeChild))); + } + return nodeTypeDefs; + } + + private @Nonnull WorkingTreeNode getNodeTypeRootNode() { + WorkingTreeNode nodeTypeRoot = rootNode; + if (nodeTypeRoot == null) { + WorkingTreeNode rootNode = workingTreeImpl.getRootNode(); + Iterable children = rootNode.getChildrenByProperty(Property.PROPERTY_NAME, WorkingTree.NAME_NODETYPE); + try { + nodeTypeRoot = Iterables.getOnlyElement(children); + } catch (IllegalArgumentException e) { + // iterable contained too many items? + throw new NodeStoreException("Too many root nodes with node type name.", e); + } catch (NoSuchElementException e) { + // returned iterable was empty + // we have to create a new root node. + } + if (nodeTypeRoot == null) { + nodeTypeRoot = rootNode.addChild(WorkingTree.NAME_NODETYPE); + } + rootNode = nodeTypeRoot; + } + return nodeTypeRoot; + } + + @Override + @Nonnull + public NodeTypeDefinition addNodeTypeDefinition(@Nonnull String nodeTypeName) { + if (getNodeTypeDefinitionByName(nodeTypeName) != null) { + throw new IllegalArgumentException("Nodetype already exists."); + } + WorkingTreeNode root = getNodeTypeRootNode(); + WorkingTreeNode child = root.addChild(); + child.setProperty(Property.PROPERTY_NAME, new Property(nodeTypeName)); + child.setProperty(WorkingTree.NAME_NODETYPE, new Property(NodeTypeDefinition.NODETYPE_NAME_NODETYPEDEFINTION)); + NodeTypeDefinitionImpl nodeTypeDefinition = new NodeTypeDefinitionImpl(child); + return nodeTypeDefinition; + } + + @Override + @Nullable + public NodeTypeDefinition getNodeTypeDefinitionByName( + @Nonnull String nodeTypeName) { + WorkingTreeNode root = getNodeTypeRootNode(); + Iterable children = root.getChildrenByProperty(Property.PROPERTY_NAME, new Property(nodeTypeName)); + try { + WorkingTreeNode node = Iterables.getOnlyElement(children); + return new NodeTypeDefinitionImpl(assertNotNull(node)); + } catch (IllegalArgumentException e) { + throw new NodeStoreException("There are two node types of this name.", e); + } catch (NoSuchElementException e) { + // empty iterable, no such child. + return null; + } + } + + +} diff --git a/src/main/java/com/dc2f/dstore/hierachynodestore/impl/nodetype/NodeTypeDefinitionImpl.java b/src/main/java/com/dc2f/dstore/hierachynodestore/impl/nodetype/NodeTypeDefinitionImpl.java new file mode 100644 index 0000000..ef282b6 --- /dev/null +++ b/src/main/java/com/dc2f/dstore/hierachynodestore/impl/nodetype/NodeTypeDefinitionImpl.java @@ -0,0 +1,55 @@ +package com.dc2f.dstore.hierachynodestore.impl.nodetype; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; + +import javax.annotation.Nonnull; + +import com.dc2f.dstore.hierachynodestore.WorkingTree; +import com.dc2f.dstore.hierachynodestore.WorkingTreeNode; +import com.dc2f.dstore.hierachynodestore.nodetype.NodeTypeDefinition; +import com.dc2f.dstore.hierachynodestore.nodetype.PropertyDefinition; +import com.dc2f.dstore.storage.Property; + +import static com.dc2f.utils.NullUtils.assertNotNull; + +public class NodeTypeDefinitionImpl implements NodeTypeDefinition { + public static final @Nonnull String PROPERTY_NAME = ":name"; + + private @Nonnull WorkingTreeNode node; + + public NodeTypeDefinitionImpl(@Nonnull WorkingTreeNode node) { + // TODO assert node type? + this.node = node; + } + + @Override + @Nonnull + public Collection listPropertyDefinitions() { + List ret = new ArrayList<>(); + for (WorkingTreeNode child : node.getChildren()) { + ret.add(new PropertyDefinitionImpl(assertNotNull(child))); + } + return assertNotNull(Collections.unmodifiableCollection(ret)); + } + + @Override + @Nonnull + public PropertyDefinition addPropertyDefinition(@Nonnull String name) { + WorkingTreeNode propertyNode = node.addChild(); + propertyNode.setProperty(PROPERTY_NAME, new Property(name)); + propertyNode.setProperty(WorkingTree.NAME_NODETYPE, new Property(NodeTypeDefinition.NODETYPE_NAME_PROPERTYDEFINITION)); + return new PropertyDefinitionImpl(propertyNode); + } + + @Override + @Nonnull + public Collection listAllowedChildNodeTypes() { + // FIXME implement me :) + return new ArrayList<>(); + } + + +} diff --git a/src/main/java/com/dc2f/dstore/hierachynodestore/impl/nodetype/PropertyDefinitionImpl.java b/src/main/java/com/dc2f/dstore/hierachynodestore/impl/nodetype/PropertyDefinitionImpl.java new file mode 100644 index 0000000..b243788 --- /dev/null +++ b/src/main/java/com/dc2f/dstore/hierachynodestore/impl/nodetype/PropertyDefinitionImpl.java @@ -0,0 +1,80 @@ +package com.dc2f.dstore.hierachynodestore.impl.nodetype; + +import javax.annotation.Nonnull; + +import com.dc2f.dstore.hierachynodestore.WorkingTreeNode; +import com.dc2f.dstore.hierachynodestore.exception.NodeStoreException; +import com.dc2f.dstore.hierachynodestore.nodetype.PropertyDefinition; +import com.dc2f.dstore.storage.Property; +import com.dc2f.dstore.storage.Property.PropertyType; + +import static com.dc2f.utils.NullUtils.assertNotNull; + +public class PropertyDefinitionImpl implements PropertyDefinition { + public static final @Nonnull String PROPERTY_NAME = NodeTypeDefinitionImpl.PROPERTY_NAME; + public static final @Nonnull String PROPERTY_TYPE = ":type"; + public static final @Nonnull String PROPERTY_REQUIRED = ":required"; + public static final @Nonnull String PROPERTY_INDEXED = ":indexed"; + + private WorkingTreeNode node; + + public PropertyDefinitionImpl(@Nonnull WorkingTreeNode node) { + this.node = node; + } + + @Override + @Nonnull + public String getName() { + // a property definition must always have a name.. + return assertNotNull(node.getProperty(PROPERTY_NAME)).getString(); + } + + @Override + public @Nonnull PropertyDefinition setType(@Nonnull PropertyType type) { + node.setProperty(PROPERTY_TYPE, new Property(assertNotNull(type.name()))); + return this; + } + + @Override + @Nonnull + public PropertyType getType() { + // property definition must always have a type. + Property typeProperty = assertNotNull(node.getProperty(PROPERTY_TYPE)); + PropertyType propertyType = PropertyType.valueOf(typeProperty.getString()); + if (propertyType == null) { + throw new NodeStoreException("Illegal property type {" + typeProperty.getString() + "}"); + } + return propertyType; + } + + @Override + public @Nonnull PropertyDefinition setRequired(boolean isRequired) { + node.setProperty(PROPERTY_REQUIRED, new Property(new Boolean(isRequired))); + return this; + } + + @Override + public boolean isRequired() { + Property requiredProperty = node.getProperty(PROPERTY_REQUIRED); + if (requiredProperty == null) { + return false; + } + return requiredProperty.getBoolean(); + } + + @Override + public @Nonnull PropertyDefinition setIndexed(boolean isIndexed) { + node.setProperty(PROPERTY_INDEXED, new Property(new Boolean(isIndexed))); + return this; + } + + @Override + public boolean isIndexed() { + Property indexedProperty = node.getProperty(PROPERTY_INDEXED); + if (indexedProperty == null) { + return false; + } + return indexedProperty.getBoolean(); + } + +} diff --git a/src/main/java/com/dc2f/dstore/hierachynodestore/nodetype/NodeTypeAccessor.java b/src/main/java/com/dc2f/dstore/hierachynodestore/nodetype/NodeTypeAccessor.java new file mode 100644 index 0000000..5b83bda --- /dev/null +++ b/src/main/java/com/dc2f/dstore/hierachynodestore/nodetype/NodeTypeAccessor.java @@ -0,0 +1,39 @@ +package com.dc2f.dstore.hierachynodestore.nodetype; + +import java.util.Collection; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +import com.dc2f.dstore.hierachynodestore.WorkingTree; + +/** + * Convenience interface to access node types in a working tree of a repository. Allows reflection and editing + * of node types. + * + * + * @see WorkingTree#getNodeTypeAccessor() + */ +public interface NodeTypeAccessor { + /** + * find all node type definitions in the current working tree and return them. + * TODO: really all, or just those within /:nodetypes/ ?? + * @return list of all node type definitions. + */ + @Nonnull Collection listNodeTypeDefinitions(); + + /** + * Creates a new empty node type definition and returns it. + * @param nodeTypeName name of the new node type - must be unique. + * @return a new node type defintion. + * @throws IllegalArgumentException if a node with that type already exists. + */ + @Nonnull NodeTypeDefinition addNodeTypeDefinition(@Nonnull String nodeTypeName) throws IllegalArgumentException; + + /** + * finds the node type definition by the given name and returns it. + * @param nodeTypeName name of the node type + * @return node type definition, or null if it does not exist. + */ + @Nullable NodeTypeDefinition getNodeTypeDefinitionByName(@Nonnull String nodeTypeName); +} diff --git a/src/main/java/com/dc2f/dstore/hierachynodestore/nodetype/NodeTypeDefinition.java b/src/main/java/com/dc2f/dstore/hierachynodestore/nodetype/NodeTypeDefinition.java new file mode 100644 index 0000000..c36549d --- /dev/null +++ b/src/main/java/com/dc2f/dstore/hierachynodestore/nodetype/NodeTypeDefinition.java @@ -0,0 +1,32 @@ +package com.dc2f.dstore.hierachynodestore.nodetype; + +import java.util.Collection; + +import javax.annotation.Nonnull; + +import com.dc2f.dstore.hierachynodestore.WorkingTreeNode; + +/** + * Defines one node type. Each node in the data store can be of exactly one (currently) node type. + * TODO: maybe we should also extend {@link WorkingTreeNode}? + */ +public interface NodeTypeDefinition { + /** + * the 'nodetype' name for node type definition + */ + public static final @Nonnull String NODETYPE_NAME_NODETYPEDEFINTION = "com.dc2f.nodetype.NodeTypeDefinition"; + public static final @Nonnull String NODETYPE_NAME_PROPERTYDEFINITION = "com.dc2f.nodetype.PropertyDefinition"; + + @Nonnull Collection listPropertyDefinitions(); + /** + * creates a new property definition. + * @return a new property definition object which was added to the current working tree. + */ + @Nonnull PropertyDefinition addPropertyDefinition(@Nonnull String name); + + + /** + * @return a list of all node types which can be added to an instance of this node type. + */ + @Nonnull Collection listAllowedChildNodeTypes(); +} diff --git a/src/main/java/com/dc2f/dstore/hierachynodestore/nodetype/PropertyDefinition.java b/src/main/java/com/dc2f/dstore/hierachynodestore/nodetype/PropertyDefinition.java new file mode 100644 index 0000000..0a45b19 --- /dev/null +++ b/src/main/java/com/dc2f/dstore/hierachynodestore/nodetype/PropertyDefinition.java @@ -0,0 +1,21 @@ +package com.dc2f.dstore.hierachynodestore.nodetype; + +import javax.annotation.Nonnull; + +import com.dc2f.dstore.storage.Property.PropertyType; + +/** + * definition of a property of a {@link NodeTypeDefinition}. + */ +public interface PropertyDefinition { + @Nonnull String getName(); + + @Nonnull PropertyDefinition setType(@Nonnull PropertyType type); + @Nonnull PropertyType getType(); + + @Nonnull PropertyDefinition setRequired(boolean isRequired); + boolean isRequired(); + + @Nonnull PropertyDefinition setIndexed(boolean isIndexed); + boolean isIndexed(); +} diff --git a/src/main/java/com/dc2f/dstore/storage/Property.java b/src/main/java/com/dc2f/dstore/storage/Property.java index 3c53ca4..7f459b9 100644 --- a/src/main/java/com/dc2f/dstore/storage/Property.java +++ b/src/main/java/com/dc2f/dstore/storage/Property.java @@ -8,9 +8,10 @@ public class Property { */ @Nonnull public static final String PROPERTY_NAME = "name"; public static enum PropertyType { - LONG(Long.class), - DOUBLE(Double.class), - STRING(String.class) ; + @Nonnull LONG(Long.class), + @Nonnull DOUBLE(Double.class), + @Nonnull STRING(String.class), + @Nonnull BOOLEAN(Boolean.class); private Class valueClass; @@ -25,10 +26,11 @@ protected Class getValueClass() { } private PropertyType type; - private Object objValue; + private @Nonnull Object objValue; - public Property(Object value) throws IllegalStateException { + public Property(@Nonnull Object value) throws IllegalStateException { setObjValue(value); + objValue = value; } public PropertyType getPropertyType() { @@ -37,7 +39,6 @@ public PropertyType getPropertyType() { private void assertType(PropertyType expectedType) { if (this.type != expectedType - || objValue == null || !expectedType.getValueClass().isAssignableFrom(objValue.getClass())) { throw new IllegalStateException("Requested {" + expectedType + "} but has {" + this.type + "} - of type: {" + objValue.getClass() + "}"); @@ -53,7 +54,7 @@ public long getLong() { // setObjValue(value); // } - private void setObjValue(Object value) { + private void setObjValue(@Nonnull Object value) { for (PropertyType type : PropertyType.values()) { if (type.getValueClass().isAssignableFrom(value.getClass())) { this.type = type; @@ -69,11 +70,16 @@ public double getDouble() { return (Double) objValue; } - public String getString() { + public @Nonnull String getString() { assertType(PropertyType.STRING); return (String) objValue; } + public boolean getBoolean() { + assertType(PropertyType.BOOLEAN); + return ((Boolean) objValue).booleanValue(); + } + // public void setString(String value) { // setObjValue(value); // } diff --git a/src/main/java/com/dc2f/utils/NullUtils.java b/src/main/java/com/dc2f/utils/NullUtils.java new file mode 100644 index 0000000..46a6915 --- /dev/null +++ b/src/main/java/com/dc2f/utils/NullUtils.java @@ -0,0 +1,12 @@ +package com.dc2f.utils; + +import javax.annotation.Nonnull; + +public class NullUtils { + public static @Nonnull T assertNotNull(T obj) { + if (obj == null) { + throw new AssertionError("Must not be null."); + } + return obj; + } +} diff --git a/src/test/java/com/dc2f/dstore/test/hierachynodestore/nodetype/NodeTypeApiTest.java b/src/test/java/com/dc2f/dstore/test/hierachynodestore/nodetype/NodeTypeApiTest.java new file mode 100644 index 0000000..47032aa --- /dev/null +++ b/src/test/java/com/dc2f/dstore/test/hierachynodestore/nodetype/NodeTypeApiTest.java @@ -0,0 +1,99 @@ +package com.dc2f.dstore.test.hierachynodestore.nodetype; + +import static com.dc2f.dstore.test.TreeAssertions.assertTree; +import static com.dc2f.dstore.test.TreeAssertions.node; +import static com.dc2f.dstore.test.TreeAssertions.properties; + +import java.io.IOException; + +import org.junit.Before; +import org.junit.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.dc2f.dstore.hierachynodestore.HierarchicalNodeStore; +import com.dc2f.dstore.hierachynodestore.WorkingTree; +import com.dc2f.dstore.hierachynodestore.WorkingTreeUtils; +import com.dc2f.dstore.hierachynodestore.nodetype.NodeTypeAccessor; +import com.dc2f.dstore.hierachynodestore.nodetype.NodeTypeDefinition; +import com.dc2f.dstore.storage.Property.PropertyType; +import com.dc2f.dstore.storage.StorageBackend; +import com.dc2f.dstore.storage.map.HashMapStorage; +import com.dc2f.dstore.test.TreeAssertions.ExpectedNode; + +public class NodeTypeApiTest { + private Logger logger = LoggerFactory.getLogger(NodeTypeApiTest.class); + private HierarchicalNodeStore nodeStore; + + @Before + public void setupDstore() throws IOException { + StorageBackend storageBackend = initStorageBackend(); + nodeStore = new HierarchicalNodeStore(storageBackend); + } + + protected StorageBackend initStorageBackend() { + return new HashMapStorage(); + } + + + @Test + public void testSimpleNodeTypeSetup() { + WorkingTree wt = nodeStore.checkoutBranch("master"); + NodeTypeAccessor nodeTypeAccessor = wt.getNodeTypeAccessor(); + + NodeTypeDefinition entryNodeType = nodeTypeAccessor.addNodeTypeDefinition("com.dc2f.blog.BlogEntry"); + + // we need a 'nodetype' property (no inheritance, grml) + entryNodeType.addPropertyDefinition(WorkingTree.NAME_NODETYPE) + // for now we have no special property type for this.. + .setType(PropertyType.STRING) + .setRequired(true); + + entryNodeType.addPropertyDefinition("title") + .setRequired(true) + .setType(PropertyType.STRING); + + entryNodeType.addPropertyDefinition("body") + .setRequired(true) + .setType(PropertyType.STRING); + + entryNodeType.addPropertyDefinition("slug") + .setRequired(true) + .setType(PropertyType.STRING); + + wt.commit(null); + + logger.info("Got tree: " + WorkingTreeUtils.debugRecursiveTree(wt.getRootNode())); + + ExpectedNode expectedNode = node(properties("name", ""), + node(properties("name", ":nodetype"), + node(properties("name", "com.dc2f.blog.BlogEntry", + ":nodetype", + "com.dc2f.nodetype.NodeTypeDefinition"), + + node(properties(":name", ":nodetype", + ":nodetype", "com.dc2f.nodetype.PropertyDefinition", + ":required", true, + ":type", "STRING")), + + node(properties(":name", "title", + ":nodetype", "com.dc2f.nodetype.PropertyDefinition", + ":required", true, + ":type", "STRING")), + + node(properties(":name", "body", + ":nodetype", "com.dc2f.nodetype.PropertyDefinition", + ":required", true, + ":type", "STRING")), + + node(properties(":name", "slug", + ":nodetype", "com.dc2f.nodetype.PropertyDefinition", + ":required", true, + ":type", "STRING")) + + ))); + + assertTree(expectedNode, wt.getRootNode()); + } + +}