/*
* 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.util.Enumeration;
import java.util.Iterator;
import java.util.Properties;
import java.util.Vector;
import org.apache.harmony.jndi.internal.nls.Messages;
/**
* A <code>CompoundName</code> is a series of string elements, and it represents
* a name in a naming service within a single namespace. Typically these names
* have a structure which is hierarchical.
* <p>
* A <code>CompoundName</code> has a sequence of zero or more elements delimited
* by the char specified in the property "jndi.syntax.separator". This property
* is required except when the direction of the name is "flat" (see
* jndi.syntax.direction). The property "jndi.syntax.separator2" allows for the
* specification of an additional separator. A separator string will be treated
* as normal characters if it is preceded by the escape string or is within
* quotes.
* </p>
* <p>
* The property "jndi.syntax.direction" specifies the direction in which the
* name is read. Permitted values are "right_to_left", "left_to_right" and
* "flat". A flat name does not have a hierarchical structure. If this property
* is not specified then the default is "flat". If this property is specified
* with an invalid value then an <code>IllegalArgumentException</code> should be
* raised.
* </p>
* <p>
* Each element can be accessed using its position. The first element is at
* position 0. The direction of the name is important. When direction is
* "left_to_right" then the leftmost element is at position 0. Conversely when
* the direction is "right_to_left" then the rightmost element is at position 0.
* </p>
* <p>
* There are other properties which affect the syntax of a
* <code>CompoundName</code>. The following properties are all optional:
* <ul>
* <li>jndi.syntax.escape - Escape sequence,The escape sequence is used to
* escape a quote, separator or escape. When preceded itself by the escape
* sequence it is treated as ordinary characters. When it is followed by chars
* which are not quote or separator strings then it is treated as ordinary
* characters</li>
* <li>jndi.syntax.beginquote - Used as start of quoted string (Defaults to
* endquote)</li>
* <li>jndi.syntax.endquote - Used as end of quoted string (Defaults to
* beginquote)</li>
* <li>jndi.syntax.beginquote2 - Additionally used as start of quoted string
* (Defaults to endquote2)</li>
* <li>jndi.syntax.endquote2 - Additionally used as end of quoted string
* (Defaults to beginquote2)</li>
* </ul>
* <p>
* When a non-escaped quote appears at the start of an element it must be
* matched at the end. That element can then be said to be quoted. When an
* escape sequence appears within a quoted element then it is treated as normal
* characters unless it precedes an occurrence of the quote in which case it is
* assumed that the quoted element contains a quote which is escaped.
* </p>
* <p>
* If the element does not start with a quote, then any quote strings within
* that element are just normal characters.
* </p>
* <p>
* <ul>
* <li>jndi.syntax.ignorecase - If 'true' then ignore case when name elements
* are compared. If false or not set then case is important.</li>
* <li>jndi.syntax.trimblanks - If 'true' then ignore leading & trailing blanks
* when name elements are compared. If false or not set then blanks are
* important.</li>
* </ul>
* </p>
* <p>
* These 2 properties relate to names where the syntax includes
* attribute/content pairs.
* <ul>
* <li>jndi.syntax.separator.ava</li>
* <li>jndi.syntax.separator.typeval</li>
* </ul>
* For example the LDAP name, "CN=Mandy Jennings, O=Apache, C=UK". In this
* example the pair separator jndi.syntax.separator.ava is ',', and the
* character that separates pairs jndi.syntax.separator.typeval is '='. See
* RFC1779 for LDAP naming conventions.
* </p>
* <p>
* The jndi.syntax.separator.ava is not used when manipulating
* <code>CompoundName</code>. The jndi.syntax.separator is still used to
* separate elements.
* </p>
* <p>
* The <code>CompoundName</code> needs to be aware of the
* jndi.syntax.separator.typeval in case of the instance where a quoted string
* is used to provide the content of a pair.
* </p>
* <p>
* Consider the string "CN=$Mandy Jennings, O=Apache, C=UK" with
* <ul>
* <li>jndi.syntax.direction set to "right_to_left"</li>
* <li>jndi.syntax.separator set to ","</li>
* <li>jndi.syntax.separator.typeval set to "="</li>
* </ul>
* When no jndi.syntax.beginquote is set then this creates a valid
* <code>CompoundName</code> with 3 elements.
* </p>
* <p>
* If jndi.syntax.beginquote is then set to "$" the name becomes invalid as the
* content part of the pair CN=$Mandy Jennings has a mismatched quote.
* </p>
* <p>
* The string "CN=$Mandy Jennings$, O=Apache, C=UK" would be fine as the $
* quotes round Mandy Jennings now balance.
* </p>
* <p>
* A <code>CompoundName</code> may be empty. An empty <code>CompoundName</code>
* has no elements. Elements may also be empty.
* </p>
*
* <pre>
* Some Examples:
* ==============
*
* Consider the following compound name from the file system namespace:
* "home/jenningm-abc/.profile"
*
* jndi.syntax.separator is set to '/' as in the UNIX filesystem.
* This name has 3 elements:
* home jenningm-abc and .profile
* The direction should be left_to_right as in the UNIX filesystem
* The element at position 0 would be home.
*
* Consider if jndi.syntax.separator had been set to '-' then this name
* would have 2 elements:
* home/jenningm and abc/.profile
* If the direction was right_to_left then the element at position 0
* would be abc/.profile.
*
* Consider the name "<ab<cd>ef>" where jndi.syntax.beginquote is <
* and jndi.syntax.endquote is >. This will give rise to an
* InvalidNameException because a close quote was encountered before
* the end of an element. The same is also true for "<abcd>ef>".
* If the name was "ab<cd>ef" then this would be valid and there would
* be one element ab<cd>ef.
* However if the name was "<abcdef>" there would be one element abcdef.
*
* An empty
* <code>
* CompoundName
* </code>
* is the name "" and has no elements.
*
* When jndi.syntax.beginquote is set to " and beginquote2 is set to '
* the behaviour is similar to CompositeName -
* The name "\"abcd" gives an InvalidNameException as there is no closing quote.
* The name "'\"abcd'" gives one element of value "abcd.
* The name "\\abcd" gives one element of value \abcd.
*
* Assuming:
* jndi.syntax.separator is "/"
* jndi.syntax.direction is "left_to_right"
* then
* "" 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.
*
*
* Assuming the only properties set are:
* jndi.syntax.separator is "/"
* jndi.syntax.direction is "left_to_right"
* then the String
* "\"" has one element with the value "
* "\\\"" has one element with the value \"
* "\\\"'" has one element with the value \"'
*
* Assuming the only properties set are:
* jndi.syntax.separator is "/"
* jndi.syntax.direction is "left_to_right"
* jndi.syntax.beginquote is "\""
*
* then the String
* "\"" is invalid because of no closing quote
* "\\\"" has one element with the value \"
* "\\\"'" has one element with the value \"'
*
* Assuming the only properties set are:
* jndi.syntax.separator is "/"
* jndi.syntax.direction is "left_to_right"
* jndi.syntax.beginquote is "\""
* jndi.syntax.beginquote2 is "\'"
* then the String
* "\"" is invalid because of no closing quote
* "\\\"" has one element with the value \"
* "\\\"'" has one element with the value \"'
* "'\\" is invalid because of no closing quote
* </pre>
*/
public class CompoundName implements Name {
/*
* Note: For serialization purposes, the specified serialVersionUID must be
* used. This class does not have serializable fields specified. Instead the
* readObject and writeObject methods are overridden.
*/
private static final long serialVersionUID = 3513100557083972036L;
// const for property key
private static final String SEPARATOR = "jndi.syntax.separator"; //$NON-NLS-1$
private static final String SEPARATOR_AVA = "jndi.syntax.separator.ava"; //$NON-NLS-1$
private static final String SEPARATOR_TYPEVAL = "jndi.syntax.separator.typeval"; //$NON-NLS-1$
private static final String ESCAPE = "jndi.syntax.escape"; //$NON-NLS-1$
private static final String BEGIN_QUOTE = "jndi.syntax.beginquote"; //$NON-NLS-1$
private static final String END_QUOTE = "jndi.syntax.endquote"; //$NON-NLS-1$
private static final String BEGIN_QUOTE2 = "jndi.syntax.beginquote2"; //$NON-NLS-1$
private static final String END_QUOTE2 = "jndi.syntax.endquote2"; //$NON-NLS-1$
private static final String IGNORE_CASE = "jndi.syntax.ignorecase"; //$NON-NLS-1$
private static final String TRIM_BLANKS = "jndi.syntax.trimblanks"; //$NON-NLS-1$
private static final String DIRECTION = "jndi.syntax.direction"; //$NON-NLS-1$
private static final String SEPARATOR2 = "jndi.syntax.separator2"; //$NON-NLS-1$
// const for direction
private static final String LEFT_TO_RIGHT = "left_to_right"; //$NON-NLS-1$
private static final String RIGHT_TO_LEFT = "right_to_left"; //$NON-NLS-1$
private static final String FLAT = "flat"; //$NON-NLS-1$
// alphabets consts
private static final String NULL_STRING = ""; //$NON-NLS-1$
// states consts
private static final int NORMAL_STATUS = 0;
private static final int QUOTE1_STATUS = 1;
private static final int QUOTE2_STATUS = 2;
private static final int INIT_STATUS = 3;
private static final int QUOTEEND_STATUS = 4;
// properties variables
private transient String separatorString;
private transient String separatorString2;
private transient String escapeString;
private transient String endQuoteString;
private transient String endQuoteString2;
private transient String beginQuoteString;
private transient String beginQuoteString2;
private transient String sepAvaString;
private transient String sepTypeValString;
private transient String direction;
private transient boolean trimBlanks;
private transient boolean ignoreCase;
private transient boolean flat;
// elements of compound name
private transient Vector<String> elems;
// property setting
protected transient Properties mySyntax;
/*
* The specification calls for a protected variable called 'impl' which is
* of a non-API type. I believe this is an error in the spec, but to be
* complaint we have implemented this as a useless class (below).
*/
protected transient NameImpl impl = new NameImpl();
/**
* Constructs a <code>CompoundName</code> with supplied
* <code>Enumeration</code> and <code>Properties</code>
*
* @param elements
* an enumeration of name elements, cannot be null
* @param props
* the properties, cannot be null but may be empty. If empty, the
* direction defaults to flat and no other properties are
* required.
*/
protected CompoundName(Enumeration<String> elements, Properties props) {
if (null == props || null == elements) {
throw new NullPointerException();
}
init(props);
this.elems = new Vector<String>();
while (elements.hasMoreElements()) {
this.elems.add(elements.nextElement());
}
}
/**
* Constructs a <code>CompoundName</code> with supplied <code>String</code>
* and <code>Properties</code>, taking the supplied <code>s</code> and
* breaking it down into its elements.
*
* @param s
* a string containing the full compound name
* @param props
* the properties, cannot be null but may be empty for a flat
* name
* @throws InvalidNameException
* thrown if the supplied <code>String s</code> is invalid
* @throws NullPointerException
* thrown if the supplied <code>String s</code> is null
*/
public CompoundName(String s, Properties props) throws InvalidNameException {
if (null == s || null == props) {
throw new NullPointerException();
}
init(props);
parseName(s);
}
/**
* init instance variables
*/
private void init(Properties props) {
trimBlanks = false;
ignoreCase = false;
this.mySyntax = props;
String property;
// read property settings
// direction's default value is FLAT
direction = null == (property = props.getProperty(DIRECTION)) ? FLAT
: property;
// if direction value must equals to one of FLAT, LEFT_TO_RIGHT and
// RIGHT_TO_LEFT, exception threw
if (!LEFT_TO_RIGHT.equals(direction)
&& !RIGHT_TO_LEFT.equals(direction) && !FLAT.equals(direction)) {
// jndi.04=Illegal direction property value, which must be one of
// right_to_left, left_to_right or flat
throw new IllegalArgumentException(Messages.getString("jndi.04")); //$NON-NLS-1$
}
flat = FLAT.equals(direction);
separatorString = flat ? NULL_STRING : props.getProperty(SEPARATOR);
// if direction is not FLAT, separator must be set
if (null == separatorString && !flat) {
// jndi.05=jndi.syntax.separator property must be set when
// jndi.syntax.direction is not flat
throw new IllegalArgumentException(Messages.getString("jndi.05")); //$NON-NLS-1$
}
separatorString2 = (flat || null == (property = props
.getProperty(SEPARATOR2))) ? NULL_STRING : property;
// ignorecase default value is false
ignoreCase = null == (property = props.getProperty(IGNORE_CASE)) ? false
: Boolean.valueOf(property).booleanValue();
// trimblanks default value is false
trimBlanks = null == (property = props.getProperty(TRIM_BLANKS)) ? false
: Boolean.valueOf(property).booleanValue();
escapeString = null == (property = props.getProperty(ESCAPE)) ? NULL_STRING
: property;
beginQuoteString = null == (property = props.getProperty(BEGIN_QUOTE)) ? NULL_STRING
: property;
beginQuoteString2 = null == (property = props.getProperty(BEGIN_QUOTE2)) ? NULL_STRING
: property;
// end quote string default value is begin quote string
endQuoteString = null == (property = props.getProperty(END_QUOTE)) ? beginQuoteString
: property;
// begin quote string default value is end quote string
if (NULL_STRING.equals(beginQuoteString)) {
beginQuoteString = endQuoteString;
}
// end quote string2 default value is begin quote string2
endQuoteString2 = null == (property = props.getProperty(END_QUOTE2)) ? beginQuoteString2
: property;
// begin quote string2 default value is end quote string2
if (NULL_STRING.equals(beginQuoteString2)) {
beginQuoteString2 = endQuoteString2;
}
sepTypeValString = null == (property = props
.getProperty(SEPARATOR_TYPEVAL)) ? NULL_STRING : property;
sepAvaString = null == (property = props.getProperty(SEPARATOR_AVA)) ? NULL_STRING
: property;
}
/*
* parse name from string to elements
*/
private void parseName(String s) throws InvalidNameException {
this.elems = new Vector<String>();
if ("".equals(s)) { //$NON-NLS-1$
// if empty string, return empty vector
return;
}
// init variables
int status = INIT_STATUS;
StringBuilder element = new StringBuilder();
int pos = 0;
int length = s.length();
boolean hasNotNullElement = false;
boolean includeQuote = false;
// scan name
while (pos < length) {
if (startsWithFromPos(s, pos, endQuoteString)
&& status == QUOTE1_STATUS) {
status = QUOTEEND_STATUS;
pos += addBuffer(element, endQuoteString, includeQuote);
} else if (startsWithFromPos(s, pos, endQuoteString2)
&& status == QUOTE2_STATUS) {
status = QUOTEEND_STATUS;
pos += addBuffer(element, endQuoteString2, includeQuote);
} else if (startsWithFromPos(s, pos, beginQuoteString)
&& status == INIT_STATUS) {
hasNotNullElement = true;
status = QUOTE1_STATUS;
pos += addBuffer(element, beginQuoteString, includeQuote);
} else if (startsWithFromPos(s, pos, beginQuoteString2)
&& status == INIT_STATUS) {
hasNotNullElement = true;
status = QUOTE2_STATUS;
pos += addBuffer(element, beginQuoteString2, includeQuote);
} else if (startsWithFromPos(s, pos, separatorString)
&& (!flat)
&& (status == INIT_STATUS || status == QUOTEEND_STATUS || status == NORMAL_STATUS)) {
hasNotNullElement = hasNotNullElement || element.length() > 0;
addElement(element);
status = INIT_STATUS;
pos += separatorString.length();
includeQuote = false;
} else if (startsWithFromPos(s, pos, separatorString2)
&& (!flat)
&& (status == INIT_STATUS || status == QUOTEEND_STATUS || status == NORMAL_STATUS)) {
hasNotNullElement = hasNotNullElement || element.length() > 0;
addElement(element);
status = INIT_STATUS;
pos += separatorString2.length();
includeQuote = false;
} else if (startsWithFromPos(s, pos, escapeString)) {
pos += escapeString.length();
if (pos == s.length()) {
// if this escape char is last character, throw exception
// jndi.06=The {0} cannot be at end of the component
throw new InvalidNameException(Messages.getString(
"jndi.06", escapeString)); //$NON-NLS-1$
}
// if one escape char followed by a special char, append the
// special char to current element
String str = extractEscapedString(s, pos, status);
if (null == str) {
pos -= escapeString.length();
element.append(s.charAt(pos++));
} else {
pos += str.length();
element.append(str);
}
} else if (startsWithFromPos(s, pos, sepTypeValString)
&& (status == INIT_STATUS || status == NORMAL_STATUS)) {
includeQuote = true;
pos += addBuffer(element, sepTypeValString, true);
status = INIT_STATUS;
} else if (startsWithFromPos(s, pos, sepAvaString)
&& (status == INIT_STATUS || status == NORMAL_STATUS)) {
includeQuote = true;
pos += addBuffer(element, sepAvaString, true);
status = INIT_STATUS;
} else if (status == QUOTEEND_STATUS) {
// jndi.07={0}: close quote must appears at end of component in
// quoted string
throw new InvalidNameException(Messages.getString("jndi.07", s)); //$NON-NLS-1$
} else {
status = status == INIT_STATUS ? NORMAL_STATUS : status;
element.append(s.charAt(pos++));
}
}
if (QUOTE1_STATUS != status && QUOTE2_STATUS != status) {
hasNotNullElement = hasNotNullElement || element.length() > 0;
addElement(element);
} else {
// jndi.08={0}: close quote is required for quoted string
throw new InvalidNameException(Messages.getString("jndi.08", s)); //$NON-NLS-1$
}
if (!hasNotNullElement) {
elems.remove(elems.size() - 1);
}
}
/*
* add des parameter to StringBuilder if include is true
*/
private int addBuffer(StringBuilder buffer, String des, boolean include) {
if (include) {
buffer.append(des);
}
return des.length();
}
/*
* add current content of supplied string buffer as one element of this
* CompoundName and reset the string buffer to empty
*/
private void addElement(StringBuilder element) {
if (LEFT_TO_RIGHT == direction) {
elems.add(element.toString());
} else {
elems.add(0, element.toString());
}
element.setLength(0);
}
/*
* find string to be escaped, if cannot find special string(which means,
* quote, separator and escape), return null
*/
private String extractEscapedString(String s, int pos, int status) {
String result = null;
if (status == QUOTE1_STATUS
&& startsWithFromPos(s, pos, endQuoteString)) {
result = endQuoteString;
} else if (status == QUOTE2_STATUS
&& startsWithFromPos(s, pos, endQuoteString2)) {
result = endQuoteString2;
} else if (status != QUOTE1_STATUS && status != QUOTE2_STATUS) {
if (startsWithFromPos(s, pos, beginQuoteString)) {
result = beginQuoteString;
} else if (startsWithFromPos(s, pos, beginQuoteString2)) {
result = beginQuoteString2;
} else if (startsWithFromPos(s, pos, endQuoteString)) {
result = endQuoteString;
} else if (startsWithFromPos(s, pos, endQuoteString2)) {
result = endQuoteString2;
} else if (startsWithFromPos(s, pos, separatorString)) {
result = separatorString;
} else if (startsWithFromPos(s, pos, separatorString2)) {
result = separatorString2;
} else if (startsWithFromPos(s, pos, escapeString)) {
result = escapeString;
}
}
return result;
}
/*
* justify if string src start with des from position pos
*/
private boolean startsWithFromPos(String src, int pos, String des) {
if (null == src || null == des || NULL_STRING.equals(des)
|| src.length() - pos < des.length()) {
return false;
}
int length = des.length();
int i = -1;
while (++i < length && src.charAt(pos + i) == des.charAt(i)) {
// empty body
}
return i == length;
}
public Enumeration<String> getAll() {
return this.elems.elements();
}
public String get(int index) {
validateIndex(index, false);
return elems.elementAt(index);
}
/*
* validate the index, if isInclude is true, index which equals to
* this.size() is considered as valid, otherwise invalid
*/
private void validateIndex(int index, boolean isInclude) {
if (0 > index || index > elems.size()
|| (!isInclude && index == elems.size())) {
throw new ArrayIndexOutOfBoundsException();
}
}
public Name getPrefix(int index) {
validateIndex(index, true);
return new CompoundName(new Vector<String>(elems.subList(0, index))
.elements(), mySyntax);
}
public Name getSuffix(int index) {
if (index == elems.size()) {
return new CompoundName(new Vector<String>().elements(), mySyntax);
}
validateIndex(index, false);
return new CompoundName(new Vector<String>(elems.subList(index, elems
.size())).elements(), mySyntax);
}
public Name addAll(Name name) throws InvalidNameException {
return addAll(elems.size(), name);
}
public Name addAll(int index, Name name) throws InvalidNameException {
if (name == null) {
// jndi.00=name must not be null
throw new NullPointerException(Messages.getString("jndi.00")); //$NON-NLS-1$
}
if (!(name instanceof CompoundName)) {
// jndi.09={0} is not a compound name.
throw new InvalidNameException(Messages.getString(
"jndi.09", name.toString())); //$NON-NLS-1$
}
if (FLAT.equals(direction) && (this.size() + name.size() > 1)) {
// jndi.0A=A flat name can only have a single component
throw new InvalidNameException(Messages.getString("jndi.0A")); //$NON-NLS-1$
}
validateIndex(index, true);
Enumeration<String> enumeration = name.getAll();
while (enumeration.hasMoreElements()) {
elems.add(index++, enumeration.nextElement());
}
return this;
}
public Name add(String element) throws InvalidNameException {
if (element == null) {
// jndi.8C=component must not be null
throw new IllegalArgumentException(Messages.getString("jndi.8C")); //$NON-NLS-1$
}
if (FLAT.equals(direction) && (size() > 0)) {
// jndi.0A=A flat name can only have a single component
throw new InvalidNameException(Messages.getString("jndi.0A")); //$NON-NLS-1$
}
elems.add(element);
return this;
}
/**
* Insert an element within this CompoundName at the specified index.
*
* @return this <code>CompoundName</code>.
* @param element
* the String to insert
* @param index
* the index of the element to insert - must be greater than or
* equal to 0 and less than size().
* @throws ArrayIndexOutOfBoundsException
* thrown when the index is invalid.
* @throws InvalidNameException
* thrown if the insertion of the element results in this
* <code>CompoundName</code> becoming invalid.
*/
public Name add(int index, String element) throws InvalidNameException {
if (element == null) {
// jndi.8C=component must not be null
throw new IllegalArgumentException(Messages.getString("jndi.8C")); //$NON-NLS-1$
}
if (FLAT.equals(direction) && (size() > 0)) {
// jndi.0A=A flat name can only have a single component
throw new InvalidNameException(Messages.getString("jndi.0A")); //$NON-NLS-1$
}
validateIndex(index, true);
elems.add(index, element);
return this;
}
/**
* Delete an element from this <code>CompoundName</code>.
*
* @return the deleted element
* @param index
* the index of the element to delete - must be greater than or
* equal to 0 and less than size().
* @throws ArrayIndexOutOfBoundsException
* thrown when the index is invalid.
* @throws InvalidNameException
* thrown if the deletion of the element results in this
* <code>CompoundName</code> becoming invalid.
*/
public Object remove(int index) throws InvalidNameException {
validateIndex(index, false);
return elems.remove(index);
}
@Override
public Object clone() {
return new CompoundName(getAll(), mySyntax);
}
public int size() {
return elems.size();
}
public boolean isEmpty() {
return elems.isEmpty();
}
public boolean startsWith(Name name) {
if (!(name instanceof CompoundName)) {
return false;
}
return equals(name, 0, name.size());
}
public boolean endsWith(Name name) {
if (!(name instanceof CompoundName)) {
return false;
}
return equals(name, this.size() - name.size(), name.size());
}
/**
* preprocess string according to trimblank and ignorecase properties
*/
private String preProcess(String string, boolean caseInsensitive,
boolean removeBlanks) {
String result = string;
if (null != string && !"".equals(string)) { //$NON-NLS-1$
result = caseInsensitive ? result.toLowerCase() : result;
result = removeBlanks ? result.trim() : result;
}
return result;
}
/**
* Writes a serialized representation of the CompoundName. It starts with
* the properties, followed by an int which is the number of elements in the
* name, and is followed by a String for each element.
*
* @throws IOException
* if an error is encountered writing to the stream.
*/
private void writeObject(ObjectOutputStream oos) throws IOException {
oos.defaultWriteObject();
oos.writeObject(mySyntax);
int elemSize = elems.size();
oos.writeInt(elemSize);
for (int i = 0; i < elemSize; i++) {
String element = elems.elementAt(i);
oos.writeObject(element);
}
}
/**
* Recreate a CompoundName from the data in the supplied stream.
* Additionally there are 2 protected fields which are not serializable. One
* of them is of a type which is a private class and cannot therefore be
* specified or implemented and so will be excluded from our deliverable.
* The one protected field which we can spec and implement is as follows:
* protected Properties mySyntax - The properties associated with a
* CompoundName.
*
* @throws IOException
* if an error is encountered reading from the stream.
* @throws ClassNotFoundException.
*/
private void readObject(ObjectInputStream ois)
throws ClassNotFoundException, IOException {
ois.defaultReadObject();
init(((Properties) ois.readObject()));
int size = ois.readInt();
elems = new Vector<String>();
for (int i = 0; i < size; i++) {
elems.add((String) ois.readObject());
}
}
/**
* Compare this <code>CompoundName</code> with the one supplied as a param.
* <p>
* See the definition of the <code>equals()</code> method to see how the
* direction, ignorecase and trimblanks properties affect the comparison of
* a <code>CompoundName</code>. Other than that the comparison is the same
* as that for a <code>CompositeName</code>.
* </p>
*
* @return a negative number means this is less than the supplied Object
* <code>o</code>. a positive number means this is greater than the
* supplied Object <code>o</code>. zero means the two objects are
* equal.
* @param o
* the object to compare - cannot be null.
* @throws ClassCastException
* when <code>o</code> is not a compatible class that can be
* compared or if the object to compare <code>o</code> is null.
*/
public int compareTo(Object o) {
if (o == this) {
return 0;
}
if (!(o instanceof CompoundName)) {
throw new ClassCastException();
}
Iterator<String> thisIter = elems.iterator();
Iterator<String> thatIter = ((CompoundName) o).elems.iterator();
int compareResult;
String thisString, thatString;
while (thisIter.hasNext() && thatIter.hasNext()) {
thisString = preProcess(thisIter.next(), ignoreCase, trimBlanks);
thatString = preProcess(thatIter.next(), ignoreCase, trimBlanks);
compareResult = thisString.compareTo(thatString);
if (0 != compareResult) {
return compareResult;
}
}
if (thisIter.hasNext()) {
return 1;
}
if (thatIter.hasNext()) {
return -1;
}
return 0;
}
/**
* Calculate the hashcode of this <code>CompoundName</code> by summing the
* hashcodes of all of its elements.
* <p>
* If jndi.syntax.trimblanks is set to true then remove any leading and
* trailing blanks from the elements before calculating the hashcode.
* </p>
* <p>
* If jndi.syntax.ignorecase is set to true then use the lowercase version
* of the element to calculate its hashcode.
* </p>
*
* @return the hashcode of this object.
*/
@Override
public int hashCode() {
int hashCode = 0;
Enumeration<String> enumeration = elems.elements();
while (enumeration.hasMoreElements()) {
hashCode += preProcess(enumeration.nextElement(), ignoreCase,
trimBlanks).hashCode();
}
return hashCode;
}
/**
* Gets the string representation of this <code>CompoundName</code>.
* <p>
* This is generated by concatenating the elements together with the
* separator string added as the separator between each of them. It may be
* necessary to add quotes and escape string to preserve the meaning. The
* resulting string should produce an equivalent <code>CompoundName</code>
* when used to create a new instance.
* </p>
*
* @return the string representation of this <code>CompoundName</code>.
*/
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
String begin = NULL_STRING.equals(beginQuoteString) ? beginQuoteString2
: beginQuoteString;
String end = NULL_STRING.equals(endQuoteString) ? endQuoteString2
: endQuoteString;
String separator = NULL_STRING.equals(separatorString) ? separatorString2
: separatorString;
int elemSize = elems.size();
if (RIGHT_TO_LEFT.equals(direction)) {
for (int i = elemSize - 1; i >= 0; i--) {
addElement(sb, i, separator, begin, end);
}
} else {
for (int i = 0; i < elemSize; i++) {
addElement(sb, i, separator, begin, end);
}
}
if (size() * separator.length() < sb.length()) {
// if the name contains non-empty element, delete the last separator
// char, which is abundant
sb.setLength(sb.length() - separator.length());
}
return sb.toString();
}
private void addElement(StringBuilder sb, int index, String separator,
String begin, final String end) {
final String elemString = elems.get(index);
final int elemStringLength = elemString.length();
if (0 == elemStringLength) {
// if empty element, append a separator and continue
sb.append(separator);
return;
}
int pos = sb.length();
sb.append(elemString);
if (!NULL_STRING.equals(begin) && !NULL_STRING.equals(end)
&& !NULL_STRING.equals(separator)
&& (0 <= elemString.indexOf(separator))) {
// if contains separator string, quoted it
sb.insert(pos, begin);
pos += begin.length();
// if quoted, then every endquote char must be escaped
int endLenght = end.length();
for (int i = 0, j = 0; 0 <= (j = elemString.indexOf(end, i)); i = j
+ endLenght) {
sb.insert(pos + j, escapeString);
pos += escapeString.length();
}
sb.append(end);
} else {
if (startsWithFromPos(elemString, 0, beginQuoteString)
|| startsWithFromPos(elemString, 0, beginQuoteString2)) {
// if not quoted and start with begin quote string, escape it
sb.insert(pos, escapeString);
pos += escapeString.length();
}
// if not quoted, escape all separator string and all escape string
for (int i = 0; i < elemStringLength;) {
if (startsWithFromPos(elemString, i, separatorString)) {
sb.insert(pos + i, escapeString);
pos += escapeString.length();
i += separatorString.length();
} else if (startsWithFromPos(elemString, i, separatorString2)) {
sb.insert(pos + i, escapeString);
pos += escapeString.length();
i += separatorString2.length();
} else if (startsWithFromPos(elemString, i, escapeString)) {
sb.insert(pos + i, escapeString);
pos += escapeString.length();
i += escapeString.length();
} else {
i++;
}
}
}
sb.append(separator);
}
/**
* Check if the supplied object <code>o</code> is equal to this
* <code>CompoundName</code>.
* <p>
* The supplied <code>Object o</code> may be null but that will cause false
* to be returned.
* </p>
* <p>
* The supplied <code>Object o</code> may be something other than a
* <code>CompoundName</code> but that will cause false to be returned.
* </p>
* <p>
* To be equal the supplied <code>CompoundName</code> must have the same
* number of elements and each element must match the corresponding element
* of this <code>CompoundName</code>. The properties jndi.syntax.ignorecase
* and jndi.syntax.trimblanks need to be considered if they have been set.
* </p>
* <p>
* The properties associated with the <code>CompoundName</code> must be
* taken into account but do not have to match. For example
* "home/jenningm-abc/.profile" with a direction of left to right is equal
* to ".profile/jenningm-abc/home" with a direction of right to left.
* </p>
*
* @param o
* the object to be compared
* @return true if supplied object <code>o</code> is equals to this
* <code>CompoundName</code>, false otherwise
*/
@Override
public boolean equals(Object o) {
if (!(o instanceof CompoundName)) {
return false;
}
CompoundName otherName = (CompoundName) o;
int otherSize = otherName.size();
if (otherSize != this.size()) {
return false;
}
// compare every element
return equals(otherName, 0, otherSize);
}
/**
* compare this name to the supplied <code>name</code> from position
* <code>start</code> to position <code>start</code>+ <code>length</code>-1
*/
private boolean equals(Name name, int offset, int length) {
if (length > this.size()) {
return false;
}
CompoundName otherName = (CompoundName) name;
Enumeration<String> otherEnum = otherName.getAll();
String thisElement, otherElement;
for (int i = 0; i < length; i++) {
thisElement = preProcess(elems.get(i + offset), ignoreCase,
trimBlanks);
otherElement = preProcess(otherEnum.nextElement(), ignoreCase,
trimBlanks);
if (!thisElement.equals(otherElement)) {
return false;
}
}
return true;
}
}