blob: e0e5fa781e7262c04ebaf2929bb54779489305c5 [file] [log] [blame]
J. Duke319a3b92007-12-01 00:00:00 +00001/*
2 * Copyright 1994-2003 Sun Microsystems, Inc. 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. Sun designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Sun 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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
22 * CA 95054 USA or visit www.sun.com if you need additional information or
23 * have any questions.
24 */
25
26package sun.tools.java;
27
28import java.util.Hashtable;
29import java.io.PrintStream;
30import 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
56public final
57class 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}