You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@jackrabbit.apache.org by an...@apache.org on 2008/04/04 17:55:39 UTC

svn commit: r644745 [1/3] - in /jackrabbit/trunk: jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/nodetype/ jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/state/ jackrabbit-spi-commons/ jackrabbit-spi-commons/src/main/jav...

Author: angela
Date: Fri Apr  4 08:55:26 2008
New Revision: 644745

URL: http://svn.apache.org/viewvc?rev=644745&view=rev
Log:
JCR-1516: Add Compact Namespace and Node Type Definition support to spi-commons

Added:
    jackrabbit/trunk/jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/nodetype/NodeTypeDefDiff.java   (with props)
    jackrabbit/trunk/jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/nodetype/ValueConstraint.java   (with props)
    jackrabbit/trunk/jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/nodetype/compact/
    jackrabbit/trunk/jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/nodetype/compact/CompactNodeTypeDefReader.java   (with props)
    jackrabbit/trunk/jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/nodetype/compact/CompactNodeTypeDefWriter.java   (with props)
    jackrabbit/trunk/jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/nodetype/compact/Lexer.java   (with props)
    jackrabbit/trunk/jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/nodetype/compact/ParseException.java   (with props)
    jackrabbit/trunk/jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/nodetype/compact/QNodeTypeDefinitionsBuilder.java   (with props)
    jackrabbit/trunk/jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/nodetype/compact/QNodeTypeDefinitionsBuilderImpl.java   (with props)
    jackrabbit/trunk/jackrabbit-spi-commons/src/test/java/org/apache/jackrabbit/spi/commons/nodetype/
    jackrabbit/trunk/jackrabbit-spi-commons/src/test/java/org/apache/jackrabbit/spi/commons/nodetype/BooleanConstraintTest.java   (with props)
    jackrabbit/trunk/jackrabbit-spi-commons/src/test/java/org/apache/jackrabbit/spi/commons/nodetype/DateConstraintTest.java   (with props)
    jackrabbit/trunk/jackrabbit-spi-commons/src/test/java/org/apache/jackrabbit/spi/commons/nodetype/NameConstraintTest.java   (with props)
    jackrabbit/trunk/jackrabbit-spi-commons/src/test/java/org/apache/jackrabbit/spi/commons/nodetype/NumericConstraintTest.java   (with props)
    jackrabbit/trunk/jackrabbit-spi-commons/src/test/java/org/apache/jackrabbit/spi/commons/nodetype/PathConstraintTest.java   (with props)
    jackrabbit/trunk/jackrabbit-spi-commons/src/test/java/org/apache/jackrabbit/spi/commons/nodetype/ReferenceConstraintTest.java   (with props)
    jackrabbit/trunk/jackrabbit-spi-commons/src/test/java/org/apache/jackrabbit/spi/commons/nodetype/StringConstraintTest.java   (with props)
    jackrabbit/trunk/jackrabbit-spi-commons/src/test/java/org/apache/jackrabbit/spi/commons/nodetype/TestAll.java   (with props)
    jackrabbit/trunk/jackrabbit-spi-commons/src/test/java/org/apache/jackrabbit/spi/commons/nodetype/ValueConstraintTest.java   (with props)
    jackrabbit/trunk/jackrabbit-spi-commons/src/test/java/org/apache/jackrabbit/spi/commons/nodetype/compact/
    jackrabbit/trunk/jackrabbit-spi-commons/src/test/java/org/apache/jackrabbit/spi/commons/nodetype/compact/CompactNodeTypeDefTest.java   (with props)
    jackrabbit/trunk/jackrabbit-spi-commons/src/test/java/org/apache/jackrabbit/spi/commons/nodetype/compact/TestAll.java   (with props)
    jackrabbit/trunk/jackrabbit-spi-commons/src/test/resources/
    jackrabbit/trunk/jackrabbit-spi-commons/src/test/resources/cnd-reader-test-input.cnd
Removed:
    jackrabbit/trunk/jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/nodetype/ValueConstraint.java
Modified:
    jackrabbit/trunk/jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/nodetype/DefinitionValidator.java
    jackrabbit/trunk/jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/nodetype/NodeTypeImpl.java
    jackrabbit/trunk/jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/nodetype/PropertyDefinitionImpl.java
    jackrabbit/trunk/jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/state/PropertyState.java
    jackrabbit/trunk/jackrabbit-spi-commons/pom.xml

Modified: jackrabbit/trunk/jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/nodetype/DefinitionValidator.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/nodetype/DefinitionValidator.java?rev=644745&r1=644744&r2=644745&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/nodetype/DefinitionValidator.java (original)
+++ jackrabbit/trunk/jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/nodetype/DefinitionValidator.java Fri Apr  4 08:55:26 2008
@@ -23,6 +23,7 @@
 import org.apache.jackrabbit.spi.QValue;
 import org.apache.jackrabbit.spi.commons.nodetype.InvalidNodeTypeDefException;
 import org.apache.jackrabbit.spi.commons.nodetype.NodeTypeConflictException;
+import org.apache.jackrabbit.spi.commons.nodetype.ValueConstraint;
 import org.apache.jackrabbit.spi.commons.name.NameConstants;
 import org.apache.jackrabbit.spi.commons.name.NameFactoryImpl;
 import org.slf4j.LoggerFactory;
@@ -568,4 +569,4 @@
             nsRegistry.getPrefix(name.getNamespaceURI());
         }
     }
-}
\ No newline at end of file
+}

Modified: jackrabbit/trunk/jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/nodetype/NodeTypeImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/nodetype/NodeTypeImpl.java?rev=644745&r1=644744&r2=644745&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/nodetype/NodeTypeImpl.java (original)
+++ jackrabbit/trunk/jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/nodetype/NodeTypeImpl.java Fri Apr  4 08:55:26 2008
@@ -16,26 +16,27 @@
  */
 package org.apache.jackrabbit.jcr2spi.nodetype;
 
-import org.apache.jackrabbit.spi.commons.conversion.NameException;
-import org.apache.jackrabbit.spi.commons.conversion.NamePathResolver;
+import org.apache.jackrabbit.jcr2spi.ManagerProvider;
 import org.apache.jackrabbit.spi.Name;
 import org.apache.jackrabbit.spi.QNodeDefinition;
-import org.apache.jackrabbit.spi.QPropertyDefinition;
 import org.apache.jackrabbit.spi.QNodeTypeDefinition;
+import org.apache.jackrabbit.spi.QPropertyDefinition;
 import org.apache.jackrabbit.spi.QValue;
 import org.apache.jackrabbit.spi.QValueFactory;
-import org.apache.jackrabbit.value.ValueHelper;
-import org.apache.jackrabbit.spi.commons.value.ValueFormat;
-import org.apache.jackrabbit.jcr2spi.ManagerProvider;
+import org.apache.jackrabbit.spi.commons.conversion.NameException;
+import org.apache.jackrabbit.spi.commons.conversion.NamePathResolver;
 import org.apache.jackrabbit.spi.commons.namespace.NamespaceResolver;
-import org.slf4j.LoggerFactory;
+import org.apache.jackrabbit.spi.commons.nodetype.ValueConstraint;
+import org.apache.jackrabbit.spi.commons.value.ValueFormat;
+import org.apache.jackrabbit.value.ValueHelper;
 import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
+import javax.jcr.NamespaceException;
 import javax.jcr.PropertyType;
 import javax.jcr.RepositoryException;
 import javax.jcr.Value;
 import javax.jcr.ValueFactory;
-import javax.jcr.NamespaceException;
 import javax.jcr.nodetype.ConstraintViolationException;
 import javax.jcr.nodetype.NoSuchNodeTypeException;
 import javax.jcr.nodetype.NodeDefinition;

Modified: jackrabbit/trunk/jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/nodetype/PropertyDefinitionImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/nodetype/PropertyDefinitionImpl.java?rev=644745&r1=644744&r2=644745&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/nodetype/PropertyDefinitionImpl.java (original)
+++ jackrabbit/trunk/jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/nodetype/PropertyDefinitionImpl.java Fri Apr  4 08:55:26 2008
@@ -21,6 +21,7 @@
 import org.apache.jackrabbit.spi.commons.value.ValueFormat;
 import org.apache.jackrabbit.spi.commons.conversion.NamePathResolver;
 import org.apache.jackrabbit.spi.commons.nodetype.InvalidConstraintException;
+import org.apache.jackrabbit.spi.commons.nodetype.ValueConstraint;
 import org.slf4j.LoggerFactory;
 import org.slf4j.Logger;
 
@@ -118,4 +119,5 @@
         return ((QPropertyDefinition) itemDef).isMultiple();
     }
 }
+
 

Modified: jackrabbit/trunk/jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/state/PropertyState.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/state/PropertyState.java?rev=644745&r1=644744&r2=644745&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/state/PropertyState.java (original)
+++ jackrabbit/trunk/jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/state/PropertyState.java Fri Apr  4 08:55:26 2008
@@ -18,11 +18,11 @@
 
 import org.apache.jackrabbit.jcr2spi.hierarchy.PropertyEntry;
 import org.apache.jackrabbit.jcr2spi.nodetype.ItemDefinitionProvider;
-import org.apache.jackrabbit.jcr2spi.nodetype.ValueConstraint;
 import org.apache.jackrabbit.spi.ItemId;
 import org.apache.jackrabbit.spi.PropertyInfo;
 import org.apache.jackrabbit.spi.QPropertyDefinition;
 import org.apache.jackrabbit.spi.QValue;
+import org.apache.jackrabbit.spi.commons.nodetype.ValueConstraint;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 

Modified: jackrabbit/trunk/jackrabbit-spi-commons/pom.xml
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-spi-commons/pom.xml?rev=644745&r1=644744&r2=644745&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit-spi-commons/pom.xml (original)
+++ jackrabbit/trunk/jackrabbit-spi-commons/pom.xml Fri Apr  4 08:55:26 2008
@@ -209,6 +209,11 @@
       <artifactId>slf4j-api</artifactId>
     </dependency>
     <dependency>
+      <groupId>org.slf4j</groupId>
+      <artifactId>slf4j-log4j12</artifactId>
+      <scope>runtime</scope>
+    </dependency>
+    <dependency>
       <groupId>junit</groupId>
       <artifactId>junit</artifactId>
       <scope>test</scope>

Added: jackrabbit/trunk/jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/nodetype/NodeTypeDefDiff.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/nodetype/NodeTypeDefDiff.java?rev=644745&view=auto
==============================================================================
--- jackrabbit/trunk/jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/nodetype/NodeTypeDefDiff.java (added)
+++ jackrabbit/trunk/jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/nodetype/NodeTypeDefDiff.java Fri Apr  4 08:55:26 2008
@@ -0,0 +1,621 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.spi.commons.nodetype;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+
+import javax.jcr.PropertyType;
+
+import org.apache.jackrabbit.spi.Name;
+import org.apache.jackrabbit.spi.QItemDefinition;
+import org.apache.jackrabbit.spi.QNodeDefinition;
+import org.apache.jackrabbit.spi.QNodeTypeDefinition;
+import org.apache.jackrabbit.spi.QPropertyDefinition;
+
+/**
+ * A <code>NodeTypeDefDiff</code> represents the result of the comparison of
+ * two node type definitions.
+ * <p/>
+ * The result of the comparison can be categorized as one of the following types:
+ * <p/>
+ * <b><code>NONE</code></b> inidcates that there is no modification at all.
+ * <p/>
+ * A <b><code>TRIVIAL</code></b> modification has no impact on the consistency
+ * of existing content and does not affect existing/assigned definition id's.
+ * The following modifications are considered <code>TRIVIAL</code>:
+ * <ul>
+ * <li>changing node type <code>orderableChildNodes</code> flag
+ * <li>changing node type <code>primaryItemName</code> value
+ * <li>adding non-<code>mandatory</code> property/child node
+ * <li>changing property/child node <code>protected</code> flag
+ * <li>changing property/child node <code>onParentVersion</code> value
+ * <li>changing property/child node <code>mandatory</code> flag to <code>false</code>
+ * <li>changing property/child node <code>autoCreated</code> flag
+ * <li>changing child node <code>defaultPrimaryType</code>
+ * <li>changing child node <code>sameNameSiblings</code> flag to <code>true</code>
+ * <li>weaken property <code>valueConstraints</code> (e.g. by removing completely
+ * or by adding to existing or by making a single constraint less restrictive)
+ * <li>changing property <code>defaultValues</code>
+ * </ul>
+ * <p/>
+ * A <b><code>MINOR</code></b> modification has no impact on the consistency
+ * of existing content but <i>does</i> affect existing/assigned definition id's.
+ * The following modifications are considered <code>MINOR</code>:
+ * <ul>
+ * <li>changing specific property/child node <code>name</code> to <code>*</code>
+ * <li>weaken child node <code>requiredPrimaryTypes</code> (e.g. by removing)
+ * <li>changing specific property <code>requiredType</code> to <code>undefined</code>
+ * <li>changing property <code>multiple</code> flag to <code>true</code>
+ * </ul>
+ * <p/>
+ * A <b><code>MAJOR</code></b> modification <i>affects</i> the consistency of
+ * existing content and <i>does</i> change existing/assigned definition id's.
+ * All modifications that are neither <b><code>TRIVIAL</code></b> nor
+ * <b><code>MINOR</code></b> are considered <b><code>MAJOR</code></b>.
+ *
+ * @see #getType()
+ */
+public class NodeTypeDefDiff {
+
+    /**
+     * no modification
+     */
+    public static final int NONE = 0;
+    /**
+     * trivial modification: does neither affect consistency of existing content
+     * nor does it change existing/assigned definition id's
+     */
+    public static final int TRIVIAL = 1;
+    /**
+     * minor modification: does not affect consistency of existing content but
+     * <i>does</i> change existing/assigned definition id's
+     */
+    public static final int MINOR = 2;
+    /**
+     * major modification: <i>does</i> affect consistency of existing content
+     * and <i>does</i> change existing/assigned definition id's
+     */
+    public static final int MAJOR = 3;
+
+    private final QNodeTypeDefinition oldDef;
+    private final QNodeTypeDefinition newDef;
+    private int type;
+
+    private final List propDefDiffs = new ArrayList();
+    private final List childNodeDefDiffs = new ArrayList();
+
+    /**
+     * Constructor
+     */
+    private NodeTypeDefDiff(QNodeTypeDefinition oldDef, QNodeTypeDefinition newDef) {
+        this.oldDef = oldDef;
+        this.newDef = newDef;
+        init();
+    }
+
+    /**
+     *
+     */
+    private void init() {
+        if (oldDef.equals(newDef)) {
+            // definitions are identical
+            type = NONE;
+        } else {
+            // definitions are not identical, determine type of modification
+
+            // assume TRIVIAL change by default
+            type = TRIVIAL;
+
+            // check supertypes
+            int tmpType = supertypesDiff();
+            if (tmpType > type) {
+                type = tmpType;
+            }
+
+            // check mixin flag (MAJOR modification)
+            tmpType = mixinFlagDiff();
+            if (tmpType > type) {
+                type = tmpType;
+            }
+
+            // no need to check orderableChildNodes flag (TRIVIAL modification)
+
+            // check property definitions
+            tmpType = buildPropDefDiffs();
+            if (tmpType > type) {
+                type = tmpType;
+            }
+
+            // check child node definitions
+            tmpType = buildChildNodeDefDiffs();
+            if (tmpType > type) {
+                type = tmpType;
+            }
+        }
+    }
+
+    /**
+     * @param oldDef
+     * @param newDef
+     * @return
+     */
+    public static NodeTypeDefDiff create(QNodeTypeDefinition oldDef, QNodeTypeDefinition newDef) {
+        if (oldDef == null || newDef == null) {
+            throw new IllegalArgumentException("arguments can not be null");
+        }
+        if (!oldDef.getName().equals(newDef.getName())) {
+            throw new IllegalArgumentException("at least node type names must be matching");
+        }
+        return new NodeTypeDefDiff(oldDef, newDef);
+    }
+
+    /**
+     * @return
+     */
+    public boolean isModified() {
+        return type != NONE;
+    }
+
+    /**
+     * @return
+     */
+    public boolean isTrivial() {
+        return type == TRIVIAL;
+    }
+
+    /**
+     * @return
+     */
+    public boolean isMinor() {
+        return type == MINOR;
+    }
+
+    /**
+     * @return
+     */
+    public boolean isMajor() {
+        return type == MAJOR;
+    }
+
+    /**
+     * Returns the type of modification as expressed by the following constants:
+     * <ul>
+     * <li><b><code>NONE</code></b>: no modification at all
+     * <li><b><code>TRIVIAL</code></b>: does neither affect consistency of
+     * existing content nor does it change existing/assigned definition id's
+     * <li><b><code>MINOR</code></b>: does not affect consistency of existing
+     * content but <i>does</i> change existing/assigned definition id's
+     * <li><b><code>MAJOR</code></b>: <i>does</i> affect consistency of existing
+     * content and <i>does</i> change existing/assigned definition id's
+     * </ul>
+     *
+     * @return the type of modification
+     */
+    public int getType() {
+        return type;
+    }
+
+    /**
+     * @return
+     */
+    public int mixinFlagDiff() {
+        return oldDef.isMixin() != newDef.isMixin() ? MAJOR : NONE;
+    }
+
+    /**
+     * @return
+     */
+    public int supertypesDiff() {
+        return !Arrays.equals(oldDef.getSupertypes(), newDef.getSupertypes()) ? MAJOR : NONE;
+    }
+
+    /**
+     * @return
+     */
+    private int buildPropDefDiffs() {
+        /**
+         * propDefId determinants: declaringNodeType, name, requiredType, multiple
+         * todo: try also to match entries with modified id's
+         */
+
+        int maxType = NONE;
+        QPropertyDefinition[] pda1 = oldDef.getPropertyDefs();
+        HashMap defs1 = new HashMap();
+        for (int i = 0; i < pda1.length; i++) {
+            defs1.put(pda1[i].getName(), pda1[i]);
+        }
+
+        QPropertyDefinition[] pda2 = newDef.getPropertyDefs();
+        HashMap defs2 = new HashMap();
+        for (int i = 0; i < pda2.length; i++) {
+            defs2.put(pda2[i].getName(), pda2[i]);
+        }
+
+        /**
+         * walk through defs1 and process all entries found in
+         * both defs1 & defs2 and those found only in defs1
+         */
+        Iterator iter = defs1.keySet().iterator();
+        while (iter.hasNext()) {
+            Name name = (Name) iter.next();
+            QPropertyDefinition def1 = (QPropertyDefinition) defs1.get(name);
+            QPropertyDefinition def2 = (QPropertyDefinition) defs2.get(name);
+            PropDefDiff diff = new PropDefDiff(def1, def2);
+            if (diff.getType() > maxType) {
+                maxType = diff.getType();
+            }
+            propDefDiffs.add(diff);
+            defs2.remove(name);
+        }
+
+        /**
+         * defs2 by now only contains entries found in defs2 only;
+         * walk through defs2 and process all remaining entries
+         */
+        iter = defs2.keySet().iterator();
+        while (iter.hasNext()) {
+            Name name = (Name) iter.next();
+            QPropertyDefinition def = (QPropertyDefinition) defs2.get(name);
+            PropDefDiff diff = new PropDefDiff(null, def);
+            if (diff.getType() > maxType) {
+                maxType = diff.getType();
+            }
+            propDefDiffs.add(diff);
+        }
+
+        return maxType;
+    }
+
+    /**
+     * @return
+     */
+    private int buildChildNodeDefDiffs() {
+        /**
+         * nodeDefId determinants: declaringNodeType, name, requiredPrimaryTypes
+         * todo: try also to match entries with modified id's
+         */
+
+        int maxType = NONE;
+        QNodeDefinition[] cnda1 = oldDef.getChildNodeDefs();
+        HashMap defs1 = new HashMap();
+        for (int i = 0; i < cnda1.length; i++) {
+            defs1.put(cnda1[i].getName(), cnda1[i]);
+        }
+
+        QNodeDefinition[] cnda2 = newDef.getChildNodeDefs();
+        HashMap defs2 = new HashMap();
+        for (int i = 0; i < cnda2.length; i++) {
+            defs2.put(cnda2[i].getName(), cnda2[i]);
+        }
+
+        /**
+         * walk through defs1 and process all entries found in
+         * both defs1 & defs2 and those found only in defs1
+         */
+        Iterator iter = defs1.keySet().iterator();
+        while (iter.hasNext()) {
+            Name name = (Name) iter.next();
+            QNodeDefinition def1 = (QNodeDefinition) defs1.get(name);
+            QNodeDefinition def2 = (QNodeDefinition) defs2.get(name);
+            ChildNodeDefDiff diff = new ChildNodeDefDiff(def1, def2);
+            if (diff.getType() > maxType) {
+                maxType = diff.getType();
+            }
+            childNodeDefDiffs.add(diff);
+            defs2.remove(name);
+        }
+
+        /**
+         * defs2 by now only contains entries found in defs2 only;
+         * walk through defs2 and process all remaining entries
+         */
+        iter = defs2.keySet().iterator();
+        while (iter.hasNext()) {
+            Name name = (Name) iter.next();
+            QNodeDefinition def = (QNodeDefinition) defs2.get(name);
+            ChildNodeDefDiff diff = new ChildNodeDefDiff(null, def);
+            if (diff.getType() > maxType) {
+                maxType = diff.getType();
+            }
+            childNodeDefDiffs.add(diff);
+        }
+
+        return maxType;
+    }
+
+    public String toString() {
+        String result = getClass().getName() + "[\n\tnodeTypeName="
+                + oldDef.getName();
+
+        result += ",\n\tmixinFlagDiff=" + modificationTypeToString(mixinFlagDiff());
+        result += ",\n\tsupertypesDiff=" + modificationTypeToString(supertypesDiff());
+
+        result += ",\n\tpropertyDifferences=[\n";
+        result += toString(propDefDiffs);
+        result += "\t]";
+
+        result += ",\n\tchildNodeDifferences=[\n";
+        result += toString(childNodeDefDiffs);
+        result += "\t]\n";
+        result += "]\n";
+
+        return result;
+    }
+
+    private String toString(List childItemDefDiffs) {
+        String result = "";
+        for (Iterator iter = childItemDefDiffs.iterator(); iter.hasNext();) {
+            ChildItemDefDiff propDefDiff = (ChildItemDefDiff) iter.next();
+            result += "\t\t" + propDefDiff;
+            if (iter.hasNext()) {
+                result += ",";
+            }
+            result += "\n";
+        }
+        return result;
+    }
+
+    private String modificationTypeToString(int modifcationType) {
+        String typeString = "unknown";
+        switch (modifcationType) {
+            case NONE:
+                typeString = "NONE";
+                break;
+            case TRIVIAL:
+                typeString = "TRIVIAL";
+                break;
+            case MINOR:
+                typeString = "MINOR";
+                break;
+            case MAJOR:
+                typeString = "MAJOR";
+                break;
+        }
+        return typeString;
+    }
+
+
+    //--------------------------------------------------------< inner classes >
+
+    abstract class ChildItemDefDiff {
+        protected final QItemDefinition oldDef;
+        protected final QItemDefinition newDef;
+        protected int type;
+
+        ChildItemDefDiff(QItemDefinition oldDef, QItemDefinition newDef) {
+            this.oldDef = oldDef;
+            this.newDef = newDef;
+            init();
+        }
+
+        protected void init() {
+            // determine type of modification
+            if (isAdded()) {
+                if (!newDef.isMandatory()) {
+                    // adding a non-mandatory child item is a TRIVIAL change
+                    type = TRIVIAL;
+                } else {
+                    // adding a mandatory child item is a MAJOR change
+                    type = MAJOR;
+                }
+            } else if (isRemoved()) {
+                // removing a child item is a MAJOR change
+                type = MAJOR;
+            } else {
+                /**
+                 * neither added nor removed => has to be either identical
+                 * or modified
+                 */
+                if (oldDef.equals(newDef)) {
+                    // identical
+                    type = NONE;
+                } else {
+                    // modified
+                    if (oldDef.isMandatory() != newDef.isMandatory()
+                            && newDef.isMandatory()) {
+                        // making a child item mandatory is a MAJOR change
+                        type = MAJOR;
+                    } else {
+                        if (!oldDef.definesResidual()
+                                && newDef.definesResidual()) {
+                            // just making a child item residual is a MINOR change
+                            type = MINOR;
+                        } else {
+                            if (!oldDef.getName().equals(newDef.getName())) {
+                                // changing the name of a child item is a MAJOR change
+                                type = MAJOR;
+                            } else {
+                                // all other changes are TRIVIAL
+                                type = TRIVIAL;
+                            }
+                        }
+                    }
+                }
+            }
+        }
+
+        public int getType() {
+            return type;
+        }
+
+        public boolean isAdded() {
+            return oldDef == null && newDef != null;
+        }
+
+        public boolean isRemoved() {
+            return oldDef != null && newDef == null;
+        }
+
+        public boolean isModified() {
+            return oldDef != null && newDef != null
+                    && !oldDef.equals(newDef);
+        }
+
+        public String toString() {
+            String typeString = modificationTypeToString(getType());
+
+            String operationString;
+            if (isAdded()) {
+                operationString = "ADDED";
+            } else if (isModified()) {
+                operationString = "MODIFIED";
+            } else if (isRemoved()) {
+                operationString = "REMOVED";
+            } else {
+                operationString = "NONE";
+            }
+
+            QItemDefinition itemDefinition = (oldDef != null) ? oldDef : newDef;
+
+            return getClass().getName() + "[itemName="
+                    + itemDefinition.getName() + ", type=" + typeString
+                    + ", operation=" + operationString + "]";
+        }
+
+    }
+
+    public class PropDefDiff extends ChildItemDefDiff {
+
+        PropDefDiff(QPropertyDefinition oldDef, QPropertyDefinition newDef) {
+            super(oldDef, newDef);
+        }
+
+        public QPropertyDefinition getOldDef() {
+            return (QPropertyDefinition) oldDef;
+        }
+
+        public QPropertyDefinition getNewDef() {
+            return (QPropertyDefinition) newDef;
+        }
+
+        protected void init() {
+            super.init();
+            /**
+             * only need to do comparison if base class implementation
+             * detected a non-MAJOR modification (i.e. TRIVIAL or MINOR);
+             * no need to check for additions or removals as this is already
+             * handled in base class implementation.
+             */
+            if (isModified() && type != NONE && type != MAJOR) {
+                /**
+                 * check if valueConstraints were made more restrictive
+                 * (constraints are ORed)
+                 */
+                String[] vca1 = getOldDef().getValueConstraints();
+                HashSet set1 = new HashSet();
+                for (int i = 0; i < vca1.length; i++) {
+                    set1.add(vca1[i]);
+                }
+                String[] vca2 = getNewDef().getValueConstraints();
+                HashSet set2 = new HashSet();
+                for (int i = 0; i < vca2.length; i++) {
+                    set2.add(vca2[i]);
+                }
+
+                if (set1.isEmpty() && !set2.isEmpty()) {
+                    // added constraint where there was no constraint (MAJOR change)
+                    type = MAJOR;
+                } else if (!set2.containsAll(set1) && !set2.isEmpty()) {
+                    // removed existing constraint (MAJOR change)
+                    type = MAJOR;
+                }
+
+                // no need to check defaultValues (TRIVIAL change)
+
+                if (type == TRIVIAL) {
+                    int t1 = getOldDef().getRequiredType();
+                    int t2 = getNewDef().getRequiredType();
+                    if (t1 != t2) {
+                        if (t2 == PropertyType.UNDEFINED) {
+                            // changed getRequiredType to UNDEFINED (MINOR change)
+                            type = MINOR;
+                        } else {
+                            // changed getRequiredType to specific type (MAJOR change)
+                            type = MAJOR;
+                        }
+                    }
+                    boolean b1 = getOldDef().isMultiple();
+                    boolean b2 = getNewDef().isMultiple();
+                    if (b1 != b2) {
+                        if (b2) {
+                            // changed multiple flag to true (MINOR change)
+                            type = MINOR;
+                        } else {
+                            // changed multiple flag to false (MAJOR change)
+                            type = MAJOR;
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    public class ChildNodeDefDiff extends ChildItemDefDiff {
+
+        ChildNodeDefDiff(QNodeDefinition oldDef, QNodeDefinition newDef) {
+            super(oldDef, newDef);
+        }
+
+        public QNodeDefinition getOldDef() {
+            return (QNodeDefinition) oldDef;
+        }
+
+        public QNodeDefinition getNewDef() {
+            return (QNodeDefinition) newDef;
+        }
+
+        protected void init() {
+            super.init();
+            /**
+             * only need to do comparison if base class implementation
+             * detected a non-MAJOR modification (i.e. TRIVIAL or MINOR);
+             * no need to check for additions or removals as this is already
+             * handled in base class implementation.
+             */
+            if (isModified() && type != NONE && type != MAJOR) {
+
+                boolean b1 = getOldDef().allowsSameNameSiblings();
+                boolean b2 = getNewDef().allowsSameNameSiblings();
+                if (b1 != b2 && !b2) {
+                    // changed sameNameSiblings flag to false (MAJOR change)
+                    type = MAJOR;
+                }
+
+                // no need to check defaultPrimaryType (TRIVIAL change)
+
+                if (type == TRIVIAL) {
+                    List l1 = Arrays.asList(getOldDef().getRequiredPrimaryTypes());
+                    List l2 = Arrays.asList(getNewDef().getRequiredPrimaryTypes());
+                    if (!l1.equals(l2)) {
+                        if (l1.containsAll(l2)) {
+                            // removed requiredPrimaryType (MINOR change)
+                            type = MINOR;
+                        } else {
+                            // added requiredPrimaryType (MAJOR change)
+                            type = MAJOR;
+                        }
+                    }
+                }
+            }
+        }
+    }
+}

Propchange: jackrabbit/trunk/jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/nodetype/NodeTypeDefDiff.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: jackrabbit/trunk/jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/nodetype/NodeTypeDefDiff.java
------------------------------------------------------------------------------
    svn:keywords = author date id revision url

Added: jackrabbit/trunk/jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/nodetype/ValueConstraint.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/nodetype/ValueConstraint.java?rev=644745&view=auto
==============================================================================
--- jackrabbit/trunk/jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/nodetype/ValueConstraint.java (added)
+++ jackrabbit/trunk/jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/nodetype/ValueConstraint.java Fri Apr  4 08:55:26 2008
@@ -0,0 +1,909 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.spi.commons.nodetype;
+
+import org.apache.jackrabbit.spi.commons.conversion.NamePathResolver;
+import org.apache.jackrabbit.spi.commons.conversion.NameResolver;
+import org.apache.jackrabbit.spi.commons.conversion.NameException;
+import org.apache.jackrabbit.spi.commons.conversion.PathResolver;
+import org.apache.jackrabbit.spi.Name;
+import org.apache.jackrabbit.spi.Path;
+import org.apache.jackrabbit.spi.QPropertyDefinition;
+import org.apache.jackrabbit.spi.QValue;
+import org.apache.jackrabbit.spi.NameFactory;
+import org.apache.jackrabbit.value.DateValue;
+import org.apache.jackrabbit.spi.commons.name.PathFactoryImpl;
+import org.apache.jackrabbit.spi.commons.name.NameFactoryImpl;
+import org.slf4j.LoggerFactory;
+import org.slf4j.Logger;
+
+import javax.jcr.PropertyType;
+import javax.jcr.RepositoryException;
+import javax.jcr.ValueFormatException;
+import javax.jcr.NamespaceException;
+import javax.jcr.nodetype.ConstraintViolationException;
+import java.util.Calendar;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.regex.PatternSyntaxException;
+
+/**
+ * <code>ValueConstraint</code> and its subclasses are used to check the
+ * syntax of a value constraint and to test if a specific value satisfies
+ * it.
+ */
+public abstract class ValueConstraint {
+
+    protected static Logger log = LoggerFactory.getLogger(ValueConstraint.class);
+
+    public static final ValueConstraint[] EMPTY_ARRAY = new ValueConstraint[0];
+
+    // TODO improve. don't rely on a specific factory impl
+    static final NameFactory NAME_FACTORY = NameFactoryImpl.getInstance();
+
+    private final String qualifiedDefinition;
+
+    protected ValueConstraint(String qualifiedDefinition) {
+        this.qualifiedDefinition = qualifiedDefinition;
+    }
+
+    /**
+     * For constraints that are not namespace prefix mapping sensitive this
+     * method returns the same result as <code>{@link #getQualifiedDefinition()}</code>.
+     * <p/>
+     * Those that are namespace prefix mapping sensitive (e.g.
+     * <code>NameConstraint</code>, <code>PathConstraint</code> and
+     * <code>ReferenceConstraint</code>) use the given <code>nsResolver</code>
+     * to reflect the current mapping in the returned value.
+     * In other words: subclasses, that need to make a conversion to JCR value
+     * must overwrite this and return a value that has all qualified names
+     * and path elements resolved.
+     *
+     * @return the definition of this constraint.
+     * @see #getQualifiedDefinition()
+     * @param resolver
+     */
+    public String getDefinition(NamePathResolver resolver) {
+        return qualifiedDefinition;
+    }
+
+    /**
+     * By default the qualified definition is the same as the JCR definition.
+     *
+     * @return the qualified definition String
+     * @see #getDefinition(NamePathResolver)
+     */
+    public String getQualifiedDefinition() {
+        return qualifiedDefinition;
+    }
+
+    /**
+     * Check if the specified value matches the this constraint.
+     *
+     * @param value The value to be tested.
+     * @throws ConstraintViolationException If the specified value is
+     * <code>null</code> or does not matches the constraint.
+     * @throws RepositoryException If another error occurs.
+     */
+    abstract void check(QValue value) throws ConstraintViolationException, RepositoryException;
+
+    //-----------------------------------------< java.lang.Object overrides >---
+    public boolean equals(Object other) {
+        if (other == this) {
+            return true;
+        } else if (other instanceof ValueConstraint) {
+            return qualifiedDefinition.equals(((ValueConstraint) other).qualifiedDefinition);
+        } else {
+            return false;
+        }
+    }
+
+    /**
+     * Returns the hashCode of the definition String
+     *
+     * @return the hashCode of the definition String
+     * @see Object#hashCode()
+     */
+    public int hashCode() {
+        return qualifiedDefinition.hashCode();
+    }
+
+    //-----------------------------------< static factory and check methods >---
+    /**
+     * Create a new <code>ValueConstraint</code> from the String representation.
+     * Note, that the definition must be in the qualified format in case the type
+     * indicates {@link PropertyType#NAME}, {@link PropertyType#PATH} or {@link PropertyType#REFERENCE}
+     *
+     * @param type
+     * @param qualifiedDefinition
+     * @return
+     * @throws InvalidConstraintException
+     */
+    public static ValueConstraint create(int type, String qualifiedDefinition)
+        throws InvalidConstraintException {
+        if (qualifiedDefinition == null) {
+            throw new IllegalArgumentException("illegal definition (null)");
+        }
+        switch (type) {
+            // constraints which are not qName senstive
+            case PropertyType.STRING:
+                return new StringConstraint(qualifiedDefinition);
+
+            case PropertyType.BOOLEAN:
+                return new BooleanConstraint(qualifiedDefinition);
+
+            case PropertyType.BINARY:
+                return new NumericConstraint(qualifiedDefinition);
+
+            case PropertyType.DATE:
+                return new DateConstraint(qualifiedDefinition);
+
+            case PropertyType.LONG:
+            case PropertyType.DOUBLE:
+                return new NumericConstraint(qualifiedDefinition);
+
+            // qName sensitive constraints: create from qualified string
+            case PropertyType.NAME:
+                return NameConstraint.create(qualifiedDefinition);
+
+            case PropertyType.PATH:
+                return PathConstraint.create(qualifiedDefinition);
+
+            case PropertyType.REFERENCE:
+                return ReferenceConstraint.create(qualifiedDefinition);
+
+            default:
+                throw new IllegalArgumentException("unknown/unsupported target type for constraint: "
+                        + PropertyType.nameFromValue(type));
+        }
+    }
+
+    /**
+     *
+     * @param type
+     * @param definition
+     * @param resolver
+     * @return
+     * @throws InvalidConstraintException
+     */
+    public static ValueConstraint create(int type, String definition,
+                                         NamePathResolver resolver)
+            throws InvalidConstraintException {
+        if (definition == null) {
+            throw new IllegalArgumentException("Illegal definition (null) for ValueConstraint.");
+        }
+        switch (type) {
+            case PropertyType.STRING:
+                return new StringConstraint(definition);
+
+            case PropertyType.BOOLEAN:
+                return new BooleanConstraint(definition);
+
+            case PropertyType.BINARY:
+                return new NumericConstraint(definition);
+
+            case PropertyType.DATE:
+                return new DateConstraint(definition);
+
+            case PropertyType.LONG:
+            case PropertyType.DOUBLE:
+                return new NumericConstraint(definition);
+
+            case PropertyType.NAME:
+                return NameConstraint.create(definition, resolver);
+
+            case PropertyType.PATH:
+                return PathConstraint.create(definition, resolver);
+
+            case PropertyType.REFERENCE:
+                return ReferenceConstraint.create(definition, resolver);
+
+            default:
+                throw new IllegalArgumentException("Unknown/unsupported target type for constraint: " + PropertyType.nameFromValue(type));
+        }
+    }
+
+    /**
+     * Tests if the value constraints defined in the property definition
+     * <code>pd</code> are satisfied by the the specified <code>values</code>.
+     * <p/>
+     * Note that the <i>protected</i> flag is not checked. Also note that no
+     * type conversions are attempted if the type of the given values does not
+     * match the required type as specified in the given definition.
+     *
+     * @param pd
+     * @param values
+     * @throws ConstraintViolationException
+     */
+    public static void checkValueConstraints(QPropertyDefinition pd, QValue[] values)
+            throws ConstraintViolationException, RepositoryException {
+        // check multi-value flag
+        if (!pd.isMultiple() && values != null && values.length > 1) {
+            throw new ConstraintViolationException("the property is not multi-valued");
+        }
+
+        String[] constraints = pd.getValueConstraints();
+        if (constraints == null || constraints.length == 0) {
+            // no constraints to check
+            return;
+        }
+        if (values != null && values.length > 0) {
+            // check value constraints on every value
+            for (int i = 0; i < values.length; i++) {
+                // constraints are OR-ed together
+                boolean satisfied = false;
+                ConstraintViolationException cve = null;
+                for (int j = 0; j < constraints.length && !satisfied; j++) {
+                    try {
+                        ValueConstraint cnstr = ValueConstraint.create(pd.getRequiredType(), constraints[j]);
+                        cnstr.check(values[i]);
+                        satisfied = true;
+                    } catch (ConstraintViolationException e) {
+                        cve = e;
+                    } catch (InvalidConstraintException e) {
+                        cve = new ConstraintViolationException(e.getMessage(), e);
+                    }
+                }
+                if (!satisfied) {
+                    // re-throw last exception we encountered
+                    throw cve;
+                }
+            }
+        }
+    }
+}
+
+//---------------------------------------------< Subclass BooleanConstraint >---
+/**
+ * <code>BooleanConstraint</code> ...
+ */
+class BooleanConstraint extends ValueConstraint {
+    final boolean reqBool;
+
+    BooleanConstraint(String definition) throws InvalidConstraintException {
+        super(definition);
+
+        // constraint format: 'true' or 'false'
+        if (definition.equals("true")) {
+            reqBool = true;
+        } else if (definition.equals("false")) {
+            reqBool = false;
+        } else {
+            String msg = "'" + definition
+                    + "' is not a valid value constraint format for BOOLEAN values";
+            log.debug(msg);
+            throw new InvalidConstraintException(msg);
+        }
+    }
+
+    /**
+     * @see ValueConstraint#check(QValue)
+     */
+    void check(QValue value) throws ConstraintViolationException, RepositoryException {
+        if (value == null) {
+            throw new ConstraintViolationException("null value does not satisfy the constraint '"  + getQualifiedDefinition() + "'");
+        }
+        switch (value.getType()) {
+            case PropertyType.BOOLEAN:
+                boolean b = Boolean.valueOf(value.getString()).booleanValue();
+                if (b != reqBool) {
+                    throw new ConstraintViolationException("'" + b + "' does not satisfy the constraint '" + getQualifiedDefinition() + "'");
+                }
+                return;
+
+            default:
+                String msg = "BOOLEAN constraint can not be applied to value of type: "
+                        + PropertyType.nameFromValue(value.getType());
+                log.debug(msg);
+                throw new RepositoryException(msg);
+        }
+    }
+}
+
+//----------------------------------------------< Subclass StringConstraint >---
+/**
+ * <code>StringConstraint</code> ...
+ */
+class StringConstraint extends ValueConstraint {
+    final Pattern pattern;
+
+    StringConstraint(String definition) throws InvalidConstraintException {
+        super(definition);
+
+        // constraint format: regexp
+        try {
+            pattern = Pattern.compile(definition);
+        } catch (PatternSyntaxException pse) {
+            String msg = "'" + definition + "' is not valid regular expression syntax";
+            log.debug(msg);
+            throw new InvalidConstraintException(msg, pse);
+        }
+    }
+
+    /**
+     * @see ValueConstraint#check(QValue)
+     */
+    void check(QValue value) throws ConstraintViolationException, RepositoryException {
+        if (value == null) {
+            throw new ConstraintViolationException("null value does not satisfy the constraint '" + getQualifiedDefinition() + "'");
+        }
+        switch (value.getType()) {
+            case PropertyType.STRING:
+                String text = value.getString();
+                Matcher matcher = pattern.matcher(text);
+                if (!matcher.matches()) {
+                    throw new ConstraintViolationException("'" + text  + "' does not satisfy the constraint '" + getQualifiedDefinition() + "'");
+                }
+                return;
+
+            default:
+                String msg = "STRING constraint can not be applied to value of type: " + PropertyType.nameFromValue(value.getType());
+                log.debug(msg);
+                throw new RepositoryException(msg);
+        }
+    }
+}
+
+//---------------------------------------------< Subclass NumericConstraint >---
+/**
+ * <code>NumericConstraint</code> ...
+ */
+class NumericConstraint extends ValueConstraint {
+    final boolean lowerInclusive;
+    final Double lowerLimit;
+    final boolean upperInclusive;
+    final Double upperLimit;
+
+    NumericConstraint(String definition) throws InvalidConstraintException {
+        super(definition);
+
+        // format: '(<min>, <max>)',  '[<min>, <max>]', '(, <max>)' etc.
+        Pattern pattern = Pattern.compile("([\\(\\[]) *(\\-?\\d+\\.?\\d*)? *, *(\\-?\\d+\\.?\\d*)? *([\\)\\]])");
+        Matcher matcher = pattern.matcher(definition);
+        if (matcher.matches()) {
+            try {
+                // group 1 is lower inclusive/exclusive
+                String s = matcher.group(1);
+                lowerInclusive = s.equals("[");
+                // group 2 is lower limit
+                s = matcher.group(2);
+                if (s == null || s.length() == 0) {
+                    lowerLimit = null;
+                } else {
+                    lowerLimit = Double.valueOf(matcher.group(2));
+                }
+                // group 3 is upper limit
+                s = matcher.group(3);
+                if (s == null || s.length() == 0) {
+                    upperLimit = null;
+                } else {
+                    upperLimit = Double.valueOf(matcher.group(3));
+                }
+                // group 4 is lower inclusive/exclusive
+                s = matcher.group(4);
+                upperInclusive = s.equals("]");
+                if (lowerLimit == null && upperLimit == null) {
+                    String msg = "'" + definition + "' is not a valid value constraint"
+                            + " format for numeric types: neither lower- nor upper-limit specified";
+                    log.debug(msg);
+                    throw new InvalidConstraintException(msg);
+                }
+                if (lowerLimit != null && upperLimit != null) {
+                    if (lowerLimit.doubleValue() > upperLimit.doubleValue()) {
+                        String msg = "'" + definition
+                                + "' is not a valid value constraint format for numeric types: lower-limit exceeds upper-limit";
+                        log.debug(msg);
+                        throw new InvalidConstraintException(msg);
+                    }
+                }
+            } catch (NumberFormatException nfe) {
+                String msg = "'" + definition
+                        + "' is not a valid value constraint format for numeric types";
+                log.debug(msg);
+                throw new InvalidConstraintException(msg, nfe);
+            }
+        } else {
+            String msg = "'" + definition
+                    + "' is not a valid value constraint format for numeric values";
+            log.debug(msg);
+            throw new InvalidConstraintException(msg);
+        }
+    }
+
+    private void check(double number) throws ConstraintViolationException {
+        if (lowerLimit != null) {
+            if (lowerInclusive) {
+                if (number < lowerLimit.doubleValue()) {
+                    throw new ConstraintViolationException(number
+                            + " does not satisfy the constraint '"
+                            + getQualifiedDefinition() + "'");
+                }
+            } else {
+                if (number <= lowerLimit.doubleValue()) {
+                    throw new ConstraintViolationException(number
+                            + " does not satisfy the constraint '"
+                            + getQualifiedDefinition() + "'");
+                }
+            }
+        }
+        if (upperLimit != null) {
+            if (upperInclusive) {
+                if (number > upperLimit.doubleValue()) {
+                    throw new ConstraintViolationException(number
+                            + " does not satisfy the constraint '"
+                            + getQualifiedDefinition() + "'");
+                }
+            } else {
+                if (number >= upperLimit.doubleValue()) {
+                    throw new ConstraintViolationException(number
+                            + " does not satisfy the constraint '"
+                            + getQualifiedDefinition() + "'");
+                }
+            }
+        }
+    }
+
+    /**
+     * @see ValueConstraint#check(QValue)
+     */
+    void check(QValue value) throws ConstraintViolationException, RepositoryException {
+        if (value == null) {
+            throw new ConstraintViolationException("null value does not satisfy the constraint '"
+                    + getQualifiedDefinition() + "'");
+        }
+        switch (value.getType()) {
+            case PropertyType.LONG:
+                check(value.getLong());
+                return;
+
+            case PropertyType.DOUBLE:
+                check(value.getDouble());
+                return;
+
+            case PropertyType.BINARY:
+                long length = value.getLength();
+                if (length != -1) {
+                    check(length);
+                } else {
+                    log.warn("failed to determine length of binary value");
+                }
+                return;
+
+            default:
+                String msg = "numeric constraint can not be applied to value of type: "
+                        + PropertyType.nameFromValue(value.getType());
+                log.debug(msg);
+                throw new RepositoryException(msg);
+        }
+    }
+}
+
+//------------------------------------------------< Subclass DateConstraint >---
+/**
+ * <code>DateConstraint</code> ...
+ */
+class DateConstraint extends ValueConstraint {
+
+    final boolean lowerInclusive;
+    final Calendar lowerLimit;
+    final boolean upperInclusive;
+    final Calendar upperLimit;
+
+    DateConstraint(String definition) throws InvalidConstraintException {
+        super(definition);
+
+        // format: '(<fromDate>, <toDate>)', '[<fromDate>, <toDate>]', '[, <toDate>]' etc.
+        Pattern pattern = Pattern.compile("([\\(\\[]) *([0-9TZ\\.\\+-:]*)? *, *([0-9TZ\\.\\+-:]*)? *([\\)\\]])");
+        Matcher matcher = pattern.matcher(definition);
+        if (matcher.matches()) {
+            try {
+                // group 1 is lower inclusive/exclusive
+                String s = matcher.group(1);
+                lowerInclusive = s.equals("[");
+                // group 2 is lower limit
+                s = matcher.group(2);
+                if (s == null || s.length() == 0) {
+                    lowerLimit = null;
+                } else {
+                    lowerLimit = DateValue.valueOf(matcher.group(2)).getDate();
+                }
+                // group 3 is upper limit
+                s = matcher.group(3);
+                if (s == null || s.length() == 0) {
+                    upperLimit = null;
+                } else {
+                    upperLimit = DateValue.valueOf(matcher.group(3)).getDate();
+                }
+                // group 4 is upepr inclusive/exclusive
+                s = matcher.group(4);
+                upperInclusive = s.equals("]");
+
+                if (lowerLimit == null && upperLimit == null) {
+                    String msg = "'" + definition
+                            + "' is not a valid value constraint format for dates: neither min- nor max-date specified";
+                    log.debug(msg);
+                    throw new InvalidConstraintException(msg);
+                }
+                if (lowerLimit != null && upperLimit != null) {
+                    if (lowerLimit.after(upperLimit)) {
+                        String msg = "'" + definition
+                                + "' is not a valid value constraint format for dates: min-date > max-date";
+                        log.debug(msg);
+                        throw new InvalidConstraintException(msg);
+                    }
+                }
+            } catch (ValueFormatException vfe) {
+                String msg = "'" + definition
+                        + "' is not a valid value constraint format for dates";
+                log.debug(msg);
+                throw new InvalidConstraintException(msg, vfe);
+            } catch (RepositoryException re) {
+                String msg = "'" + definition
+                        + "' is not a valid value constraint format for dates";
+                log.debug(msg);
+                throw new InvalidConstraintException(msg, re);
+            }
+        } else {
+            String msg = "'" + definition
+                    + "' is not a valid value constraint format for dates";
+            log.debug(msg);
+            throw new InvalidConstraintException(msg);
+        }
+    }
+
+    private void check(Calendar cal) throws ConstraintViolationException {
+        if (cal == null) {
+            throw new ConstraintViolationException("null value does not satisfy the constraint '" + getQualifiedDefinition() + "'");
+        }
+        if (lowerLimit != null) {
+            if (lowerInclusive) {
+                if (cal.getTimeInMillis() < lowerLimit.getTimeInMillis()) {
+                    throw new ConstraintViolationException(cal
+                            + " does not satisfy the constraint '"
+                            + getQualifiedDefinition() + "'");
+                }
+            } else {
+                if (cal.getTimeInMillis() <= lowerLimit.getTimeInMillis()) {
+                    throw new ConstraintViolationException(cal
+                            + " does not satisfy the constraint '"
+                            + getQualifiedDefinition() + "'");
+                }
+            }
+        }
+        if (upperLimit != null) {
+            if (upperInclusive) {
+                if (cal.getTimeInMillis() > upperLimit.getTimeInMillis()) {
+                    throw new ConstraintViolationException(cal
+                            + " does not satisfy the constraint '"
+                            + getQualifiedDefinition() + "'");
+                }
+            } else {
+                if (cal.getTimeInMillis() >= upperLimit.getTimeInMillis()) {
+                    throw new ConstraintViolationException(cal
+                            + " does not satisfy the constraint '"
+                            + getQualifiedDefinition() + "'");
+                }
+            }
+        }
+    }
+
+    /**
+     * @see ValueConstraint#check(QValue)
+     */
+    void check(QValue value) throws ConstraintViolationException, RepositoryException {
+        if (value == null) {
+            throw new ConstraintViolationException("null value does not satisfy the constraint '" + getQualifiedDefinition() + "'");
+        }
+        switch (value.getType()) {
+            case PropertyType.DATE:
+                check(value.getCalendar());
+                return;
+
+            default:
+                String msg = "DATE constraint can not be applied to value of type: "
+                        + PropertyType.nameFromValue(value.getType());
+                log.debug(msg);
+                throw new RepositoryException(msg);
+        }
+    }
+}
+
+//------------------------------------------------< Subclass PathConstraint >---
+/**
+ * <code>PathConstraint</code> ...
+ */
+class PathConstraint extends ValueConstraint {
+
+    final Path path;
+    final boolean deep;
+
+    static PathConstraint create(String qualifiedDefinition) throws InvalidConstraintException {
+        // constraint format: qualified absolute or relative path with optional trailing wildcard
+        boolean deep = qualifiedDefinition.endsWith("*");
+        Path path;
+        // TODO improve. don't rely on a specific factory impl
+        if (deep) {
+            path = PathFactoryImpl.getInstance().create(qualifiedDefinition.substring(0, qualifiedDefinition.length() - 1));
+        } else {
+            path = PathFactoryImpl.getInstance().create(qualifiedDefinition);
+        }
+        return new PathConstraint(qualifiedDefinition, path, deep);
+    }
+
+    static PathConstraint create(String definition, PathResolver resolver)
+            throws InvalidConstraintException {
+        try {
+            StringBuffer qualifiedDefinition = new StringBuffer();
+            // constraint format: absolute or relative path with optional
+            // trailing wildcard
+            boolean deep = definition.endsWith("/*");
+            if (deep) {
+                // trim trailing wildcard before building path
+                if (definition.equals("/*")) {
+                    definition = "/";
+                    qualifiedDefinition.append('*');
+                } else {
+                    definition = definition.substring(0, definition.length() - 2);
+                    qualifiedDefinition.append("/*");
+                }
+            }
+            Path path = resolver.getQPath(definition);
+            qualifiedDefinition.insert(0, path.getString());
+
+            return new PathConstraint(qualifiedDefinition.toString(), path, deep);
+        } catch (NameException e) {
+            String msg = "Invalid path expression specified as value constraint: " + definition;
+            log.debug(msg);
+            throw new InvalidConstraintException(msg, e);
+        } catch (NamespaceException e) {
+            String msg = "Invalid path expression specified as value constraint: " + definition;
+            log.debug(msg);
+            throw new InvalidConstraintException(msg, e);
+        }
+    }
+
+    private PathConstraint(String qualifiedDefinition, Path path, boolean deep) throws InvalidConstraintException {
+        super(qualifiedDefinition);
+        this.path = path;
+        this.deep = deep;
+    }
+
+    /**
+     * Uses {@link NamePathResolver#getJCRPath(Path)} to convert the
+     * qualified <code>Path</code> into a JCR path.
+     *
+     * @see ValueConstraint#getDefinition(NamePathResolver)
+     * @param resolver
+     */
+    public String getDefinition(NamePathResolver resolver) {
+        try {
+            String p = resolver.getJCRPath(path);
+            if (!deep) {
+                return p;
+            } else if (path.denotesRoot()) {
+                return p + "*";
+            } else {
+                return p + "/*";
+            }
+        } catch (NamespaceException e) {
+            // should never get here, return raw definition as fallback
+            return getQualifiedDefinition();
+        }
+    }
+
+    /**
+     * @see ValueConstraint#check(QValue)
+     */
+    void check(QValue value) throws ConstraintViolationException, RepositoryException {
+        if (value == null) {
+            throw new ConstraintViolationException("null value does not satisfy the constraint '" + getQualifiedDefinition() + "'");
+        }
+        switch (value.getType()) {
+            case PropertyType.PATH:
+                Path p = value.getPath();
+                // normalize paths before comparing them
+                Path p0, p1;
+                try {
+                    p0 = path.getNormalizedPath();
+                    p1 = p.getNormalizedPath();
+                } catch (RepositoryException e) {
+                    throw new ConstraintViolationException("path not valid: " + e);
+                }
+                if (deep) {
+                    try {
+                        if (!p0.isAncestorOf(p1)) {
+                            throw new ConstraintViolationException(p
+                                + " does not satisfy the constraint '"
+                                + getQualifiedDefinition() + "'");
+                        }
+                    } catch (RepositoryException e) {
+                        // can't compare relative with absolute path
+                        throw new ConstraintViolationException(p
+                            + " does not satisfy the constraint '"
+                            + getQualifiedDefinition() + "'");
+                    }
+                } else {
+                    // exact match required
+                    if (!p0.equals(p1)) {
+                        throw new ConstraintViolationException(p
+                            + " does not satisfy the constraint '"
+                            + getQualifiedDefinition() + "'");
+                    }
+                }
+                return;
+
+            default:
+                String msg = "PATH constraint can not be applied to value of type: "
+                        + PropertyType.nameFromValue(value.getType());
+                log.debug(msg);
+                throw new RepositoryException(msg);
+        }
+    }
+}
+
+//------------------------------------------------< Subclass NameConstraint >---
+/**
+ * <code>NameConstraint</code> ...
+ */
+class NameConstraint extends ValueConstraint {
+
+    private final Name name;
+
+    static NameConstraint create(String qualifiedDefinition) {
+        // constraint format: String representation of qualified name
+        return new NameConstraint(qualifiedDefinition, NAME_FACTORY.create(qualifiedDefinition));
+    }
+
+    static NameConstraint create(String definition, NameResolver resolver)
+            throws InvalidConstraintException {
+        // constraint format: JCR name in prefix form
+        try {
+            Name name = resolver.getQName(definition);
+            return new NameConstraint(name.toString(), name);
+        } catch (NameException e) {
+            String msg = "Invalid name constraint: " + definition;
+            log.debug(msg);
+            throw new InvalidConstraintException(msg, e);
+        } catch (NamespaceException e) {
+            String msg = "Invalid name constraint: " + definition;
+            log.debug(msg);
+            throw new InvalidConstraintException(msg, e);
+        }
+    }
+
+    private NameConstraint(String qualifiedDefinition, Name name) {
+        super(qualifiedDefinition);
+        this.name = name;
+    }
+
+    /**
+     * Uses {@link NamePathResolver#getJCRName(Name)} to convert the
+     * qualified <code>Name</code> into a JCR name.
+     *
+     * @see ValueConstraint#getDefinition(NamePathResolver)
+     * @param resolver
+     */
+    public String getDefinition(NamePathResolver resolver) {
+        try {
+            return resolver.getJCRName(name);
+        } catch (NamespaceException e) {
+            // should never get here, return raw definition as fallback
+            return getQualifiedDefinition();
+        }
+    }
+
+    /**
+     * @see ValueConstraint#check(QValue)
+     */
+    void check(QValue value) throws ConstraintViolationException, RepositoryException {
+        if (value == null) {
+            throw new ConstraintViolationException("null value does not satisfy the constraint '" + getQualifiedDefinition() + "'");
+        }
+        switch (value.getType()) {
+            case PropertyType.NAME:
+                Name n = value.getName();
+                if (!name.equals(n)) {
+                    throw new ConstraintViolationException(n
+                            + " does not satisfy the constraint '"
+                            + getQualifiedDefinition() + "'");
+                }
+                return;
+
+            default:
+                String msg = "NAME constraint can not be applied to value of type: "
+                        + PropertyType.nameFromValue(value.getType());
+                log.debug(msg);
+                throw new RepositoryException(msg);
+        }
+    }
+}
+
+//-------------------------------------------< Subclass ReferenceConstraint >---
+/**
+ * <code>ReferenceConstraint</code> ...
+ */
+class ReferenceConstraint extends ValueConstraint {
+
+    private final Name ntName;
+
+    static ReferenceConstraint create(String qualifiedDefinition) {
+        // constraint format: String representation of qualified name
+        return new ReferenceConstraint(qualifiedDefinition, NAME_FACTORY.create(qualifiedDefinition));
+    }
+
+    static ReferenceConstraint create(String definition, NameResolver resolver)
+            throws InvalidConstraintException {
+        // constraint format: JCR name in prefix form
+        try {
+            Name name = resolver.getQName(definition);
+            return new ReferenceConstraint(name.toString(), name);
+        } catch (NameException e) {
+            String msg = "Invalid name constraint: " + definition;
+            log.debug(msg);
+            throw new InvalidConstraintException(msg, e);
+        } catch (NamespaceException e) {
+            String msg = "Invalid name constraint: " + definition;
+            log.debug(msg);
+            throw new InvalidConstraintException(msg, e);
+        }
+    }
+
+    private ReferenceConstraint(String qualifiedDefinition, Name ntName) {
+        super(qualifiedDefinition);
+        this.ntName = ntName;
+    }
+
+    /**
+     * Uses {@link NamePathResolver#getJCRName(Name)} to convert the
+     * qualified <code>Name</code> into a JCR name.
+     *
+     * @see ValueConstraint#getDefinition(NamePathResolver)
+     * @param resolver
+     */
+    public String getDefinition(NamePathResolver resolver) {
+        try {
+            return resolver.getJCRName(ntName);
+        } catch (NamespaceException e) {
+            // should never get here, return raw definition as fallback
+            return getQualifiedDefinition();
+        }
+    }
+
+    /**
+     * @see ValueConstraint#check(QValue)
+     */
+    void check(QValue value) throws ConstraintViolationException, RepositoryException {
+        if (value == null) {
+            throw new ConstraintViolationException("Null value does not satisfy the constraint '" + getQualifiedDefinition() + "'");
+        }
+        switch (value.getType()) {
+            case PropertyType.REFERENCE:
+                // TODO check REFERENCE value constraint (requires a session)
+                log.warn("validation of REFERENCE constraint is not yet implemented");
+                return;
+
+            default:
+                String msg = "REFERENCE constraint can not be applied to value of type: "
+                        + PropertyType.nameFromValue(value.getType());
+                log.debug(msg);
+                throw new RepositoryException(msg);
+        }
+    }
+}
+
+
+

Propchange: jackrabbit/trunk/jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/nodetype/ValueConstraint.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: jackrabbit/trunk/jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/nodetype/ValueConstraint.java
------------------------------------------------------------------------------
    svn:keywords = author date id revision url