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 gnu.java.lang.CPStringBuilder;
042    
043    import java.io.IOException;
044    import java.io.ObjectInputStream;
045    import java.io.ObjectOutputStream;
046    import java.io.Serializable;
047    import java.util.Enumeration;
048    import java.util.NoSuchElementException;
049    import java.util.Properties;
050    import 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     */
067    public 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    }