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
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<String, Identifier> 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 = 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 = 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:  {@code java.util.Vector. Enumerator}.
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 {@code getQualifier} and {@code getName}.
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}
330