/* * Copyright (c) 1994, 2019, 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.*; import java.io.OutputStream; import java.io.PrintStream; import sun.tools.tree.Context; import sun.tools.tree.Vset; import sun.tools.tree.Expression; import sun.tools.tree.LocalMember; import sun.tools.tree.UplevelReference; /** * This class is a Java class definition * * 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. */ @SuppressWarnings("deprecation") public class ClassDefinition implements Constants { protected Object source; protected long where; protected int modifiers; protected Identifier localName; // for local classes protected ClassDeclaration declaration; protected IdentifierToken superClassId; protected IdentifierToken interfaceIds[]; protected ClassDeclaration superClass; protected ClassDeclaration interfaces[]; protected ClassDefinition outerClass; protected MemberDefinition outerMember; protected MemberDefinition innerClassMember; // field for me in outerClass protected MemberDefinition firstMember; protected MemberDefinition lastMember; protected boolean resolved; protected String documentation; protected boolean error; protected boolean nestError; protected UplevelReference references; protected boolean referencesFrozen; private Hashtable fieldHash = new Hashtable<>(31); private int abstr; // Table of local and anonymous classes whose internal names are constructed // using the current class as a prefix. This is part of a fix for // bugid 4054523 and 4030421. See also 'Environment.getClassDefinition' // and 'BatchEnvironment.makeClassDefinition'. Allocated on demand. private Hashtable localClasses = null; private final int LOCAL_CLASSES_SIZE = 31; // The immediately surrounding context in which the class appears. // Set at the beginning of checking, upon entry to 'SourceClass.checkInternal'. // Null for classes that are not local or inside a local class. // At present, this field exists only for the benefit of 'resolveName' as part // of the fix for 4095716. protected Context classContext; // The saved class context is now also used in 'SourceClass.getAccessMember'. // Provide read-only access via this method. Part of fix for 4098093. public Context getClassContext() { return classContext; } /** * Constructor */ protected ClassDefinition(Object source, long where, ClassDeclaration declaration, int modifiers, IdentifierToken superClass, IdentifierToken interfaces[]) { this.source = source; this.where = where; this.declaration = declaration; this.modifiers = modifiers; this.superClassId = superClass; this.interfaceIds = interfaces; } /** * Get the source of the class */ public final Object getSource() { return source; } /** * Check if there were any errors in this class. */ public final boolean getError() { return error; } /** * Mark this class to be erroneous. */ public final void setError() { this.error = true; setNestError(); } /** * Check if there were any errors in our class nest. */ public final boolean getNestError() { // Check to see if our error value is set, or if any of our // outer classes' error values are set. This will work in // conjunction with setError(), which sets the error value // of its outer class, to yield true is any of our nest // siblings has an error. This addresses bug 4111488: either // code should be generated for all classes in a nest, or // none of them. return nestError || ((outerClass != null) ? outerClass.getNestError() : false); } /** * Mark this class, and all siblings in its class nest, to be * erroneous. */ public final void setNestError() { this.nestError = true; if (outerClass != null) { // If we have an outer class, set it to be erroneous as well. // This will work in conjunction with getError(), which checks // the error value of its outer class, to set the whole class // nest to be erroneous. This address bug 4111488: either // code should be generated for all classes in a nest, or // none of them. outerClass.setNestError(); } } /** * Get the position in the input */ public final long getWhere() { return where; } /** * Get the class declaration */ public final ClassDeclaration getClassDeclaration() { return declaration; } /** * Get the class' modifiers */ public final int getModifiers() { return modifiers; } public final void subModifiers(int mod) { modifiers &= ~mod; } public final void addModifiers(int mod) { modifiers |= mod; } // *** DEBUG *** protected boolean supersCheckStarted = !(this instanceof sun.tools.javac.SourceClass); /** * Get the class' super class */ public final ClassDeclaration getSuperClass() { /*--- if (superClass == null && superClassId != null) throw new CompilerError("getSuperClass "+superClassId); // There are obscure cases where null is the right answer, // in order to enable some error reporting later on. // For example: class T extends T.N { class N { } } ---*/ // *** DEBUG *** // This method should not be called if the superclass has not been resolved. if (!supersCheckStarted) throw new CompilerError("unresolved super"); return superClass; } /** * Get the super class, and resolve names now if necessary. * * It is only possible to resolve names at this point if we are * a source class. The provision of this method at this level * in the class hierarchy is dubious, but see 'getInnerClass' below. * All other calls to 'getSuperClass(env)' appear in 'SourceClass'. * NOTE: An older definition of this method has been moved to * 'SourceClass', where it overrides this one. * * @see #resolveTypeStructure */ public ClassDeclaration getSuperClass(Environment env) { return getSuperClass(); } /** * Get the class' interfaces */ public final ClassDeclaration[] getInterfaces() { if (interfaces == null) throw new CompilerError("getInterfaces"); return interfaces; } /** * Get the class' enclosing class (or null if not inner) */ public final ClassDefinition getOuterClass() { return outerClass; } /** * Set the class' enclosing class. Must be done at most once. */ protected final void setOuterClass(ClassDefinition outerClass) { if (this.outerClass != null) throw new CompilerError("setOuterClass"); this.outerClass = outerClass; } /** * Set the class' enclosing current instance pointer. * Must be done at most once. */ protected final void setOuterMember(MemberDefinition outerMember) { if (isStatic() || !isInnerClass()) throw new CompilerError("setOuterField"); if (this.outerMember != null) throw new CompilerError("setOuterField"); this.outerMember = outerMember; } /** * Tell if the class is inner. * This predicate also returns true for top-level nested types. * To test for a true inner class as seen by the programmer, * use {@code !isTopLevel()}. */ public final boolean isInnerClass() { return outerClass != null; } /** * Tell if the class is a member of another class. * This is false for package members and for block-local classes. */ public final boolean isMember() { return outerClass != null && !isLocal(); } /** * Tell if the class is "top-level", which is either a package member, * or a static member of another top-level class. */ public final boolean isTopLevel() { return outerClass == null || isStatic() || isInterface(); } /** * Tell if the class is local or inside a local class, * which means it cannot be mentioned outside of its file. */ // The comment above is true only because M_LOCAL is set // whenever M_ANONYMOUS is. I think it is risky to assume that // isAnonymous(x) => isLocal(x). public final boolean isInsideLocal() { return isLocal() || (outerClass != null && outerClass.isInsideLocal()); } /** * Tell if the class is local or anonymous class, or inside * such a class, which means it cannot be mentioned outside of * its file. */ public final boolean isInsideLocalOrAnonymous() { return isLocal() || isAnonymous () || (outerClass != null && outerClass.isInsideLocalOrAnonymous()); } /** * Return a simple identifier for this class (idNull if anonymous). */ public Identifier getLocalName() { if (localName != null) { return localName; } // This is also the name of the innerClassMember, if any: return getName().getFlatName().getName(); } /** * Set the local name of a class. Must be a local class. */ public void setLocalName(Identifier name) { if (isLocal()) { localName = name; } } /** * If inner, get the field for this class in the enclosing class */ public final MemberDefinition getInnerClassMember() { if (outerClass == null) return null; if (innerClassMember == null) { // We must find the field in the outer class. Identifier nm = getName().getFlatName().getName(); for (MemberDefinition field = outerClass.getFirstMatch(nm); field != null; field = field.getNextMatch()) { if (field.isInnerClass()) { innerClassMember = field; break; } } if (innerClassMember == null) throw new CompilerError("getInnerClassField"); } return innerClassMember; } /** * If inner, return an innermost uplevel self pointer, if any exists. * Otherwise, return null. */ public final MemberDefinition findOuterMember() { return outerMember; } /** * See if this is a (nested) static class. */ public final boolean isStatic() { return (modifiers & ACC_STATIC) != 0; } /** * Get the class' top-level enclosing class */ public final ClassDefinition getTopClass() { ClassDefinition p, q; for (p = this; (q = p.outerClass) != null; p = q) ; return p; } /** * Get the class' first field or first match */ public final MemberDefinition getFirstMember() { return firstMember; } public final MemberDefinition getFirstMatch(Identifier name) { return fieldHash.get(name); } /** * Get the class' name */ public final Identifier getName() { return declaration.getName(); } /** * Get the class' type */ public final Type getType() { return declaration.getType(); } /** * Get the class' documentation */ public String getDocumentation() { return documentation; } /** * Return true if the given documentation string contains a deprecation * paragraph. This is true if the string contains the tag @deprecated * is the first word in a line. */ public static boolean containsDeprecated(String documentation) { if (documentation == null) { return false; } doScan: for (int scan = 0; (scan = documentation.indexOf(paraDeprecated, scan)) >= 0; scan += paraDeprecated.length()) { // make sure there is only whitespace between this word // and the beginning of the line for (int beg = scan-1; beg >= 0; beg--) { char ch = documentation.charAt(beg); if (ch == '\n' || ch == '\r') { break; // OK } if (!Character.isSpace(ch)) { continue doScan; } } // make sure the char after the word is space or end of line int end = scan+paraDeprecated.length(); if (end < documentation.length()) { char ch = documentation.charAt(end); if (!(ch == '\n' || ch == '\r') && !Character.isSpace(ch)) { continue doScan; } } return true; } return false; } public final boolean inSamePackage(ClassDeclaration c) { // find out if the class stored in c is defined in the same // package as the current class. return inSamePackage(c.getName().getQualifier()); } public final boolean inSamePackage(ClassDefinition c) { // find out if the class stored in c is defined in the same // package as the current class. return inSamePackage(c.getName().getQualifier()); } public final boolean inSamePackage(Identifier packageName) { return (getName().getQualifier().equals(packageName)); } /** * Checks */ public final boolean isInterface() { return (getModifiers() & M_INTERFACE) != 0; } public final boolean isClass() { return (getModifiers() & M_INTERFACE) == 0; } public final boolean isPublic() { return (getModifiers() & M_PUBLIC) != 0; } public final boolean isPrivate() { return (getModifiers() & M_PRIVATE) != 0; } public final boolean isProtected() { return (getModifiers() & M_PROTECTED) != 0; } public final boolean isPackagePrivate() { return (modifiers & (M_PUBLIC | M_PRIVATE | M_PROTECTED)) == 0; } public final boolean isFinal() { return (getModifiers() & M_FINAL) != 0; } public final boolean isAbstract() { return (getModifiers() & M_ABSTRACT) != 0; } public final boolean isSynthetic() { return (getModifiers() & M_SYNTHETIC) != 0; } public final boolean isDeprecated() { return (getModifiers() & M_DEPRECATED) != 0; } public final boolean isAnonymous() { return (getModifiers() & M_ANONYMOUS) != 0; } public final boolean isLocal() { return (getModifiers() & M_LOCAL) != 0; } public final boolean hasConstructor() { return getFirstMatch(idInit) != null; } /** * Check to see if a class must be abstract. This method replaces * isAbstract(env) */ public final boolean mustBeAbstract(Environment env) { // If it is declared abstract, return true. // (Fix for 4110534.) if (isAbstract()) { return true; } // Check to see if the class should have been declared to be // abstract. // We make sure that the inherited method collection has been // performed. collectInheritedMethods(env); // We check for any abstract methods inherited or declared // by this class. Iterator methods = getMethods(); while (methods.hasNext()) { MemberDefinition method = methods.next(); if (method.isAbstract()) { return true; } } // We check for hidden "permanently abstract" methods in // our superclasses. return getPermanentlyAbstractMethods().hasNext(); } /** * Check if this is a super class of another class */ public boolean superClassOf(Environment env, ClassDeclaration otherClass) throws ClassNotFound { while (otherClass != null) { if (getClassDeclaration().equals(otherClass)) { return true; } otherClass = otherClass.getClassDefinition(env).getSuperClass(); } return false; } /** * Check if this is an enclosing class of another class */ public boolean enclosingClassOf(ClassDefinition otherClass) { while ((otherClass = otherClass.getOuterClass()) != null) { if (this == otherClass) { return true; } } return false; } /** * Check if this is a sub class of another class */ public boolean subClassOf(Environment env, ClassDeclaration otherClass) throws ClassNotFound { ClassDeclaration c = getClassDeclaration(); while (c != null) { if (c.equals(otherClass)) { return true; } c = c.getClassDefinition(env).getSuperClass(); } return false; } /** * Check if this class is implemented by another class */ public boolean implementedBy(Environment env, ClassDeclaration c) throws ClassNotFound { for (; c != null ; c = c.getClassDefinition(env).getSuperClass()) { if (getClassDeclaration().equals(c)) { return true; } ClassDeclaration intf[] = c.getClassDefinition(env).getInterfaces(); for (int i = 0 ; i < intf.length ; i++) { if (implementedBy(env, intf[i])) { return true; } } } return false; } /** * Check to see if a class which implements interface `this' could * possibly implement the interface `intDef'. Note that the only * way that this can fail is if `this' and `intDef' have methods * which are of the same signature and different return types. This * method is used by Environment.explicitCast() to determine if a * cast between two interfaces is legal. * * This method should only be called on a class after it has been * basicCheck()'ed. */ public boolean couldImplement(ClassDefinition intDef) { // Check to see if we could have done the necessary checks. if (!doInheritanceChecks) { throw new CompilerError("couldImplement: no checks"); } // This method should only be called for interfaces. if (!isInterface() || !intDef.isInterface()) { throw new CompilerError("couldImplement: not interface"); } // Make sure we are not called before we have collected our // inheritance information. if (allMethods == null) { throw new CompilerError("couldImplement: called early"); } // Get the other classes' methods. getMethods() in // general can return methods which are not visible to the // current package. We need to make sure that these do not // prevent this class from being implemented. Iterator otherMethods = intDef.getMethods(); while (otherMethods.hasNext()) { // Get one of the methods from intDef... MemberDefinition method = otherMethods.next(); Identifier name = method.getName(); Type type = method.getType(); // See if we implement a method of the same signature... MemberDefinition myMethod = allMethods.lookupSig(name, type); //System.out.println("Comparing\n\t" + myMethod + // "\nand\n\t" + method); if (myMethod != null) { // We do. Make sure the methods have the same return type. if (!myMethod.sameReturnType(method)) { return false; } } } return true; } /** * Check if another class can be accessed from the 'extends' or 'implements' * clause of this class. */ public boolean extendsCanAccess(Environment env, ClassDeclaration c) throws ClassNotFound { // Names in the 'extends' or 'implements' clause of an inner class // are checked as if they appeared in the body of the surrounding class. if (outerClass != null) { return outerClass.canAccess(env, c); } // We are a package member. ClassDefinition cdef = c.getClassDefinition(env); if (cdef.isLocal()) { // No locals should be in scope in the 'extends' or // 'implements' clause of a package member. throw new CompilerError("top local"); } if (cdef.isInnerClass()) { MemberDefinition f = cdef.getInnerClassMember(); // Access to public member is always allowed. if (f.isPublic()) { return true; } // Private access is ok only from the same class nest. This can // happen only if the class represented by 'this' encloses the inner // class represented by 'f'. if (f.isPrivate()) { return getClassDeclaration().equals(f.getTopClass().getClassDeclaration()); } // Protected or default access -- allow access if in same package. return getName().getQualifier().equals(f.getClassDeclaration().getName().getQualifier()); } // Access to public member is always allowed. if (cdef.isPublic()) { return true; } // Default access -- allow access if in same package. return getName().getQualifier().equals(c.getName().getQualifier()); } /** * Check if another class can be accessed from within the body of this class. */ public boolean canAccess(Environment env, ClassDeclaration c) throws ClassNotFound { ClassDefinition cdef = c.getClassDefinition(env); if (cdef.isLocal()) { // if it's in scope, it's accessible return true; } if (cdef.isInnerClass()) { return canAccess(env, cdef.getInnerClassMember()); } // Public access is always ok if (cdef.isPublic()) { return true; } // It must be in the same package return getName().getQualifier().equals(c.getName().getQualifier()); } /** * Check if a field can be accessed from a class */ public boolean canAccess(Environment env, MemberDefinition f) throws ClassNotFound { // Public access is always ok if (f.isPublic()) { return true; } // Protected access is ok from a subclass if (f.isProtected() && subClassOf(env, f.getClassDeclaration())) { return true; } // Private access is ok only from the same class nest if (f.isPrivate()) { return getTopClass().getClassDeclaration() .equals(f.getTopClass().getClassDeclaration()); } // It must be in the same package return getName().getQualifier().equals(f.getClassDeclaration().getName().getQualifier()); } /** * Check if a class is entitled to inline access to a class from * another class. */ public boolean permitInlinedAccess(Environment env, ClassDeclaration c) throws ClassNotFound { return (env.opt() && c.equals(declaration)) || (env.opt_interclass() && canAccess(env, c)); } /** * Check if a class is entitled to inline access to a method from * another class. */ public boolean permitInlinedAccess(Environment env, MemberDefinition f) throws ClassNotFound { return (env.opt() && (f.clazz.getClassDeclaration().equals(declaration))) || (env.opt_interclass() && canAccess(env, f)); } /** * We know the field is marked protected (and not public) and that * the field is visible (as per canAccess). Can we access the field as * ., where has the type ? * * Protected fields can only be accessed when the accessorType is a * subclass of the current class */ public boolean protectedAccess(Environment env, MemberDefinition f, Type accessorType) throws ClassNotFound { return // static protected fields are accessible f.isStatic() || // allow array.clone() (accessorType.isType(TC_ARRAY) && (f.getName() == idClone) && (f.getType().getArgumentTypes().length == 0)) || // is a subtype of the current class (accessorType.isType(TC_CLASS) && env.getClassDefinition(accessorType.getClassName()) .subClassOf(env, getClassDeclaration())) || // we are accessing the field from a friendly class (same package) (getName().getQualifier() .equals(f.getClassDeclaration().getName().getQualifier())); } /** * Find or create an access method for a private member, * or return null if this is not possible. */ public MemberDefinition getAccessMember(Environment env, Context ctx, MemberDefinition field, boolean isSuper) { throw new CompilerError("binary getAccessMember"); } /** * Find or create an update method for a private member, * or return null if this is not possible. */ public MemberDefinition getUpdateMember(Environment env, Context ctx, MemberDefinition field, boolean isSuper) { throw new CompilerError("binary getUpdateMember"); } /** * Get a field from this class. Report ambiguous fields. * If no accessible field is found, this method may return an * inaccessible field to allow a useful error message. * * getVariable now takes the source class `source' as an argument. * This allows getVariable to check whether a field is inaccessible * before it signals that a field is ambiguous. The compiler used to * signal an ambiguity even when one of the fields involved was not * accessible. (bug 4053724) */ public MemberDefinition getVariable(Environment env, Identifier nm, ClassDefinition source) throws AmbiguousMember, ClassNotFound { return getVariable0(env, nm, source, true, true); } /* * private fields are never inherited. package-private fields are * not inherited across package boundaries. To capture this, we * take two booleans as parameters: showPrivate indicates whether * we have passed a class boundary, and showPackage indicates whether * we have crossed a package boundary. */ private MemberDefinition getVariable0(Environment env, Identifier nm, ClassDefinition source, boolean showPrivate, boolean showPackage) throws AmbiguousMember, ClassNotFound { // Check to see if this field is defined in the current class for (MemberDefinition member = getFirstMatch(nm); member != null; member = member.getNextMatch()) { if (member.isVariable()) { if ((showPrivate || !member.isPrivate()) && (showPackage || !member.isPackagePrivate())) { // It is defined in this class. return member; } else { // Even though this definition is not inherited, // it hides all definitions in supertypes. return null; } } } // Find the field in our superclass. ClassDeclaration sup = getSuperClass(); MemberDefinition field = null; if (sup != null) { field = sup.getClassDefinition(env) .getVariable0(env, nm, source, false, showPackage && inSamePackage(sup)); } // Find the field in our superinterfaces. for (int i = 0 ; i < interfaces.length ; i++) { // Try to look up the field in an interface. Since interfaces // only have public fields, the values of the two boolean // arguments are not important. MemberDefinition field2 = interfaces[i].getClassDefinition(env) .getVariable0(env, nm, source, true, true); if (field2 != null) { // If we have two different, accessible fields, then // we've found an ambiguity. if (field != null && source.canAccess(env, field) && field2 != field) { throw new AmbiguousMember(field2, field); } field = field2; } } return field; } /** * Tells whether to report a deprecation error for this class. */ public boolean reportDeprecated(Environment env) { return (isDeprecated() || (outerClass != null && outerClass.reportDeprecated(env))); } /** * Note that this class is being used somehow by {@code ref}. * Report deprecation errors, etc. */ public void noteUsedBy(ClassDefinition ref, long where, Environment env) { // (Have this deal with canAccess() checks, too?) if (reportDeprecated(env)) { env.error(where, "warn.class.is.deprecated", this); } } /** * Get an inner class. * Look in supers but not outers. * (This is used directly to resolve expressions like "site.K", and * inside a loop to resolve lone names like "K" or the "K" in "K.L".) * * Called from 'Context' and 'FieldExpression' as well as this class. * * @see FieldExpression.checkCommon * @see resolveName */ public MemberDefinition getInnerClass(Environment env, Identifier nm) throws ClassNotFound { // Note: AmbiguousClass will not be thrown unless and until // inner classes can be defined inside interfaces. // Check if it is defined in the current class for (MemberDefinition field = getFirstMatch(nm); field != null ; field = field.getNextMatch()) { if (field.isInnerClass()) { if (field.getInnerClass().isLocal()) { continue; // ignore this name; it is internally generated } return field; } } // Get it from the super class // It is likely that 'getSuperClass()' could be made to work here // but we would have to assure somehow that 'resolveTypeStructure' // has been called on the current class nest. Since we can get // here from 'resolveName', which is called from 'resolveSupers', // it is possible that the first attempt to resolve the superclass // will originate here, instead of in the call to 'getSuperClass' // in 'checkSupers'. See 'resolveTypeStructure', in which a call // to 'resolveSupers' precedes the call to 'checkSupers'. Why is // name resolution done twice, first in 'resolveName'? // NOTE: 'SourceMember.resolveTypeStructure' may initiate type // structure resolution for an inner class. Normally, this // occurs during the resolution of the outer class, but fields // added after the resolution of their containing class will // be resolved late -- see 'addMember(env,field)' below. // This should only happen for synthetic members, which should // never be an inner class. ClassDeclaration sup = getSuperClass(env); if (sup != null) return sup.getClassDefinition(env).getInnerClass(env, nm); return null; } /** * Lookup a method. This code implements the method lookup * mechanism specified in JLS 15.11.2. * * This mechanism cannot be used to lookup synthetic methods. */ private MemberDefinition matchMethod(Environment env, ClassDefinition accessor, Identifier methodName, Type[] argumentTypes, boolean isAnonConstCall, Identifier accessPackage) throws AmbiguousMember, ClassNotFound { if (allMethods == null || !allMethods.isFrozen()) { // This may be too restrictive. throw new CompilerError("matchMethod called early"); // collectInheritedMethods(env); } // A tentative maximally specific method. MemberDefinition tentative = null; // A list of other methods which may be maximally specific too. List candidateList = null; // Get all the methods inherited by this class which // have the name `methodName'. Iterator methods = allMethods.lookupName(methodName); while (methods.hasNext()) { MemberDefinition method = methods.next(); // See if this method is applicable. if (!env.isApplicable(method, argumentTypes)) { continue; } // See if this method is accessible. if (accessor != null) { if (!accessor.canAccess(env, method)) { continue; } } else if (isAnonConstCall) { if (method.isPrivate() || (method.isPackagePrivate() && accessPackage != null && !inSamePackage(accessPackage))) { // For anonymous constructor accesses, we // haven't yet built an accessing class. // We disallow anonymous classes from seeing // private/package-private inaccessible // constructors in their superclass. continue; } } else { // If accessor is null, we assume that the access // is allowed. Query: is this option used? } if (tentative == null) { // `method' becomes our tentative maximally specific match. tentative = method; } else { if (env.isMoreSpecific(method, tentative)) { // We have found a method which is a strictly better // match than `tentative'. Replace it. tentative = method; } else { // If this method could possibly be another // maximally specific method, add it to our // list of other candidates. if (!env.isMoreSpecific(tentative,method)) { if (candidateList == null) { candidateList = new ArrayList<>(); } candidateList.add(method); } } } } if (tentative != null && candidateList != null) { // Find out if our `tentative' match is a uniquely // maximally specific. Iterator candidates = candidateList.iterator(); while (candidates.hasNext()) { MemberDefinition method = candidates.next(); if (!env.isMoreSpecific(tentative, method)) { throw new AmbiguousMember(tentative, method); } } } return tentative; } /** * Lookup a method. This code implements the method lookup * mechanism specified in JLS 15.11.2. * * This mechanism cannot be used to lookup synthetic methods. */ public MemberDefinition matchMethod(Environment env, ClassDefinition accessor, Identifier methodName, Type[] argumentTypes) throws AmbiguousMember, ClassNotFound { return matchMethod(env, accessor, methodName, argumentTypes, false, null); } /** * Lookup a method. This code implements the method lookup * mechanism specified in JLS 15.11.2. * * This mechanism cannot be used to lookup synthetic methods. */ public MemberDefinition matchMethod(Environment env, ClassDefinition accessor, Identifier methodName) throws AmbiguousMember, ClassNotFound { return matchMethod(env, accessor, methodName, Type.noArgs, false, null); } /** * A version of matchMethod to be used only for constructors * when we cannot pass in a sourceClass argument. We just assert * our package name. * * This is used only for anonymous classes, where we have to look up * a (potentially) protected constructor with no valid sourceClass * parameter available. */ public MemberDefinition matchAnonConstructor(Environment env, Identifier accessPackage, Type argumentTypes[]) throws AmbiguousMember, ClassNotFound { return matchMethod(env, null, idInit, argumentTypes, true, accessPackage); } /** * Find a method, ie: exact match in this class or any of the super * classes. * * Only called by javadoc. For now I am holding off rewriting this * code to rely on collectInheritedMethods(), as that code has * not gotten along with javadoc in the past. */ public MemberDefinition findMethod(Environment env, Identifier nm, Type t) throws ClassNotFound { // look in the current class MemberDefinition f; for (f = getFirstMatch(nm) ; f != null ; f = f.getNextMatch()) { // Note that non-method types return false for equalArguments(). if (f.getType().equalArguments(t)) { return f; } } // constructors are not inherited if (nm.equals(idInit)) { return null; } // look in the super class ClassDeclaration sup = getSuperClass(); if (sup == null) return null; return sup.getClassDefinition(env).findMethod(env, nm, t); } // We create a stub for this. Source classes do more work. protected void basicCheck(Environment env) throws ClassNotFound { // Do the outer class first. if (outerClass != null) outerClass.basicCheck(env); } /** * Check this class. */ public void check(Environment env) throws ClassNotFound { } public Vset checkLocalClass(Environment env, Context ctx, Vset vset, ClassDefinition sup, Expression args[], Type argTypes[] ) throws ClassNotFound { throw new CompilerError("checkLocalClass"); } //--------------------------------------------------------------- // The non-synthetic methods defined in this class or in any // of its parents (class or interface). This member is used // to cache work done in collectInheritedMethods for use by // getMethods() and matchMethod(). It should be accessed by // no other method without forethought. MethodSet allMethods = null; // One of our superclasses may contain an abstract method which // we are unable to ever implement. This happens when there is // a package-private abstract method in our parent and we are in // a different package than our parent. In these cases, we // keep a list of the "permanently abstract" or "unimplementable" // methods so that we can correctly detect that this class is // indeed abstract and so that we can give somewhat comprehensible // error messages. private List permanentlyAbstractMethods = new ArrayList<>(); /** * This method returns an Iterator of all abstract methods * in our superclasses which we are unable to implement. */ protected Iterator getPermanentlyAbstractMethods() { // This method can only be called after collectInheritedMethods. if (allMethods == null) { throw new CompilerError("isPermanentlyAbstract() called early"); } return permanentlyAbstractMethods.iterator(); } /** * A flag used by turnOffInheritanceChecks() to indicate if * inheritance checks are on or off. */ protected static boolean doInheritanceChecks = true; /** * This is a workaround to allow javadoc to turn off certain * inheritance/override checks which interfere with javadoc * badly. In the future it might be good to eliminate the * shared sources of javadoc and javac to avoid the need for this * sort of workaround. */ public static void turnOffInheritanceChecks() { doInheritanceChecks = false; } /** * Add all of the methods declared in or above `parent' to * `allMethods', the set of methods in the current class. * `myMethods' is the set of all methods declared in this * class, and `mirandaMethods' is a repository for Miranda methods. * If mirandaMethods is null, no mirandaMethods will be * generated. * * For a definition of Miranda methods, see the comment above the * method addMirandaMethods() which occurs later in this file. */ private void collectOneClass(Environment env, ClassDeclaration parent, MethodSet myMethods, MethodSet allMethods, MethodSet mirandaMethods) { // System.out.println("Inheriting methods from " + parent); try { ClassDefinition pClass = parent.getClassDefinition(env); Iterator methods = pClass.getMethods(env); while (methods.hasNext()) { MemberDefinition method = methods.next(); // Private methods are not inherited. // // Constructors are not inherited. // // Any non-abstract methods in an interface come // from java.lang.Object. This means that they // should have already been added to allMethods // when we walked our superclass lineage. if (method.isPrivate() || method.isConstructor() || (pClass.isInterface() && !method.isAbstract())) { continue; } // Get the components of the methods' signature. Identifier name = method.getName(); Type type = method.getType(); // Check for a method of the same signature which // was locally declared. MemberDefinition override = myMethods.lookupSig(name, type); // Is this method inaccessible due to package-private // visibility? if (method.isPackagePrivate() && !inSamePackage(method.getClassDeclaration())) { if (override != null && this instanceof sun.tools.javac.SourceClass) { // We give a warning when a class shadows an // inaccessible package-private method from // its superclass. This warning is meant // to prevent people from relying on overriding // when it does not happen. This warning should // probably be removed to be consistent with the // general "no warnings" policy of this // compiler. // // The `instanceof' above is a hack so that only // SourceClass generates this warning, not a // BinaryClass, for example. env.error(method.getWhere(), "warn.no.override.access", override, override.getClassDeclaration(), method.getClassDeclaration()); } // If our superclass has a package-private abstract // method that we have no access to, then we add // this method to our list of permanently abstract // methods. The idea is, since we cannot override // the method, we can never make this class // non-abstract. if (method.isAbstract()) { permanentlyAbstractMethods.add(method); } // `method' is inaccessible. We do not inherit it. continue; } if (override != null) { // `method' and `override' have the same signature. // We are required to check that `override' is a // legal override of `method' //System.out.println ("About to check override of " + // method); override.checkOverride(env, method); } else { // In the absence of a definition in the class // itself, we check to see if this definition // can be successfully merged with any other // inherited definitions. // Have we added a member of the same signature // to `allMethods' already? MemberDefinition formerMethod = allMethods.lookupSig(name, type); // If the previous definition is nonexistent or // ignorable, replace it. if (formerMethod == null) { //System.out.println("Added " + method + " to " + // this); if (mirandaMethods != null && pClass.isInterface() && !isInterface()) { // Whenever a class inherits a method // from an interface, that method is // one of our "miranda" methods. Early // VMs require that these methods be // added as true members to the class // to enable method lookup to work in the // VM. method = new sun.tools.javac.SourceMember(method,this, env); mirandaMethods.add(method); //System.out.println("Added " + method + // " to " + this + " as a Miranda"); } // There is no previous inherited definition. // Add `method' to `allMethods'. allMethods.add(method); } else if (isInterface() && !formerMethod.isAbstract() && method.isAbstract()) { // If we are in an interface and we have inherited // both an abstract method and a non-abstract method // then we know that the non-abstract method is // a placeholder from Object put in for type checking // and the abstract method was already checked to // be proper by our superinterface. allMethods.replace(method); } else { // Okay, `formerMethod' and `method' both have the // same signature. See if they are compatible. //System.out.println ("About to check meet of " + // method); if (!formerMethod.checkMeet(env, method, this.getClassDeclaration())) { // The methods are incompatible. Skip to // next method. continue; } if (formerMethod.couldOverride(env, method)) { // Do nothing. The current definition // is specific enough. //System.out.println("trivial meet of " + // method); continue; } if (method.couldOverride(env, formerMethod)) { // `method' is more specific than // `formerMethod'. replace `formerMethod'. //System.out.println("new def of " + method); if (mirandaMethods != null && pClass.isInterface() && !isInterface()) { // Whenever a class inherits a method // from an interface, that method is // one of our "miranda" methods. Early // VMs require that these methods be // added as true members to the class // to enable method lookup to work in the // VM. method = new sun.tools.javac.SourceMember(method, this,env); mirandaMethods.replace(method); //System.out.println("Added " + method + // " to " + this + " as a Miranda"); } allMethods.replace(method); continue; } // Neither method is more specific than the other. // Oh well. We need to construct a nontrivial // meet of the two methods. // // This is not yet implemented, so we give // a message with a helpful workaround. env.error(this.where, "nontrivial.meet", method, formerMethod.getClassDefinition(), method.getClassDeclaration() ); } } } } catch (ClassNotFound ee) { env.error(getWhere(), "class.not.found", ee.name, this); } } /** *

Collect all methods defined in this class or inherited from * any of our superclasses or interfaces. Look for any * incompatible definitions. * *

This function is also responsible for collecting the * Miranda methods for a class. For a definition of * Miranda methods, see the comment in addMirandaMethods() * below. */ protected void collectInheritedMethods(Environment env) { // The methods defined in this class. MethodSet myMethods; MethodSet mirandaMethods; //System.out.println("Called collectInheritedMethods() for " + // this); if (allMethods != null) { if (allMethods.isFrozen()) { // We have already done the collection. No need to // do it again. return; } else { // We have run into a circular need to collect our methods. // This should not happen at this stage. throw new CompilerError("collectInheritedMethods()"); } } myMethods = new MethodSet(); allMethods = new MethodSet(); // For testing, do not generate miranda methods. if (env.version12()) { mirandaMethods = null; } else { mirandaMethods = new MethodSet(); } // Any methods defined in the current class get added // to both the myMethods and the allMethods MethodSets. for (MemberDefinition member = getFirstMember(); member != null; member = member.nextMember) { // We only collect methods. Initializers are not relevant. if (member.isMethod() && !member.isInitializer()) { //System.out.println("Declared in " + this + ", " + member); //////////////////////////////////////////////////////////// // PCJ 2003-07-30 modified the following code because with // the covariant return type feature of the 1.5 compiler, // there might be multiple methods with the same signature // but different return types, and MethodSet doesn't // support that. We use a new utility method that attempts // to ensure that the appropriate method winds up in the // MethodSet. See 4892308. //////////////////////////////////////////////////////////// // myMethods.add(member); // allMethods.add(member); //////////////////////////////////////////////////////////// methodSetAdd(env, myMethods, member); methodSetAdd(env, allMethods, member); //////////////////////////////////////////////////////////// } } // We're ready to start adding inherited methods. First add // the methods from our superclass. //System.out.println("About to start superclasses for " + this); ClassDeclaration scDecl = getSuperClass(env); if (scDecl != null) { collectOneClass(env, scDecl, myMethods, allMethods, mirandaMethods); // Make sure that we add all unimplementable methods from our // superclass to our list of unimplementable methods. ClassDefinition sc = scDecl.getClassDefinition(); Iterator supIter = sc.getPermanentlyAbstractMethods(); while (supIter.hasNext()) { permanentlyAbstractMethods.add(supIter.next()); } } // Now we inherit all of the methods from our interfaces. //System.out.println("About to start interfaces for " + this); for (int i = 0; i < interfaces.length; i++) { collectOneClass(env, interfaces[i], myMethods, allMethods, mirandaMethods); } allMethods.freeze(); // Now we have collected all of our methods from our superclasses // and interfaces into our `allMethods' member. Good. As a last // task, we add our collected miranda methods to this class. // // If we do not add the mirandas to the class explicitly, there // will be no code generated for them. if (mirandaMethods != null && mirandaMethods.size() > 0) { addMirandaMethods(env, mirandaMethods.iterator()); } } //////////////////////////////////////////////////////////// // PCJ 2003-07-30 added this utility method to insulate // MethodSet additions from the covariant return type // feature of the 1.5 compiler. When there are multiple // methods with the same signature and different return // types to be added, we try to ensure that the one with // the most specific return type winds up in the MethodSet. // This logic was not put into MethodSet itself because it // requires access to an Environment for type relationship // checking. No error checking is performed here, but that // should be OK because this code is only still used by // rmic. See 4892308. //////////////////////////////////////////////////////////// private static void methodSetAdd(Environment env, MethodSet methodSet, MemberDefinition newMethod) { MemberDefinition oldMethod = methodSet.lookupSig(newMethod.getName(), newMethod.getType()); if (oldMethod != null) { Type oldReturnType = oldMethod.getType().getReturnType(); Type newReturnType = newMethod.getType().getReturnType(); try { if (env.isMoreSpecific(newReturnType, oldReturnType)) { methodSet.replace(newMethod); } } catch (ClassNotFound ignore) { } } else { methodSet.add(newMethod); } } //////////////////////////////////////////////////////////// /** * Get an Iterator of all methods which could be accessed in an * instance of this class. */ public Iterator getMethods(Environment env) { if (allMethods == null) { collectInheritedMethods(env); } return getMethods(); } /** * Get an Iterator of all methods which could be accessed in an * instance of this class. Throw a compiler error if we haven't * generated this information yet. */ public Iterator getMethods() { if (allMethods == null) { throw new CompilerError("getMethods: too early"); } return allMethods.iterator(); } // In early VM's there was a bug -- the VM didn't walk the interfaces // of a class looking for a method, they only walked the superclass // chain. This meant that abstract methods defined only in interfaces // were not being found. To fix this bug, a counter-bug was introduced // in the compiler -- the so-called Miranda methods. If a class // does not provide a definition for an abstract method in one of // its interfaces then the compiler inserts one in the class artificially. // That way the VM didn't have to bother looking at the interfaces. // // This is a problem. Miranda methods are not part of the specification. // But they continue to be inserted so that old VM's can run new code. // Someday, when the old VM's are gone, perhaps classes can be compiled // without Miranda methods. Towards this end, the compiler has a // flag, -nomiranda, which can turn off the creation of these methods. // Eventually that behavior should become the default. // // Why are they called Miranda methods? Well the sentence "If the // class is not able to provide a method, then one will be provided // by the compiler" is very similar to the sentence "If you cannot // afford an attorney, one will be provided by the court," -- one // of the so-called "Miranda" rights in the United States. /** * Add a list of methods to this class as miranda methods. This * gets overridden with a meaningful implementation in SourceClass. * BinaryClass should not need to do anything -- it should already * have its miranda methods and, if it doesn't, then that doesn't * affect our compilation. */ protected void addMirandaMethods(Environment env, Iterator mirandas) { // do nothing. } //--------------------------------------------------------------- public void inlineLocalClass(Environment env) { } /** * We create a stub for this. Source classes do more work. * Some calls from 'SourceClass.checkSupers' execute this method. * @see sun.tools.javac.SourceClass#resolveTypeStructure */ public void resolveTypeStructure(Environment env) { } /** * Look up an inner class name, from somewhere inside this class. * Since supers and outers are in scope, search them too. *

* If no inner class is found, env.resolveName() is then called, * to interpret the ambient package and import directives. *

* This routine operates on a "best-efforts" basis. If * at some point a class is not found, the partially-resolved * identifier is returned. Eventually, someone else has to * try to get the ClassDefinition and diagnose the ClassNotFound. *

* resolveName() looks at surrounding scopes, and hence * pulling in both inherited and uplevel types. By contrast, * resolveInnerClass() is intended only for interpreting * explicitly qualified names, and so look only at inherited * types. Also, resolveName() looks for package prefixes, * which appear similar to "very uplevel" outer classes. *

* A similar (but more complex) name-lookup process happens * when field and identifier expressions denoting qualified names * are type-checked. The added complexity comes from the fact * that variables may occur in such names, and take precedence * over class and package names. *

* In the expression type-checker, resolveInnerClass() is paralleled * by code in FieldExpression.checkAmbigName(), which also calls * ClassDefinition.getInnerClass() to interpret names of the form * "OuterClass.Inner" (and also outerObject.Inner). The checking * of an identifier expression that fails to be a variable is referred * directly to resolveName(). */ public Identifier resolveName(Environment env, Identifier name) { if (tracing) env.dtEvent("ClassDefinition.resolveName: " + name); // This logic is pretty much exactly parallel to that of // Environment.resolveName(). if (name.isQualified()) { // Try to resolve the first identifier component, // because inner class names take precedence over // package prefixes. (Cf. Environment.resolveName.) Identifier rhead = resolveName(env, name.getHead()); if (rhead.hasAmbigPrefix()) { // The first identifier component refers to an // ambiguous class. Limp on. We throw away the // rest of the classname as it is irrelevant. // (part of solution for 4059855). return rhead; } if (!env.classExists(rhead)) { return env.resolvePackageQualifiedName(name); } try { return env.getClassDefinition(rhead). resolveInnerClass(env, name.getTail()); } catch (ClassNotFound ee) { // return partially-resolved name someone else can fail on return Identifier.lookupInner(rhead, name.getTail()); } } // This method used to fail to look for local classes, thus a // reference to a local class within, e.g., the type of a member // declaration, would fail to resolve if the immediately enclosing // context was an inner class. The code added below is ugly, but // it works, and is lifted from existing code in 'Context.resolveName' // and 'Context.getClassCommon'. See the comments there about the design. // Fixes 4095716. int ls = -2; LocalMember lf = null; if (classContext != null) { lf = classContext.getLocalClass(name); if (lf != null) { ls = lf.getScopeNumber(); } } // Look for an unqualified name in enclosing scopes. for (ClassDefinition c = this; c != null; c = c.outerClass) { try { MemberDefinition f = c.getInnerClass(env, name); if (f != null && (lf == null || classContext.getScopeNumber(c) > ls)) { // An uplevel member was found, and was nested more deeply than // any enclosing local of the same name. return f.getInnerClass().getName(); } } catch (ClassNotFound ee) { // a missing superclass, or something catastrophic } } // No uplevel member found, so use the enclosing local if one was found. if (lf != null) { return lf.getInnerClass().getName(); } // look in imports, etc. return env.resolveName(name); } /** * Interpret a qualified class name, which may have further subcomponents.. * Follow inheritance links, as in: * class C { class N { } } class D extends C { } ... new D.N() ... * Ignore outer scopes and packages. * @see resolveName */ public Identifier resolveInnerClass(Environment env, Identifier nm) { if (nm.isInner()) throw new CompilerError("inner"); if (nm.isQualified()) { Identifier rhead = resolveInnerClass(env, nm.getHead()); try { return env.getClassDefinition(rhead). resolveInnerClass(env, nm.getTail()); } catch (ClassNotFound ee) { // return partially-resolved name someone else can fail on return Identifier.lookupInner(rhead, nm.getTail()); } } else { try { MemberDefinition f = getInnerClass(env, nm); if (f != null) { return f.getInnerClass().getName(); } } catch (ClassNotFound ee) { // a missing superclass, or something catastrophic } // Fake a good name for a diagnostic. return Identifier.lookupInner(this.getName(), nm); } } /** * While resolving import directives, the question has arisen: * does a given inner class exist? If the top-level class exists, * we ask it about an inner class via this method. * This method looks only at the literal name of the class, * and does not attempt to follow inheritance links. * This is necessary, since at the time imports are being * processed, inheritance links have not been resolved yet. * (Thus, an import directive must always spell a class * name exactly.) */ public boolean innerClassExists(Identifier nm) { for (MemberDefinition field = getFirstMatch(nm.getHead()) ; field != null ; field = field.getNextMatch()) { if (field.isInnerClass()) { if (field.getInnerClass().isLocal()) { continue; // ignore this name; it is internally generated } return !nm.isQualified() || field.getInnerClass().innerClassExists(nm.getTail()); } } return false; } /** * Find any method with a given name. */ public MemberDefinition findAnyMethod(Environment env, Identifier nm) throws ClassNotFound { MemberDefinition f; for (f = getFirstMatch(nm) ; f != null ; f = f.getNextMatch()) { if (f.isMethod()) { return f; } } // look in the super class ClassDeclaration sup = getSuperClass(); if (sup == null) return null; return sup.getClassDefinition(env).findAnyMethod(env, nm); } /** * Given the fact that this class has no method "nm" matching "argTypes", * find out if the mismatch can be blamed on a particular actual argument * which disagrees with all of the overloadings. * If so, return the code (i<<2)+(castOK<<1)+ambig, where * "i" is the number of the offending argument, and * "castOK" is 1 if a cast could fix the problem. * The target type for the argument is returned in margTypeResult[0]. * If not all methods agree on this type, "ambig" is 1. * If there is more than one method, the choice of target type is * arbitrary.

* Return -1 if every argument is acceptable to at least one method. * Return -2 if there are no methods of the required arity. * The value "start" gives the index of the first argument to begin * checking. */ public int diagnoseMismatch(Environment env, Identifier nm, Type argTypes[], int start, Type margTypeResult[]) throws ClassNotFound { int haveMatch[] = new int[argTypes.length]; Type margType[] = new Type[argTypes.length]; if (!diagnoseMismatch(env, nm, argTypes, start, haveMatch, margType)) return -2; for (int i = start; i < argTypes.length; i++) { if (haveMatch[i] < 4) { margTypeResult[0] = margType[i]; return (i<<2) | haveMatch[i]; } } return -1; } private boolean diagnoseMismatch(Environment env, Identifier nm, Type argTypes[], int start, int haveMatch[], Type margType[]) throws ClassNotFound { // look in the current class boolean haveOne = false; MemberDefinition f; for (f = getFirstMatch(nm) ; f != null ; f = f.getNextMatch()) { if (!f.isMethod()) { continue; } Type fArgTypes[] = f.getType().getArgumentTypes(); if (fArgTypes.length == argTypes.length) { haveOne = true; for (int i = start; i < argTypes.length; i++) { Type at = argTypes[i]; Type ft = fArgTypes[i]; if (env.implicitCast(at, ft)) { haveMatch[i] = 4; continue; } else if (haveMatch[i] <= 2 && env.explicitCast(at, ft)) { if (haveMatch[i] < 2) margType[i] = null; haveMatch[i] = 2; } else if (haveMatch[i] > 0) { continue; } if (margType[i] == null) margType[i] = ft; else if (margType[i] != ft) haveMatch[i] |= 1; } } } // constructors are not inherited if (nm.equals(idInit)) { return haveOne; } // look in the super class ClassDeclaration sup = getSuperClass(); if (sup != null) { if (sup.getClassDefinition(env).diagnoseMismatch(env, nm, argTypes, start, haveMatch, margType)) haveOne = true; } return haveOne; } /** * Add a field (no checks) */ public void addMember(MemberDefinition field) { //System.out.println("ADD = " + field); if (firstMember == null) { firstMember = lastMember = field; } else if (field.isSynthetic() && field.isFinal() && field.isVariable()) { // insert this at the front, because of initialization order field.nextMember = firstMember; firstMember = field; field.nextMatch = fieldHash.get(field.name); } else { lastMember.nextMember = field; lastMember = field; field.nextMatch = fieldHash.get(field.name); } fieldHash.put(field.name, field); } /** * Add a field (subclasses make checks) */ public void addMember(Environment env, MemberDefinition field) { addMember(field); if (resolved) { // a late addition field.resolveTypeStructure(env); } } /** * Find or create an uplevel reference for the given target. */ public UplevelReference getReference(LocalMember target) { for (UplevelReference r = references; r != null; r = r.getNext()) { if (r.getTarget() == target) { return r; } } return addReference(target); } protected UplevelReference addReference(LocalMember target) { if (target.getClassDefinition() == this) { throw new CompilerError("addReference "+target); } referencesMustNotBeFrozen(); UplevelReference r = new UplevelReference(this, target); references = r.insertInto(references); return r; } /** * Return the list of all uplevel references. */ public UplevelReference getReferences() { return references; } /** * Return the same value as getReferences. * Also, mark the set of references frozen. * After that, it is an error to add new references. */ public UplevelReference getReferencesFrozen() { referencesFrozen = true; return references; } /** * assertion check */ public final void referencesMustNotBeFrozen() { if (referencesFrozen) { throw new CompilerError("referencesMustNotBeFrozen "+this); } } /** * Get helper method for class literal lookup. */ public MemberDefinition getClassLiteralLookup(long fwhere) { throw new CompilerError("binary class"); } /** * Add a dependency */ public void addDependency(ClassDeclaration c) { throw new CompilerError("addDependency"); } /** * Maintain a hash table of local and anonymous classes * whose internal names are prefixed by the current class. * The key is the simple internal name, less the prefix. */ public ClassDefinition getLocalClass(String name) { if (localClasses == null) { return null; } else { return localClasses.get(name); } } public void addLocalClass(ClassDefinition c, String name) { if (localClasses == null) { localClasses = new Hashtable<>(LOCAL_CLASSES_SIZE); } localClasses.put(name, c); } /** * Print for debugging */ public void print(PrintStream out) { if (isPublic()) { out.print("public "); } if (isInterface()) { out.print("interface "); } else { out.print("class "); } out.print(getName() + " "); if (getSuperClass() != null) { out.print("extends " + getSuperClass().getName() + " "); } if (interfaces.length > 0) { out.print("implements "); for (int i = 0 ; i < interfaces.length ; i++) { if (i > 0) { out.print(", "); } out.print(interfaces[i].getName()); out.print(" "); } } out.println("{"); for (MemberDefinition f = getFirstMember() ; f != null ; f = f.getNextMember()) { out.print(" "); f.print(out); } out.println("}"); } /** * Convert to String */ public String toString() { return getClassDeclaration().toString(); } /** * After the class has been written to disk, try to free up * some storage. */ public void cleanup(Environment env) { if (env.dump()) { env.output("[cleanup " + getName() + "]"); } for (MemberDefinition f = getFirstMember() ; f != null ; f = f.getNextMember()) { f.cleanup(env); } // keep "references" around, for the sake of local subclasses documentation = null; } }