You are viewing a plain text version of this content. The canonical link for it is here.
Posted to oak-commits@jackrabbit.apache.org by ju...@apache.org on 2013/03/21 14:40:10 UTC

svn commit: r1459306 - in /jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/nodetype: EffectiveType.java TypeEditor.java

Author: jukka
Date: Thu Mar 21 13:40:10 2013
New Revision: 1459306

URL: http://svn.apache.org/r1459306
Log:
OAK-411: Validator for node type management

Extract generic type information code from the validator to a new EffectiveType class

Added:
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/nodetype/EffectiveType.java
Modified:
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/nodetype/TypeEditor.java

Added: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/nodetype/EffectiveType.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/nodetype/EffectiveType.java?rev=1459306&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/nodetype/EffectiveType.java (added)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/nodetype/EffectiveType.java Thu Mar 21 13:40:10 2013
@@ -0,0 +1,290 @@
+/*
+ * 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.oak.plugins.nodetype;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static org.apache.jackrabbit.JcrConstants.JCR_MANDATORY;
+import static org.apache.jackrabbit.JcrConstants.JCR_MIXINTYPES;
+import static org.apache.jackrabbit.JcrConstants.JCR_PRIMARYTYPE;
+import static org.apache.jackrabbit.JcrConstants.JCR_UUID;
+import static org.apache.jackrabbit.oak.api.Type.BOOLEAN;
+
+import java.util.List;
+
+import javax.annotation.Nonnull;
+import javax.jcr.nodetype.ConstraintViolationException;
+
+import org.apache.jackrabbit.oak.api.PropertyState;
+import org.apache.jackrabbit.oak.api.Type;
+import org.apache.jackrabbit.oak.spi.state.ChildNodeEntry;
+import org.apache.jackrabbit.oak.spi.state.NodeState;
+
+class EffectiveType {
+
+    private final List<NodeState> types;
+
+    EffectiveType(List<NodeState> types) {
+        this.types = checkNotNull(types);
+    }
+
+    void checkMandatoryItems(NodeState node)
+            throws ConstraintViolationException {
+        for (NodeState type : types) {
+            NodeState properties =
+                    type.getChildNode("oak:namedPropertyDefinitions");
+            if (properties != null) {
+                for (ChildNodeEntry entry : properties.getChildNodeEntries()) {
+                    String name = entry.getName();
+                    if ("oak:primaryType".equals(name)) {
+                        name = JCR_PRIMARYTYPE;
+                    } else if ("oak:mixinTypes".equals(name)) {
+                        name = JCR_MIXINTYPES;
+                    } else if ("oak:uuid".equals(name)) {
+                        name = JCR_UUID;
+                    }
+                    if (node.getProperty(name) == null
+                            && isMandatory(name, entry.getNodeState())) {
+                        throw new ConstraintViolationException(
+                                "Missing mandatory property " + name);
+                    }
+                }
+            }
+
+            NodeState childNodes =
+                    type.getChildNode("oak:namedChildNodeDefinitions");
+            if (childNodes != null) {
+                for (ChildNodeEntry entry : childNodes.getChildNodeEntries()) {
+                    String name = entry.getName();
+                    if (!node.hasChildNode(name)
+                            && isMandatory(name, entry.getNodeState())) {
+                        throw new ConstraintViolationException(
+                                "Missing mandatory child node " + name);
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * Finds a matching definition for a property with the given name and type.
+     *
+     * @param property modified property
+     * @return matching property definition
+     * @throws ConstraintViolationException if a matching definition was not found
+     */
+    @Nonnull
+    NodeState getDefinition(PropertyState property)
+            throws ConstraintViolationException {
+        String propertyName = property.getName();
+        Type<?> propertyType = property.getType();
+
+        String escapedName;
+        if (JCR_PRIMARYTYPE.equals(propertyName)) {
+            escapedName = "oak:primaryType";
+        } else if (JCR_MIXINTYPES.equals(propertyName)) {
+            escapedName = "oak:mixinTypes";
+        } else if (JCR_UUID.equals(propertyName)) {
+            escapedName = "oak:uuid";
+        } else {
+            escapedName = propertyName;
+        }
+
+        String definedType = getTypeKey(propertyType);
+        String undefinedType;
+        if (propertyType.isArray()) {
+            undefinedType = "UNDEFINEDS";
+        } else {
+            undefinedType = "UNDEFINED";
+        }
+
+        // Find matching named property definition
+        for (NodeState type : types) {
+            NodeState named = type.getChildNode("oak:namedPropertyDefinitions");
+            if (named != null) {
+                NodeState definitions = named.getChildNode(escapedName);
+                if (definitions != null) {
+                    NodeState definition = definitions.getChildNode(definedType);
+                    if (definition == null) {
+                        definition = definitions.getChildNode(undefinedType);
+                    }
+                    if (definition != null) {
+                        return definition;
+                    } else {
+                        throw new ConstraintViolationException(
+                                "No matching definition found for property "
+                                        + propertyName);
+                    }
+                }
+            }
+        }
+
+        // Find matching residual property definition
+        for (NodeState type : types) {
+            NodeState residual =
+                    type.getChildNode("oak:residualPropertyDefinitions");
+            if (residual != null) {
+                NodeState definition = residual.getChildNode(definedType);
+                if (definition == null) {
+                    definition = residual.getChildNode(undefinedType);
+                }
+                if (definition != null) {
+                    return definition;
+                }
+            }
+        }
+
+        throw new ConstraintViolationException(
+                "No matching definition found for property " + propertyName);
+    }
+
+    /**
+     * Finds a matching definition for a child node with the given name and
+     * types.
+     *
+     * @param nodeName child node name
+     * @param nodeType effective types of the child node
+     * @return matching child node definition
+     * @throws ConstraintViolationException if a matching definition was not found
+     */
+    @Nonnull
+    NodeState getDefinition(String nodeName, Iterable<String> nodeType)
+            throws ConstraintViolationException {
+        boolean sns = false;
+        int n = nodeName.length();
+        if (n > 3 && nodeName.charAt(n - 1) == ']') {
+            int i = n - 2;
+            while (i > 1 && Character.isDigit(nodeName.charAt(i))) {
+                i--;
+            }
+            if (nodeName.charAt(i) == '[') {
+                nodeName = nodeName.substring(0, i);
+                sns = true;
+            }
+        }
+
+        // Find matching named child node definition
+        for (NodeState type : types) {
+            NodeState named = type.getChildNode("oak:namedChildNodeDefinitions");
+            if (named != null) {
+                NodeState definitions = named.getChildNode(nodeName);
+                if (definitions != null) {
+                    for (String typeName : nodeType) {
+                        NodeState definition = definitions.getChildNode(typeName);
+                        if (definition != null) {
+                            return definition;
+                        }
+                    }
+
+                    throw new ConstraintViolationException(
+                            "Incorrect node type of child node " + nodeName);
+                }
+            }
+        }
+
+        // Find matching residual child node definition
+        for (NodeState type : types) {
+            NodeState residual =
+                    type.getChildNode("oak:residualChildNodeDefinitions");
+            if (residual != null) {
+                for (String typeName : nodeType) {
+                    NodeState definition = residual.getChildNode(typeName);
+                    if (definition != null) {
+                        return definition;
+                    }
+                }
+            }
+        }
+
+        throw new ConstraintViolationException(
+                "Incorrect node type of child node " + nodeName);
+    }
+
+
+    //-----------------------------------------------------------< private >--
+    
+    private boolean isMandatory(String name, NodeState definitions) {
+        for (ChildNodeEntry entry : definitions.getChildNodeEntries()) {
+            NodeState definition = entry.getNodeState();
+            if (getBoolean(definition, JCR_MANDATORY)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private boolean getBoolean(NodeState node, String name) {
+        PropertyState property = node.getProperty(name);
+        return property != null
+                && property.getType() == BOOLEAN
+                && property.getValue(BOOLEAN);
+    }
+
+    private String getTypeKey(Type<?> type) {
+        if (type == Type.BINARIES) {
+            return "BINARIES";
+        } else if (type == Type.BINARY) {
+            return "BINARY";
+        } else if (type == Type.BOOLEAN) {
+            return "BOOLEAN";
+        } else if (type == Type.BOOLEANS) {
+            return "BOOLEANS";
+        } else if (type == Type.DATE) {
+            return "DATE";
+        } else if (type == Type.DATES) {
+            return "DATES";
+        } else if (type == Type.DECIMAL) {
+            return "DECIMAL";
+        } else if (type == Type.DECIMALS) {
+            return "DECIMALS";
+        } else if (type == Type.DOUBLE) {
+            return "DOUBLE";
+        } else if (type == Type.DOUBLES) {
+            return "DOUBLES";
+        } else if (type == Type.LONG) {
+            return "LONG";
+        } else if (type == Type.LONGS) {
+            return "LONGS";
+        } else if (type == Type.NAME) {
+            return "NAME";
+        } else if (type == Type.NAMES) {
+            return "NAMES";
+        } else if (type == Type.PATH) {
+            return "PATH";
+        } else if (type == Type.PATHS) {
+            return "PATHS";
+        } else if (type == Type.REFERENCE) {
+            return "REFERENCE";
+        } else if (type == Type.REFERENCES) {
+            return "REFERENCES";
+        } else if (type == Type.STRING) {
+            return "STRING";
+        } else if (type == Type.STRINGS) {
+            return "STRINGS";
+        } else if (type == Type.URI) {
+            return "URI";
+        } else if (type == Type.URIS) {
+            return "URIS";
+        } else if (type == Type.WEAKREFERENCE) {
+            return "WEAKREFERENCE";
+        } else if (type == Type.WEAKREFERENCES) {
+            return "WEAKREFERENCES";
+        } else {
+            return "unknown";
+        }
+    }
+
+}

Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/nodetype/TypeEditor.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/nodetype/TypeEditor.java?rev=1459306&r1=1459305&r2=1459306&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/nodetype/TypeEditor.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/nodetype/TypeEditor.java Thu Mar 21 13:40:10 2013
@@ -18,12 +18,10 @@ package org.apache.jackrabbit.oak.plugin
 
 import static com.google.common.base.Preconditions.checkNotNull;
 import static org.apache.jackrabbit.JcrConstants.JCR_ISMIXIN;
-import static org.apache.jackrabbit.JcrConstants.JCR_MANDATORY;
 import static org.apache.jackrabbit.JcrConstants.JCR_MIXINTYPES;
 import static org.apache.jackrabbit.JcrConstants.JCR_PRIMARYTYPE;
 import static org.apache.jackrabbit.JcrConstants.JCR_REQUIREDTYPE;
 import static org.apache.jackrabbit.JcrConstants.JCR_SUPERTYPES;
-import static org.apache.jackrabbit.JcrConstants.JCR_UUID;
 import static org.apache.jackrabbit.JcrConstants.JCR_VALUECONSTRAINTS;
 import static org.apache.jackrabbit.JcrConstants.NT_BASE;
 import static org.apache.jackrabbit.oak.api.Type.BOOLEAN;
@@ -38,18 +36,15 @@ import java.util.List;
 import java.util.Queue;
 import java.util.Set;
 
-import javax.annotation.Nonnull;
 import javax.jcr.PropertyType;
 import javax.jcr.Value;
 import javax.jcr.nodetype.ConstraintViolationException;
 
 import org.apache.jackrabbit.oak.api.CommitFailedException;
 import org.apache.jackrabbit.oak.api.PropertyState;
-import org.apache.jackrabbit.oak.api.Type;
 import org.apache.jackrabbit.oak.plugins.value.ValueFactoryImpl;
 import org.apache.jackrabbit.oak.spi.commit.DefaultEditor;
 import org.apache.jackrabbit.oak.spi.commit.Editor;
-import org.apache.jackrabbit.oak.spi.state.ChildNodeEntry;
 import org.apache.jackrabbit.oak.spi.state.NodeState;
 
 import com.google.common.base.Predicate;
@@ -74,7 +69,7 @@ class TypeEditor extends DefaultEditor {
 
     private final NodeState types;
 
-    private final List<NodeState> effective = Lists.newArrayList();
+    private EffectiveType effective = null;
 
     TypeEditor(NodeState types) {
         this.parent = null;
@@ -98,7 +93,11 @@ class TypeEditor extends DefaultEditor {
 
         // find matching entry in the parent node's effective type
         if (parent != null) {
-            parent.getDefinition(nodeName, names);
+            try {
+                parent.effective.getDefinition(nodeName, names);
+            } catch (ConstraintViolationException e) {
+                throw new CommitFailedException(e);
+            }
         }
     }
 
@@ -108,68 +107,37 @@ class TypeEditor extends DefaultEditor {
         // TODO: add any auto-created items that are still missing
 
         // verify the presence of all mandatory items
-        for (NodeState type : effective) {
-            NodeState properties =
-                    type.getChildNode("oak:namedPropertyDefinitions");
-            if (properties != null) {
-                for (ChildNodeEntry entry : properties.getChildNodeEntries()) {
-                    String name = entry.getName();
-                    if ("oak:primaryType".equals(name)) {
-                        name = JCR_PRIMARYTYPE;
-                    } else if ("oak:mixinTypes".equals(name)) {
-                        name = JCR_MIXINTYPES;
-                    } else if ("oak:uuid".equals(name)) {
-                        name = JCR_UUID;
-                    }
-                    if (after.getProperty(name) == null
-                            && isMandatory(name, entry.getNodeState())) {
-                        throw constraintViolation(
-                                "Missing mandatory property " + name);
-                    }
-                }
-            }
-
-            NodeState childNodes =
-                    type.getChildNode("oak:namedChildNodeDefinitions");
-            if (childNodes != null) {
-                for (ChildNodeEntry entry : childNodes.getChildNodeEntries()) {
-                    String name = entry.getName();
-                    if (!after.hasChildNode(name)
-                            && isMandatory(name, entry.getNodeState())) {
-                        throw constraintViolation(
-                                "Missing mandatory child node " + name);
-                    }
-                }
-            }
-        }
-    }
-
-    private boolean isMandatory(String name, NodeState definitions) {
-        for (ChildNodeEntry entry : definitions.getChildNodeEntries()) {
-            NodeState definition = entry.getNodeState();
-            if (getBoolean(definition, JCR_MANDATORY)) {
-                return true;
-            }
+        try {
+            effective.checkMandatoryItems(after);
+        } catch (ConstraintViolationException e) {
+            throw new CommitFailedException(e);
         }
-        return false;
     }
 
-;    private CommitFailedException constraintViolation(String message) {
+    private CommitFailedException constraintViolation(String message) {
         return new CommitFailedException(
                 new ConstraintViolationException(message));
     }
 
     @Override
     public void propertyAdded(PropertyState after) throws CommitFailedException {
-        NodeState definition = getDefinition(after);
-        checkValueConstraints(definition, after);
+        try {
+            NodeState definition = effective.getDefinition(after);
+            checkValueConstraints(definition, after);
+        } catch (ConstraintViolationException e) {
+            throw new CommitFailedException(e);
+        }
     }
 
     @Override
     public void propertyChanged(PropertyState before, PropertyState after)
             throws CommitFailedException {
-        NodeState definition = getDefinition(after);
-        checkValueConstraints(definition, after);
+        try {
+            NodeState definition = effective.getDefinition(after);
+            checkValueConstraints(definition, after);
+        } catch (ConstraintViolationException e) {
+            throw new CommitFailedException(e);
+        }
     }
 
     @Override
@@ -194,194 +162,6 @@ class TypeEditor extends DefaultEditor {
 
     //-----------------------------------------------------------< private >--
 
-    private String getTypeKey(Type<?> type) {
-        if (type == Type.BINARIES) {
-            return "BINARIES";
-        } else if (type == Type.BINARY) {
-            return "BINARY";
-        } else if (type == Type.BOOLEAN) {
-            return "BOOLEAN";
-        } else if (type == Type.BOOLEANS) {
-            return "BOOLEANS";
-        } else if (type == Type.DATE) {
-            return "DATE";
-        } else if (type == Type.DATES) {
-            return "DATES";
-        } else if (type == Type.DECIMAL) {
-            return "DECIMAL";
-        } else if (type == Type.DECIMALS) {
-            return "DECIMALS";
-        } else if (type == Type.DOUBLE) {
-            return "DOUBLE";
-        } else if (type == Type.DOUBLES) {
-            return "DOUBLES";
-        } else if (type == Type.LONG) {
-            return "LONG";
-        } else if (type == Type.LONGS) {
-            return "LONGS";
-        } else if (type == Type.NAME) {
-            return "NAME";
-        } else if (type == Type.NAMES) {
-            return "NAMES";
-        } else if (type == Type.PATH) {
-            return "PATH";
-        } else if (type == Type.PATHS) {
-            return "PATHS";
-        } else if (type == Type.REFERENCE) {
-            return "REFERENCE";
-        } else if (type == Type.REFERENCES) {
-            return "REFERENCES";
-        } else if (type == Type.STRING) {
-            return "STRING";
-        } else if (type == Type.STRINGS) {
-            return "STRINGS";
-        } else if (type == Type.URI) {
-            return "URI";
-        } else if (type == Type.URIS) {
-            return "URIS";
-        } else if (type == Type.WEAKREFERENCE) {
-            return "WEAKREFERENCE";
-        } else if (type == Type.WEAKREFERENCES) {
-            return "WEAKREFERENCES";
-        } else {
-            return "unknown";
-        }
-    }
-
-    /**
-     * Finds a matching definition for a property with the given name and type.
-     *
-     * @param property modified property
-     * @return matching property definition
-     * @throws CommitFailedException if a matching definition was not found
-     */
-    @Nonnull
-    private NodeState getDefinition(PropertyState property)
-            throws CommitFailedException {
-        String propertyName = property.getName();
-        Type<?> propertyType = property.getType();
-
-        String escapedName;
-        if (JCR_PRIMARYTYPE.equals(propertyName)) {
-            escapedName = "oak:primaryType";
-        } else if (JCR_MIXINTYPES.equals(propertyName)) {
-            escapedName = "oak:mixinTypes";
-        } else if (JCR_UUID.equals(propertyName)) {
-            escapedName = "oak:uuid";
-        } else {
-            escapedName = propertyName;
-        }
-
-        String definedType = getTypeKey(propertyType);
-        String undefinedType;
-        if (propertyType.isArray()) {
-            undefinedType = "UNDEFINEDS";
-        } else {
-            undefinedType = "UNDEFINED";
-        }
-
-        // Find matching named property definition
-        for (NodeState type : effective) {
-            NodeState named = type.getChildNode("oak:namedPropertyDefinitions");
-            if (named != null) {
-                NodeState definitions = named.getChildNode(escapedName);
-                if (definitions != null) {
-                    NodeState definition = definitions.getChildNode(definedType);
-                    if (definition == null) {
-                        definition = definitions.getChildNode(undefinedType);
-                    }
-                    if (definition != null) {
-                        return definition;
-                    } else {
-                        throw constraintViolation(
-                                "No matching definition found for property "
-                                        + propertyName);
-                    }
-                }
-            }
-        }
-
-        // Find matching residual property definition
-        for (NodeState type : effective) {
-            NodeState residual =
-                    type.getChildNode("oak:residualPropertyDefinitions");
-            if (residual != null) {
-                NodeState definition = residual.getChildNode(definedType);
-                if (definition == null) {
-                    definition = residual.getChildNode(undefinedType);
-                }
-                if (definition != null) {
-                    return definition;
-                }
-            }
-        }
-
-        throw constraintViolation(
-                "No matching definition found for property " + propertyName);
-    }
-
-    /**
-     * Finds a matching definition for a child node with the given name and
-     * types.
-     *
-     * @param nodeName child node name
-     * @param nodeType effective types of the child node
-     * @return matching child node definition
-     * @throws CommitFailedException if a matching definition was not found
-     */
-    @Nonnull
-    private NodeState getDefinition(String nodeName, Iterable<String> nodeType)
-            throws CommitFailedException {
-        boolean sns = false;
-        int n = nodeName.length();
-        if (n > 3 && nodeName.charAt(n - 1) == ']') {
-            int i = n - 2;
-            while (i > 1 && Character.isDigit(nodeName.charAt(i))) {
-                i--;
-            }
-            if (nodeName.charAt(i) == '[') {
-                nodeName = nodeName.substring(0, i);
-                sns = true;
-            }
-        }
-
-        // Find matching named property definition
-        for (NodeState type : effective) {
-            NodeState named = type.getChildNode("oak:namedChildNodeDefinitions");
-            if (named != null) {
-                NodeState definitions = named.getChildNode(nodeName);
-                if (definitions != null) {
-                    for (String typeName : nodeType) {
-                        NodeState definition = definitions.getChildNode(typeName);
-                        if (definition != null) {
-                            return definition;
-                        }
-                    }
-
-                    throw constraintViolation(
-                            "Incorrect node type of child node " + nodeName);
-                }
-            }
-        }
-
-        // Find matching residual property definition
-        for (NodeState type : effective) {
-            NodeState residual =
-                    type.getChildNode("oak:residualChildNodeDefinitions");
-            if (residual != null) {
-                for (String typeName : nodeType) {
-                    NodeState definition = residual.getChildNode(typeName);
-                    if (definition != null) {
-                        return definition;
-                    }
-                }
-            }
-        }
-
-        throw constraintViolation(
-                "Incorrect node type of child node " + nodeName);
-    }
-
     private void checkValueConstraints(
             NodeState definition, PropertyState property)
             throws CommitFailedException {
@@ -452,6 +232,7 @@ class TypeEditor extends DefaultEditor {
      */
     private Iterable<String> computeEffectiveType(NodeState node)
             throws CommitFailedException {
+        List<NodeState> list = Lists.newArrayList();
         Set<String> names = Sets.newLinkedHashSet();
 
         // primary type
@@ -472,7 +253,7 @@ class TypeEditor extends DefaultEditor {
                         "Can not use abstract type " + name + " as primary");
             }
 
-            effective.add(type);
+            list.add(type);
         }
 
         // mixin types
@@ -492,13 +273,13 @@ class TypeEditor extends DefaultEditor {
                                 "Can not use abstract type " + name + " as mixin");
                     }
 
-                    effective.add(type);
+                    list.add(type);
                 }
             }
         }
 
         // supertypes
-        Queue<NodeState> queue = Queues.newArrayDeque(effective);
+        Queue<NodeState> queue = Queues.newArrayDeque(list);
         while (!queue.isEmpty()) {
             NodeState type = queue.remove();
 
@@ -508,7 +289,7 @@ class TypeEditor extends DefaultEditor {
                     if (names.add(name)) {
                         NodeState supertype = types.getChildNode(name);
                         if (supertype != null) {
-                            effective.add(supertype);
+                            list.add(supertype);
                             queue.add(supertype);
                         } else {
                             // TODO: ignore/warning/error?
@@ -522,12 +303,13 @@ class TypeEditor extends DefaultEditor {
         if (names.add(NT_BASE)) {
             NodeState base = types.getChildNode(NT_BASE);
             if (base != null) {
-                effective.add(base);
+                list.add(base);
             } else {
                 // TODO: ignore/warning/error?
             }
         }
 
+        effective = new EffectiveType(list);
         return names;
     }