/* * 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.name; import org.apache.jackrabbit.spi.NameFactory; import org.apache.jackrabbit.spi.Name; /** * <code>NameFactoryImpl</code>... */ public class NameFactoryImpl implements NameFactory { private static final NameFactory INSTANCE = new NameFactoryImpl(); /** * Cache of flyweight name instances. * * @see <a href="https://issues.apache.org/jira/browse/JCR-1663">JCR-1663</a> */ private final HashCache<Name> cache = new HashCache<Name>(); private NameFactoryImpl() {}; public static NameFactory getInstance() { return INSTANCE; } //--------------------------------------------------------< NameFactory >--- /** * @see NameFactory#create(String, String) */ public Name create(String namespaceURI, String localName) throws IllegalArgumentException { // NOTE: an empty localName and/or URI is valid (e.g. the root node name) if (namespaceURI == null) { throw new IllegalArgumentException("No namespaceURI specified"); } if (localName == null) { throw new IllegalArgumentException("No localName specified"); } return cache.get(new NameImpl(namespaceURI, localName)); } /** * @see NameFactory#create(String) */ public Name create(String nameString) throws IllegalArgumentException { if (nameString == null || "".equals(nameString)) { throw new IllegalArgumentException("No Name literal specified"); } if (nameString.charAt(0) != '{') { throw new IllegalArgumentException( "Invalid Name literal: " + nameString); } int i = nameString.indexOf('}'); if (i == -1) { throw new IllegalArgumentException( "Invalid Name literal: " + nameString); } if (i == nameString.length() - 1) { throw new IllegalArgumentException( "Invalid Name literal: " + nameString); } return (Name) cache.get(new NameImpl( nameString.substring(1, i), nameString.substring(i + 1))); } //--------------------------------------------------------< inner class >--- /** * Inner class implementing the <code>Name</code> interface. */ private static class NameImpl implements Name { /** The empty namespace uri */ private static final String EMPTY = "".intern(); /** The memorized hash code of this name. */ private transient int hash; /** The memorized string representation of this name. */ private transient String string; /** The internalized namespace URI of this name. */ private final String namespaceURI; /** The local part of this name. */ private final String localName; private NameImpl(String namespaceURI, String localName) { // internalize namespaceURI to improve performance of comparisons. if (namespaceURI.length() == 0) { // see JCR-2464 this.namespaceURI = EMPTY; } else { this.namespaceURI = namespaceURI.intern(); } // localName is not internalized in order not to risk huge perm // space for large repositories this.localName = localName; hash = 0; } //-----------------------------------------------------------< Name >--- /** * @see Name#getLocalName() */ public String getLocalName() { return localName; } /** * @see Name#getNamespaceURI() */ public String getNamespaceURI() { return namespaceURI; } //---------------------------------------------------------< Object >--- /** * Returns the string representation of this <code>Name</code> in the * following format: * <p> * <code><b>{</b>namespaceURI<b>}</b>localName</code> * * @return the string representation of this <code>Name</code>. * @see NameFactory#create(String) * @see Object#toString() */ @Override public String toString() { // Name is immutable, we can store the string representation if (string == null) { string = '{' + namespaceURI + '}' + localName; } return string; } /** * Compares two names for equality. Returns <code>true</code> * if the given object is a <code>Name</code> and has the same namespace * URI and local part as this <code>Name</code>. * * @param obj the object to compare. * @return <code>true</code> if the object is equal to this <code>Name</code>, * <code>false</code> otherwise. * @see Object#equals(Object) */ @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (obj instanceof NameImpl) { NameImpl other = (NameImpl) obj; // we can use == operator for namespaceURI since it is internalized return namespaceURI == other.namespaceURI && localName.equals(other.localName); } // some other Name implementation if (obj instanceof Name) { Name other = (Name) obj; return namespaceURI.equals(other.getNamespaceURI()) && localName.equals(other.getLocalName()); } return false; } /** * Returns the hash code of this name. The hash code is * computed from the namespace URI and local part of the * name and memorized for better performance. * * @return hash code * @see Object#hashCode() */ @Override public int hashCode() { // Name is immutable, we can store the computed hash code value int h = hash; if (h == 0) { h = 17; h = 37 * h + namespaceURI.hashCode(); h = 37 * h + localName.hashCode(); hash = h; } return h; } //------------------------------------------------------< Cloneable >--- /** * Creates a clone of this <code>Name</code>. * Overridden in order to make <code>clone()</code> public. * * @return a clone of this instance * @throws CloneNotSupportedException never thrown * @see Object#clone() */ @Override public Object clone() throws CloneNotSupportedException { // Name is immutable, no special handling required return super.clone(); } //-----------------------------------------------------< Comparable >--- /** * Compares two <code>Name</code>s. * * @param o the object to compare. * @return comparison result * @throws ClassCastException if the given object is not a <code>Name</code>. * @see Comparable#compareTo(Object) */ public int compareTo(Object o) { if (this == o) { return 0; } Name other = (Name) o; if (namespaceURI.equals(other.getNamespaceURI())) { return localName.compareTo(other.getLocalName()); } else { return namespaceURI.compareTo(other.getNamespaceURI()); } } //---------------------------------------------------< Serializable >--- /** * Creates a new <code>Name</code> instance using the proper constructor * during deserialization in order to make sure that internalized strings * are used where appropriate. */ private Object readResolve() { return new NameImpl(namespaceURI, localName); } } }