/* * Copyright (c) 1994, 2004, 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.javac; import sun.tools.java.*; import sun.tools.tree.Node; import sun.tools.java.Package; import java.util.*; import java.io.*; /** * Main environment of the batch version of the Java compiler, * this needs more work. * * 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. */ @Deprecated public class BatchEnvironment extends Environment implements ErrorConsumer { /** * The stream where error message are printed. */ OutputStream out; /** * The path we use for finding source files. */ protected ClassPath sourcePath; /** * The path we use for finding class (binary) files. */ protected ClassPath binaryPath; /** * A hashtable of resource contexts. */ Hashtable<Identifier, Package> packages = new Hashtable<>(31); /** * The classes, in order of appearance. */ Vector<ClassDeclaration> classesOrdered = new Vector<>(); /** * The classes, keyed by ClassDeclaration. */ Hashtable<Type, ClassDeclaration> classes = new Hashtable<>(351); /** * flags */ public int flags; /** * Major and minor versions to use for generated class files. * Environments that extend BatchEnvironment (such as javadoc's * Env class) get the default values below. * * javac itself may override these versions with values determined * from the command line "-target" option. */ public short majorVersion = JAVA_DEFAULT_VERSION; public short minorVersion = JAVA_DEFAULT_MINOR_VERSION; // JCOV /** * coverage data file */ public File covFile; // end JCOV /** * The number of errors and warnings */ public int nerrors; public int nwarnings; public int ndeprecations; /** * A list of files containing deprecation warnings. */ Vector<Object> deprecationFiles = new Vector<>(); /** * writes out error messages */ ErrorConsumer errorConsumer; /** * Old constructors -- these constructors build a BatchEnvironment * with an old-style class path. */ public BatchEnvironment(ClassPath path) { this(System.out, path); } public BatchEnvironment(OutputStream out, ClassPath path) { this(out, path, (ErrorConsumer) null); } public BatchEnvironment(OutputStream out, ClassPath path, ErrorConsumer errorConsumer) { this(out, path, path, errorConsumer); } /** * New constructors -- these constructors build a BatchEnvironment * with a source path and a binary path. */ public BatchEnvironment(ClassPath sourcePath, ClassPath binaryPath) { this(System.out, sourcePath, binaryPath); } public BatchEnvironment(OutputStream out, ClassPath sourcePath, ClassPath binaryPath) { this(out, sourcePath, binaryPath, (ErrorConsumer) null); } public BatchEnvironment(OutputStream out, ClassPath sourcePath, ClassPath binaryPath, ErrorConsumer errorConsumer) { this.out = out; this.sourcePath = sourcePath; this.binaryPath = binaryPath; this.errorConsumer = (errorConsumer == null) ? this : errorConsumer; } /** * Factory */ static BatchEnvironment create(OutputStream out, String srcPathString, String classPathString, String sysClassPathString) { ClassPath[] classPaths = classPaths(srcPathString, classPathString, sysClassPathString); return new BatchEnvironment(out, classPaths[0], classPaths[1]); } protected static ClassPath[] classPaths(String srcPathString, String classPathString, String sysClassPathString) { // Create our source classpath and our binary classpath ClassPath sourcePath; ClassPath binaryPath; StringBuffer binaryPathBuffer = new StringBuffer(); if (classPathString == null) { // The env.class.path property is the user's CLASSPATH // environment variable, and it set by the wrapper (ie, // javac.exe). classPathString = System.getProperty("env.class.path"); if (classPathString == null) { classPathString = "."; } } if (srcPathString == null) { srcPathString = classPathString; } if (sysClassPathString == null) { sysClassPathString = System.getProperty("sun.boot.class.path"); if (sysClassPathString == null) { // shouldn't happen; recover gracefully sysClassPathString = classPathString; } } appendPath(binaryPathBuffer, sysClassPathString); appendPath(binaryPathBuffer, classPathString); sourcePath = new ClassPath(srcPathString); binaryPath = new ClassPath(binaryPathBuffer.toString()); return new ClassPath[]{sourcePath, binaryPath}; } private static void appendPath(StringBuffer buf, String str) { if (str.length() > 0) { if (buf.length() > 0) { buf.append(File.pathSeparator); } buf.append(str); } } /** * Return flags */ public int getFlags() { return flags; } /** * Return major version to use for generated class files */ public short getMajorVersion() { return majorVersion; } /** * Return minor version to use for generated class files */ public short getMinorVersion() { return minorVersion; } // JCOV /** * Return coverage data file */ public File getcovFile() { return covFile; } // end JCOV /** * Return an enumeration of all the currently defined classes * in order of appearance to getClassDeclaration(). */ public Enumeration<ClassDeclaration> getClasses() { return classesOrdered.elements(); } /** * A set of Identifiers for all packages exempt from the "exists" * check in Imports#resolve(). These are the current packages for * all classes being compiled as of the first call to isExemptPackage. */ private Set<Identifier> exemptPackages; /** * Tells whether an Identifier refers to a package which should be * exempt from the "exists" check in Imports#resolve(). */ public boolean isExemptPackage(Identifier id) { if (exemptPackages == null) { // Collect a list of the packages of all classes currently // being compiled. setExemptPackages(); } return exemptPackages.contains(id); } /** * Set the set of packages which are exempt from the exists check * in Imports#resolve(). */ private void setExemptPackages() { // The JLS gives us the freedom to define "accessibility" of // a package in whatever manner we wish. After the evaluation // of bug 4093217, we have decided to consider a package P // accessible if either: // // 1. The directory corresponding to P exists on the classpath. // 2. For any class C currently being compiled, C belongs to // package P. // 3. For any class C currently being compiled, C belongs to // package Q and Q is a subpackage of P. // // In order to implement this, we collect the current packages // (and prefixes) of all packages we have found so far. These // will be exempt from the "exists" check in // sun.tools.java.Imports#resolve(). exemptPackages = new HashSet<>(101); // Add all of the current packages and their prefixes to our set. for (Enumeration<ClassDeclaration> e = getClasses(); e.hasMoreElements(); ) { ClassDeclaration c = e.nextElement(); if (c.getStatus() == CS_PARSED) { SourceClass def = (SourceClass) c.getClassDefinition(); if (def.isLocal()) continue; Identifier pkg = def.getImports().getCurrentPackage(); // Add the name of this package and all of its prefixes // to our set. while (pkg != idNull && exemptPackages.add(pkg)) { pkg = pkg.getQualifier(); } } } // Before we go any further, we make sure java.lang is // accessible and that it is not ambiguous. These checks // are performed for "ordinary" packages in // sun.tools.java.Imports#resolve(). The reason we perform // them specially for java.lang is that we want to report // the error once, and outside of any particular file. // Check to see if java.lang is accessible. if (!exemptPackages.contains(idJavaLang)) { // Add java.lang to the set of exempt packages. exemptPackages.add(idJavaLang); try { if (!getPackage(idJavaLang).exists()) { // java.lang doesn't exist. error(0, "package.not.found.strong", idJavaLang); return; } } catch (IOException ee) { // We got an IO exception checking to see if the package // java.lang exists. error(0, "io.exception.package", idJavaLang); } } // Next we ensure that java.lang is not both a class and // a package. (Fix for 4101529) // // This change has been backed out because, on WIN32, it // failed to take character case into account. It will // be put back in later. // // Identifier resolvedName = // resolvePackageQualifiedName(idJavaLang); // Identifier topClassName = resolvedName.getTopName(); // //if (Imports.importable(topClassName, env)) { // if (Imports.importable(topClassName, this)) { // // It is a package and a class. Emit the error. // error(0, "package.class.conflict.strong", // idJavaLang, topClassName); // return; // } } /** * Get a class, given the fully qualified class name */ public ClassDeclaration getClassDeclaration(Identifier nm) { return getClassDeclaration(Type.tClass(nm)); } public ClassDeclaration getClassDeclaration(Type t) { ClassDeclaration c = classes.get(t); if (c == null) { classes.put(t, c = new ClassDeclaration(t.getClassName())); classesOrdered.addElement(c); } return c; } /** * Check if a class exists * Applies only to package members (non-nested classes). */ public boolean classExists(Identifier nm) { if (nm.isInner()) { nm = nm.getTopName(); // just in case } Type t = Type.tClass(nm); try { ClassDeclaration c = classes.get(t); return (c != null) ? c.getName().equals(nm) : getPackage(nm.getQualifier()).classExists(nm.getName()); } catch (IOException e) { return true; } } /** * Generate a new name similar to the given one. * Do it in such a way that repeated compilations of * the same source generate the same series of names. */ // This code does not perform as stated above. // Correction below is part of fix for bug id 4056065. // // NOTE: The method 'generateName' has now been folded into its // single caller, 'makeClassDefinition', which appears later in // this file. /*--------------------------* public Identifier generateName(ClassDefinition outerClass, Identifier nm) { Identifier outerNm = outerClass.getName(); Identifier flat = outerNm.getFlatName(); Identifier stem = Identifier.lookup(outerNm.getQualifier(), flat.getHead()); for (int i = 1; ; i++) { String name = i + (nm.equals(idNull) ? "" : SIG_INNERCLASS + nm); Identifier nm1 = Identifier.lookupInner(stem, Identifier.lookup(name)); if (classes.get(Type.tClass(nm1)) == null) return nm1; } } *--------------------------*/ /** * Get the package path for a package */ public Package getPackage(Identifier pkg) throws IOException { Package p = packages.get(pkg); if (p == null) { packages.put(pkg, p = new Package(sourcePath, binaryPath, pkg)); } return p; } /** * Parse a source file */ public void parseFile(ClassFile file) throws FileNotFoundException { long tm = System.currentTimeMillis(); InputStream input; BatchParser p; if (tracing) dtEnter("parseFile: PARSING SOURCE " + file); Environment env = new Environment(this, file); try { input = file.getInputStream(); env.setCharacterEncoding(getCharacterEncoding()); // p = new BatchParser(e, new BufferedInputStream(input)); p = new BatchParser(env, input); } catch(IOException ex) { if (tracing) dtEvent("parseFile: IO EXCEPTION " + file); throw new FileNotFoundException(); } try { p.parseFile(); } catch(Exception e) { throw new CompilerError(e); } try { input.close(); } catch (IOException ex) { // We're turn with the input, so ignore this. } if (verbose()) { tm = System.currentTimeMillis() - tm; output(Main.getText("benv.parsed_in", file.getPath(), Long.toString(tm))); } if (p.classes.size() == 0) { // The JLS allows a file to contain no compilation units -- // that is, it allows a file to contain no classes or interfaces. // In this case, we are still responsible for checking that the // imports resolve properly. The way the compiler is organized, // this is the last point at which we still have enough information // to do so. (Fix for 4041851). p.imports.resolve(env); } else { // In an attempt to see that classes which come from the // same source file are all recompiled when any one of them // would be recompiled (when using the -depend option) we // introduce artificial dependencies between these classes. // We do this by calling the addDependency() method, which // adds a (potentially unused) class reference to the constant // pool of the class. // // Previously, we added a dependency from every class in the // file, to every class in the file. This introduced, in // total, a quadratic number of potentially bogus constant // pool entries. This was bad. Now we add our artificial // dependencies in such a way that the classes are connected // in a circle. While single links is probably sufficient, the // code below adds double links just to be diligent. // (Fix for 4108286). // // Note that we don't chain in inner classes. The links // between them and their outerclass should be sufficient // here. // (Fix for 4107960). // // The dependency code was previously in BatchParser.java. Enumeration<SourceClass> e = p.classes.elements(); // first will not be an inner class. ClassDefinition first = e.nextElement(); if (first.isInnerClass()) { throw new CompilerError("BatchEnvironment, first is inner"); } ClassDefinition current = first; ClassDefinition next; while (e.hasMoreElements()) { next = e.nextElement(); // Don't chain in inner classes. if (next.isInnerClass()) { continue; } current.addDependency(next.getClassDeclaration()); next.addDependency(current.getClassDeclaration()); current = next; } // Make a circle. Don't bother to add a dependency if there // is only one class in the file. if (current != first) { current.addDependency(first.getClassDeclaration()); first.addDependency(current.getClassDeclaration()); } } if (tracing) dtExit("parseFile: SOURCE PARSED " + file); } /** * Load a binary file */ BinaryClass loadFile(ClassFile file) throws IOException { long tm = System.currentTimeMillis(); InputStream input = file.getInputStream(); BinaryClass c = null; if (tracing) dtEnter("loadFile: LOADING CLASSFILE " + file); try { DataInputStream is = new DataInputStream(new BufferedInputStream(input)); c = BinaryClass.load(new Environment(this, file), is, loadFileFlags()); } catch (ClassFormatError e) { error(0, "class.format", file.getPath(), e.getMessage()); if (tracing) dtExit("loadFile: CLASS FORMAT ERROR " + file); return null; } catch (java.io.EOFException e) { // If we get an EOF while processing a class file, then // it has been truncated. We let other I/O errors pass // through. Fix for 4088443. error(0, "truncated.class", file.getPath()); return null; } input.close(); if (verbose()) { tm = System.currentTimeMillis() - tm; output(Main.getText("benv.loaded_in", file.getPath(), Long.toString(tm))); } if (tracing) dtExit("loadFile: CLASSFILE LOADED " + file); return c; } /** * Default flags for loadFile. Subclasses may override this. */ int loadFileFlags() { return 0; } /** * Load a binary class */ boolean needsCompilation(Hashtable<ClassDeclaration, ClassDeclaration> check, ClassDeclaration c) { switch (c.getStatus()) { case CS_UNDEFINED: if (tracing) dtEnter("needsCompilation: UNDEFINED " + c.getName()); loadDefinition(c); return needsCompilation(check, c); case CS_UNDECIDED: if (tracing) dtEnter("needsCompilation: UNDECIDED " + c.getName()); if (check.get(c) == null) { check.put(c, c); BinaryClass bin = (BinaryClass)c.getClassDefinition(); for (Enumeration<ClassDeclaration> e = bin.getDependencies() ; e.hasMoreElements() ;) { ClassDeclaration dep = e.nextElement(); if (needsCompilation(check, dep)) { // It must be source, dependencies need compilation c.setDefinition(bin, CS_SOURCE); if (tracing) dtExit("needsCompilation: YES (source) " + c.getName()); return true; } } } if (tracing) dtExit("needsCompilation: NO (undecided) " + c.getName()); return false; case CS_BINARY: if (tracing) { dtEnter("needsCompilation: BINARY " + c.getName()); dtExit("needsCompilation: NO (binary) " + c.getName()); } return false; } if (tracing) dtExit("needsCompilation: YES " + c.getName()); return true; } /** * Load the definition of a class * or at least determine how to load it. * The caller must repeat calls to this method * until it the state converges to CS_BINARY, CS_PARSED, or the like.. * @see ClassDeclaration#getClassDefinition */ public void loadDefinition(ClassDeclaration c) { if (tracing) dtEnter("loadDefinition: ENTER " + c.getName() + ", status " + c.getStatus()); switch (c.getStatus()) { case CS_UNDEFINED: { if (tracing) dtEvent("loadDefinition: STATUS IS UNDEFINED"); Identifier nm = c.getName(); Package pkg; try { pkg = getPackage(nm.getQualifier()); } catch (IOException e) { // If we can't get at the package, then we'll just // have to set the class to be not found. c.setDefinition(null, CS_NOTFOUND); error(0, "io.exception", c); if (tracing) dtExit("loadDefinition: IO EXCEPTION (package)"); return; } ClassFile binfile = pkg.getBinaryFile(nm.getName()); if (binfile == null) { // must be source, there is no binary c.setDefinition(null, CS_SOURCE); if (tracing) dtExit("loadDefinition: MUST BE SOURCE (no binary) " + c.getName()); return; } ClassFile srcfile = pkg.getSourceFile(nm.getName()); if (srcfile == null) { if (tracing) dtEvent("loadDefinition: NO SOURCE " + c.getName()); BinaryClass bc = null; try { bc = loadFile(binfile); } catch (IOException e) { // If we can't access the binary, set the class to // be not found. (bug id 4030497) c.setDefinition(null, CS_NOTFOUND); error(0, "io.exception", binfile); if (tracing) dtExit("loadDefinition: IO EXCEPTION (binary)"); return; } if ((bc != null) && !bc.getName().equals(nm)) { error(0, "wrong.class", binfile.getPath(), c, bc); bc = null; if (tracing) dtEvent("loadDefinition: WRONG CLASS (binary)"); } if (bc == null) { // no source nor binary found c.setDefinition(null, CS_NOTFOUND); if (tracing) dtExit("loadDefinition: NOT FOUND (source or binary)"); return; } // Couldn't find the source, try the one mentioned in the binary if (bc.getSource() != null) { srcfile = ClassFile.newClassFile(new File((String)bc.getSource())); // Look for the source file srcfile = pkg.getSourceFile(srcfile.getName()); if ((srcfile != null) && srcfile.exists()) { if (tracing) dtEvent("loadDefinition: FILENAME IN BINARY " + srcfile); if (srcfile.lastModified() > binfile.lastModified()) { // must be source, it is newer than the binary c.setDefinition(bc, CS_SOURCE); if (tracing) dtEvent("loadDefinition: SOURCE IS NEWER " + srcfile); bc.loadNested(this); if (tracing) dtExit("loadDefinition: MUST BE SOURCE " + c.getName()); return; } if (dependencies()) { c.setDefinition(bc, CS_UNDECIDED); if (tracing) dtEvent("loadDefinition: UNDECIDED " + c.getName()); } else { c.setDefinition(bc, CS_BINARY); if (tracing) dtEvent("loadDefinition: MUST BE BINARY " + c.getName()); } bc.loadNested(this); if (tracing) dtExit("loadDefinition: EXIT " + c.getName() + ", status " + c.getStatus()); return; } } // It must be binary, there is no source c.setDefinition(bc, CS_BINARY); if (tracing) dtEvent("loadDefinition: MUST BE BINARY (no source) " + c.getName()); bc.loadNested(this); if (tracing) dtExit("loadDefinition: EXIT " + c.getName() + ", status " + c.getStatus()); return; } BinaryClass bc = null; try { if (srcfile.lastModified() > binfile.lastModified()) { // must be source, it is newer than the binary c.setDefinition(null, CS_SOURCE); if (tracing) dtEvent("loadDefinition: MUST BE SOURCE (younger than binary) " + c.getName()); return; } bc = loadFile(binfile); } catch (IOException e) { error(0, "io.exception", binfile); if (tracing) dtEvent("loadDefinition: IO EXCEPTION (binary)"); } if ((bc != null) && !bc.getName().equals(nm)) { error(0, "wrong.class", binfile.getPath(), c, bc); bc = null; if (tracing) dtEvent("loadDefinition: WRONG CLASS (binary)"); } if (bc != null) { Identifier name = bc.getName(); if (name.equals(c.getName())) { if (dependencies()) { c.setDefinition(bc, CS_UNDECIDED); if (tracing) dtEvent("loadDefinition: UNDECIDED " + name); } else { c.setDefinition(bc, CS_BINARY); if (tracing) dtEvent("loadDefinition: MUST BE BINARY " + name); } } else { c.setDefinition(null, CS_NOTFOUND); if (tracing) dtEvent("loadDefinition: NOT FOUND (source or binary)"); if (dependencies()) { getClassDeclaration(name).setDefinition(bc, CS_UNDECIDED); if (tracing) dtEvent("loadDefinition: UNDECIDED " + name); } else { getClassDeclaration(name).setDefinition(bc, CS_BINARY); if (tracing) dtEvent("loadDefinition: MUST BE BINARY " + name); } } } else { c.setDefinition(null, CS_NOTFOUND); if (tracing) dtEvent("loadDefinition: NOT FOUND (source or binary)"); } if (bc != null && bc == c.getClassDefinition()) bc.loadNested(this); if (tracing) dtExit("loadDefinition: EXIT " + c.getName() + ", status " + c.getStatus()); return; } case CS_UNDECIDED: { if (tracing) dtEvent("loadDefinition: STATUS IS UNDECIDED"); Hashtable<ClassDeclaration, ClassDeclaration> tab = new Hashtable<>(); if (!needsCompilation(tab, c)) { // All undecided classes that this class depends on must be binary for (Enumeration<ClassDeclaration> e = tab.keys() ; e.hasMoreElements() ; ) { ClassDeclaration dep = e.nextElement(); if (dep.getStatus() == CS_UNDECIDED) { // must be binary, dependencies need compilation dep.setDefinition(dep.getClassDefinition(), CS_BINARY); if (tracing) dtEvent("loadDefinition: MUST BE BINARY " + dep); } } } if (tracing) dtExit("loadDefinition: EXIT " + c.getName() + ", status " + c.getStatus()); return; } case CS_SOURCE: { if (tracing) dtEvent("loadDefinition: STATUS IS SOURCE"); ClassFile srcfile = null; Package pkg = null; if (c.getClassDefinition() != null) { // Use the source file name from the binary class file try { pkg = getPackage(c.getName().getQualifier()); srcfile = pkg.getSourceFile((String)c.getClassDefinition().getSource()); } catch (IOException e) { error(0, "io.exception", c); if (tracing) dtEvent("loadDefinition: IO EXCEPTION (package)"); } if (srcfile == null) { String fn = (String)c.getClassDefinition().getSource(); srcfile = ClassFile.newClassFile(new File(fn)); } } else { // Get a source file name from the package Identifier nm = c.getName(); try { pkg = getPackage(nm.getQualifier()); srcfile = pkg.getSourceFile(nm.getName()); } catch (IOException e) { error(0, "io.exception", c); if (tracing) dtEvent("loadDefinition: IO EXCEPTION (package)"); } if (srcfile == null) { // not found, there is no source c.setDefinition(null, CS_NOTFOUND); if (tracing) dtExit("loadDefinition: SOURCE NOT FOUND " + c.getName() + ", status " + c.getStatus()); return; } } try { parseFile(srcfile); } catch (FileNotFoundException e) { error(0, "io.exception", srcfile); if (tracing) dtEvent("loadDefinition: IO EXCEPTION (source)"); } if ((c.getClassDefinition() == null) || (c.getStatus() == CS_SOURCE)) { // not found after parsing the file error(0, "wrong.source", srcfile.getPath(), c, pkg); c.setDefinition(null, CS_NOTFOUND); if (tracing) dtEvent("loadDefinition: WRONG CLASS (source) " + c.getName()); } if (tracing) dtExit("loadDefinition: EXIT " + c.getName() + ", status " + c.getStatus()); return; } } if (tracing) dtExit("loadDefinition: EXIT " + c.getName() + ", status " + c.getStatus()); } /** * Create a new class. */ public ClassDefinition makeClassDefinition(Environment toplevelEnv, long where, IdentifierToken name, String doc, int modifiers, IdentifierToken superClass, IdentifierToken interfaces[], ClassDefinition outerClass) { Identifier nm = name.getName(); long nmpos = name.getWhere(); Identifier pkgNm; String mangledName = null; ClassDefinition localContextClass = null; // Provide name for a local class. This used to be set after // the class was created, but it is needed for checking within // the class constructor. // NOTE: It seems that we could always provide the simple name, // and thereby avoid the test in 'ClassDefinition.getLocalName()' // for the definedness of the local name. There, if the local // name is not set, a simple name is extracted from the result of // 'getName()'. That name can potentially change, however, as // it is ultimately derived from 'ClassType.className', which is // set by 'Type.changeClassName'. Better leave this alone... Identifier localName = null; if (nm.isQualified() || nm.isInner()) { pkgNm = nm; } else if ((modifiers & (M_LOCAL | M_ANONYMOUS)) != 0) { // Inaccessible class. Create a name of the form // 'PackageMember.N$localName' or 'PackageMember.N'. // Note that the '.' will be converted later to a '$'. // pkgNm = generateName(outerClass, nm); localContextClass = outerClass.getTopClass(); // Always use the smallest number in generating the name that // renders the complete name unique within the top-level class. // This is required to make the names more predictable, as part // of a serialization-related workaround, and satisfies an obscure // requirement that the name of a local class be of the form // 'PackageMember$1$localName' when this name is unique. for (int i = 1 ; ; i++) { mangledName = i + (nm.equals(idNull) ? "" : SIG_INNERCLASS + nm); if (localContextClass.getLocalClass(mangledName) == null) { break; } } Identifier outerNm = localContextClass.getName(); pkgNm = Identifier.lookupInner(outerNm, Identifier.lookup(mangledName)); //System.out.println("LOCAL CLASS: " + pkgNm + " IN " + localContextClass); if ((modifiers & M_ANONYMOUS) != 0) { localName = idNull; } else { // Local class has a locally-scoped name which is independent of pkgNm. localName = nm; } } else if (outerClass != null) { // Accessible inner class. Qualify name with surrounding class name. pkgNm = Identifier.lookupInner(outerClass.getName(), nm); } else { pkgNm = nm; } // Find the class ClassDeclaration c = toplevelEnv.getClassDeclaration(pkgNm); // Make sure this is the first definition if (c.isDefined()) { toplevelEnv.error(nmpos, "class.multidef", c.getName(), c.getClassDefinition().getSource()); // Don't mess with the existing class declarations with same name c = new ClassDeclaration (pkgNm); } if (superClass == null && !pkgNm.equals(idJavaLangObject)) { superClass = new IdentifierToken(idJavaLangObject); } ClassDefinition sourceClass = new SourceClass(toplevelEnv, where, c, doc, modifiers, superClass, interfaces, (SourceClass) outerClass, localName); if (outerClass != null) { // It is a member of its enclosing class. outerClass.addMember(toplevelEnv, new SourceMember(sourceClass)); // Record local (or anonymous) class in the class whose name will // serve as the prefix of the local class name. This is necessary // so that the class may be retrieved from its name, which does not // fully represent the class nesting structure. // See 'ClassDefinition.getClassDefinition'. // This is part of a fix for bugid 4054523 and 4030421. if ((modifiers & (M_LOCAL | M_ANONYMOUS)) != 0) { localContextClass.addLocalClass(sourceClass, mangledName); } } // The local name of an anonymous or local class used to be set here // with a call to 'setLocalName'. This has been moved to the constructor // for 'SourceClass', which now takes a 'localName' argument. return sourceClass; } /* * makeMemberDefinition method is left with rawtypes and with lint messages suppressed. * The addition of Generics to com.sun.tools.* has uncovered an inconsistency * in usage though tools still work correctly as long as this function is allowed to * function as is. */ /** * Create a new field. */ @SuppressWarnings({"rawtypes","unchecked"}) public MemberDefinition makeMemberDefinition(Environment origEnv, long where, ClassDefinition clazz, String doc, int modifiers, Type type, Identifier name, IdentifierToken argNames[], IdentifierToken expIds[], Object value) { if (tracing) dtEvent("makeMemberDefinition: " + name + " IN " + clazz); Vector v = null; if (argNames != null) { v = new Vector(argNames.length); for (int i = 0 ; i < argNames.length ; i++) { v.addElement(argNames[i]); } } SourceMember f = new SourceMember(where, clazz, doc, modifiers, type, name, v, expIds, (Node)value); clazz.addMember(origEnv, f); return f; } /** * Release resources in classpath. */ public void shutdown() { try { if (sourcePath != null) { sourcePath.close(); } if (binaryPath != null && binaryPath != sourcePath) { binaryPath.close(); } } catch (IOException ee) { output(Main.getText("benv.failed_to_close_class_path", ee.toString())); } sourcePath = null; binaryPath = null; super.shutdown(); } /** * Error String */ public String errorString(String err, Object arg1, Object arg2, Object arg3) { String key = null; if(err.startsWith("warn.")) key = "javac.err." + err.substring(5); else key = "javac.err." + err; return Main.getText(key, arg1 != null ? arg1.toString() : null, arg2 != null ? arg2.toString() : null, arg3 != null ? arg3.toString() : null); } /** * The filename where the last errors have occurred */ String errorFileName; /** * List of outstanding error messages */ ErrorMessage errors; /** * Insert an error message in the list of outstanding error messages. * The list is sorted on input position and contains no duplicates. * The return value indicates whether or not the message was * actually inserted. * * The method flushErrors() used to check for duplicate error messages. * It would only detect duplicates if they were contiguous. Removing * non-contiguous duplicate error messages is slightly less complicated * at insertion time, so the functionality was moved here. This also * saves a miniscule number of allocations. */ protected boolean insertError(long where, String message) { //output("ERR = " + message); if (errors == null || errors.where > where) { // If the list is empty, or the error comes before any other // errors, insert it at the beginning of the list. ErrorMessage newMsg = new ErrorMessage(where, message); newMsg.next = errors; errors = newMsg; } else if (errors.where == where && errors.message.equals(message)) { // The new message is an exact duplicate of the first message // in the list. Don't insert it. return false; } else { // Okay, we know that the error doesn't come first. Walk // the list until we find the right position for insertion. ErrorMessage current = errors; ErrorMessage next; while ((next = current.next) != null && next.where < where) { current = next; } // Now walk over any errors with the same location, looking // for duplicates. If we find a duplicate, don't insert the // error. while ((next = current.next) != null && next.where == where) { if (next.message.equals(message)) { // We have found an exact duplicate. Don't bother to // insert the error. return false; } current = next; } // Now insert after current. ErrorMessage newMsg = new ErrorMessage(where, message); newMsg.next = current.next; current.next = newMsg; } // Indicate that the insertion occurred. return true; } private int errorsPushed; /** * Maximum number of errors to print. */ public int errorLimit = 100; private boolean hitErrorLimit; /** * Flush outstanding errors */ public void pushError(String errorFileName, int line, String message, String referenceText, String referenceTextPointer) { int limit = errorLimit + nwarnings; if (++errorsPushed >= limit && errorLimit >= 0) { if (!hitErrorLimit) { hitErrorLimit = true; output(errorString("too.many.errors", errorLimit,null,null)); } return; } if (errorFileName.endsWith(".java")) { output(errorFileName + ":" + line + ": " + message); output(referenceText); output(referenceTextPointer); } else { // It wasn't really a source file (probably an error or // warning because of a malformed or badly versioned // class file. output(errorFileName + ": " + message); } } public void flushErrors() { if (errors == null) { return; } boolean inputAvail = false; // Read the file char data[] = null; int dataLength = 0; // A malformed file encoding could cause a CharConversionException. // If something bad happens while trying to find the source file, // don't bother trying to show lines. try { FileInputStream in = new FileInputStream(errorFileName); data = new char[in.available()]; InputStreamReader reader = (getCharacterEncoding() != null ? new InputStreamReader(in, getCharacterEncoding()) : new InputStreamReader(in)); dataLength = reader.read(data); reader.close(); inputAvail = true; } catch(IOException e) { // inputAvail will not be set } // Report the errors for (ErrorMessage msg = errors ; msg != null ; msg = msg.next) { // There used to be code here which checked // for duplicate error messages. This functionality // has been moved to the method insertError(). See // the comments on that method for more information. int ln = (int) (msg.where >>> WHEREOFFSETBITS); int off = (int) (msg.where & ((1L << WHEREOFFSETBITS) - 1)); if (off > dataLength) off = dataLength; String referenceString = ""; String markerString = ""; if(inputAvail) { int i, j; for (i = off ; (i > 0) && (data[i - 1] != '\n') && (data[i - 1] != '\r') ; i--); for (j = off ; (j < dataLength) && (data[j] != '\n') && (data[j] != '\r') ; j++); referenceString = new String(data, i, j - i); char strdata[] = new char[(off - i) + 1]; for (j = i ; j < off ; j++) { strdata[j-i] = (data[j] == '\t') ? '\t' : ' '; } strdata[off-i] = '^'; markerString = new String(strdata); } errorConsumer.pushError(errorFileName, ln, msg.message, referenceString, markerString); } errors = null; } /** * Report error */ public void reportError(Object src, long where, String err, String msg) { if (src == null) { if (errorFileName != null) { flushErrors(); errorFileName = null; } if (err.startsWith("warn.")) { if (warnings()) { nwarnings++; output(msg); } return; } output("error: " + msg); nerrors++; flags |= F_ERRORSREPORTED; } else if (src instanceof String) { String fileName = (String)src; // Flush errors if we've moved on to a new file. if (!fileName.equals(errorFileName)) { flushErrors(); errorFileName = fileName; } // Classify `err' as a warning, deprecation warning, or // error message. Proceed accordingly. if (err.startsWith("warn.")) { if (err.indexOf("is.deprecated") >= 0) { // This is a deprecation warning. Add `src' to the // list of files with deprecation warnings. if (!deprecationFiles.contains(src)) { deprecationFiles.addElement(src); } // If we are reporting deprecations, try to add it // to our list. Otherwise, just increment the // deprecation count. if (deprecation()) { if (insertError(where, msg)) { ndeprecations++; } } else { ndeprecations++; } } else { // This is a regular warning. If we are reporting // warnings, try to add it to the list. Otherwise, just // increment the warning count. if (warnings()) { if (insertError(where, msg)) { nwarnings++; } } else { nwarnings++; } } } else { // This is an error. Try to add it to the list of errors. // If it isn't a duplicate, increment our error count. if (insertError(where, msg)) { nerrors++; flags |= F_ERRORSREPORTED; } } } else if (src instanceof ClassFile) { reportError(((ClassFile)src).getPath(), where, err, msg); } else if (src instanceof Identifier) { reportError(src.toString(), where, err, msg); } else if (src instanceof ClassDeclaration) { try { reportError(((ClassDeclaration)src).getClassDefinition(this), where, err, msg); } catch (ClassNotFound e) { reportError(((ClassDeclaration)src).getName(), where, err, msg); } } else if (src instanceof ClassDefinition) { ClassDefinition c = (ClassDefinition)src; if (!err.startsWith("warn.")) { c.setError(); } reportError(c.getSource(), where, err, msg); } else if (src instanceof MemberDefinition) { reportError(((MemberDefinition)src).getClassDeclaration(), where, err, msg); } else { output(src + ":error=" + err + ":" + msg); } } /** * Issue an error */ public void error(Object source, long where, String err, Object arg1, Object arg2, Object arg3) { if (errorsPushed >= errorLimit + nwarnings) { // Don't bother to queue any more errors if they won't get printed. return; } if (System.getProperty("javac.dump.stack") != null) { output("javac.err."+err+": "+errorString(err, arg1, arg2, arg3)); new Exception("Stack trace").printStackTrace(new PrintStream(out)); } reportError(source, where, err, errorString(err, arg1, arg2, arg3)); } /** * Output a string. This can either be an error message or something * for debugging. */ public void output(String msg) { PrintStream out = this.out instanceof PrintStream ? (PrintStream)this.out : new PrintStream(this.out, true); out.println(msg); } }