Index: jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/name/NameResolver.java
===================================================================
--- jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/name/NameResolver.java	(revision 0)
+++ jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/name/NameResolver.java	(revision 0)
@@ -0,0 +1,46 @@
+/*
+ * 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.name;
+
+import javax.jcr.NamespaceException;
+
+/**
+ * Resolver for prefixed JCR names and namespace-qualified
+ * {@link QName QNames}.
+ */
+public interface NameResolver {
+
+    /**
+     * Returns the qualified name for the given prefixed JCR name.
+     *
+     * @param name prefixed JCR name
+     * @return qualified name
+     * @throws NameException if the JCR name format is invalid
+     * @throws NamespaceException if the namespace prefix can not be resolved
+     */
+    QName getQName(String name) throws NameException, NamespaceException;
+
+    /**
+     * Returns the prefixed JCR name for the given qualified name.
+     *
+     * @param name qualified name
+     * @return prefixed JCR name
+     * @throws NamespaceException if the namespace URI can not be resolved
+     */
+    String getJCRName(QName name) throws NamespaceException;
+
+}

Property changes on: jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/name/NameResolver.java
___________________________________________________________________
Name: svn:eol-style
   + native

Index: jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/name/CachingNameResolver.java
===================================================================
--- jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/name/CachingNameResolver.java	(revision 0)
+++ jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/name/CachingNameResolver.java	(revision 0)
@@ -0,0 +1,204 @@
+/*
+ * 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.name;
+
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+
+import javax.jcr.NamespaceException;
+
+/**
+ * Name resolver decorator that uses a generational cache to speed up
+ * parsing of prefixed JCR names. Uncached names are resolved using the
+ * decorated name resolver.
+ * <p>
+ * The cache consists of three parts: a long term cache and two generations
+ * of recent cache entries. The two generations are used to collect recent new
+ * entries, and those entries that are used within two successive generations
+ * get promoted to the long term cache. The entries within the long term cache
+ * are discarded only when the size of the cache exceeds the given maximum
+ * cache size.
+ */
+public class CachingNameResolver implements NameResolver {
+
+    /**
+     * Default maximum cache size.
+     */
+    private static final int DEFAULT_MAX_CACHE_SIZE = 1000;
+
+    /**
+     * Divisor used to determine the default generation age from the
+     * maximum cache size.
+     */
+    private static final int DEFAULT_AGE_DIVISOR = 10;
+
+    /**
+     * Decorated name resolver.
+     */
+    private final NameResolver resolver;
+
+    /**
+     * Maximum size of the name cache.
+     */
+    private final int maxCacheSize;
+
+    /**
+     * Maximum age of a cache generation.
+     */
+    private final int maxGenerationAge;
+
+    /**
+     * Long term name cache. The cache map is always read-only, cache updates
+     * are performed by atomically replacing the map reference.
+     */
+    private Map cache = new HashMap();
+
+    /**
+     * Old cache generation. Synchronized acces.
+     */
+    private Map old = new HashMap();
+
+    /**
+     * Young cache generation. Synchronized acces.
+     */
+    private Map young = new HashMap();
+
+    /**
+     * Age of the young cache generation. Synchronized acces.
+     */
+    private int generationAge = 0;
+
+    /**
+     * Creates a caching name resolver.
+     *
+     * @param resolver decorated name resolver
+     * @param maxCacheSize maximum size of the long term cache
+     * @param maxGenerationAge maximum age of a cache generation
+     */
+    public CachingNameResolver(
+            NameResolver resolver, int maxCacheSize, int maxGenerationAge) {
+        this.resolver = resolver;
+        this.maxCacheSize = maxCacheSize;
+        this.maxGenerationAge = maxGenerationAge;
+    }
+
+    /**
+     * Creates a caching name resolver using the default generation age for
+     * the given cache size.
+     *
+     * @param resolver decorated name resolver
+     * @param maxCacheSize maximum size of the long term cache
+     */
+    public CachingNameResolver(NameResolver resolver, int maxCacheSize) {
+        this(resolver, maxCacheSize, maxCacheSize / DEFAULT_AGE_DIVISOR);
+    }
+
+    /**
+     * Creates a caching name resolver using the default size and
+     * generation age.
+     *
+     * @param resolver decorated name resolver
+     */
+    public CachingNameResolver(NameResolver resolver) {
+        this(resolver, DEFAULT_MAX_CACHE_SIZE);
+    }
+
+    /**
+     * Increases the age of the current cache generation. When the maximum
+     * age of a generation is reached, the following steps are taken:
+     * <ol>
+     *   <li>The union of the two cache generations is calculated</li>
+     *   <li>The union is added to the long term name cache</li>
+     *   <li>If the cache size exceeds the maximum, only the union is kept</li>
+     *   <li>A new cache generation is started</li>
+     * </ol>
+     */
+    private void increaseGenerationAge() {
+        if (++generationAge == maxGenerationAge) {
+            Iterator iterator = old.keySet().iterator();
+            while (iterator.hasNext()) {
+                if (!young.containsKey(iterator.next())) {
+                    iterator.remove();
+                }
+            }
+
+            if (!old.isEmpty()) {
+                Map newCache = new HashMap();
+                newCache.putAll(cache);
+                newCache.putAll(old);
+                if (newCache.size() > maxCacheSize) {
+                    newCache = old;
+                }
+                cache = newCache;
+            }
+
+            old = young;
+            young = new HashMap();
+            generationAge = 0;
+        }
+    }
+
+    //--------------------------------------------------------< NameResolver >
+
+    /**
+     * Returns the qualified name for the given prefixed JCR name. The name
+     * is first looked up form the generational cache and the call gets
+     * delegated to the decorated name resolver only if the cache misses.
+     *
+     * @param name prefixed JCR name
+     * @return qualified name
+     * @throws NameException if the JCR name format is invalid
+     * @throws NamespaceException if the namespace prefix can not be resolved
+     */
+    public QName getQName(String name)
+            throws NameException, NamespaceException {
+        // Check for the name in the long-time cache
+        QName qname = (QName) cache.get(name);
+        if (qname == null) {
+            synchronized (this) {
+                // Have we already seen this name during this generation?
+                qname = (QName) young.get(name);
+                if (qname == null) {
+                    // No, check the old generation or delegate the call
+                    qname = (QName) old.get(name);
+                    if (qname == null) {
+                        qname = resolver.getQName(name);
+                    }
+                    // Mark the name as resolved during this generation
+                    young.put(name, qname);
+                }
+                increaseGenerationAge();
+            }
+        }
+        return qname;
+    }
+
+
+    /**
+     * Returns the prefixed JCR name for the given qualified name.
+     * The call is delegated to the decorated name resolver.
+     *
+     * @param name qualified name
+     * @return prefixed JCR name
+     * @throws NamespaceException if the namespace URI can not be resolved
+     */
+    public String getJCRName(QName name) throws NamespaceException {
+        return resolver.getJCRName(name);
+    }
+
+}

Property changes on: jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/name/CachingNameResolver.java
___________________________________________________________________
Name: svn:eol-style
   + native

Index: jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/name/ParsingNameResolver.java
===================================================================
--- jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/name/ParsingNameResolver.java	(revision 0)
+++ jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/name/ParsingNameResolver.java	(revision 0)
@@ -0,0 +1,105 @@
+/*
+ * 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.name;
+
+import javax.jcr.NamespaceException;
+
+/**
+ * Name resolver that parsers and formats prefixed JCR names.
+ * A {@link NamespaceResolver} is used for resolving the namespace prefixes.
+ */
+public class ParsingNameResolver implements NameResolver {
+
+    /**
+     * Namespace resolver.
+     */
+    private final NamespaceResolver resolver;
+
+    /**
+     * Creates a parsing name resolver.
+     *
+     * @param resolver namespace resolver
+     */
+    public ParsingNameResolver(NamespaceResolver resolver) {
+        this.resolver = resolver;
+    }
+
+    //--------------------------------------------------------< NameResolver >
+
+    /**
+     * Parses the prefixed JCR name and returns the resolved qualified name.
+     *
+     * @param name prefixed JCR name
+     * @return qualified name
+     * @throws NameException if the JCR name format is invalid
+     * @throws NamespaceException if the namespace prefix can not be resolved
+     */
+    public QName getQName(String name)
+            throws NameException, NamespaceException {
+        // Check for invalid names
+        if (name == null || ".".equals(name) || "..".equals(name)) {
+            throw new IllegalNameException("Illegal name: " + name);
+        }
+
+        // Find the prefix colon, and check for an empty name
+        int colon = name.indexOf(':');
+        int length = name.length();
+        if (colon + 1 == length) {
+            throw new IllegalNameException("Empty local name: " + name);
+        }
+
+        // Check for invalid characters within the local part of the name
+        for (int i = colon + 1; i < length; i++) {
+            char ch = name.charAt(i);
+            if (ch == '/' || ch == ':' || ch == '[' || ch == ']'
+                || ch == '*' || ch == '\'' || ch == '"' || ch == '|'
+                || (Character.isSpaceChar(ch)
+                    && !(ch == ' ' && i > colon + 1 && i < length - 1))) {
+                throw new IllegalNameException(
+                        "Invalid character in name: " + name);
+            }
+        }
+
+        // Construct and return the QName
+        if (colon == -1) {
+            return new QName(QName.NS_DEFAULT_URI, name);
+        } else {
+            String uri = resolver.getURI(name.substring(0, colon));
+            return new QName(uri, name.substring(colon + 1));
+        }
+    }
+
+    /**
+     * Returns the prefixed JCR name for the given qualified name.
+     * If the name is in the default namespace, then the local name
+     * is returned without a prefix. Otherwise the prefix for the
+     * namespace is resolved and used to construct returned the JCR name.
+     *
+     * @param name qualified name
+     * @return prefixed JCR name
+     * @throws NamespaceException if the namespace URI can not be resolved
+     */
+    public String getJCRName(QName name) throws NamespaceException {
+        String uri = name.getNamespaceURI();
+        if (QName.NS_DEFAULT_URI.equals(uri)) {
+            return name.getLocalName();
+        } else {
+            return resolver.getPrefix(uri) + ":" + name.getLocalName();
+        }
+    }
+
+}

Property changes on: jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/name/ParsingNameResolver.java
___________________________________________________________________
Name: svn:eol-style
   + native

Index: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/NamespaceRegistryImpl.java
===================================================================
--- jackrabbit-core/src/main/java/org/apache/jackrabbit/core/NamespaceRegistryImpl.java	(revision 493464)
+++ jackrabbit-core/src/main/java/org/apache/jackrabbit/core/NamespaceRegistryImpl.java	(working copy)
@@ -21,7 +21,11 @@
 import org.apache.jackrabbit.core.fs.FileSystem;
 import org.apache.jackrabbit.core.fs.FileSystemResource;
 import org.apache.jackrabbit.name.AbstractNamespaceResolver;
+import org.apache.jackrabbit.name.CachingNameResolver;
 import org.apache.jackrabbit.name.NameCache;
+import org.apache.jackrabbit.name.NameException;
+import org.apache.jackrabbit.name.NameResolver;
+import org.apache.jackrabbit.name.ParsingNameResolver;
 import org.apache.jackrabbit.name.QName;
 import org.apache.jackrabbit.util.XMLChar;
 import org.slf4j.Logger;
@@ -83,7 +87,7 @@
 
     private int lastIndex = 0;
 
-    private final CachingNamespaceResolver resolver;
+    private NameResolver resolver;
 
     private final FileSystem nsRegStore;
 
@@ -102,7 +106,7 @@
             throws RepositoryException {
         super(true); // enable listener support
         this.nsRegStore = nsRegStore;
-        resolver = new CachingNamespaceResolver(this, 1000);
+        resolver = new CachingNameResolver(new ParsingNameResolver(this));
         load();
     }
 
@@ -383,7 +387,7 @@
         }
         return uri;
     }
-    
+
     //----------------------------------------------------< NamespaceRegistry >
     /**
      * {@inheritDoc}
@@ -524,23 +528,28 @@
      * {@inheritDoc}
      */
     public QName retrieveName(String jcrName) {
-        // just delegate to internal cache
-        return resolver.retrieveName(jcrName);
+        try {
+            return resolver.getQName(jcrName);
+        } catch (NameException e) {
+            return null;
+        } catch (NamespaceException e) {
+            return null;
+        }
     }
 
     public String retrieveName(QName name) {
-        // just delegate to internal cache
-        return resolver.retrieveName(name);
+        try {
+            return resolver.getJCRName(name);
+        } catch (NamespaceException e) {
+            return null;
+        }
     }
 
     public void cacheName(String jcrName, QName name) {
-        // just delegate to internal cache
-        resolver.cacheName(jcrName, name);
     }
 
     public void evictAllNames() {
-        // just delegate to internal cache
-        resolver.evictAllNames();
+        resolver = new CachingNameResolver(new ParsingNameResolver(this));
     }
 
     //-----------------------------------------------< NamespaceEventListener >
Index: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/CachingNamespaceResolver.java
===================================================================
--- jackrabbit-core/src/main/java/org/apache/jackrabbit/core/CachingNamespaceResolver.java	(revision 493464)
+++ jackrabbit-core/src/main/java/org/apache/jackrabbit/core/CachingNamespaceResolver.java	(working copy)
@@ -1,164 +0,0 @@
-/*
- * 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.core;
-
-import org.apache.commons.collections.map.LRUMap;
-import org.apache.jackrabbit.name.AbstractNamespaceResolver;
-import org.apache.jackrabbit.name.IllegalNameException;
-import org.apache.jackrabbit.name.NameFormat;
-import org.apache.jackrabbit.name.NamespaceListener;
-import org.apache.jackrabbit.name.NamespaceResolver;
-import org.apache.jackrabbit.name.NoPrefixDeclaredException;
-import org.apache.jackrabbit.name.QName;
-import org.apache.jackrabbit.name.NameCache;
-import org.apache.jackrabbit.name.UnknownPrefixException;
-
-import javax.jcr.NamespaceException;
-import java.util.Map;
-
-/**
- * Implements a {@link NamespaceResolver} that caches QName to resolved jcr names
- * and vice versa. The cache is invalidated when a namespace uri to prefix
- * mapping is changed.
- */
-class CachingNamespaceResolver
-        implements NamespaceResolver, NamespaceListener, NameCache {
-
-    /**
-     * The base namespace resolver.
-     */
-    private final AbstractNamespaceResolver base;
-
-    /**
-     * Maps QName instances to resolved jcr name Strings.
-     */
-    private final Map qnameToJCRName;
-
-    /**
-     * Maps resolved jcr name Strings to QName instances.
-     */
-    private final Map jcrNameToQName;
-
-    /**
-     * Creates a new <code>CachingNamespaceResolver</code>.
-     *
-     * @param base      a base namespace resolver with support for listener
-     *                  registration.
-     * @param cacheSize number of mappings this resolver may cache.
-     */
-    public CachingNamespaceResolver(AbstractNamespaceResolver base, int cacheSize) {
-        this.base = base;
-        qnameToJCRName = new LRUMap(cacheSize);
-        jcrNameToQName = new LRUMap(cacheSize);
-        this.base.addListener(this);
-    }
-
-    /**
-     * @inheritDoc
-     */
-    public String getURI(String prefix) throws NamespaceException {
-        return base.getURI(prefix);
-    }
-
-    /**
-     * @inheritDoc
-     */
-    public String getPrefix(String uri) throws NamespaceException {
-        return base.getPrefix(uri);
-    }
-
-    /**
-     * @deprecated use {@link NameFormat#parse(String, NamespaceResolver)}
-     */
-    public QName getQName(String name)
-            throws IllegalNameException, UnknownPrefixException {
-        return NameFormat.parse(name, this);
-    }
-
-    /**
-     * @deprecated use {@link NameFormat#format(QName, NamespaceResolver)}
-     */
-    public String getJCRName(QName name)
-            throws NoPrefixDeclaredException {
-        return NameFormat.format(name, this);
-    }
-
-    /**
-     * Disposes this <code>CachingNamespaceResolver</code>.
-     */
-    public void dispose() {
-        base.removeListener(this);
-    }
-
-    //------------------------------------------------------------< NameCache >
-
-    /**
-     * @inheritDoc
-     */
-    public synchronized QName retrieveName(String jcrName) {
-        return (QName) jcrNameToQName.get(jcrName);
-    }
-
-    /**
-     * @inheritDoc
-     */
-    public synchronized String retrieveName(QName name) {
-        return (String) qnameToJCRName.get(name);
-    }
-
-    /**
-     * @inheritDoc
-     */
-    public synchronized void cacheName(String jcrName, QName name) {
-        qnameToJCRName.put(name, jcrName);
-        jcrNameToQName.put(jcrName, name);
-    }
-
-    /**
-     * @inheritDoc
-     */
-    public synchronized void evictAllNames() {
-        qnameToJCRName.clear();
-        jcrNameToQName.clear();
-    }
-
-    //----------------------------------------------------< NamespaceListener >
-
-    /**
-     * @inheritDoc
-     */
-    public void namespaceAdded(String prefix, String uri) {
-        // since it is a new namespace there's no need to flush the
-        // cached mappings
-    }
-
-    /**
-     * @inheritDoc
-     * Invalidates all cached mappings.
-     */
-    public void namespaceRemapped(String oldPrefix, String newPrefix, String uri) {
-        evictAllNames();
-    }
-
-    /**
-     * @inheritDoc
-     * Invalidates all cached mappings.
-     */
-    public void namespaceRemoved(String uri) {
-        evictAllNames();
-    }
-}
