/* * Copyright (c) 1999, 2004, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package com.sun.jndi.ldap; import java.util.Enumeration;import java.util.Vector; import javax.naming.*;import javax.naming.directory.Attributes;import javax.naming.directory.Attribute;import javax.naming.directory.BasicAttributes; /** * <code>LdapName</code> implements compound names for LDAP v3 as * specified by RFC 2253. *<p> * RFC 2253 has a few ambiguities and outright inconsistencies. These * are resolved as follows: * <ul> * <li> RFC 2253 leaves the term "whitespace" undefined. The * definition of "optional-space" given in RFC 1779 is used in * its place: either a space character or a carriage return ("\r"). * <li> Whitespace is allowed on either side of ',', ';', '=', and '+'. * Such whitespace is accepted but not generated by this code, * and is ignored when comparing names. * <li> AttributeValue strings containing '=' or non-leading '#' * characters (unescaped) are accepted. * </ul> *<p> * String names passed to <code>LdapName</code> or returned by it * use the full 16-bit Unicode character set. They may also contain * characters encoded into UTF-8 with each octet represented by a * three-character substring such as "\\B4". * They may not, however, contain characters encoded into UTF-8 with * each octet represented by a single character in the string: the * meaning would be ambiguous. *<p> * <code>LdapName</code> will properly parse all valid names, but * does not attempt to detect all possible violations when parsing * invalid names. It's "generous". *<p> * When names are tested for equality, attribute types and binary * values are case-insensitive, and string values are by default * case-insensitive. * String values with different but equivalent usage of quoting, * escaping, or UTF8-hex-encoding are considered equal. The order of * components in multi-valued RDNs (such as "ou=Sales+cn=Bob") is not * significant. * * @author Scott Seligman */ public final class LdapName implements Name { private transient String unparsed; // if non-null, the DN in unparsed form private transient Vector rdns; // parsed name components private transient boolean valuesCaseSensitive = false; /** * Constructs an LDAP name from the given DN. * * @param name An LDAP DN. To JNDI, a compound name. * * @throws InvalidNameException if a syntax violation is detected. */ public LdapName(String name) throws InvalidNameException { unparsed = name; parse(); } /* * Constructs an LDAP name given its parsed components and, optionally * (if "name" is not null), the unparsed DN. */ private LdapName(String name, Vector rdns) { unparsed = name; this.rdns = (Vector)rdns.clone(); } /* * Constructs an LDAP name given its parsed components (the elements * of "rdns" in the range [beg,end)) and, optionally * (if "name" is not null), the unparsed DN. */ private LdapName(String name, Vector rdns, int beg, int end) { unparsed = name; this.rdns = new Vector(); for (int i = beg; i < end; i++) { this.rdns.addElement(rdns.elementAt(i)); } } public Object clone() { return new LdapName(unparsed, rdns); } public String toString() { if (unparsed != null) { return unparsed; } StringBuffer buf = new StringBuffer(); for (int i = rdns.size() - 1; i >= 0; i--) { if (i < rdns.size() - 1) { buf.append(','); } Rdn rdn = (Rdn)rdns.elementAt(i); buf.append(rdn); } unparsed = new String(buf); return unparsed; } public boolean equals(Object obj) { return ((obj instanceof LdapName) && (compareTo(obj) == 0)); } public int compareTo(Object obj) { LdapName that = (LdapName)obj; if ((obj == this) || // check possible shortcuts (unparsed != null && unparsed.equals(that.unparsed))) { return 0; } // Compare RDNs one by one, lexicographically. int minSize = Math.min(rdns.size(), that.rdns.size()); for (int i = 0 ; i < minSize; i++) { // Compare a single pair of RDNs. Rdn rdn1 = (Rdn)rdns.elementAt(i); Rdn rdn2 = (Rdn)that.rdns.elementAt(i); int diff = rdn1.compareTo(rdn2); if (diff != 0) { return diff; } } return (rdns.size() - that.rdns.size()); // longer DN wins } public int hashCode() { // Sum up the hash codes of the components. int hash = 0; // For each RDN... for (int i = 0; i < rdns.size(); i++) { Rdn rdn = (Rdn)rdns.elementAt(i); hash += rdn.hashCode(); } return hash; } public int size() { return rdns.size(); } public boolean isEmpty() { return rdns.isEmpty(); } public Enumeration getAll() { final Enumeration enum_ = rdns.elements(); return new Enumeration () { public boolean hasMoreElements() { return enum_.hasMoreElements(); } public Object nextElement() { return enum_.nextElement().toString(); } }; } public String get(int pos) { return rdns.elementAt(pos).toString(); } public Name getPrefix(int pos) { return new LdapName(null, rdns, 0, pos); } public Name getSuffix(int pos) { return new LdapName(null, rdns, pos, rdns.size()); } public boolean startsWith(Name n) { int len1 = rdns.size(); int len2 = n.size(); return (len1 >= len2 && matches(0, len2, n)); } public boolean endsWith(Name n) { int len1 = rdns.size(); int len2 = n.size(); return (len1 >= len2 && matches(len1 - len2, len1, n)); } /** * Controls whether string-values are treated as case-sensitive * when the string values within names are compared. The default * behavior is case-insensitive comparison. */ public void setValuesCaseSensitive(boolean caseSensitive) { toString(); rdns = null; // clear any cached information try { parse(); } catch (InvalidNameException e) { // shouldn't happen throw new IllegalStateException("Cannot parse name: " + unparsed); } valuesCaseSensitive = caseSensitive; } /* * Helper method for startsWith() and endsWith(). * Returns true if components [beg,end) match the components of "n". * If "n" is not an LdapName, each of its components is parsed as * the string form of an RDN. * The following must hold: end - beg == n.size(). */ private boolean matches(int beg, int end, Name n) { for (int i = beg; i < end; i++) { Rdn rdn; if (n instanceof LdapName) { LdapName ln = (LdapName)n; rdn = (Rdn)ln.rdns.elementAt(i - beg); } else { String rdnString = n.get(i - beg); try { rdn = (new DnParser(rdnString, valuesCaseSensitive)).getRdn(); } catch (InvalidNameException e) { return false; } } if (!rdn.equals(rdns.elementAt(i))) { return false; } } return true; } public Name addAll(Name suffix) throws InvalidNameException { return addAll(size(), suffix); } /* * If "suffix" is not an LdapName, each of its components is parsed as * the string form of an RDN. */ public Name addAll(int pos, Name suffix) throws InvalidNameException { if (suffix instanceof LdapName) { LdapName s = (LdapName)suffix; for (int i = 0; i < s.rdns.size(); i++) { rdns.insertElementAt(s.rdns.elementAt(i), pos++); } } else { Enumeration comps = suffix.getAll(); while (comps.hasMoreElements()) { DnParser p = new DnParser((String)comps.nextElement(), valuesCaseSensitive); rdns.insertElementAt(p.getRdn(), pos++); } } unparsed = null; // no longer valid return this; } public Name add(String comp) throws InvalidNameException { return add(size(), comp); } public Name add(int pos, String comp) throws InvalidNameException { Rdn rdn = (new DnParser(comp, valuesCaseSensitive)).getRdn(); rdns.insertElementAt(rdn, pos); unparsed = null; // no longer valid return this; } public Object remove(int pos) throws InvalidNameException { String comp = get(pos); rdns.removeElementAt(pos); unparsed = null; // no longer valid return comp; } private void parse() throws InvalidNameException { rdns = (new DnParser(unparsed, valuesCaseSensitive)).getDn(); } /* * Best guess as to what RFC 2253 means by "whitespace". */ private static boolean isWhitespace(char c) { return (c == ' ' || c == '\r'); } /** * Given the value of an attribute, returns a string suitable * for inclusion in a DN. If the value is a string, this is * accomplished by using backslash (\) to escape the following * characters: *<ul> *<li>leading and trailing whitespace *<li><pre>, = + < > # ; " \</pre> *</ul> * If the value is a byte array, it is converted to hex * notation (such as "#CEB1DF80"). */ public static String escapeAttributeValue(Object val) { return TypeAndValue.escapeValue(val); } /** * Given an attribute value formated according to RFC 2253, * returns the unformated value. Returns a string value as * a string, and a binary value as a byte array. */ public static Object unescapeAttributeValue(String val) { return TypeAndValue.unescapeValue(val); } /** * Serializes only the unparsed DN, for compactness and to avoid * any implementation dependency. * * @serialdata The DN string and a boolean indicating whether * the values are case sensitive. */ private void writeObject(java.io.ObjectOutputStream s) throws java.io.IOException { s.writeObject(toString()); s.writeBoolean(valuesCaseSensitive); } private void readObject(java.io.ObjectInputStream s) throws java.io.IOException, ClassNotFoundException { unparsed = (String)s.readObject(); valuesCaseSensitive = s.readBoolean(); try { parse(); } catch (InvalidNameException e) { // shouldn't happen throw new java.io.StreamCorruptedException( "Invalid name: " + unparsed); } } static final long serialVersionUID = -1595520034788997356L; /* * DnParser implements a recursive descent parser for a single DN. */ static class DnParser { private final String name; // DN being parsed private final char[] chars; // characters in LDAP name being parsed private final int len; // length of "chars" private int cur = 0; // index of first unconsumed char in "chars" private boolean valuesCaseSensitive; /* * Given an LDAP DN in string form, returns a parser for it. */ DnParser(String name, boolean valuesCaseSensitive) throws InvalidNameException { this.name = name; len = name.length(); chars = name.toCharArray(); this.valuesCaseSensitive = valuesCaseSensitive; } /* * Parses the DN, returning a Vector of its RDNs. */ Vector getDn() throws InvalidNameException { cur = 0; Vector rdns = new Vector(len / 3 + 10); // leave room for growth if (len == 0) { return rdns; } rdns.addElement(parseRdn()); while (cur < len) { if (chars[cur] == ',' || chars[cur] == ';') { ++cur; rdns.insertElementAt(parseRdn(), 0); } else { throw new InvalidNameException("Invalid name: " + name); } } return rdns; } /* * Parses the DN, if it is known to contain a single RDN. */ Rdn getRdn() throws InvalidNameException { Rdn rdn = parseRdn(); if (cur < len) { throw new InvalidNameException("Invalid RDN: " + name); } return rdn; } /* * Parses the next RDN and returns it. Throws an exception if * none is found. Leading and trailing whitespace is consumed. */ private Rdn parseRdn() throws InvalidNameException { Rdn rdn = new Rdn(); while (cur < len) { consumeWhitespace(); String attrType = parseAttrType(); consumeWhitespace(); if (cur >= len || chars[cur] != '=') { throw new InvalidNameException("Invalid name: " + name); } ++cur; // consume '=' consumeWhitespace(); String value = parseAttrValue(); consumeWhitespace(); rdn.add(new TypeAndValue(attrType, value, valuesCaseSensitive)); if (cur >= len || chars[cur] != '+') { break; } ++cur; // consume '+' } return rdn; } /* * Returns the attribute type that begins at the next unconsumed * char. No leading whitespace is expected. * This routine is more generous than RFC 2253. It accepts * attribute types composed of any nonempty combination of Unicode * letters, Unicode digits, '.', '-', and internal space characters. */ private String parseAttrType() throws InvalidNameException { final int beg = cur; while (cur < len) { char c = chars[cur]; if (Character.isLetterOrDigit(c) || c == '.' || c == '-' || c == ' ') { ++cur; } else { break; } } // Back out any trailing spaces. while ((cur > beg) && (chars[cur - 1] == ' ')) { --cur; } if (beg == cur) { throw new InvalidNameException("Invalid name: " + name); } return new String(chars, beg, cur - beg); } /*Lines 1–500 of 1017. Load more
/* * Copyright (c) 1999, 2004, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package com.sun.jndi.ldap; import java.util.Enumeration; import java.util.Vector; import javax.naming.*; import javax.naming.directory.Attributes; import javax.naming.directory.Attribute; import javax.naming.directory.BasicAttributes; /** * <code>LdapName</code> implements compound names for LDAP v3 as * specified by RFC 2253. *<p> * RFC 2253 has a few ambiguities and outright inconsistencies. These * are resolved as follows: * <ul> * <li> RFC 2253 leaves the term "whitespace" undefined. The * definition of "optional-space" given in RFC 1779 is used in * its place: either a space character or a carriage return ("\r"). * <li> Whitespace is allowed on either side of ',', ';', '=', and '+'. * Such whitespace is accepted but not generated by this code, * and is ignored when comparing names. * <li> AttributeValue strings containing '=' or non-leading '#' * characters (unescaped) are accepted. * </ul> *<p> * String names passed to <code>LdapName</code> or returned by it * use the full 16-bit Unicode character set. They may also contain * characters encoded into UTF-8 with each octet represented by a * three-character substring such as "\\B4". * They may not, however, contain characters encoded into UTF-8 with * each octet represented by a single character in the string: the * meaning would be ambiguous. *<p> * <code>LdapName</code> will properly parse all valid names, but * does not attempt to detect all possible violations when parsing * invalid names. It's "generous". *<p> * When names are tested for equality, attribute types and binary * values are case-insensitive, and string values are by default * case-insensitive. * String values with different but equivalent usage of quoting, * escaping, or UTF8-hex-encoding are considered equal. The order of * components in multi-valued RDNs (such as "ou=Sales+cn=Bob") is not * significant. * * @author Scott Seligman */ public final class LdapName implements Name { private transient String unparsed; // if non-null, the DN in unparsed form private transient Vector rdns; // parsed name components private transient boolean valuesCaseSensitive = false; /** * Constructs an LDAP name from the given DN. * * @param name An LDAP DN. To JNDI, a compound name. * * @throws InvalidNameException if a syntax violation is detected. */ public LdapName(String name) throws InvalidNameException { unparsed = name; parse(); } /* * Constructs an LDAP name given its parsed components and, optionally * (if "name" is not null), the unparsed DN. */ private LdapName(String name, Vector rdns) { unparsed = name; this.rdns = (Vector)rdns.clone(); } /* * Constructs an LDAP name given its parsed components (the elements * of "rdns" in the range [beg,end)) and, optionally * (if "name" is not null), the unparsed DN. */ private LdapName(String name, Vector rdns, int beg, int end) { unparsed = name; this.rdns = new Vector(); for (int i = beg; i < end; i++) { this.rdns.addElement(rdns.elementAt(i)); } } public Object clone() { return new LdapName(unparsed, rdns); } public String toString() { if (unparsed != null) { return unparsed; } StringBuffer buf = new StringBuffer(); for (int i = rdns.size() - 1; i >= 0; i--) { if (i < rdns.size() - 1) { buf.append(','); } Rdn rdn = (Rdn)rdns.elementAt(i); buf.append(rdn); } unparsed = new String(buf); return unparsed; } public boolean equals(Object obj) { return ((obj instanceof LdapName) && (compareTo(obj) == 0)); } public int compareTo(Object obj) { LdapName that = (LdapName)obj; if ((obj == this) || // check possible shortcuts (unparsed != null && unparsed.equals(that.unparsed))) { return 0; } // Compare RDNs one by one, lexicographically. int minSize = Math.min(rdns.size(), that.rdns.size()); for (int i = 0 ; i < minSize; i++) { // Compare a single pair of RDNs. Rdn rdn1 = (Rdn)rdns.elementAt(i); Rdn rdn2 = (Rdn)that.rdns.elementAt(i); int diff = rdn1.compareTo(rdn2); if (diff != 0) { return diff; } } return (rdns.size() - that.rdns.size()); // longer DN wins } public int hashCode() { // Sum up the hash codes of the components. int hash = 0; // For each RDN... for (int i = 0; i < rdns.size(); i++) { Rdn rdn = (Rdn)rdns.elementAt(i); hash += rdn.hashCode(); } return hash; } public int size() { return rdns.size(); } public boolean isEmpty() { return rdns.isEmpty(); } public Enumeration getAll() { final Enumeration enum_ = rdns.elements(); return new Enumeration () { public boolean hasMoreElements() { return enum_.hasMoreElements(); } public Object nextElement() { return enum_.nextElement().toString(); } }; } public String get(int pos) { return rdns.elementAt(pos).toString(); } public Name getPrefix(int pos) { return new LdapName(null, rdns, 0, pos); } public Name getSuffix(int pos) { return new LdapName(null, rdns, pos, rdns.size()); } public boolean startsWith(Name n) { int len1 = rdns.size(); int len2 = n.size(); return (len1 >= len2 && matches(0, len2, n)); } public boolean endsWith(Name n) { int len1 = rdns.size(); int len2 = n.size(); return (len1 >= len2 && matches(len1 - len2, len1, n)); } /** * Controls whether string-values are treated as case-sensitive * when the string values within names are compared. The default * behavior is case-insensitive comparison. */ public void setValuesCaseSensitive(boolean caseSensitive) { toString(); rdns = null; // clear any cached information try { parse(); } catch (InvalidNameException e) { // shouldn't happen throw new IllegalStateException("Cannot parse name: " + unparsed); } valuesCaseSensitive = caseSensitive; } /* * Helper method for startsWith() and endsWith(). * Returns true if components [beg,end) match the components of "n". * If "n" is not an LdapName, each of its components is parsed as * the string form of an RDN. * The following must hold: end - beg == n.size(). */ private boolean matches(int beg, int end, Name n) { for (int i = beg; i < end; i++) { Rdn rdn; if (n instanceof LdapName) { LdapName ln = (LdapName)n; rdn = (Rdn)ln.rdns.elementAt(i - beg); } else { String rdnString = n.get(i - beg); try { rdn = (new DnParser(rdnString, valuesCaseSensitive)).getRdn(); } catch (InvalidNameException e) { return false; } } if (!rdn.equals(rdns.elementAt(i))) { return false; } } return true; } public Name addAll(Name suffix) throws InvalidNameException { return addAll(size(), suffix); } /* * If "suffix" is not an LdapName, each of its components is parsed as * the string form of an RDN. */ public Name addAll(int pos, Name suffix) throws InvalidNameException { if (suffix instanceof LdapName) { LdapName s = (LdapName)suffix; for (int i = 0; i < s.rdns.size(); i++) { rdns.insertElementAt(s.rdns.elementAt(i), pos++); } } else { Enumeration comps = suffix.getAll(); while (comps.hasMoreElements()) { DnParser p = new DnParser((String)comps.nextElement(), valuesCaseSensitive); rdns.insertElementAt(p.getRdn(), pos++); } } unparsed = null; // no longer valid return this; } public Name add(String comp) throws InvalidNameException { return add(size(), comp); } public Name add(int pos, String comp) throws InvalidNameException { Rdn rdn = (new DnParser(comp, valuesCaseSensitive)).getRdn(); rdns.insertElementAt(rdn, pos); unparsed = null; // no longer valid return this; } public Object remove(int pos) throws InvalidNameException { String comp = get(pos); rdns.removeElementAt(pos); unparsed = null; // no longer valid return comp; } private void parse() throws InvalidNameException { rdns = (new DnParser(unparsed, valuesCaseSensitive)).getDn(); } /* * Best guess as to what RFC 2253 means by "whitespace". */ private static boolean isWhitespace(char c) { return (c == ' ' || c == '\r'); } /** * Given the value of an attribute, returns a string suitable * for inclusion in a DN. If the value is a string, this is * accomplished by using backslash (\) to escape the following * characters: *<ul> *<li>leading and trailing whitespace *<li><pre>, = + < > # ; " \</pre> *</ul> * If the value is a byte array, it is converted to hex * notation (such as "#CEB1DF80"). */ public static String escapeAttributeValue(Object val) { return TypeAndValue.escapeValue(val); } /** * Given an attribute value formated according to RFC 2253, * returns the unformated value. Returns a string value as * a string, and a binary value as a byte array. */ public static Object unescapeAttributeValue(String val) { return TypeAndValue.unescapeValue(val); } /** * Serializes only the unparsed DN, for compactness and to avoid * any implementation dependency. * * @serialdata The DN string and a boolean indicating whether * the values are case sensitive. */ private void writeObject(java.io.ObjectOutputStream s) throws java.io.IOException { s.writeObject(toString()); s.writeBoolean(valuesCaseSensitive); } private void readObject(java.io.ObjectInputStream s) throws java.io.IOException, ClassNotFoundException { unparsed = (String)s.readObject(); valuesCaseSensitive = s.readBoolean(); try { parse(); } catch (InvalidNameException e) { // shouldn't happen throw new java.io.StreamCorruptedException( "Invalid name: " + unparsed); } } static final long serialVersionUID = -1595520034788997356L; /* * DnParser implements a recursive descent parser for a single DN. */ static class DnParser { private final String name; // DN being parsed private final char[] chars; // characters in LDAP name being parsed private final int len; // length of "chars" private int cur = 0; // index of first unconsumed char in "chars" private boolean valuesCaseSensitive; /* * Given an LDAP DN in string form, returns a parser for it. */ DnParser(String name, boolean valuesCaseSensitive) throws InvalidNameException { this.name = name; len = name.length(); chars = name.toCharArray(); this.valuesCaseSensitive = valuesCaseSensitive; } /* * Parses the DN, returning a Vector of its RDNs. */ Vector getDn() throws InvalidNameException { cur = 0; Vector rdns = new Vector(len / 3 + 10); // leave room for growth if (len == 0) { return rdns; } rdns.addElement(parseRdn()); while (cur < len) { if (chars[cur] == ',' || chars[cur] == ';') { ++cur; rdns.insertElementAt(parseRdn(), 0); } else { throw new InvalidNameException("Invalid name: " + name); } } return rdns; } /* * Parses the DN, if it is known to contain a single RDN. */ Rdn getRdn() throws InvalidNameException { Rdn rdn = parseRdn(); if (cur < len) { throw new InvalidNameException("Invalid RDN: " + name); } return rdn; } /* * Parses the next RDN and returns it. Throws an exception if * none is found. Leading and trailing whitespace is consumed. */ private Rdn parseRdn() throws InvalidNameException { Rdn rdn = new Rdn(); while (cur < len) { consumeWhitespace(); String attrType = parseAttrType(); consumeWhitespace(); if (cur >= len || chars[cur] != '=') { throw new InvalidNameException("Invalid name: " + name); } ++cur; // consume '=' consumeWhitespace(); String value = parseAttrValue(); consumeWhitespace(); rdn.add(new TypeAndValue(attrType, value, valuesCaseSensitive)); if (cur >= len || chars[cur] != '+') { break; } ++cur; // consume '+' } return rdn; } /* * Returns the attribute type that begins at the next unconsumed * char. No leading whitespace is expected. * This routine is more generous than RFC 2253. It accepts * attribute types composed of any nonempty combination of Unicode * letters, Unicode digits, '.', '-', and internal space characters. */ private String parseAttrType() throws InvalidNameException { final int beg = cur; while (cur < len) { char c = chars[cur]; if (Character.isLetterOrDigit(c) || c == '.' || c == '-' || c == ' ') { ++cur; } else { break; } } // Back out any trailing spaces. while ((cur > beg) && (chars[cur - 1] == ' ')) { --cur; } if (beg == cur) { throw new InvalidNameException("Invalid name: " + name); } return new String(chars, beg, cur - beg); } /*