/*
* 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());
}
}
}