blob: a3e2385cdad21820d6afad6924dc49c41e668032 [file] [log] [blame]
/*
* 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 packages = new Hashtable(31);
/**
* The classes, in order of appearance.
*/
Vector classesOrdered = new Vector();
/**
* The classes, keyed by ClassDeclaration.
*/
Hashtable 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 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,
String extDirsString){
ClassPath[] classPaths = classPaths(srcPathString, classPathString,
sysClassPathString, extDirsString);
return new BatchEnvironment(out, classPaths[0], classPaths[1]);
}
protected static ClassPath[] classPaths(String srcPathString,
String classPathString,
String sysClassPathString,
String extDirsString) {
// 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);
if (extDirsString == null) {
extDirsString = System.getProperty("java.ext.dirs");
}
if (extDirsString != null) {
StringTokenizer st = new StringTokenizer(extDirsString,
File.pathSeparator);
while (st.hasMoreTokens()) {
String dirName = st.nextToken();
File dir = new File(dirName);
if (!dirName.endsWith(File.separator)) {
dirName += File.separator;
}
if (dir.isDirectory()) {
String[] files = dir.list();
for (int i = 0; i < files.length; ++i) {
String name = files[i];
if (name.endsWith(".jar")) {
appendPath(binaryPathBuffer, dirName + name);
}
}
}
}
}
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 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 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 e = getClasses(); e.hasMoreElements(); ) {
ClassDeclaration c = (ClassDeclaration) 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 = (ClassDeclaration)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 = (ClassDeclaration)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 = (Package)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 e = p.classes.elements();
// first will not be an inner class.
ClassDefinition first = (ClassDefinition) e.nextElement();
if (first.isInnerClass()) {
throw new CompilerError("BatchEnvironment, first is inner");
}
ClassDefinition current = first;
ClassDefinition next;
while (e.hasMoreElements()) {
next = (ClassDefinition) 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 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 e = bin.getDependencies() ; e.hasMoreElements() ;) {
ClassDeclaration dep = (ClassDeclaration)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 = new ClassFile(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 tab = new Hashtable();
if (!needsCompilation(tab, c)) {
// All undecided classes that this class depends on must be binary
for (Enumeration e = tab.keys() ; e.hasMoreElements() ; ) {
ClassDeclaration dep = (ClassDeclaration)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 = new ClassFile(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;
}
/**
* Create a new field.
*/
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",
new Integer(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);
}
}