/*
* Copyright (c) 1994, 2003, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package sun.tools.java;
import java.util.Stack;
import java.io.IOException;
import sun.tools.tree.Context;
//JCOV
import java.io.File;
//end JCOV
/**
* This class defines the environment for a compilation.
* It is used to load classes, resolve class names and
* report errors. It is an abstract class, a subclass
* must define implementations for some of the functions.<p>
*
* An environment has a source object associated with it.
* This is the thing against which errors are reported, it
* is usually a file name, a field or a class.<p>
*
* Environments can be nested to change the source object.<p>
*
* WARNING: The contents of this source file are not part of any
* supported API. Code that depends on them does so at its own risk:
* they are subject to change or removal without notice.
*
* @author Arthur van Hoff
*/
public class Environment implements Constants {
/**
* The actual environment to which everything is forwarded.
*/
Environment env;
/**
* External character encoding name
*/
String encoding;
/**
* The object that is currently being parsed/compiled.
* It is either a file name (String) or a field (MemberDefinition)
* or a class (ClassDeclaration or ClassDefinition).
*/
Object source;
public Environment(Environment env, Object source) {
if (env != null && env.env != null && env.getClass() == this.getClass())
env = env.env; // a small optimization
this.env = env;
this.source = source;
}
public Environment() {
this(null, null);
}
/**
* Tells whether an Identifier refers to a package which should be
* exempt from the "exists" check in Imports#resolve().
*/
public boolean isExemptPackage(Identifier id) {
return env.isExemptPackage(id);
}
/**
* Return a class declaration given a fully qualified class name.
*/
public ClassDeclaration getClassDeclaration(Identifier nm) {
return env.getClassDeclaration(nm);
}
/**
* Return a class definition given a fully qualified class name.
* <p>
* Should be called only with 'internal' class names, i.e., the result
* of a call to 'resolveName' or a synthetic class name.
*/
public final ClassDefinition getClassDefinition(Identifier nm) throws ClassNotFound {
if (nm.isInner()) {
ClassDefinition c = getClassDefinition(nm.getTopName());
Identifier tail = nm.getFlatName();
walkTail:
while (tail.isQualified()) {
tail = tail.getTail();
Identifier head = tail.getHead();
//System.out.println("CLASS: " + c + " HEAD: " + head + " TAIL: " + tail);
String hname = head.toString();
// If the name is of the form 'ClassName.N$localName', where N is
// a number, the field 'N$localName' may not necessarily be a member
// of the class named by 'ClassName', but might be a member of some
// inaccessible class contained within it. We use 'getLocalClass'
// to do the lookup in this case. This is part of a fix for bugid
// 4054523 and 4030421. See also 'BatchEnvironment.makeClassDefinition'.
// This should also work for anonymous class names of the form
// 'ClassName.N'. Note that the '.' qualifications get converted to
// '$' characters when determining the external name of the class and
// the name of the class file.
if (hname.length() > 0
&& Character.isDigit(hname.charAt(0))) {
ClassDefinition localClass = c.getLocalClass(hname);
if (localClass != null) {
c = localClass;
continue walkTail;
}
} else {
for (MemberDefinition f = c.getFirstMatch(head);
f != null; f = f.getNextMatch()) {
if (f.isInnerClass()) {
c = f.getInnerClass();
continue walkTail;
}
}
}
throw new ClassNotFound(Identifier.lookupInner(c.getName(), head));
}
//System.out.println("FOUND " + c + " FOR " + nm);
return c;
}
return getClassDeclaration(nm).getClassDefinition(this);
}
/**
* Return a class declaration given a type. Only works for
* class types.
*/
public ClassDeclaration getClassDeclaration(Type t) {
return getClassDeclaration(t.getClassName());
}
/**
* Return a class definition given a type. Only works for
* class types.
*/
public final ClassDefinition getClassDefinition(Type t) throws ClassNotFound {
return getClassDefinition(t.getClassName());
}
/**
* Check if a class exists (without actually loading it).
* (Since inner classes cannot in general be examined without
* loading source, this method does not accept inner names.)
*/
public boolean classExists(Identifier nm) {
return env.classExists(nm);
}
public final boolean classExists(Type t) {
return !t.isType(TC_CLASS) || classExists(t.getClassName());
}
/**
* Get the package path for a package
*/
public Package getPackage(Identifier pkg) throws IOException {
return env.getPackage(pkg);
}
/**
* Load the definition of a class.
*/
public void loadDefinition(ClassDeclaration c) {
env.loadDefinition(c);
}
/**
* Return the source of the environment (ie: the thing being compiled/parsed).
*/
public final Object getSource() {
return source;
}
/**
* Resolve a type. Make sure that all the classes referred to by
* the type have a definition. Report errors. Return true if
* the type is well-formed. Presently used for types appearing
* in member declarations, which represent named types internally as
* qualified identifiers. Type names appearing in local variable
* declarations and within expressions are represented as identifier
* or field expressions, and are resolved by 'toType', which delegates
* handling of the non-inner portion of the name to this method.
* <p>
* In 'toType', the various stages of qualification are represented by
* separate AST nodes. Here, we are given a single identifier which
* contains the entire qualification structure. It is not possible in
* general to set the error location to the exact position of a component
* that is in error, so an error message must refer to the entire qualified
* name. An attempt to keep track of the string length of the components of
* the name and to offset the location accordingly fails because the initial
* prefix of the name may have been rewritten by an earlier call to
* 'resolveName'. See 'SourceMember.resolveTypeStructure'. The situation
* is actually even worse than this, because only a single location is
* passed in for an entire declaration, which may contain many type names.
* All error messages are thus poorly localized. These checks should be
* done while traversing the parse tree for the type, not the type descriptor.
* <p>
* DESIGN NOTE:
* As far as I can tell, the two-stage resolution of names represented in
* string form is an artifact of the late implementation of inner classes
* and the use of mangled names internally within the compiler. All
* qualified names should have their hiearchical structure made explicit
* in the parse tree at the phase at which they are presented for static
* semantic checking. This would affect class names appearing in 'extends',
* 'implements', and 'throws' clauses, as well as in member declarations.
*/
public boolean resolve(long where, ClassDefinition c, Type t) {
switch (t.getTypeCode()) {
case TC_CLASS: {
ClassDefinition def;
try {
Identifier nm = t.getClassName();
if (!nm.isQualified() && !nm.isInner() && !classExists(nm)) {
resolve(nm); // elicit complaints about ambiguity
}
def = getQualifiedClassDefinition(where, nm, c, false);
if (!c.canAccess(this, def.getClassDeclaration())) {
// Reported error location may be imprecise
// if the name is qualified.
error(where, "cant.access.class", def);
return true; // return false later
}
def.noteUsedBy(c, where, env);
} catch (AmbiguousClass ee) {
error(where, "ambig.class", ee.name1, ee.name2);
return false;
} catch (ClassNotFound e) {
// For now, report "class.and.package" only when the code
// is going to fail anyway.
try {
if (e.name.isInner() &&
getPackage(e.name.getTopName()).exists()) {
env.error(where, "class.and.package",
e.name.getTopName());
}
} catch (IOException ee) {
env.error(where, "io.exception", "package check");
}
// This error message is also emitted for 'new' expressions.
// error(where, "class.not.found", e.name, "declaration");
error(where, "class.not.found.no.context", e.name);
return false;
}
return true;
}
case TC_ARRAY:
return resolve(where, c, t.getElementType());
case TC_METHOD:
boolean ok = resolve(where, c, t.getReturnType());
Type args[] = t.getArgumentTypes();
for (int i = args.length ; i-- > 0 ; ) {
ok &= resolve(where, c, args[i]);
}
return ok;
}
return true;
}
/**
* Given its fully-qualified name, verify that a class is defined and accessible.
* Used to check components of qualified names in contexts where a class is expected.
* Like 'resolve', but is given a single type name, not a type descriptor.
*/
public boolean resolveByName(long where, ClassDefinition c, Identifier nm) {
return resolveByName(where, c, nm, false);
}
public boolean resolveExtendsByName(long where, ClassDefinition c, Identifier nm) {
return resolveByName(where, c, nm, true);
}
private boolean resolveByName(long where, ClassDefinition c,
Identifier nm, boolean isExtends) {
ClassDefinition def;
try {
if (!nm.isQualified() && !nm.isInner() && !classExists(nm)) {
resolve(nm); // elicit complaints about ambiguity
}
def = getQualifiedClassDefinition(where, nm, c, isExtends);
ClassDeclaration decl = def.getClassDeclaration();
if (!((!isExtends && c.canAccess(this, decl))
||
(isExtends && c.extendsCanAccess(this, decl)))) {
error(where, "cant.access.class", def);
return true; // return false later
}
} catch (AmbiguousClass ee) {
error(where, "ambig.class", ee.name1, ee.name2);
return false;
} catch (ClassNotFound e) {
// For now, report "class.and.package" only when the code
// is going to fail anyway.
try {
if (e.name.isInner() &&
getPackage(e.name.getTopName()).exists()) {
env.error(where, "class.and.package",
e.name.getTopName());
}
} catch (IOException ee) {
env.error(where, "io.exception", "package check");
}
error(where, "class.not.found", e.name, "type name");
return false;
}
return true;
}
/**
* Like 'getClassDefinition(env)', but check access on each component.
* Currently called only by 'resolve' above. It is doubtful that calls
* to 'getClassDefinition(env)' are appropriate now.
*/
public final ClassDefinition
getQualifiedClassDefinition(long where,
Identifier nm,
ClassDefinition ctxClass,
boolean isExtends) throws ClassNotFound {
if (nm.isInner()) {
ClassDefinition c = getClassDefinition(nm.getTopName());
Identifier tail = nm.getFlatName();
walkTail:
while (tail.isQualified()) {
tail = tail.getTail();
Identifier head = tail.getHead();
// System.out.println("CLASS: " + c + " HEAD: " + head + " TAIL: " + tail);
String hname = head.toString();
// Handle synthesized names of local and anonymous classes.
// See 'getClassDefinition(env)' above.
if (hname.length() > 0
&& Character.isDigit(hname.charAt(0))) {
ClassDefinition localClass = c.getLocalClass(hname);
if (localClass != null) {
c = localClass;
continue walkTail;
}
} else {
for (MemberDefinition f = c.getFirstMatch(head);
f != null; f = f.getNextMatch()) {
if (f.isInnerClass()) {
ClassDeclaration rdecl = c.getClassDeclaration();
c = f.getInnerClass();
ClassDeclaration fdecl = c.getClassDeclaration();
// This check is presumably applicable even if the
// original source-code name (expanded by 'resolveNames')
// was a simple, unqualified name. Hopefully, JLS 2e
// will clarify the matter.
if ((!isExtends
&& !ctxClass.canAccess(env, fdecl))
||
(isExtends
&& !ctxClass.extendsCanAccess(env, fdecl))) {
// Reported error location is imprecise.
env.error(where, "no.type.access", head, rdecl, ctxClass);
}
// The JLS 6.6.2 restrictions on access to protected members
// depend in an essential way upon the syntactic form of the name.
// Since the compiler has previously expanded the class names
// here into fully-qualified form ('resolveNames'), this check
// cannot be performed here. Unfortunately, the original names
// are clobbered during 'basicCheck', which is also the phase that
// resolves the inheritance structure, required to implement the
// access restrictions. Pending a large-scale revision of the
// name-resolution machinery, we forgo this check, with the result
// that the JLS 6.6.2 restrictions are not enforced for some cases
// of qualified access to inner classes. Some qualified names are
// resolved elsewhere via a different mechanism, and will be
// treated correctly -- see 'FieldExpression.checkCommon'.
/*---------------------------------------*
if (f.isProtected()) {
Type rty = Type.tClass(rdecl.getName()); // hack
if (!ctxClass.protectedAccess(env, f, rty)) {
// Reported error location is imprecise.
env.error(where, "invalid.protected.type.use",
head, ctxClass, rty);
}
}
*---------------------------------------*/
continue walkTail;
}
}
}
throw new ClassNotFound(Identifier.lookupInner(c.getName(), head));
}
//System.out.println("FOUND " + c + " FOR " + nm);
return c;
}
return getClassDeclaration(nm).getClassDefinition(this);
}
/**
* Resolve the names within a type, returning the adjusted type.
* Adjust class names to reflect scoping.
* Do not report errors.
* <p>
* NOTE: It would be convenient to check for errors here, such as
* verifying that each component of a qualified name exists and is
* accessible. Why must this be done in a separate phase?
* <p>
* If the 'synth' argument is true, indicating that the member whose
* type is being resolved is synthetic, names are resolved with respect
* to the package scope. (Fix for 4097882)
*/
public Type resolveNames(ClassDefinition c, Type t, boolean synth) {
if (tracing) dtEvent("Environment.resolveNames: " + c + ", " + t);
switch (t.getTypeCode()) {
case TC_CLASS: {
Identifier name = t.getClassName();
Identifier rname;
if (synth) {
rname = resolvePackageQualifiedName(name);
} else {
rname = c.resolveName(this, name);
}
if (name != rname) {
t = Type.tClass(rname);
}
break;
}
case TC_ARRAY:
t = Type.tArray(resolveNames(c, t.getElementType(), synth));
break;
case TC_METHOD: {
Type ret = t.getReturnType();
Type rret = resolveNames(c, ret, synth);
Type args[] = t.getArgumentTypes();
Type rargs[] = new Type[args.length];
boolean changed = (ret != rret);
for (int i = args.length ; i-- > 0 ; ) {
Type arg = args[i];
Type rarg = resolveNames(c, arg, synth);
rargs[i] = rarg;
if (arg != rarg) {
changed = true;
}
}
if (changed) {
t = Type.tMethod(rret, rargs);
}
break;
}
}
return t;
}
/**
* Resolve a class name, using only package and import directives.
* Report no errors.
* <p>
*/
public Identifier resolveName(Identifier name) {
// This logic is pretty exactly parallel to that of
// ClassDefinition.resolveName().
if (name.isQualified()) {
// Try to resolve the first identifier component,
// because inner class names take precedence over
// package prefixes. (Cf. ClassDefinition.resolveName.)
Identifier rhead = resolveName(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 (!this.classExists(rhead)) {
return this.resolvePackageQualifiedName(name);
}
try {
return this.getClassDefinition(rhead).
resolveInnerClass(this, name.getTail());
} catch (ClassNotFound ee) {
// return partially-resolved name someone else can fail on
return Identifier.lookupInner(rhead, name.getTail());
}
}
try {
return resolve(name);
} catch (AmbiguousClass ee) {
// Don't force a resolution of the name if it is ambiguous.
// Forcing the resolution would tack the current package
// name onto the front of the class, which would be wrong.
// Instead, mark the name as ambiguous and let a later stage
// find the error by calling env.resolve(name).
// (part of solution for 4059855).
if (name.hasAmbigPrefix()) {
return name;
} else {
return name.addAmbigPrefix();
}
} catch (ClassNotFound ee) {
// last chance to make something halfway sensible
Imports imports = getImports();
if (imports != null)
return imports.forceResolve(this, name);
}
return name;
}
/**
* Discover if name consists of a package prefix, followed by the
* name of a class (that actually exists), followed possibly by
* some inner class names. If we can't find a class that exists,
* return the name unchanged.
* <p>
* This routine is used after a class name fails to
* be resolved by means of imports or inner classes.
* However, import processing uses this routine directly,
* since import names must be exactly qualified to start with.
*/
public final Identifier resolvePackageQualifiedName(Identifier name) {
Identifier tail = null;
for (;;) {
if (classExists(name)) {
break;
}
if (!name.isQualified()) {
name = (tail == null) ? name : Identifier.lookup(name, tail);
tail = null;
break;
}
Identifier nm = name.getName();
tail = (tail == null)? nm: Identifier.lookup(nm, tail);
name = name.getQualifier();
}
if (tail != null)
name = Identifier.lookupInner(name, tail);
return name;
}
/**
* Resolve a class name, using only package and import directives.
*/
public Identifier resolve(Identifier nm) throws ClassNotFound {
if (env == null) return nm; // a pretty useless no-op
return env.resolve(nm);
}
/**
* Get the imports used to resolve class names.
*/
public Imports getImports() {
if (env == null) return null; // lame default
return env.getImports();
}
/**
* Create a new class.
*/
public ClassDefinition makeClassDefinition(Environment origEnv, long where,
IdentifierToken name,
String doc, int modifiers,
IdentifierToken superClass,
IdentifierToken interfaces[],
ClassDefinition outerClass) {
if (env == null) return null; // lame default
return env.makeClassDefinition(origEnv, where, name,
doc, modifiers,
superClass, interfaces, outerClass);
}
/**
* 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 (env == null) return null; // lame default
return env.makeMemberDefinition(origEnv, where, clazz, doc, modifiers,
type, name, argNames, expIds, value);
}
/**
* Returns true if the given method is applicable to the given arguments
*/
public boolean isApplicable(MemberDefinition m, Type args[]) throws ClassNotFound {
Type mType = m.getType();
if (!mType.isType(TC_METHOD))
return false;
Type mArgs[] = mType.getArgumentTypes();
if (args.length != mArgs.length)
return false;
for (int i = args.length ; --i >= 0 ;)
if (!isMoreSpecific(args[i], mArgs[i]))
return false;
return true;
}
/**
* Returns true if "best" is in every argument at least as good as "other"
*/
public boolean isMoreSpecific(MemberDefinition best, MemberDefinition other)
throws ClassNotFound {
Type bestType = best.getClassDeclaration().getType();
Type otherType = other.getClassDeclaration().getType();
boolean result = isMoreSpecific(bestType, otherType)
&& isApplicable(other, best.getType().getArgumentTypes());
// System.out.println("isMoreSpecific: " + best + "/" + other
// + " => " + result);
return result;
}
/**
* Returns true if "from" is a more specific type than "to"
*/
public boolean isMoreSpecific(Type from, Type to) throws ClassNotFound {
return implicitCast(from, to);
}
/**
* Return true if an implicit cast from this type to
* the given type is allowed.
*/
public boolean implicitCast(Type from, Type to) throws ClassNotFound {
if (from == to)
return true;
int toTypeCode = to.getTypeCode();
switch(from.getTypeCode()) {
case TC_BYTE:
if (toTypeCode == TC_SHORT)
return true;
case TC_SHORT:
case TC_CHAR:
if (toTypeCode == TC_INT) return true;
case TC_INT:
if (toTypeCode == TC_LONG) return true;
case TC_LONG:
if (toTypeCode == TC_FLOAT) return true;
case TC_FLOAT:
if (toTypeCode == TC_DOUBLE) return true;
case TC_DOUBLE:
default:
return false;
case TC_NULL:
return to.inMask(TM_REFERENCE);
case TC_ARRAY:
if (!to.isType(TC_ARRAY)) {
return (to == Type.tObject || to == Type.tCloneable
|| to == Type.tSerializable);
} else {
// both are arrays. recurse down both until one isn't an array
do {
from = from.getElementType();
to = to.getElementType();
} while (from.isType(TC_ARRAY) && to.isType(TC_ARRAY));
if ( from.inMask(TM_ARRAY|TM_CLASS)
&& to.inMask(TM_ARRAY|TM_CLASS)) {
return isMoreSpecific(from, to);
} else {
return (from.getTypeCode() == to.getTypeCode());
}
}
case TC_CLASS:
if (toTypeCode == TC_CLASS) {
ClassDefinition fromDef = getClassDefinition(from);
ClassDefinition toDef = getClassDefinition(to);
return toDef.implementedBy(this,
fromDef.getClassDeclaration());
} else {
return false;
}
}
}
/**
* Return true if an explicit cast from this type to
* the given type is allowed.
*/
public boolean explicitCast(Type from, Type to) throws ClassNotFound {
if (implicitCast(from, to)) {
return true;
}
if (from.inMask(TM_NUMBER)) {
return to.inMask(TM_NUMBER);
}
if (from.isType(TC_CLASS) && to.isType(TC_CLASS)) {
ClassDefinition fromClass = getClassDefinition(from);
ClassDefinition toClass = getClassDefinition(to);
if (toClass.isFinal()) {
return fromClass.implementedBy(this,
toClass.getClassDeclaration());
}
if (fromClass.isFinal()) {
return toClass.implementedBy(this,
fromClass.getClassDeclaration());
}
// The code here used to omit this case. If both types
// involved in a cast are interfaces, then JLS 5.5 requires
// that we do a simple test -- make sure none of the methods
// in toClass and fromClass have the same signature but
// different return types. (bug number 4028359)
if (toClass.isInterface() && fromClass.isInterface()) {
return toClass.couldImplement(fromClass);
}
return toClass.isInterface() ||
fromClass.isInterface() ||
fromClass.superClassOf(this, toClass.getClassDeclaration());
}
if (to.isType(TC_ARRAY)) {
if (from.isType(TC_ARRAY)) {
Type t1 = from.getElementType();
Type t2 = to.getElementType();
while ((t1.getTypeCode() == TC_ARRAY)
&& (t2.getTypeCode() == TC_ARRAY)) {
t1 = t1.getElementType();
t2 = t2.getElementType();
}
if (t1.inMask(TM_ARRAY|TM_CLASS) &&
t2.inMask(TM_ARRAY|TM_CLASS)) {
return explicitCast(t1, t2);
}
} else if (from == Type.tObject || from == Type.tCloneable
|| from == Type.tSerializable)
return true;
}
return false;
}
/**
* Flags.
*/
public int getFlags() {
return env.getFlags();
}
/**
* Debugging flags. There used to be a method debug()
* that has been replaced because -g has changed meaning
* (it now cooperates with -O and line number, variable
* range and source file info can be toggled separately).
*/
public final boolean debug_lines() {
return (getFlags() & F_DEBUG_LINES) != 0;
}
public final boolean debug_vars() {
return (getFlags() & F_DEBUG_VARS) != 0;
}
public final boolean debug_source() {
return (getFlags() & F_DEBUG_SOURCE) != 0;
}
/**
* Optimization flags. There used to be a method optimize()
* that has been replaced because -O has changed meaning in
* javac to be replaced with -O and -O:interclass.
*/
public final boolean opt() {
return (getFlags() & F_OPT) != 0;
}
public final boolean opt_interclass() {
return (getFlags() & F_OPT_INTERCLASS) != 0;
}
/**
* Verbose
*/
public final boolean verbose() {
return (getFlags() & F_VERBOSE) != 0;
}
/**
* Dump debugging stuff
*/
public final boolean dump() {
return (getFlags() & F_DUMP) != 0;
}
/**
* Verbose
*/
public final boolean warnings() {
return (getFlags() & F_WARNINGS) != 0;
}
/**
* Dependencies
*/
public final boolean dependencies() {
return (getFlags() & F_DEPENDENCIES) != 0;
}
/**
* Print Dependencies to stdout
*/
public final boolean print_dependencies() {
return (getFlags() & F_PRINT_DEPENDENCIES) != 0;
}
/**
* Deprecation warnings are enabled.
*/
public final boolean deprecation() {
return (getFlags() & F_DEPRECATION) != 0;
}
/**
* Do not support virtual machines before version 1.2.
* This option is not supported and is only here for testing purposes.
*/
public final boolean version12() {
return (getFlags() & F_VERSION12) != 0;
}
/**
* Floating point is strict by default
*/
public final boolean strictdefault() {
return (getFlags() & F_STRICTDEFAULT) != 0;
}
/**
* Release resources, if any.
*/
public void shutdown() {
if (env != null) {
env.shutdown();
}
}
/**
* Issue an error.
* source - the input source, usually a file name string
* offset - the offset in the source of the error
* err - the error number (as defined in this interface)
* arg1 - an optional argument to the error (null if not applicable)
* arg2 - a second optional argument to the error (null if not applicable)
/**代码未完, 请加载全部代码(NowJava.com).**/