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