View Javadoc
1   /*
2    * Copyright (c) 1994, 2003, Oracle and/or its affiliates. All rights reserved.
3    * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4    *
5    * This code is free software; you can redistribute it and/or modify it
6    * under the terms of the GNU General Public License version 2 only, as
7    * published by the Free Software Foundation.  Oracle designates this
8    * particular file as subject to the "Classpath" exception as provided
9    * by Oracle in the LICENSE file that accompanied this code.
10   *
11   * This code is distributed in the hope that it will be useful, but WITHOUT
12   * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13   * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14   * version 2 for more details (a copy is included in the LICENSE file that
15   * accompanied this code).
16   *
17   * You should have received a copy of the GNU General Public License version
18   * 2 along with this work; if not, write to the Free Software Foundation,
19   * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20   *
21   * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22   * or visit www.oracle.com if you need additional information or have any
23   * questions.
24   */
25  
26  package sun.tools.java;
27  
28  import java.util.Hashtable;
29  import java.io.PrintStream;
30  import java.util.Enumeration;
31  
32  /**
33   * A class to represent identifiers.<p>
34   *
35   * An identifier instance is very similar to a String. The difference
36   * is that identifier can't be instanciated directly, instead they are
37   * looked up in a hash table. This means that identifiers with the same
38   * name map to the same identifier object. This makes comparisons of
39   * identifiers much faster.<p>
40   *
41   * A lot of identifiers are qualified, that is they have '.'s in them.
42   * Each qualified identifier is chopped up into the qualifier and the
43   * name. The qualifier is cached in the value field.<p>
44   *
45   * Unqualified identifiers can have a type. This type is an integer that
46   * can be used by a scanner as a token value. This value has to be set
47   * using the setType method.<p>
48   *
49   * WARNING: The contents of this source file are not part of any
50   * supported API.  Code that depends on them does so at its own risk:
51   * they are subject to change or removal without notice.
52   *
53   * @author      Arthur van Hoff
54   */
55  
56  public final
57  class Identifier implements Constants {
58      /**
59       * The hashtable of identifiers
60       */
61      static Hashtable hash = new Hashtable(3001, 0.5f);
62  
63      /**
64       * The name of the identifier
65       */
66      String name;
67  
68      /**
69       * The value of the identifier, for keywords this is an
70       * instance of class Integer, for qualified names this is
71       * another identifier (the qualifier).
72       */
73      Object value;
74  
75      /**
76       * The Type which corresponds to this Identifier.  This is used as
77       * cache for Type.tClass() and shouldn't be used outside of that
78       * context.
79       */
80      Type typeObject = null;
81  
82      /**
83       * The index of INNERCLASS_PREFIX in the name, or -1 if none.
84       */
85      private int ipos;
86  
87      /**
88       * Construct an identifier. Don't call this directly,
89       * use lookup instead.
90       * @see Identifier.lookup
91       */
92      private Identifier(String name) {
93          this.name = name;
94          this.ipos = name.indexOf(INNERCLASS_PREFIX);
95      }
96  
97      /**
98       * Get the type of the identifier.
99       */
100     int getType() {
101         return ((value != null) && (value instanceof Integer)) ?
102                 ((Integer)value).intValue() : IDENT;
103     }
104 
105     /**
106      * Set the type of the identifier.
107      */
108     void setType(int t) {
109         value = new Integer(t);
110         //System.out.println("type(" + this + ")=" + t);
111     }
112 
113     /**
114      * Lookup an identifier.
115      */
116     public static synchronized Identifier lookup(String s) {
117         //System.out.println("lookup(" + s + ")");
118         Identifier id = (Identifier)hash.get(s);
119         if (id == null) {
120             hash.put(s, id = new Identifier(s));
121         }
122         return id;
123     }
124 
125     /**
126      * Lookup a qualified identifier.
127      */
128     public static Identifier lookup(Identifier q, Identifier n) {
129         // lookup("", x) => x
130         if (q == idNull)  return n;
131         // lookup(lookupInner(c, ""), n) => lookupInner(c, lookup("", n))
132         if (q.name.charAt(q.name.length()-1) == INNERCLASS_PREFIX)
133             return lookup(q.name+n.name);
134         Identifier id = lookup(q + "." + n);
135         if (!n.isQualified() && !q.isInner())
136             id.value = q;
137         return id;
138     }
139 
140     /**
141      * Lookup an inner identifier.
142      * (Note:  n can be idNull.)
143      */
144     public static Identifier lookupInner(Identifier c, Identifier n) {
145         Identifier id;
146         if (c.isInner()) {
147             if (c.name.charAt(c.name.length()-1) == INNERCLASS_PREFIX)
148                 id = lookup(c.name+n);
149             else
150                 id = lookup(c, n);
151         } else {
152             id = lookup(c + "." + INNERCLASS_PREFIX + n);
153         }
154         id.value = c.value;
155         return id;
156     }
157 
158     /**
159      * Convert to a string.
160      */
161     public String toString() {
162         return name;
163     }
164 
165     /**
166      * Check if the name is qualified (ie: it contains a '.').
167      */
168     public boolean isQualified() {
169         if (value == null) {
170             int idot = ipos;
171             if (idot <= 0)
172                 idot = name.length();
173             else
174                 idot -= 1;      // back up over previous dot
175             int index = name.lastIndexOf('.', idot-1);
176             value = (index < 0) ? idNull : Identifier.lookup(name.substring(0, index));
177         }
178         return (value instanceof Identifier) && (value != idNull);
179     }
180 
181     /**
182      * Return the qualifier. The null identifier is returned if
183      * the name was not qualified.  The qualifier does not include
184      * any inner part of the name.
185      */
186     public Identifier getQualifier() {
187         return isQualified() ? (Identifier)value : idNull;
188     }
189 
190     /**
191      * Return the unqualified name.
192      * In the case of an inner name, the unqualified name
193      * will itself contain components.
194      */
195     public Identifier getName() {
196         return isQualified() ?
197             Identifier.lookup(name.substring(((Identifier)value).name.length() + 1)) : this;
198     }
199 
200     /** A space character, which precedes the first inner class
201      *  name in a qualified name, and thus marks the qualification
202      *  as involving inner classes, instead of merely packages.<p>
203      *  Ex:  <tt>java.util.Vector. Enumerator</tt>.
204      */
205     public static final char INNERCLASS_PREFIX = ' ';
206 
207     /* Explanation:
208      * Since much of the compiler's low-level name resolution code
209      * operates in terms of Identifier objects.  This includes the
210      * code which walks around the file system and reports what
211      * classes are where.  It is important to get nesting information
212      * right as early as possible, since it affects the spelling of
213      * signatures.  Thus, the low-level import and resolve code must
214      * be able Identifier type must be able to report the nesting
215      * of types, which implied that that information must be carried
216      * by Identifiers--or that the low-level interfaces be significantly
217      * changed.
218      */
219 
220     /**
221      * Check if the name is inner (ie: it contains a ' ').
222      */
223     public boolean isInner() {
224         return (ipos > 0);
225     }
226 
227     /**
228      * Return the class name, without its qualifier,
229      * and with any nesting flattened into a new qualfication structure.
230      * If the original identifier is inner,
231      * the result will be qualified, and can be further
232      * decomposed by means of <tt>getQualifier</tt> and <tt>getName</tt>.
233      * <p>
234      * For example:
235      * <pre>
236      * Identifier id = Identifier.lookup("pkg.Foo. Bar");
237      * id.getName().name      =>  "Foo. Bar"
238      * id.getFlatName().name  =>  "Foo.Bar"
239      * </pre>
240      */
241     public Identifier getFlatName() {
242         if (isQualified()) {
243             return getName().getFlatName();
244         }
245         if (ipos > 0 && name.charAt(ipos-1) == '.') {
246             if (ipos+1 == name.length()) {
247                 // last component is idNull
248                 return Identifier.lookup(name.substring(0,ipos-1));
249             }
250             String n = name.substring(ipos+1);
251             String t = name.substring(0,ipos);
252             return Identifier.lookup(t+n);
253         }
254         // Not inner.  Just return the same as getName()
255         return this;
256     }
257 
258     public Identifier getTopName() {
259         if (!isInner())  return this;
260         return Identifier.lookup(getQualifier(), getFlatName().getHead());
261     }
262 
263     /**
264      * Yet another way to slice qualified identifiers:
265      * The head of an identifier is its first qualifier component,
266      * and the tail is the rest of them.
267      */
268     public Identifier getHead() {
269         Identifier id = this;
270         while (id.isQualified())
271             id = id.getQualifier();
272         return id;
273     }
274 
275     /**
276      * @see getHead
277      */
278     public Identifier getTail() {
279         Identifier id = getHead();
280         if (id == this)
281             return idNull;
282         else
283             return Identifier.lookup(name.substring(id.name.length() + 1));
284     }
285 
286     // Unfortunately, the current structure of the compiler requires
287     // that the resolveName() family of methods (which appear in
288     // Environment.java, Context.java, and ClassDefinition.java) raise
289     // no exceptions and emit no errors.  When we are in resolveName()
290     // and we find a method that is ambiguous, we need to
291     // unambiguously mark it as such, so that later stages of the
292     // compiler realize that they should give an ambig.class rather than
293     // a class.not.found error.  To mark it we add a special prefix
294     // which cannot occur in the program source.  The routines below
295     // are used to check, add, and remove this prefix.
296     // (part of solution for 4059855).
297 
298     /**
299      * A special prefix to add to ambiguous names.
300      */
301     private static final String ambigPrefix = "<<ambiguous>>";
302 
303     /**
304      * Determine whether an Identifier has been marked as ambiguous.
305      */
306     public boolean hasAmbigPrefix() {
307         return (name.startsWith(ambigPrefix));
308     }
309 
310     /**
311      * Add ambigPrefix to `this' to make a new Identifier marked as
312      * ambiguous.  It is important that this new Identifier not refer
313      * to an existing class.
314      */
315     public Identifier addAmbigPrefix() {
316         return Identifier.lookup(ambigPrefix + name);
317     }
318 
319     /**
320      * Remove the ambigPrefix from `this' to get the original identifier.
321      */
322     public Identifier removeAmbigPrefix() {
323         if (hasAmbigPrefix()) {
324             return Identifier.lookup(name.substring(ambigPrefix.length()));
325         } else {
326             return this;
327         }
328     }
329 }