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<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 }