001/* CompoundName.java --
002   Copyright (C) 2001, 2004, 2005  Free Software Foundation, Inc.
003
004This file is part of GNU Classpath.
005
006GNU Classpath is free software; you can redistribute it and/or modify
007it under the terms of the GNU General Public License as published by
008the Free Software Foundation; either version 2, or (at your option)
009any later version.
010
011GNU Classpath is distributed in the hope that it will be useful, but
012WITHOUT ANY WARRANTY; without even the implied warranty of
013MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
014General Public License for more details.
015
016You should have received a copy of the GNU General Public License
017along with GNU Classpath; see the file COPYING.  If not, write to the
018Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
01902110-1301 USA.
020
021Linking this library statically or dynamically with other modules is
022making a combined work based on this library.  Thus, the terms and
023conditions of the GNU General Public License cover the whole
024combination.
025
026As a special exception, the copyright holders of this library give you
027permission to link this library with independent modules to produce an
028executable, regardless of the license terms of these independent
029modules, and to copy and distribute the resulting executable under
030terms of your choice, provided that you also meet, for each linked
031independent module, the terms and conditions of the license of that
032module.  An independent module is a module which is not derived from
033or based on this library.  If you modify this library, you may extend
034this exception to your version of the library, but you are not
035obligated to do so.  If you do not wish to do so, delete this
036exception statement from your version. */
037
038
039package javax.naming;
040
041import gnu.java.lang.CPStringBuilder;
042
043import java.io.IOException;
044import java.io.ObjectInputStream;
045import java.io.ObjectOutputStream;
046import java.io.Serializable;
047import java.util.Enumeration;
048import java.util.NoSuchElementException;
049import java.util.Properties;
050import java.util.Vector;
051
052/**
053 * Represents hierarchical names from the single namespace. For instance,
054 * the path /home/audriusa/classpath/file.txt is the compound name, using
055 * the filesystem namespace.
056 *
057 * @author Tom Tromey (tromey@redhat.com)
058 * @date May 16, 2001
059 *
060 * FIXME: this class is underspecified.  For instance, the `flat'
061 * direction is never described.  If it means that the CompoundName
062 * can only have a single element, then the Enumeration-based
063 * constructor ought to throw InvalidNameException.
064 *
065 * @since 1.3
066 */
067public class CompoundName implements Name, Cloneable, Serializable
068{
069  private static final long serialVersionUID = 3513100557083972036L;
070
071  private CompoundName (Properties syntax)
072  {
073    elts = new Vector<String> ();
074    mySyntax = syntax;
075    initializeSyntax ();
076  }
077
078  protected CompoundName (Enumeration<String> comps, Properties syntax)
079  {
080    elts = new Vector<String> ();
081    mySyntax = syntax;
082    initializeSyntax ();
083    try
084      {
085        while (comps.hasMoreElements ())
086          elts.add (comps.nextElement ());
087      }
088    catch (NoSuchElementException ignore)
089      {
090      }
091  }
092
093  public CompoundName (String n, Properties syntax)
094    throws InvalidNameException
095  {
096    elts = new Vector<String> ();
097    mySyntax = syntax;
098    initializeSyntax ();
099
100    StringBuilder new_element = new StringBuilder ();
101    int i = 0;
102    // QUOTE==null means no quoting right now.  When it is set it is
103    // the value of the closing quote.
104    String quote = null;
105    while (i < n.length ())
106      {
107        String special = isSpecial (n, i);
108
109        if (special == escape && escape != null)
110          {
111            if (n.length () == i + special.length ())
112              {
113                // A trailing escape is treated as itself.
114                new_element.append (special);
115                i += special.length ();
116              }
117            else
118              {
119                String eSpecial = isSpecial (n, i + special.length ());
120                if (eSpecial != null)
121                  {
122                    // Treat the escape as an escape.
123                    new_element.append (eSpecial);
124                    i += special.length () + eSpecial.length ();
125                  }
126                else
127                  {
128                    // Treat the escape as itself.
129                    new_element.append (special);
130                    i += special.length ();
131                  }
132                continue;
133              }
134          }
135        else if (quote != null)
136          {
137            // It is safe to use == here.
138            if (quote == special)
139              {
140                // Quotes must surround a complete component.
141                if (i + quote.length () < n.length ()
142                    && ! n.startsWith (separator, i + quote.length ()))
143                  throw new InvalidNameException ("close quote before end of component");
144                elts.add (new_element.toString ());
145                new_element.setLength (0);
146                i += quote.length ();
147                quote = null;
148                continue;
149              }
150            // Otherwise, fall through.
151          }
152        // Quotes are only special at the start of a component.
153        else if (new_element.length () == 0
154                 && special == beginQuote
155                 && beginQuote != null)
156          {
157            quote = endQuote;
158            i += special.length ();
159            continue;
160          }
161        else if (new_element.length () == 0
162                 && special == beginQuote2
163                 && beginQuote2 != null)
164          {
165            quote = endQuote2;
166            i += special.length ();
167            continue;
168          }
169        else if (direction != FLAT && special == separator)
170          {
171            elts.add (new_element.toString ());
172            new_element.setLength (0);
173            i += special.length ();
174            continue;
175          }
176
177        // Nothing in particular, so try the next character.
178        new_element.append (n.charAt (i));
179        ++i;
180      }
181
182    if (new_element.length () != 0)
183      elts.add (new_element.toString ());
184
185    if (direction == RIGHT_TO_LEFT)
186      {
187        // Reverse the order of the elements.
188        int len = elts.size ();
189        for (i = 0; i < len / 2; ++i)
190          {
191            String t = elts.set (i, elts.get (len - i - 1));
192            elts.set (len - i - 1, t);
193          }
194      }
195
196    // Error checking.
197    if (quote != null)
198      throw new InvalidNameException ("unterminated quote");
199  }
200
201  public Name add (int posn, String comp) throws InvalidNameException
202  {
203    elts.add (posn, comp);
204    return this;
205  }
206
207  public Name add (String comp) throws InvalidNameException
208  {
209    elts.add (comp);
210    return this;
211  }
212
213  public Name addAll (int posn, Name n) throws InvalidNameException
214  {
215    Enumeration<String> e = n.getAll ();
216    try
217      {
218        while (e.hasMoreElements ())
219          {
220            elts.add (posn, e.nextElement ());
221            ++posn;
222          }
223      }
224    catch (NoSuchElementException ignore)
225      {
226      }
227    return this;
228  }
229
230  public Name addAll (Name suffix) throws InvalidNameException
231  {
232    Enumeration<String> e = suffix.getAll ();
233    try
234      {
235        while (e.hasMoreElements ())
236          elts.add (e.nextElement ());
237      }
238    catch (NoSuchElementException ignore)
239      {
240      }
241    return this;
242  }
243
244  public Object clone ()
245  {
246    return new CompoundName (elts.elements (), mySyntax);
247  }
248
249  public int compareTo (Object obj)
250  {
251    if (! (obj instanceof CompoundName))
252      throw new ClassCastException ("CompoundName.compareTo() expected CompoundName");
253    CompoundName cn = (CompoundName) obj;
254    int last = Math.min (cn.elts.size (), elts.size ());
255    for (int i = 0; i < last; ++i)
256      {
257        String f = canonicalize (elts.get (i));
258        int comp = f.compareTo (canonicalize (cn.elts.get (i)));
259        if (comp != 0)
260          return comp;
261      }
262    return elts.size () - cn.elts.size ();
263  }
264
265  public boolean endsWith (Name n)
266  {
267    if (! (n instanceof CompoundName))
268      return false;
269    CompoundName cn = (CompoundName) n;
270    if (cn.elts.size () > elts.size ())
271      return false;
272    int delta = elts.size () - cn.elts.size ();
273    for (int i = 0; i < cn.elts.size (); ++i)
274      {
275        String f = canonicalize (elts.get (delta + i));
276        if (! f.equals (canonicalize (cn.elts.get (i))))
277          return false;
278      }
279    return true;
280  }
281
282  public boolean equals (Object obj)
283  {
284    if (! (obj instanceof CompoundName))
285      return false;
286    return compareTo (obj) == 0;
287  }
288
289  public String get (int posn)
290  {
291    return elts.get (posn);
292  }
293
294  public Enumeration<String> getAll ()
295  {
296    return elts.elements ();
297  }
298
299  public Name getPrefix (int posn)
300  {
301    CompoundName cn = new CompoundName (mySyntax);
302    for (int i = 0; i < posn; ++i)
303      cn.elts.add (elts.get (i));
304    return cn;
305  }
306
307  public Name getSuffix (int posn)
308  {
309    if (posn > elts.size ())
310      throw new ArrayIndexOutOfBoundsException (posn);
311    CompoundName cn = new CompoundName (mySyntax);
312    for (int i = posn; i < elts.size (); ++i)
313      cn.elts.add (elts.get (i));
314    return cn;
315  }
316
317  public int hashCode ()
318  {
319    int h = 0;
320    for (int i = 0; i < elts.size (); ++i)
321      h += canonicalize (elts.get (i)).hashCode ();
322    return h;
323  }
324
325  public boolean isEmpty ()
326  {
327    return elts.isEmpty ();
328  }
329
330  public Object remove (int posn) throws InvalidNameException
331  {
332    return elts.remove (posn);
333  }
334
335  public int size ()
336  {
337    return elts.size ();
338  }
339
340  public boolean startsWith (Name n)
341  {
342    if (! (n instanceof CompoundName))
343      return false;
344    CompoundName cn = (CompoundName) n;
345    if (cn.elts.size () > elts.size ())
346      return false;
347    for (int i = 0; i < cn.elts.size (); ++i)
348      {
349        String f = canonicalize (elts.get (i));
350        if (! f.equals (canonicalize (cn.elts.get (i))))
351          return false;
352      }
353    return true;
354  }
355
356  // If ELEMENT starts with some meta-sequence at OFFSET, then return
357  // the string representing the meta-sequence.  Otherwise return
358  // null.
359  private String isSpecial (String element, int offset)
360  {
361    String special = null;
362    if (separator != null && element.startsWith (separator, offset))
363      special = separator;
364    else if (escape != null && element.startsWith (escape, offset))
365      special = escape;
366    else if (beginQuote != null && element.startsWith (beginQuote, offset))
367      special = beginQuote;
368    else if (endQuote != null && element.startsWith (endQuote, offset))
369      special = endQuote;
370    else if (beginQuote2 != null
371             && element.startsWith (beginQuote2, offset))
372      special = beginQuote2;
373    else if (endQuote2 != null && element.startsWith (endQuote2, offset))
374      special = endQuote2;
375
376    return special;
377  }
378
379  public String toString ()
380  {
381    CPStringBuilder result = new CPStringBuilder ();
382    int size = elts.size ();
383    for (int i = 0; i < size; ++i)
384      {
385        // Find the appropriate element.  FIXME: not clear what FLAT
386        // means.
387        int offset = (direction == RIGHT_TO_LEFT) ? (size - i - 1) : i;
388        String element = elts.get (offset);
389        if (i > 0
390            || (i == size - 1 && element.equals ("")))
391          result.append (separator);
392
393        int k = 0;
394        while (k < element.length ())
395          {
396            String special = isSpecial (element, k);
397            if (special != null)
398              {
399                result.append (escape);
400                result.append (special);
401                k += special.length ();
402              }
403            else
404              {
405                result.append (element.charAt (k));
406                ++k;
407              }
408          }
409      }
410
411    return result.toString ();
412  }
413
414  // This canonicalizes a String, based on the syntax, for comparison
415  // or other similar purposes.
416  private String canonicalize (String element)
417  {
418    String ret = element;
419
420    if (ignoreCase)
421      ret = ret.toLowerCase ();
422
423    if (trimBlanks)
424      {
425        int first = 0;
426        while (first < ret.length ()
427               && Character.isWhitespace (ret.charAt (first)))
428          ++first;
429
430        int last = ret.length () - 1;
431        while (last >= first
432               && Character.isWhitespace (ret.charAt (last)))
433          --last;
434
435        ret = ret.substring (first, last);
436      }
437
438    return ret;
439  }
440
441  // This initializes all the syntax variables.  This seems easier
442  // than re-querying the properties every time.  We're allowed to do
443  // this because the spec says that subclasses should consider the
444  // syntax as being read-only.
445  private void initializeSyntax ()
446  {
447    String t = mySyntax.getProperty ("jndi.syntax.direction", "flat");
448    if (t.equals ("right_to_left"))
449      this.direction = RIGHT_TO_LEFT;
450    else if (t.equals ("left_to_right"))
451      this.direction = LEFT_TO_RIGHT;
452    else
453      {
454        // If we don't recognize it, default to flat.
455        this.direction = FLAT;
456      }
457
458    // This is required unless the direction is FLAT.  Unfortunately
459    // there is no way to report this error.
460    this.separator = mySyntax.getProperty ("jndi.syntax.separator", "");
461
462    this.ignoreCase
463      = Boolean.valueOf (mySyntax.getProperty ("jndi.syntax.ignorecase",
464                                               "false")).booleanValue ();
465    this.escape = mySyntax.getProperty ("jndi.syntax.escape", null);
466    this.beginQuote = mySyntax.getProperty ("jndi.syntax.beginquote", null);
467    this.endQuote = mySyntax.getProperty ("jndi.syntax.endquote",
468                                          this.beginQuote);
469    this.beginQuote2 = mySyntax.getProperty ("jndi.syntax.beginquote2",
470                                             null);
471    this.endQuote2 = mySyntax.getProperty ("jndi.syntax.endquote2",
472                                           this.beginQuote2);
473    this.trimBlanks
474      = Boolean.valueOf (mySyntax.getProperty ("jndi.syntax.trimblanks",
475                                               "false")).booleanValue ();
476  }
477
478  private void readObject(ObjectInputStream s)
479    throws IOException, ClassNotFoundException
480  {
481    mySyntax = (Properties) s.readObject();
482    int count = s.readInt();
483    elts = new Vector<String>(count);
484    for (int i = 0; i < count; i++)
485      elts.addElement((String) s.readObject());
486  }
487
488  private void writeObject(ObjectOutputStream s)
489    throws IOException
490  {
491    s.writeObject(mySyntax);
492    s.writeInt(elts.size());
493    for (int i = 0; i < elts.size(); i++)
494        s.writeObject(elts.elementAt(i));
495  }
496
497  // The spec specifies this but does not document it in any way (it
498  // is a package-private class).  It is useless as far as I can tell.
499  // So we ignore it.
500  // protected transient NameImpl impl;
501  protected transient Properties mySyntax;
502
503  // The actual elements.
504  private transient Vector<String> elts;
505
506  // The following are all used for syntax.
507  private transient int direction;
508  private transient String separator;
509  private transient boolean ignoreCase;
510  private transient String escape;
511  private transient String beginQuote;
512  private transient String endQuote;
513  private transient String beginQuote2;
514  private transient String endQuote2;
515  private transient boolean trimBlanks;
516  // We didn't need these for parsing, so they are gone.
517  // private transient String avaSeparator;
518  // private transient String typevalSeparator;
519
520  private static final int RIGHT_TO_LEFT = -1;
521  private static final int LEFT_TO_RIGHT = 1;
522  private static final int FLAT = 0;
523}