/* * Copyright (c) 1994, 2003, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package sun.tools.java; import java.util.Hashtable; import java.io.PrintStream; import java.util.Enumeration; /** * A class to represent identifiers.<p> * * An identifier instance is very similar to a String. The difference * is that identifier can't be instanciated directly, instead they are * looked up in a hash table. This means that identifiers with the same * name map to the same identifier object. This makes comparisons of * identifiers much faster.<p> * * A lot of identifiers are qualified, that is they have '.'s in them. * Each qualified identifier is chopped up into the qualifier and the * name. The qualifier is cached in the value field.<p> * * Unqualified identifiers can have a type. This type is an integer that * can be used by a scanner as a token value. This value has to be set * using the setType method.<p> * * WARNING: The contents of this source file are not part of any * supported API. Code that depends on them does so at its own risk: * they are subject to change or removal without notice. * * @author Arthur van Hoff */ public final class Identifier implements Constants { /** * The hashtable of identifiers */ static Hashtable<String, Identifier> hash = new Hashtable<>(3001, 0.5f); /** * The name of the identifier */ String name; /** * The value of the identifier, for keywords this is an * instance of class Integer, for qualified names this is * another identifier (the qualifier). */ Object value; /** * The Type which corresponds to this Identifier. This is used as * cache for Type.tClass() and shouldn't be used outside of that * context. */ Type typeObject = null; /** * The index of INNERCLASS_PREFIX in the name, or -1 if none. */ private int ipos; /** * Construct an identifier. Don't call this directly, * use lookup instead. * @see Identifier.lookup */ private Identifier(String name) { this.name = name; this.ipos = name.indexOf(INNERCLASS_PREFIX); } /** * Get the type of the identifier. */ int getType() { return ((value != null) && (value instanceof Integer)) ? ((Integer)value).intValue() : IDENT; } /** * Set the type of the identifier. */ void setType(int t) { value = t; //System.out.println("type(" + this + ")=" + t); } /** * Lookup an identifier. */ public static synchronized Identifier lookup(String s) { //System.out.println("lookup(" + s + ")"); Identifier id = hash.get(s); if (id == null) { hash.put(s, id = new Identifier(s)); } return id; } /** * Lookup a qualified identifier. */ public static Identifier lookup(Identifier q, Identifier n) { // lookup("", x) => x if (q == idNull) return n; // lookup(lookupInner(c, ""), n) => lookupInner(c, lookup("", n)) if (q.name.charAt(q.name.length()-1) == INNERCLASS_PREFIX) return lookup(q.name+n.name); Identifier id = lookup(q + "." + n); if (!n.isQualified() && !q.isInner()) id.value = q; return id; } /** * Lookup an inner identifier. * (Note: n can be idNull.) */ public static Identifier lookupInner(Identifier c, Identifier n) { Identifier id; if (c.isInner()) { if (c.name.charAt(c.name.length()-1) == INNERCLASS_PREFIX) id = lookup(c.name+n); else id = lookup(c, n); } else { id = lookup(c + "." + INNERCLASS_PREFIX + n); } id.value = c.value; return id; } /** * Convert to a string. */ public String toString() { return name; } /** * Check if the name is qualified (ie: it contains a '.'). */ public boolean isQualified() { if (value == null) { int idot = ipos; if (idot <= 0) idot = name.length(); else idot -= 1; // back up over previous dot int index = name.lastIndexOf('.', idot-1); value = (index < 0) ? idNull : Identifier.lookup(name.substring(0, index)); } return (value instanceof Identifier) && (value != idNull); } /** * Return the qualifier. The null identifier is returned if * the name was not qualified. The qualifier does not include * any inner part of the name. */ public Identifier getQualifier() { return isQualified() ? (Identifier)value : idNull; } /** * Return the unqualified name. * In the case of an inner name, the unqualified name * will itself contain components. */ public Identifier getName() { return isQualified() ? Identifier.lookup(name.substring(((Identifier)value).name.length() + 1)) : this; } /** A space character, which precedes the first inner class * name in a qualified name, and thus marks the qualification * as involving inner classes, instead of merely packages.<p> * Ex: {@code java.util.Vector. Enumerator}. */ public static final char INNERCLASS_PREFIX = ' '; /* Explanation: * Since much of the compiler's low-level name resolution code * operates in terms of Identifier objects. This includes the * code which walks around the file system and reports what * classes are where. It is important to get nesting information * right as early as possible, since it affects the spelling of * signatures. Thus, the low-level import and resolve code must * be able Identifier type must be able to report the nesting * of types, which implied that that information must be carried * by Identifiers--or that the low-level interfaces be significantly * changed. */ /** * Check if the name is inner (ie: it contains a ' '). */ public boolean isInner() { return (ipos > 0); } /** * Return the class name, without its qualifier, * and with any nesting flattened into a new qualfication structure. * If the original identifier is inner, * the result will be qualified, and can be further * decomposed by means of {@code getQualifier} and {@code getName}. * <p> * For example: * <pre> * Identifier id = Identifier.lookup("pkg.Foo. Bar"); * id.getName().name => "Foo. Bar" * id.getFlatName().name => "Foo.Bar" * </pre> */ public Identifier getFlatName() { if (isQualified()) { return getName().getFlatName(); } if (ipos > 0 && name.charAt(ipos-1) == '.') { if (ipos+1 == name.length()) { // last component is idNull return Identifier.lookup(name.substring(0,ipos-1)); } String n = name.substring(ipos+1); String t = name.substring(0,ipos); return Identifier.lookup(t+n); } // Not inner. Just return the same as getName() return this; } public Identifier getTopName() { if (!isInner()) return this; return Identifier.lookup(getQualifier(), getFlatName().getHead()); } /** * Yet another way to slice qualified identifiers: * The head of an identifier is its first qualifier component, * and the tail is the rest of them. */ public Identifier getHead() { Identifier id = this; while (id.isQualified()) id = id.getQualifier(); return id; } /** * @see getHead */ public Identifier getTail() { Identifier id = getHead(); if (id == this) return idNull; else return Identifier.lookup(name.substring(id.name.length() + 1)); } // Unfortunately, the current structure of the compiler requires // that the resolveName() family of methods (which appear in // Environment.java, Context.java, and ClassDefinition.java) raise // no exceptions and emit no errors. When we are in resolveName() // and we find a method that is ambiguous, we need to // unambiguously mark it as such, so that later stages of the // compiler realize that they should give an ambig.class rather than // a class.not.found error. To mark it we add a special prefix // which cannot occur in the program source. The routines below // are used to check, add, and remove this prefix. // (part of solution for 4059855). /** * A special prefix to add to ambiguous names. */ private static final String ambigPrefix = "<<ambiguous>>"; /** * Determine whether an Identifier has been marked as ambiguous. */ public boolean hasAmbigPrefix() { return (name.startsWith(ambigPrefix)); } /** * Add ambigPrefix to `this' to make a new Identifier marked as * ambiguous. It is important that this new Identifier not refer * to an existing class. */ public Identifier addAmbigPrefix() { return Identifier.lookup(ambigPrefix + name); } /** * Remove the ambigPrefix from `this' to get the original identifier. */ public Identifier removeAmbigPrefix() { if (hasAmbigPrefix()) { return Identifier.lookup(name.substring(ambigPrefix.length())); } else { return this; } } }