/* * 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 javax.naming; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.OptionalDataException; import java.util.Enumeration; import java.util.Iterator; import java.util.List; import java.util.Vector; import org.apache.harmony.jndi.internal.nls.Messages; /** * A <code>CompositeName</code> represents a name in a naming service which * spans multiple namespaces. For example the name "www.eclipse.org/index.html" * spans the DNS and file system namespaces. * <p> * A <code>CompositeName</code> is a series of string elements. A composite * name has a sequence of zero or more elements delimited by the '/' char. Each * element can be accessed using its position. The first element is at position * 0. * </p> * <p> * A <code>CompositeName</code> may be empty. An empty composite name has no * elements. Elements may also be empty. * </p> * <p> * <code>CompositeName</code>s are read from left to right unlike * <code>CompoundName</code>s which may have their direction of ordering * specified by properties. * </p> * <p> * Special characters are as follows: * </p> * <ul> * <li>The separator is /</li> * <li>The escape character is \</li> * <li>Quotes can be used - both single quotes and double quotes are allowed. * This allows you to quote strings which contain chars such as / which are part * of a <code>CompositeName</code> element to avoid them being read as a * separator.</li> * </ul> * <p> * See the examples for further clarification. * </p> * <p> * Some Examples:<br /> * ============== * </p> * <p> * The composite name "www.eclipse.org/index.html" has 2 elements. * "www.eclipse.org" is a name from the DNS namespace. "index.html" is a name * from the file system namespace. * </p> * <p> * Another example of a composite name is: "www.eclipse.org/org/index.html". * This name has 3 elements "www.eclipse.org", "org" and "index.html". * www.eclipse.org is a name from the DNS namespace. The last 2 elements are * each from the file system namespace. * </p> * <p> * Some more examples to clarify empty names and elements: * </p> * <p> * An empty CompositeName is the name "" and has no elements. * </p> * <p> * A CompositeName with just one empty element is the name "/". * </p> * <p> * The name "/org/" has 3 elements. The first and last are empty. * </p> * <p> * The name "/a" has 2 elements. The first element is empty and the second * element is "a". * </p> * <p> * The name "a//a" has 3 elements. The middle element is empty and the first & * third elements are both "a". * </p> * <p> * The name "a/'b/a" is invalid as there is no closing quote for the ' * character. * </p> * <p> * The name "a/'a/b/b" is invalid as there is no closing quote for the ' * character. * </p> * <p> * The name "a/\"b/a" is interpreted as a/"b/a and is invalid as there is no * closing quote for the embedded escaped " character. * </p> * <p> * The name "a/'b/c'/a" has 3 elements. The middle element is b/c. * <p> * The name "a/a'a/b'/b" has 4 elements: Element 0 is "a". Element 1 is "a'a". * Element 2 is "b'". Element 3 is "b". * </p> * <p> * Interestingly the name "a/a'a/b/b" is valid and has 4 elements. This is * because the single quote char ' is not a leading quote and is embedded in an * element so is treated as a character. Element 0 is "a". Element 1 is "a'a". * Element 2 is "b". Element 3 is "b". * </p> * <p> * The name "\"abcd" gives an <code>InvalidNameException</code> as there is no * closing quote. * </p> * <p> * The name "'\"abcd'" gives one element of value "abcd. * </p> * <p> * The name "\\abcd" gives one element of value \abcd. * </p> * <p> "" is empty. It has no elements. "/" has one empty element. "//" has 2 * empty elements. "/a/" has 3 elements the middle one is set to a. "///" has 3 * empty elements. "//a/" has 4 elements, the last but one is set to a. * </p> */ public class CompositeName implements Name { private static final long serialVersionUID = 1667768148915813118L; // status used by parse() private static final int OUT_OF_QUOTE = 0; private static final int IN_SINGLE_QUOTE = 1; private static final int IN_DOUBLE_QUOTE = 2; private static final int QUOTE_ENDED = 3; /* a list holding elements */ private transient Vector<String> elems; /** * Private copy constructor. * * @param elements * a list of name elements */ private CompositeName(List<String> elements) { super(); elems = new Vector<String>(elements); } /** * Construct a composite name with given elements. * * @param elements * an enumeration of name elements */ protected CompositeName(Enumeration<String> elements) { super(); elems = new Vector<String>(); while (elements.hasMoreElements()) { elems.add(elements.nextElement()); } } /** * Default constructor, creates an empty name with zero elements. */ public CompositeName() { super(); elems = new Vector<String>(); } /** * This constructor takes the supplied name and breaks it down into its * elements. * * @param name * a string containing the full composite name * @throws InvalidNameException * if the supplied name is invalid */ public CompositeName(String name) throws InvalidNameException { super(); elems = parseName(name); } /** * Parse string name elements. Delimiter is "/". Escape is "\" and both * single quote and double quote are supported. */ private static Vector<String> parseName(String name) throws InvalidNameException { Vector<String> result = new Vector<String>(); // special case: all '/', means same number of empty elements if (isAllSlash(name)) { int length = name.length(); for (int index = 0; index < length; index++) { result.add(""); //$NON-NLS-1$ } return result; } // general simple case, without escape and quote if (name.indexOf('"') < 0 && name.indexOf('\'') < 0 && name.indexOf('\\') < 0) { int i = 0, j = 0; while ((j = name.indexOf('/', i)) >= 0) { result.add(name.substring(i, j)); i = j + 1; } result.add(name.substring(i)); return result; } // general complicated case, consider escape and quote char curC, nextC; char chars[] = name.toCharArray(); StringBuilder buf = new StringBuilder(); int status = OUT_OF_QUOTE; for (int index = 0; index < chars.length; index++) { curC = chars[index]; // check end quote violation if (status == QUOTE_ENDED) { if (curC == '/') { result.add(buf.toString()); buf.setLength(0); status = OUT_OF_QUOTE; continue; } // jndi.0C=End quote is not at the end of element throw new InvalidNameException(Messages.getString("jndi.0C")); //$NON-NLS-1$ } if (curC == '\\') { // escape char try { nextC = chars[++index]; if (nextC == '\\' || nextC == '\'' || nextC == '"' || nextC == '/') { buf.append(nextC); } else { buf.append(curC); buf.append(nextC); } } catch (ArrayIndexOutOfBoundsException e) { // jndi.0D=Escape cannot be at the end of element throw new InvalidNameException(Messages .getString("jndi.0D")); //$NON-NLS-1$ } continue; } if (curC != '/' && curC != '"' && curC != '\'') { // normal char buf.append(curC); continue; } // special char if (status == OUT_OF_QUOTE && curC == '/') { result.add(buf.toString()); buf.setLength(0); } else if (status == OUT_OF_QUOTE && curC == '\'' && buf.length() == 0) { status = IN_SINGLE_QUOTE; } else if (status == OUT_OF_QUOTE && curC == '"' && buf.length() == 0) { status = IN_DOUBLE_QUOTE; } else if (status == IN_SINGLE_QUOTE && curC == '\'') { status = QUOTE_ENDED; } else if (status == IN_DOUBLE_QUOTE && curC == '"') { status = QUOTE_ENDED; } else { buf.append(curC); } } result.add(buf.toString()); // check end status if (status != OUT_OF_QUOTE && status != QUOTE_ENDED) { // jndi.0E=Wrong quote usage. throw new InvalidNameException(Messages.getString("jndi.0E")); //$NON-NLS-1$ } return result; } private static boolean isAllSlash(String name) { char[] nameChars = name.toCharArray(); for (int index = 0; index < nameChars.length; index++) { if (nameChars[index] != '/') { return false; } } return true; } /* * Format name elements to its string representation. */ private static String formatName(Vector<String> elems) { StringBuilder sb = new StringBuilder(); int elemSize = elems.size(); if (isAllEmptyElements(elems)) { // special case: all empty elements for (int index = 0; index < elemSize; index++) { sb.append('/'); } return sb.toString(); } // general case String elem = null; for (int index = 0; index < elemSize; index++) { elem = elems.get(index); if (index > 0) { sb.append('/'); //$NON-NLS-1$ } // Add quotation while elem contains separator char if (elem.indexOf('/') != -1) { sb.append('\"'); sb.append(elem); sb.append('\"'); } else { sb.append(elem); } } return sb.toString(); } private static boolean isAllEmptyElements(Vector<String> elems) { int elemSize = elems.size(); for (int index = 0; index < elemSize; index++) { if (elems.get(index).length() > 0) { return false; } } return true; } public Enumeration<String> getAll() { return elems.elements(); } public String get(int index) { return elems.get(index); } public Name getPrefix(int index) { if (index < 0 || index > elems.size()) { throw new ArrayIndexOutOfBoundsException(); } return new CompositeName(elems.subList(0, index)); } public Name getSuffix(int index) { if (index < 0 || index > elems.size()) { throw new ArrayIndexOutOfBoundsException(); } return new CompositeName(elems.subList(index, elems.size())); } public Name addAll(Name name) throws InvalidNameException { if (null == name) { throw new NullPointerException(); } if (!(name instanceof CompositeName)) { // jndi.0F=Must be a CompositeName throw new InvalidNameException(Messages.getString("jndi.0F")); //$NON-NLS-1$ } Enumeration<String> enumeration = name.getAll(); while (enumeration.hasMoreElements()) { elems.add(enumeration.nextElement()); } return this; } public Name addAll(int index, Name name) throws InvalidNameException { if (null == name) { throw new NullPointerException(); } if (!(name instanceof CompositeName)) { // jndi.0F=Must be a CompositeName throw new InvalidNameException(Messages.getString("jndi.0F")); //$NON-NLS-1$ } if (index < 0 || index > elems.size()) { throw new ArrayIndexOutOfBoundsException(); } Enumeration<String> enumeration = name.getAll(); while (enumeration.hasMoreElements()) { elems.add(index++, enumeration.nextElement()); } return this; } public Name add(String element) throws InvalidNameException { elems.add(element); return this; } public Name add(int index, String element) throws InvalidNameException { if (index < 0 || index > elems.size()) { throw new ArrayIndexOutOfBoundsException(); } elems.add(index, element); return this; } public Object remove(int index) throws InvalidNameException { if (index < 0 || index >= elems.size()) { throw new ArrayIndexOutOfBoundsException(); } return elems.remove(index); } public int size() { return elems.size(); } public boolean isEmpty() { return elems.isEmpty(); } public boolean startsWith(Name name) { if (!(name instanceof CompositeName)) { return false; } // check size if (name.size() > elems.size()) { return false; } // compare 1 by 1 Enumeration<String> enumeration = name.getAll(); for (int index = 0; enumeration.hasMoreElements(); index++) { if (!elems.get(index).equals(enumeration.nextElement())) { return false; } } return true; } public boolean endsWith(Name name) { if (!(name instanceof CompositeName)) { return false; } // check size if (name.size() > elems.size()) { return false; } // compare 1 by 1 Enumeration<String> enumeration = name.getAll(); for (int index = elems.size() - name.size(); enumeration .hasMoreElements(); index++) { if (!elems.get(index).equals(enumeration.nextElement())) { return false; } } return true; } /** * Compare this <code>Name</code> with the one supplied as a parameter. * The elements of the names are compared in the same way as strings are * compared to determine whether this <code>CompositeName</code> is less * than, greater than or equal to the supplied object <code>o</code>. * * @param o * the object to compare, cannot be null * @return a negative number means this is less than the supplied object; a * positive number means this is greater than the supplied object; * zero means this CompositeName equals the object as specified in * the description for the equals method of * <code>CompositeName</code>. * @throws ClassCastException * when <code>o</code> is not a <code>CompositeName</code>. */ public int compareTo(Object o) { if (o == this) { return 0; } if (!(o instanceof CompositeName)) { throw new ClassCastException(); } Iterator<String> thisIter = elems.iterator(); Iterator<String> thatIter = ((CompositeName) o).elems.iterator(); int compareResult; while (thisIter.hasNext() && thatIter.hasNext()) { compareResult = thisIter.next().compareTo(thatIter.next()); if (0 != compareResult) { return compareResult; } } if (thisIter.hasNext()) { return 1; } if (thatIter.hasNext()) { return -1; } return 0; } /** * Create a copy of this composite name, a complete (deep) copy of the * object. * * @return a complete (deep) copy of the object. */ @Override public Object clone() { return new CompositeName(elems); } /** * Returns the string representation of this <code>CompositeName</code>. * This is generated by concatenating the elements together with the '/' * char added as the separator between each of them. It may be necessary to * add quotes and escape chars to preserve the meaning. The resulting string * should produce an equivalent <code>CompositeName</code> when used to * create a new instance. * * @return the string representation of this composite name. */ @Override public String toString() { return formatName(elems); } /** * Check if this <code>CompositeName</code> is equal to the supplied * object. * * @param o * the <code>CompositeName</code> to compare - can be null but * then returns false. * @return true if they have the same number of elements all of which are * equal. false if they are not equal. */ @Override public boolean equals(Object o) { // check type if (!(o instanceof CompositeName)) { return false; } CompositeName that = (CompositeName) o; if (this.size() != that.size()) { return false; } Iterator<String> thisIter = elems.iterator(); Iterator<String> thatIter = that.elems.iterator(); while (thisIter.hasNext() && thatIter.hasNext()) { if (!thisIter.next().equals(thatIter.next())) { return false; } } return true; } /** * Calculate the hashcode of this <code>CompositeName</code> by summing * the hash codes of all of its elements. * * @return the hashcode of this object. */ @Override public int hashCode() { int hashCode = 0; for (int index = 0; index < elems.size(); index++) { hashCode += elems.get(index).hashCode(); } return hashCode; } /** * Writes a serialized representation of the CompositeName. It starts with * an int which is the number of elements in the name, and is followed by a * String for each element. * * @param oos * @throws IOException * if an error is encountered writing to the stream. */ private void writeObject(ObjectOutputStream oos) throws IOException { oos.defaultWriteObject(); oos.writeInt(elems.size()); for (Object element : elems) { oos.writeObject(element); } } /** * Recreate a CompositeName from the data in the supplied stream. * * @param ois * @throws IOException * if an error is encountered reading from the stream. * @throws ClassNotFoundException. */ private void readObject(ObjectInputStream ois) throws OptionalDataException, ClassNotFoundException, IOException { ois.defaultReadObject(); int size = ois.readInt(); elems = new Vector<String>(); for (int i = 0; i < size; i++) { elems.add((String) ois.readObject()); } } }