LanguageLevelVisitor.java from DrJava at Krugle
Show LanguageLevelVisitor.java syntax highlighted
/*BEGIN_COPYRIGHT_BLOCK
*
* Copyright (c) 2001-2007, JavaPLT group at Rice University (javaplt@rice.edu)
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the names of DrJava, the JavaPLT group, Rice University, nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* This software is Open Source Initiative approved Open Source Software.
* Open Source Initative Approved is a trademark of the Open Source Initiative.
*
* This file is part of DrJava. Download the current version of this project
* from http://www.drjava.org/ or http://sourceforge.net/projects/drjava/
*
* END_COPYRIGHT_BLOCK*/
package edu.rice.cs.javalanglevels;
import org.apache.bcel.Repository;
import org.apache.bcel.classfile.*;
import org.apache.bcel.util.*;
import edu.rice.cs.javalanglevels.tree.*;
import edu.rice.cs.javalanglevels.tree.SourceFile; // Resolve ambiguity
import edu.rice.cs.javalanglevels.parser.JExprParser;
import edu.rice.cs.javalanglevels.parser.ParseException;
import java.util.*;
import java.io.*;
import edu.rice.cs.plt.reflect.JavaVersion;
import junit.framework.TestCase;
/**
* Top-level Language Level Visitor that represents what is common between all Language Levels.
* Enforces constraints during the first walk of the AST (checking for general errors and building
* the symbol table).
* This class enforces things that are common to all contexts reachable at any Language Level, as well as
* top level constraints.
*/
public class LanguageLevelVisitor extends JExpressionIFPrunableDepthFirstVisitor_void {
/**Errors we have encountered during this pass: string is the text of the error, JExpressionIF is the part of
* the AST where the error occurs*/
protected static LinkedList<Pair<String, JExpressionIF>> errors;
/**Stores the classes we have referenced, and all their information, once they are resolved.*/
static Symboltable symbolTable;
/*Stores the information on the refernce as well as the visitor we were using when we encountered the
* reference, for a type that has not been resolved yet*/
static Hashtable<String, Pair<SourceInfo, LanguageLevelVisitor>> continuations;
/**Stores all the SymbolDatas we have created as well as the visitor they were created from.*/
static Hashtable<SymbolData, LanguageLevelVisitor> _newSDs;
/*
* A list of other files that are visited. If the SourceFile is not null, then the source file was
* visited as opposed to the class file. This info is used by LanguageLevelConverter in DrJava.
* We keep the LLV rather than the file, because the LLV has a file, and we need some other information
* stored in the LLV to properly look up the file.
*/
static LinkedList<Pair<LanguageLevelVisitor, SourceFile>> visitedFiles;
/**True once we have encountered an error we cannot recover from.*/
static boolean _errorAdded;
/**The version of the compiler used.*/
static JavaVersion targetVersion;
/**The source file that is being compiled */
File _file;
/** The package of the current file */
String _package;
/** A list of file names (classes) imported by the current file. */
LinkedList<String> _importedFiles;
/** A list of package names imported by the current file. */
LinkedList<String> _importedPackages;
/** A list of ClassDefs and InterfaceDefs in the current file. */
LinkedList<String> _classNamesInThisFile;
/**
* A list of qualified class names of the subclasses of this class that have been traversed and
* continuations pending the resolution of their superclasses.
*/
static Hashtable<String, TypeDefBase> _hierarchy;
/**
* A Hashtable containing a list of qualified class names of classes waiting to be parsed
* within SourceFiles we're in the process of compiling mapped to a pair containing their
* ClassDefs and LanguageLevelVisitors.
*/
static Hashtable<String, Pair<TypeDefBase, LanguageLevelVisitor>> _classesToBeParsed;
/**
* This constructor is called from the subclasses of LanguageLevelVisitor.
* @param file The File corresponding to the source file we are visiting
* @param packageName The name of the package corresponding to the file
* @param importedFiles The list of files (classes) imported by this source file
* @param importedPackages The list of packages imported by this source file
* @param classNamesInThisFile The list of names of classes defined in this file
* @param continuations The table of classes we have encountered but still need to resolve
*/
public LanguageLevelVisitor(File file, String packageName, LinkedList<String> importedFiles,
LinkedList<String> importedPackages, LinkedList<String> classNamesInThisFile, Hashtable<String, Pair<SourceInfo, LanguageLevelVisitor>> continuations) {
_file = file;
_package = packageName;
_importedFiles = importedFiles;
_importedPackages = importedPackages;
_classNamesInThisFile = classNamesInThisFile;
this.continuations = continuations;
}
/**Create a new LanguageLevelVisitor corresponding to the new file*/
public LanguageLevelVisitor createANewInstanceOfMe(File file) {
return new LanguageLevelVisitor(file, "", new LinkedList<String>(), new LinkedList<String>(), new LinkedList<String>(), continuations);
}
/**
* This is a special constructor called from the TypeChecker that sets classesToBeParsed.
* Normally, classesToBeParsed is set by the concrete instantiation of the LangaugeLevelVisitor,
* but in the case of the TypeChecker, we just create a LangaugeLevelVisitor so we can use its
* getSymbolData code. Thus, we must give it a classesToBeParsed to avoid a null pointer exception.
*/
public LanguageLevelVisitor(File file, String packageName, LinkedList<String> importedFiles,
LinkedList<String> importedPackages, LinkedList<String> classNamesInThisFile,
Hashtable<String, Pair<TypeDefBase, LanguageLevelVisitor>> classesToBeParsed,
Hashtable<String, Pair<SourceInfo, LanguageLevelVisitor>> continuations) {
this(file, packageName, importedFiles, importedPackages, classNamesInThisFile, continuations);
_classesToBeParsed = classesToBeParsed;
}
/*Reset the nonStatic fields of this visitor. Used during testing. */
protected void _resetNonStaticFields() {
_file = new File("");
_package = "";
_importedFiles = new LinkedList<String>();
_importedPackages = new LinkedList<String>();
_classNamesInThisFile = new LinkedList<String>();
}
/**
* Originally wanted to take parameter name and return getName, but this faces
* problems if there are two fields with the same name but one is uppercase and
* one is lower or if one starts with an underscore and the other doesn't.
* So we just return name for now.
*/
public static String getFieldAccessorName(String name) {
return name;
}
/**@return the source file*/
public File getFile() {
return _file;
}
/**
* Return true if this data is a constructor.
* It is considered to be a constructor if it is a method data,
* its name and return type are the same, and its return type matches
* its enclosing sd.
*/
protected boolean isConstructor(Data d) {
if (!(d instanceof MethodData)) return false;
MethodData md = (MethodData) d;
return (md.getReturnType() != null) && (md.getSymbolData() != null) &&
(md.getReturnType().getName().indexOf(md.getName()) != -1) &&
(md.getReturnType() == md.getSymbolData());
}
/**
* Takes a classname and returns only the final segment of it. This removes all the
* dots and dollar signs.
*/
public static String getUnqualifiedClassName(String className) {
int lastIndexOfDot = className.lastIndexOf(".");
if (lastIndexOfDot != -1) {
className = className.substring(lastIndexOfDot + 1);
}
int lastIndexOfDollar = className.lastIndexOf("$");
if (lastIndexOfDollar != -1) {
className = className.substring(lastIndexOfDollar + 1);
}
//Remove any leading numbers
while (className.length() > 0 && Character.isDigit(className.charAt(0))) {className = className.substring(1, className.length());}
return className;
}
/**
* Convert the ReferenceType[] to a String[] with the names of the ReferenceTypes.
*/
protected String[] referenceType2String(ReferenceType[] rts) {
String[] throwStrings = new String[rts.length];
for (int i = 0; i < throwStrings.length; i++) {
throwStrings[i] = rts[i].getName();
}
return throwStrings;
}
/**
* Use the BCEL class reader to read the class file corresponding to the class in
* the specified directory, and use the information from BCEL to build a SymbolData corresponding
* to teh class.
* @param qualifiedClassName The fully qualified class name of the class we are looking up
* @param directoryName The directory where the class is located.
*/
private SymbolData _classFile2SymbolData(String qualifiedClassName, String directoryName) {
if (directoryName != null) {
// Add the current directory to the class path bcel uses to find class files.
ClassPath cp = new ClassPath(ClassPath.SYSTEM_CLASS_PATH + System.getProperty("path.separator") + directoryName);
SyntheticRepository sr = SyntheticRepository.getInstance(cp);
Repository.setRepository(sr);
}
JavaClass jc = Repository.lookupClass(qualifiedClassName);
if (jc == null) {
return null;
}
//This is done so that the SymbolData in the Symboltable is updated and returned.
SymbolData sd = symbolTable.get(qualifiedClassName);
if (sd == null) {
sd = new SymbolData(qualifiedClassName);
symbolTable.put(qualifiedClassName, sd);
}
//make it be a non-continuation, since we are filing it in
sd.setIsContinuation(false);
// Set the modifiers and visibility
sd.setMav(_createMav(jc));
// Set the fields
Field[] fields = jc.getFields();
for (int i = 0; i < fields.length; i++) {
String typeString = fields[i].getType().toString();
SymbolData type;
type = getSymbolDataForClassFile(typeString, _makeSourceInfo(qualifiedClassName));
// I assume we do not have to check for duplicate field names here since we're in a class file.
// Pass in true for the second argument because we don't want to generate an accessor if it's from a class file.
sd.addVar(new VariableData(fields[i].getName(), _createMav(fields[i]), type, true, sd)); //True by default
}
// Set the methods
String unqualifiedClassName = getUnqualifiedClassName(qualifiedClassName);
Method[] methods = jc.getMethods();
//We use a label here so that if a parameter's type can't be resolved, we skip this method.
methodLoop: for (int i = 0; i < methods.length; i++) {
SymbolData returnType;
String methodName = methods[i].getName();
// Check for a constructor, BCEL has them as void methods named <init>.
if (methodName.equals("<init>")) {
// rename the name of the method to the unqualified class name and the return type to this SymbolData.
methodName = unqualifiedClassName;
returnType = sd;
}
else {
//System.out.println("Trying to resolve method return type.");
returnType = getSymbolDataForClassFile(methods[i].getReturnType().toString(), _makeSourceInfo(qualifiedClassName));
}
ExceptionTable eTable = methods[i].getExceptionTable();
// Get parameters
org.apache.bcel.generic.Type[] paramTypes = methods[i].getArgumentTypes();
LinkedList<VariableData> vdsList = new LinkedList<VariableData>();
for (int j = 0; j < paramTypes.length; j++) {
SymbolData tempSd = getSymbolDataForClassFile(paramTypes[j].toString(), _makeSourceInfo(qualifiedClassName));
if (tempSd == null) {
// see getSymbolDataForClassFile's comments to see why this check is necessary.
continue methodLoop;
}
vdsList.addLast(new VariableData(tempSd)); // a VariableData with only one argument is a parameter
}
VariableData[] vds = (VariableData[]) vdsList.toArray(new VariableData[vdsList.size()]);
// Get thrown exceptions
String[] throwStrings;
if (eTable != null) {
throwStrings = eTable.getExceptionNames();
}
else {
throwStrings = new String[0];
}
MethodData newMethod = new MethodData(methodName,
_createMav(methods[i]),
new TypeParameter[0],
returnType,
vds,
throwStrings,
sd,
null); // no SourceInfo
for (int k = 0; k<newMethod.getParams().length; k++) {
newMethod.getParams()[k].setEnclosingData(newMethod);
}
sd.addMethod(newMethod, false, true);
}
// Set the superclass
JavaClass superClass = jc.getSuperClass();
if (superClass == null) {
sd.setSuperClass(null);
}
else {
sd.setSuperClass(getSymbolDataForClassFile(superClass.getClassName(), _makeSourceInfo(qualifiedClassName)));
}
// Set the interfaces
JavaClass[] interfaces = jc.getInterfaces();
if (interfaces != null) {
for (int i = 0; i < interfaces.length; i++) {
sd.addInterface(getSymbolDataForClassFile(interfaces[i].getClassName(), _makeSourceInfo(qualifiedClassName)));
}
}
//Set the package
String pakage = jc.getPackageName();
sd.setPackage(pakage);
//set the isInterface field (if it's not a class, then it is an interface)
sd.setInterface(!jc.isClass());
//Remove the class from the list of continuations to resolve.
continuations.remove(qualifiedClassName);
return sd;
}
/**
* Build a SourceInfo corresponding to the specified class name, with -1 as the
* value for row and column of the start and finish.
*/
protected SourceInfo _makeSourceInfo(String qualifiedClassName) {
return new SourceInfo(new File(qualifiedClassName), -1, -1, -1, -1);
}
/**
* Looks up the SymbolData for a qualified name that is in the list of classes to be parsed.
* This method assumes that qualifiedClassName is in _classesToBeParsed.
*/
private SymbolData _lookupFromClassesToBeParsed(String qualifiedClassName, SourceInfo si, boolean resolve) {
if (resolve) {
Pair<TypeDefBase, LanguageLevelVisitor> p = _classesToBeParsed.get(qualifiedClassName);
if (p == null) {
// This occurs when a class depends upon another class in the same file that has a bogus super class.
// Perhaps occurs elsewhere...?
return null;
}
// Check for cyclic inheritance.
TypeDefBase cd = p.getFirst();
LanguageLevelVisitor llv = p.getSecond();
cd.visit(llv);
return symbolTable.get(qualifiedClassName);
}
else {
// Return a continuation, since it shouldn't be in the symbolTable yet based on where we call this method from.
//The visitor we pair here doesn't matter because it should always get removed from the continuations list before it is visited.
continuations.put(qualifiedClassName, new Pair<SourceInfo, LanguageLevelVisitor>(si, this));
SymbolData sd = new SymbolData(qualifiedClassName);
symbolTable.put(qualifiedClassName, sd);
return sd;
}
}
/**
* Check to see if the specified classname is the name of a fully qualified java library class.
*/
public static boolean isJavaLibraryClass(String className) {
return className.startsWith("java.") ||
className.startsWith("javax.") ||
className.startsWith("org.ietf.") ||
className.startsWith("org.omg.") ||
className.startsWith("org.w3c.") ||
className.startsWith("org.xml.") ||
className.startsWith("sun.") ||
className.startsWith("junit.framework."); //TODO: help!
}
/**
* Return true if the specified VariableData overwrites one of the members of the list of VariableDatas
* and false otherwise. A VariableData is overwritten if its name is shadowed.
*/
public static boolean isDuplicateVariableData(LinkedList<VariableData> vds, VariableData toInsert) {
for (int i = 0; i<vds.size(); i++) {
VariableData temp = vds.get(i);
if (temp.getName().equals(toInsert.getName())) {
return true;
}
}
return false;
}
/**
* Set resolve to true since we're in a superclass and should be
* able to find the class. If the result is null, give an error if this is not a java class.
* Remove the symbol data from the continuations list, and return it.
* @return the result of trying to resolve className.
*/
protected SymbolData getSymbolDataForClassFile(String className, SourceInfo si) {
SymbolData sd = getSymbolDataHelper(className, si, true, true, true, false);
if (sd == null) {
// This should typically never happen. However, I am getting a case where bcel finds that
// java.net.URL has a method called openConnection which takes in a java.net.Proxy.
// This method and class Proxy don't appear in any API I can find. This causes a NoClassDefFoundError.
// For now, the offending method will be skipped if it came from a java class file but an error will
// be thrown otherwise since a user may have deleted a class file and should be notified.
// We know that className is already qualified because it's coming from a class file.
// Adding sun.* because we can't find sun.reflect.ConstantPool for some reason 3.28.2004 JH
if (isJavaLibraryClass(className)) {
// don't throw an error
}
else {
// This is an error in the user's class file so throw an error.
// The NullLiteral is a hack to get a JExpression with the correct SourceInfo inside.
_addAndIgnoreError("Class " + className + " not found.", new NullLiteral(si));
}
// return null to tell _classFile2SymbolData to skip this method.
return null;
}
sd.setIsContinuation(false);
continuations.remove(sd.getName());
return sd;
}
/**
* Checks to see if the provided class name is the name of a primative type, and if so,
* returns the corresponding SymbolData. Otherwise, returns null.
*/
private SymbolData _getSymbolData_Primitive(String className) {
if (className.equals("boolean")) {
return SymbolData.BOOLEAN_TYPE;
}
else if (className.equals("char")) {
return SymbolData.CHAR_TYPE;
}
else if (className.equals("byte")) {
return SymbolData.BYTE_TYPE;
}
else if (className.equals("short")) {
return SymbolData.SHORT_TYPE;
}
else if (className.equals("int")) {
return SymbolData.INT_TYPE;
}
else if (className.equals("long")) {
return SymbolData.LONG_TYPE;
}
else if (className.equals("float")) {
return SymbolData.FLOAT_TYPE;
}
else if (className.equals("double")) {
return SymbolData.DOUBLE_TYPE;
}
else if (className.equals("void")) {
return SymbolData.VOID_TYPE;
}
else if (className.equals("null")) {
return SymbolData.NULL_TYPE;
}
return null;
}
/**
* Assume the class name is qualified already, and try to look it up.
* If it's a continuation and resolve is true, this resolves the class
* by reading its class file if it's a java library file or looking it
* up in the file system otherwise.
* If it's not qualified with either the package name or java.lang, throw an
* error somehow.
* @param className The name to look up. Presumed to be qualified.
* @param si The source info of where this was called from.
* @param resolve true if we want to resolve the SymbolData.
* @param addError Whether we want to throw an error or not if we can't find the class
*/
private SymbolData _getSymbolData_IsQualified(String className, SourceInfo si, boolean resolve, boolean fromClassFile, boolean addError) {
// We assume a period in a class name means it is qualified, make sure it's either in
// this package, is imported specifically, is in an imported package, or is in java.lang.
// We can't directly check it, because parsing class files adds every class that is recursively
// referenced by that class file, and we shouldn't be allowed to see some of them.
SymbolData sd = symbolTable.get(className);
/* If sd is not null then if it's not a continuation, we're done, return it.
* If we're from a class file, then a continuation is ok because we assume
* that we'll find it later. If you don't return here, you can get into
* an infinite loop if there's a self-referencing class.
*/
if (sd != null && (!sd.isContinuation() || fromClassFile)) { return sd; }
// Look it up in the symbol table, see if it's a Java library class, look it up from the filesystem.
if (isJavaLibraryClass(className)) {
return _classFile2SymbolData(className, null);
}
else {
sd = _getSymbolData_FromFileSystem(className, si, resolve, addError);
if (sd != SymbolData.KEEP_GOING) {
return sd;
}
if (addError) {
_addAndIgnoreError("The class " + className + " was not found.", new NullLiteral(si));
}
return null;
}
}
/**
* Get the inner SymbolData for this array type. If it's not null, then try to lookup the ArrayData
* corresponding to that type plus '[]'. If it's already there, return it. If not, create a new ArrayData
* and put it in the Symbol Table.
* @param className The String name of the array type we're trying to resolve.
* @param si The SourceInfo corresponding to the class. Used if an error is encountered.
* @param resolve true if this SymbolData needs to be completely resolved.
* @param fromClassFile true if this type is from a class file.
*/
private SymbolData _getSymbolData_ArrayType(String className, SourceInfo si, boolean resolve, boolean fromClassFile, boolean addError, boolean checkImportedPackages) {
// shouldn't be resolving an array type since you can't extend one, so resolve should be false
SymbolData innerSd = getSymbolDataHelper(className.substring(0, className.length() - 2), si, resolve, fromClassFile, addError, checkImportedPackages);
if (innerSd != null) {
SymbolData sd = symbolTable.get(innerSd.getName() + "[]");
if (sd != null) { return sd; }
else {
sd = new ArrayData(innerSd, this, si);
symbolTable.put(sd.getName(), sd);
return sd;
}
}
else { return null; }
}
/**
* The SymbolData we want is defined later in the current file. If it needs to be looked up or
* resolved, do so.
* @param qualifiedClassName The name of the class we're looking up.
* @param si Information about where the class was called from.
* @param resolve true if we want to fully resolve the SymbolData.
*/
private SymbolData _getSymbolData_FromCurrFile(String qualifiedClassName, SourceInfo si, boolean resolve) {
SymbolData sd = symbolTable.get(qualifiedClassName);
if (sd == null || (sd.isContinuation() && resolve)) {
// The class is below the one we're currently parsing or there was an error in parsing one of the classes.
return _lookupFromClassesToBeParsed(qualifiedClassName, si, resolve);
}
else {
return sd;
}
}
/**
* Check the file system for the class name.
* @param qualifiedClassName The name of the class we're looking up.
* @param si Information about where the class was called from.
* @param resolve true if we want to fully resolve the SymbolData.
* @param addError true if we want to throw errors.
*/
private SymbolData _getSymbolData_FromFileSystem(final String qualifiedClassName, SourceInfo si, boolean resolve, boolean addError) {
// Check is the qualified class name is in _classesToBeParsed to save the time it would take to parse
// all the java files in this package.
Pair<TypeDefBase, LanguageLevelVisitor> pair = _classesToBeParsed.get(qualifiedClassName);
if (pair != null) {
return _lookupFromClassesToBeParsed(qualifiedClassName, si, resolve);
}
// If it's not in the symbol table or list of classes to be parsed,
// check if the class is defined in one of the files in this package.
// We have to begin looking for files at the source root of the current _file.
File parent = _file.getParentFile();
String directoryName = "";
if (parent != null) {
directoryName = parent.getAbsolutePath();
}
final String path;
String qualifiedClassNameWithSlashes = qualifiedClassName.replace('.', System.getProperty("file.separator").charAt(0));
if (directoryName != null) {
String newPackage = _package.replace('.', System.getProperty("file.separator").charAt(0));
int indexOfPackage = directoryName.length();
if (newPackage.length() > 0) {
// Removes the slash after the package
indexOfPackage = directoryName.indexOf(newPackage) - 1;
}
//this should only be necessary when testing.
if (indexOfPackage < 0) {
indexOfPackage = directoryName.length();
}
directoryName = directoryName.substring(0, indexOfPackage);
path = directoryName + System.getProperty("file.separator") + qualifiedClassNameWithSlashes;
}
else {
path = qualifiedClassName;
}
/* newPath is the directory of the file we're trying to resolve */
String newPath;
/* newPackage is the package of the file we're trying to resolve */
String newPackage = "";
int lastSlash = qualifiedClassNameWithSlashes.lastIndexOf(System.getProperty("file.separator"));
if (lastSlash != -1) {
newPackage = qualifiedClassNameWithSlashes.substring(0, lastSlash);
newPath = directoryName + System.getProperty("file.separator") + newPackage;
newPackage = newPackage.replace(System.getProperty("file.separator").charAt(0), '.');
}
else {
int lastSlashInPath = path.lastIndexOf(System.getProperty("file.separator"));
if (lastSlashInPath != -1) {
newPath = path.substring(0, lastSlashInPath);
}
else {
newPath = "";
}
}
// First look for the .class file
File classFile = new File(path + ".class");
// Then look for the most recently modified .java, .djx file.
File[] sourceFiles = new File(newPath).listFiles(new FileFilter() {
public boolean accept(File f) {
try {
f = f.getCanonicalFile();
return new File(path + ".dj0").getCanonicalFile().equals(f) ||
new File(path + ".dj1").getCanonicalFile().equals(f) ||
new File(path + ".dj2").getCanonicalFile().equals(f);
}
catch (IOException e) { return false; }
// TODO: Do something with Java files.
}});
File sourceFile = null; // sourceFile is either null or an existing file
if (sourceFiles != null) {
long mostRecentTime = 0;
for (File f : sourceFiles) {
long currentLastModified = f.lastModified();
if (f.exists() && mostRecentTime < currentLastModified) {
mostRecentTime = currentLastModified;
sourceFile = f;
}
}
}
// Check if sourceFile is the current file. If so, there's an error because if the class to look for
// is in the current file, it would've been in _classNamesInThisFile.
//TODO: it is possible this should be an error. But I am no longer positive.
// if (sourceFile.equals(_file)) {
// if (addError) {
// _addAndIgnoreError("The class " + qualifiedClassName + " was not found in the file " + sourceFile, new NullLiteral(si));
// }
// System.out.println("Just added an error. returning null. class should have been in current file.");
// return null;
// }
// if (qualifiedClassName.contains("List")) {System.out.println("Line 777: There are " + continuations.size() + " continuations " + continuations);}
// Then check the corresponding class file to see if it's up to date.
SymbolData sd = symbolTable.get(qualifiedClassName);
if (sd != null && !sd.isContinuation()) { return sd; }
if (sourceFile != null) {
// First see if we even need to resolve this class. If not, create a continuation and return it.
if (!resolve) {
if (sd != null) { return sd; }
else {
sd = new SymbolData(qualifiedClassName);
continuations.put(qualifiedClassName, new Pair<SourceInfo, LanguageLevelVisitor>(si, createANewInstanceOfMe(sourceFile)));//this));
symbolTable.put(qualifiedClassName, sd);
return sd;
}
}
// Get the last modified times of the java file and class file.
// if the classfile doesn't exist, classFileLastModified will get 0, and we will parse it.
if (sourceFile.lastModified() > classFile.lastModified()) {
// the class file is out of date, parse the java file
//First, make sure the java file is readable:
if (!sourceFile.canRead()) {
if (addError) {
_addAndIgnoreError("The file " + sourceFile.getAbsolutePath() + " is present, but does not have proper read permissions", new NullLiteral(si));
}
return null;
}
//Also make sure the directory where the new class file will eventually be written is writable.
if (!new File(newPath).canWrite()) {
if (addError) {
_addAndIgnoreError("The file " + sourceFile.getAbsolutePath() + " needs to be recompiled, but its directory does not have proper write permissions", new NullLiteral(si));
}
return null;
}
/** Insure that this file hasn't already been processed */
try {
File canonicalSource = sourceFile.getCanonicalFile();
boolean alreadyVisited = false;
try { alreadyVisited |= _file.getCanonicalFile().equals(canonicalSource); }
catch (IOException e) { /* ignore */ }
if (!alreadyVisited) {
for (Pair<LanguageLevelVisitor, SourceFile> p : visitedFiles) {
try {
alreadyVisited |= p.getFirst()._file.getCanonicalFile().equals(canonicalSource);
if (alreadyVisited) { break; }
}
catch (IOException e) { /* ignore */ }
}
}
if (alreadyVisited) { return SymbolData.KEEP_GOING; }
}
catch (IOException e) {
if (addError) {
_addAndIgnoreError("The file " + sourceFile.getAbsolutePath() + " is present, but its full path cannot be resolved (symbolic links may not have proper permissions)", new NullLiteral(si));
}
return null;
}
LanguageLevelVisitor lv;
if (LanguageLevelConverter.isElementaryFile(sourceFile)) {
lv = new ElementaryVisitor(sourceFile, errors, symbolTable, continuations, visitedFiles, _newSDs, targetVersion);
}
else if (LanguageLevelConverter.isIntermediateFile(sourceFile)) {
lv = new IntermediateVisitor(sourceFile, errors, symbolTable, continuations, visitedFiles, _newSDs, targetVersion);
}
else if (LanguageLevelConverter.isAdvancedFile(sourceFile)) {
lv = new AdvancedVisitor(sourceFile, errors, symbolTable, continuations, visitedFiles, _newSDs, targetVersion);
}
else {
throw new RuntimeException("Internal Program Error: Invalid file format not caught initially" + sourceFile.getName() + ". Please report this bug");
}
SourceFile sf;
try {
JExprParser jep = new JExprParser(sourceFile);
sf = jep.SourceFile();
}
catch(ParseException pe) {
if (addError) {
// The NullLiteral is a hack to get a JExpression with the correct SourceInfo inside.
_addAndIgnoreError(pe.getMessage(),
new NullLiteral(new SourceInfo(sourceFile,
pe.currentToken.beginLine,
pe.currentToken.beginColumn,
pe.currentToken.endLine,
pe.currentToken.endColumn)));
}
return null;
}
catch (FileNotFoundException fnfe) {
// This should never happen.
if (addError) {
_addAndIgnoreError("File " + sourceFile + " could not be found.", new NullLiteral(si));
}
return null;
}
// See if there were any errors caused by the first pass on the java file.
int numErrors = errors.size();
sf.visit(lv);
if (numErrors != errors.size()) {
return null;
}
else {
//Resolve any continuations.
while (!lv.continuations.isEmpty()) {
Enumeration<String> en = lv.continuations.keys();
while (en.hasMoreElements()) {
String className = en.nextElement();
Pair<SourceInfo, LanguageLevelVisitor> p = lv.continuations.remove(className);
SymbolData returnedSd = p.getSecond().getSymbolData(className, p.getFirst(), true);
//if (returnedSd == null) {
// errors.add(new Pair<String, JExpressionIF>("Could not resolve " + className, new NullLiteral(p.getFirst())));
//}
}
}
while (!lv._newSDs.isEmpty()) {
Enumeration<SymbolData> en = lv._newSDs.keys();
while (en.hasMoreElements()) {
SymbolData first = en.nextElement();
LanguageLevelVisitor sdlv = lv._newSDs.remove(first);
sdlv.createConstructor(first);
}
}
sf.visit(new TypeChecker(sourceFile, newPackage, errors, symbolTable, lv._importedFiles, lv._importedPackages, JavaVersion.JAVA_5));
}
// will this put entries into the symbol table that this class shouldn't be able to see?
// The symbol table should now contain the SymbolData of the class we're looking for.
sd = symbolTable.get(qualifiedClassName);
if (sd == null || sd.isContinuation()) {
if (addError) {
_addAndIgnoreError("File " + sourceFile + " does not contain class " + qualifiedClassName, sf);
}
return null;
}
else {
visitedFiles.add(new Pair<LanguageLevelVisitor, SourceFile>(lv, sf));
return sd;
}
}
}
if (classFile.exists()) {
// read this classfile, create the SymbolData and return it
// This is the only place we are using BCEL right now. If there's a better way, we
// can remove the bcel.jar.
sd = _classFile2SymbolData(qualifiedClassName, directoryName);
if (sd == null) {
if (addError) {
_addAndIgnoreError("File " + classFile + " is not a valid class file.", null);
}
return null;
}
else {
if (sourceFile != null) {
// Visit the sourceFile anyway even though the classFile is up to date so we
// can pass the SourceFile to the LanguageLevelConverter.
SourceFile sf;
try {
sf = new JExprParser(sourceFile).SourceFile();
}
catch(ParseException pe) {
if (addError) {
// The NullLiteral is a hack to get a JExpression with the correct SourceInfo inside.
_addAndIgnoreError(pe.getMessage(),
new NullLiteral(new SourceInfo(sourceFile,
pe.currentToken.beginLine,
pe.currentToken.beginColumn,
pe.currentToken.endLine,
pe.currentToken.endColumn)));
}
return null;
}
catch (FileNotFoundException fnfe) {
// This should never happen.
if (addError) {
_addAndIgnoreError("File " + sourceFile + " could not be found.", new NullLiteral(si));
}
return null;
}
LanguageLevelVisitor lv = createANewInstanceOfMe(sourceFile);
lv._package = newPackage;
visitedFiles.add(new Pair<LanguageLevelVisitor, SourceFile>(lv, null));//lv, sf));
}
return sd;
}
}
return SymbolData.KEEP_GOING;
}
/**
* Call getSymbolData with default values
* By default, resolve will be false. It's only true when looking up a superclass.
* By default, fromClassFile will be false, since this is only true when we are trying to resolve types from the context of a class file.
* By default addError will be true, since we want to display errors.
* By default checkImportedStuff will be true, since we want to consider imported packages and classes initially.
* @param className The String name of the class to resolve
* @param si The SourceInfo corresponding to the reference to the type
*/
protected SymbolData getSymbolData(String className, SourceInfo si) {
return getSymbolData(className, si, false, false, true, true);
}
/**
* Call getSymbolData with some default values. Once it has returned, check to see if you got
* back the resolved class if resolve is true.
* By default, fromClassFile will be false, since this is only true when we are trying to resolve types from the context of a class file.
* By default addError will be true, since we want to display errors.
* By default checkImportedStuff will be true, since we want to consider imported packages and classes initially.
* @param className The String name of the class to resolve
* @param si The SourceInfo corresponding to the reference to the type
* @param resolve true if we want to resolve the symbol data corresponding to this class, false if we want to leave it as a continuation
*/
public SymbolData getSymbolData(String className, SourceInfo si, boolean resolve) {
SymbolData sd = getSymbolData(className, si, resolve, false, true, true);
if (resolve && sd != null) {
if (sd.isContinuation()) {throw new RuntimeException("Internal Program Error: " + sd.getName() + " should not be a continuation. Please report this bug.");}
continuations.remove(sd.getName());
}
return sd;
}
/**
* Call getSymbolData with some default values
* By default addError will be true, since we want to display errors.
* By default checkImportedStuff will be true, since we want to consider imported packages and classes initially.
* @param className The String name of the class to resolve
* @param si The SourceInfo corresponding to the reference to the type
* @param resolve true if we want to resolve the symbol data corresponding to this class, false if we want to leave it as a continuation
* @param fromClassFile true only when we are trying to resolve types from the context of a class file.
*/
protected SymbolData getSymbolData(String className, SourceInfo si, boolean resolve, boolean fromClassFile) {
return getSymbolData(className, si, resolve, fromClassFile, true, true);
}
/**
* Call getSymbolData with some default values
* By default checkImportedStuff will be true, since we want to consider imported packages and classes initially.
* @param className The String name of the class to resolve
* @param si The SourceInfo corresponding to the reference to the type
* @param resolve true if we want to resolve the symbol data corresponding to this class, false if we want to leave it as a continuation
* @param fromClassFile true only when we are trying to resolve types from the context of a class file.
* @param addError true if we want to give an error if this class cannot be resolved.
*/
protected SymbolData getSymbolData(String className, SourceInfo si, boolean resolve, boolean fromClassFile, boolean addError) {
return this.getSymbolData(className, si, resolve, fromClassFile, addError, true);
}
/**
* This wrapper method processes qualified class names by looking up each piece sequentially. Once
* a SymbolData is found corresponding to the class name processed thus far, we look up each piece of the
* rest of the class name as inner classes. This method calls getSymbolDataHelper to look up classes.
* @param className the name of the class to lookup.
* @param si The SourceInfo of the reference to className used in case of an error.
* @param resolve Whether to return a continuation or fully parse the class.
* @param fromClassFile Whether this was called from the class file reader.
* @param addError Whether to add errors or not
* @param checkImportedStuff Whether to try prepending the imported package names
*/
protected SymbolData getSymbolData(String className, SourceInfo si, boolean resolve, boolean fromClassFile, boolean addError, boolean checkImportedStuff) {
int indexOfNextDot = className.indexOf(".");
int indexOfNextDollar = className.indexOf("$"); //we don't think this is necessay, but as a safety percausion, check the $ that denotes anonymous inner classes and inner classes
if (indexOfNextDot == -1 && indexOfNextDollar == -1) {
return getSymbolDataHelper(className, si, resolve, fromClassFile, addError, checkImportedStuff);
}
else { indexOfNextDot = 0; }
SymbolData whatever;
int length = className.length();
while (indexOfNextDot != length) {
indexOfNextDot = className.indexOf(".", indexOfNextDot + 1);
if (indexOfNextDot == -1) { indexOfNextDot = length; }
String s = className.substring(0, indexOfNextDot);
/* We want to resolve after every piece until the last one because we need to know
* when we actually have a class so that we can tell that the rest of the pieces
* are inner classes. We use the resolve parameter's value for the last piece
* since that means there are no inner classes
*/
boolean newResolve = resolve || (indexOfNextDot != length);
whatever = getSymbolDataHelper(s, si, newResolve, fromClassFile, false, checkImportedStuff);
if (whatever != null) {
String innerClassName = "";
SymbolData outerClass = whatever;
if (whatever != null && indexOfNextDot != length) {
outerClass = whatever;
innerClassName = className.substring(indexOfNextDot + 1);
whatever = outerClass.getInnerClassOrInterface(innerClassName);
}
if (whatever == SymbolData.AMBIGUOUS_REFERENCE) {
_addAndIgnoreError("Ambiguous reference to class or interface " + className, new NullLiteral(si));
return null;
}
if (whatever != null) { return whatever;}
else {
//perhaps this was an array type--try to resolve it without the [], and then put it in the symbol table
if (className.endsWith("[]")) {
SymbolData sd = getSymbolData(className.substring(0, className.length() - 2), si, resolve, fromClassFile, addError, checkImportedStuff);
if (sd != null) {
ArrayData ad = new ArrayData(sd, this, si);
symbolTable.put(ad.getName(), ad);
return ad;
}
return sd;
}
if (addError) {
_addAndIgnoreError("Class " + innerClassName +
" is not an inner class of the class " +
outerClass.getName(),
new NullLiteral(si));
}
return null;
}
}
}
if (!fromClassFile && addError) {
String newName = className;
int lastDollar = newName.lastIndexOf("$");
newName = newName.substring(lastDollar + 1, newName.length());
_addAndIgnoreError("Invalid class name " + newName, new NullLiteral(si));
}
return null;
}
/**
* Try to look up name from the context of the lhs.
* @param lhs The TypeData correpsonding to the enclosing of this name reference
* @param name The name piece to look up from the context of lhs
* @param si The SourceInfo corresponding to this reference
* @param addError true if an error should be added
* @return The SymbolData corresponding to this lookup, or KEEP_GOING or null if it could not be found
*/
protected SymbolData getSymbolData(TypeData lhs, String name, SourceInfo si, boolean addError) {
//arguments we do not need to pass in
boolean resolve = false;
boolean fromClassFile = false;
boolean checkImportedStuff = false;
if (lhs == null) {return null;}
else if (lhs instanceof PackageData) {
String className = lhs.getName() + "." + name;
return getSymbolDataHelper(className, si, resolve, fromClassFile, addError, checkImportedStuff);
}
else { //if (lhs instanceof SymbolData) {
SymbolData result = lhs.getInnerClassOrInterface(name);
if (result == SymbolData.AMBIGUOUS_REFERENCE) {
if (addError) { _addAndIgnoreError("Ambiguous reference to class or interface " + name, new NullLiteral(si)); }
return null;
}
return result;
}
}
/**
* This method takes in a class name (it may or may not be qualified) and tries to get it from the symbol table
* as is. If this fails, it will see if the class name corresponds to one of the imported files.
* Then it will prepend the current package and try again. It will then try to prepend the imported
* packages to the class name. Finally, it will try prepending java.lang and see if it is in the symbol table.
* After each check, this method also checks if such a class file (.class or .java) exists and if so, will
* build up a SymbolData for the class by compiling the class if the class is out of date or does not exist,
* and then reading the class file for the needed information. The newly created SymbolData will be put
* into the symbol table. Returns null if the className is not found.
* @param className The name of the class to lookup.
* @param si The SourceInfo of the reference to className used in case of an error.
* @param resolve Whether to return a continuation or fully parse the class.
* @param fromClassFile Whether this was called from the class file reader.
* @param addError Whether to add errors. We don't add errors when iterating through a qualified class name's
* package.
*/
protected SymbolData getSymbolDataHelper(String className, SourceInfo si, boolean resolve, boolean fromClassFile, boolean addError, boolean checkImportedStuff) {
// Check for primitive types.
SymbolData sd = _getSymbolData_Primitive(className);
if (sd != null) { return sd; }
// Check for array types.
if (className.endsWith("[]")) {
return _getSymbolData_ArrayType(className, si, resolve, fromClassFile, addError, checkImportedStuff);
}
// Check for qualified types.
if (className.indexOf(".") != -1) {
return _getSymbolData_IsQualified(className, si, resolve, fromClassFile, addError);
}
String name = null; // name of the SymbolData to be returned
String qualifiedClassName = getQualifiedClassName(className);
// Check if className is defined in this file.
if (_classNamesInThisFile.contains(qualifiedClassName)) {
return _getSymbolData_FromCurrFile(qualifiedClassName, si, resolve);
}
// Check if className was specifically imported. --Not done at elementary level.
// At this point, we know that class name is not qualified.
//We will not check that the package is correct here, because it is caught in the type checker.
Iterator<String> iter = _importedFiles.iterator();
if (checkImportedStuff) {
while (iter.hasNext()) {
String s = iter.next();
if (s.endsWith(className)) {
// All imported files should be in the symbol table.
SymbolData tempSd = symbolTable.get(s);
// Only need to fully resolve if resolve is on and the imported file is a continuation.
if (resolve && tempSd.isContinuation()) {
return getSymbolData(s, si, resolve, fromClassFile, addError, false); // addError??
}
else return tempSd;
}
}
}
// Check if the qualified class name is already in the symbol table at this package level.
// Skip checking if this class is in the package if it's qualified and not qualified with this
// package.
if (className.indexOf(".") == -1 || (!_package.equals("") && className.startsWith(_package))) {
sd = symbolTable.get(qualifiedClassName);
if (sd == null || (sd.isContinuation() && resolve)) {
sd = _getSymbolData_FromFileSystem(qualifiedClassName, si, resolve, addError);
if (sd != SymbolData.KEEP_GOING) {
return sd;
}
}
else {
// Either we're in the default package and we found the unqualified name or we found a continuation and don't
// need to resolve it.
return sd;
}
}
SymbolData resultSd = null;
// Check if the className's package was imported.
if (checkImportedStuff) {
iter = _importedPackages.iterator();
while (iter.hasNext()) {
String s = iter.next() + "." + className;
SymbolData tempSd;
if (s.indexOf("$") != -1) {
tempSd = getSymbolData(s, si, resolve, fromClassFile, false, false);
}
else {
tempSd = getSymbolDataHelper(s, si, resolve, fromClassFile, false, false);
}
if (tempSd != null) {
if (resultSd == null) { resultSd = tempSd; }
else {
if (addError) {
_addAndIgnoreError("The class name " + className + " is ambiguous. It could be " + resultSd.getName() + " or " + tempSd.getName(), new NullLiteral(si));
return null;
}
}
}
}
}
return resultSd;
}
/**
* Creates a ModifiersAndVisibility from the provided AccessFlags.
*/
private ModifiersAndVisibility _createMav(AccessFlags af) {
LinkedList<String> strings = new LinkedList<String>();
if (af.isAbstract()) {
strings.addLast("abstract");
}
if (af.isFinal()) {
strings.addLast("final");
}
if (af.isNative()) {
strings.addLast("native");
}
if (af.isPrivate()) {
strings.addLast("private");
}
if (af.isProtected()) {
strings.addLast("protected");
}
if (af.isPublic()) {
strings.addLast("public");
}
if (af.isStatic()) {
strings.addLast("static");
}
if (af.isStrictfp()) {
strings.addLast("strictfp");
}
if (af.isSynchronized()) {
strings.addLast("synchronized");
}
if (af.isTransient()) {
strings.addLast("transient");
}
if (af.isVolatile()) {
strings.addLast("volatile");
}
return new ModifiersAndVisibility(JExprParser.NO_SOURCE_INFO, strings.toArray(new String[strings.size()]));
}
/**
* The Qualified Class Name is the package, followed by a dot, followed by the rest of the class name.
* If the provided className is already qualified, just return it. If the package is not empty,
* and the className does not start with the package, append the package name onto the className, and return it.
* @param className The className to qualify.
*/
protected String getQualifiedClassName(String className) {
if (!_package.equals("") && !className.startsWith(_package)) {return _package + "." + className;}
else { return className;}
}
/**
* Do what is necessary to process this TypeDefBase from the context of enclosing.
* This method is very similar to addSymbolData, except that it uses an enclosing data for reference.
*/
protected SymbolData addInnerSymbolData(TypeDefBase typeDefBase, String qualifiedClassName, String partialName, Data enclosing, boolean isClass) {
//try to look up in symbol table, in case it has already been defined
SymbolData sd = symbolTable.get(qualifiedClassName);
//try to look up in enclosing's list of inner classes
if (sd == null) {sd = enclosing.getInnerClassOrInterface(partialName);}
if (sd != null && !sd.isContinuation()) {
_addAndIgnoreError("This class has already been defined.", typeDefBase);
return null;
}
if (sd != null) {
//make sure it is a direct inner class or interface of this data.
if (sd.getOuterData() != enclosing) {sd = null;}
}
//create a new symbolData for it--this is the first time we've seen it
if (sd == null) {
sd = new SymbolData(qualifiedClassName);
sd.setOuterData(enclosing);
if (isClass) {enclosing.getSymbolData().addInnerClass(sd);}
else {(enclosing.getSymbolData()).addInnerInterface(sd);}
}
// create the LinkedList for the SymbolDatas of the interfaces
LinkedList<SymbolData> interfaces = new LinkedList<SymbolData>();
SymbolData tempSd;
ReferenceType[] rts = typeDefBase.getInterfaces();
for (int i = 0; i < rts.length; i++) {
tempSd = getSymbolData(rts[i].getName(), rts[i].getSourceInfo(), false, false, false);
if (tempSd != null) {
interfaces.addLast(tempSd);
}
else if (enclosing instanceof SymbolData) {
//check to see if this is an inner class referencing an inner interface
tempSd = enclosing.getInnerClassOrInterface(rts[i].getName());
if (tempSd == null) {
String qualifyingPart = qualifiedClassName.substring(0, qualifiedClassName.lastIndexOf("$"));
tempSd = new SymbolData(qualifyingPart + "$" + rts[i].getName());
tempSd.setInterface(true);
enclosing.getSymbolData().addInnerInterface(tempSd); //interfaces can only be defined in symbol datas
tempSd.setOuterData(enclosing);
continuations.put(rts[i].getName(), new Pair<SourceInfo, LanguageLevelVisitor>(rts[i].getSourceInfo(), this));
}
interfaces.addLast(tempSd);
}
else {
_addAndIgnoreError("Cannot resolve interface " + rts[i].getName(), rts[i]);
return null;
}
}
//Set the package to be the current package
sd.setPackage(_package);
SymbolData superClass = null;
if (typeDefBase instanceof InterfaceDef) {
//add Object as the super class of this, so that it will know it implements Object's methods.
superClass = getSymbolData("Object", typeDefBase.getSourceInfo(), false);
sd.setInterface(true);
}
else if (typeDefBase instanceof ClassDef) {
ClassDef cd = (ClassDef) typeDefBase;
ReferenceType rt = cd.getSuperclass();
String superClassName = rt.getName();
superClass = getSymbolData(superClassName, rt.getSourceInfo(), false, false, false);
if (superClass == null) {
superClass = enclosing.getInnerClassOrInterface(superClassName);
if (superClass == null) {
String qualifyingPart = qualifiedClassName.substring(0, qualifiedClassName.lastIndexOf("$"));
superClass = new SymbolData(qualifyingPart + "$" + superClassName);
enclosing.addInnerClass(superClass);
superClass.setOuterData(enclosing);
continuations.put(superClassName, new Pair<SourceInfo, LanguageLevelVisitor>(rt.getSourceInfo(), this));
}
}
sd.setInterface(false);
}
else {throw new RuntimeException("Internal Program Error: typeDefBase was not a ClassDef or InterfaceDef. Please report this bug.");}
// get the SymbolData of the superclass which must be in the symbol table
// since we visited the type in forClassDef() although it may be a continuation.
// there is a continuation in the symbol table, update the fields
sd.setMav(typeDefBase.getMav());
sd.setTypeParameters(typeDefBase.getTypeParameters());
sd.setSuperClass(superClass);
sd.setInterfaces(interfaces);
sd.setIsContinuation(false);
continuations.remove(sd.getName());
if (sd != null && !sd.isInterface()) {_newSDs.put(sd, this); }
return sd;
}
/**
* This method takes in a TypeDefBase (which is either a ClassDef or an InterfaceDef), generates a SymbolData, and adds the name and SymbolData
* pair to the symbol table. It checks that this class is not already in the symbol table.
* @param typeDefBase The AST node for the class def, interface def, inner class def, or inner interface def.
* @param qualifiedClassName The name for the class.
*/
protected SymbolData addSymbolData(TypeDefBase typeDefBase, String qualifiedClassName) {
String name = qualifiedClassName;
SymbolData sd = symbolTable.get(name);
if (sd != null && !sd.isContinuation()) {
_addAndIgnoreError("This class has already been defined.", typeDefBase);
return null;
}
// create the LinkedList for the SymbolDatas of the interfaces
LinkedList<SymbolData> interfaces = new LinkedList<SymbolData>();
SymbolData tempSd;
ReferenceType[] rts = typeDefBase.getInterfaces();
for (int i = 0; i < rts.length; i++) {
tempSd = getSymbolData(rts[i].getName(), rts[i].getSourceInfo(), false, false, false);
if (tempSd != null) {
interfaces.addLast(tempSd);
}
else if (qualifiedClassName.indexOf("$") != -1) {
//check to see if this is an inner class referencing an inner interface
String qualifyingPart = qualifiedClassName.substring(0, qualifiedClassName.lastIndexOf("$"));
tempSd = getSymbolData(qualifyingPart + "$" + rts[i].getName(), rts[i].getSourceInfo(), false, false, false);
if (tempSd == null) {
tempSd = new SymbolData(qualifyingPart + "$" + rts[i].getName());
tempSd.setInterface(true);
continuations.put(rts[i].getName(), new Pair<SourceInfo, LanguageLevelVisitor>(rts[i].getSourceInfo(), this));
}
interfaces.addLast(tempSd);
}
else if (tempSd == null) {
_addAndIgnoreError("Could not resolve " + rts[i].getName(), rts[i]);
// Couldn't resolve the interface.
return null;
}
}
if (sd == null) { // create a new SymbolData.
sd = new SymbolData(name);
symbolTable.put(name, sd);
}
//Set the package to be the current package
sd.setPackage(_package);
SymbolData superClass = null;
if (typeDefBase instanceof InterfaceDef) {
//add Object as the super class of this, so that it will know it implements Object's methods.
superClass = getSymbolData("Object", typeDefBase.getSourceInfo(), false);
sd.setInterface(true);
}
else if (typeDefBase instanceof ClassDef) {
ClassDef cd = (ClassDef) typeDefBase;
ReferenceType rt = cd.getSuperclass();
// rt cannot be null because every user-defined class extends something. We must resolve
// the superclass before proceeding in order to properly identify words as
// fields or static references to classes.
String superClassName = rt.getName();
superClass = getSymbolData(superClassName, rt.getSourceInfo(), false); //TODO: change this back to true?
if (superClass == null) {
// Couldn't resolve the super class: make it Object by default
superClass = getSymbolData("java.lang.Object", typeDefBase.getSourceInfo(), false);
}
sd.setInterface(false);
}
else {throw new RuntimeException("Internal Program Error: typeDefBase was not a ClassDef or InterfaceDef. Please report this bug.");}
// get the SymbolData of the superclass which must be in the symbol table
// since we visited the type in forClassDef() although it may be a continuation.
// there is a continuation in the symbol table, update the fields
sd.setMav(typeDefBase.getMav());
sd.setTypeParameters(typeDefBase.getTypeParameters());
sd.setSuperClass(superClass);
sd.setInterfaces(interfaces);
sd.setIsContinuation(false);
continuations.remove(sd.getName());
if (!sd.isInterface()) {_newSDs.put(sd, this); }
return sd;
}
/**
* Convert the specified array of FormalParameters into an array of VariableDatas which is then returned.
* All formal parameters are automatically made final.
* TODO: At the advanced level, this may need to be overwritten?
*/
protected VariableData[] formalParameters2VariableData(FormalParameter[] fps, Data enclosing) {
VariableData[] varData = new VariableData[fps.length];
VariableDeclarator vd;
String[] mav;
for (int i = 0; i < varData.length; i++) {
vd = fps[i].getDeclarator();
mav = new String[] {"final"};
String name = vd.getName().getText();
SymbolData type = getSymbolData(vd.getType().getName(), vd.getType().getSourceInfo());
if (type != null) {
varData[i] = new VariableData(name,
new ModifiersAndVisibility(JExprParser.NO_SOURCE_INFO, mav),
type, true, enclosing);
varData[i].gotValue();
}
else { //if type is null, then there was an error, trying to resolve it. Just put 'null' in the array, because we will never try to access it.
_addError("Class or Interface " + vd.getType().getName() + " not found", vd);
varData[i]=null;
}
}
return varData;
}
/* Create a MethodData corresponding to the MethodDef within the context of the SymbolData sd. */
protected MethodData createMethodData(MethodDef that, SymbolData sd) {
that.getMav().visit(this);
that.getName().visit(this);
// Turn the thrown exceptions from a ReferenceType[] to a String[]
String[] throwStrings = referenceType2String(that.getThrows());
// Turn the ReturnTypeI into a SymbolData
String rtString = that.getResult().getName();
SymbolData returnType;
if (rtString.equals("void")) {// && level.equals("Elementary")) { //TODO: Overwrite this at the Advanced level (or maybe not)
returnType = SymbolData.VOID_TYPE;
}
else {
returnType = getSymbolData(rtString, that.getResult().getSourceInfo());
}
MethodData md = new MethodData(that.getName().getText(), that.getMav(), that.getTypeParams(), returnType,
new VariableData[0], throwStrings, sd, that);
// Turn the parameters from a FormalParameterList to a VariableData[]
VariableData[] vds = formalParameters2VariableData(that.getParams(), md);
if (_checkError()) { //if there was an error converting the formalParameters, don't use them.
return md;
}
md.setParams(vds);
// Adds the formal parameters to the list of vars defined in this method.
if (!md.addVars(vds)) { //TODO: should this not have been changed from addFinalVars?
_addAndIgnoreError("You cannot have two method parameters with the same name", that);
}
return md;
}
/**
* This method assumes that the modifiers for this particular VariableDeclaration
* have already been checked. It does no semantics checking. It simiply converts
* the declarators to variable datas, by trying to resolve the types of each declarator.
*/
protected VariableData[] _variableDeclaration2VariableData(VariableDeclaration vd, Data enclosing) {
LinkedList<VariableData> vds = new LinkedList<VariableData>();
ModifiersAndVisibility mav = vd.getMav();
VariableDeclarator[] declarators = vd.getDeclarators();
Type type;
String name;
for (int i = 0; i < declarators.length; i++) {
declarators[i].visit(this);
type = declarators[i].getType();
name = declarators[i].getName().getText();
SymbolData sd = getSymbolData(type.getName(), type.getSourceInfo());
if (sd == null) {
//see if this is a partially qualified field reference
sd = enclosing.getInnerClassOrInterface(type.getName());
}
if (sd == null) {
//if we still couldn't resolve sd, create a continuation for it.
sd = new SymbolData(enclosing.getSymbolData().getName() + "$" + type.getName());
enclosing.getSymbolData().addInnerClass(sd);
sd.setOuterData(enclosing.getSymbolData());
continuations.put(sd.getName(), new Pair<SourceInfo, LanguageLevelVisitor>(type.getSourceInfo(), this));
}
if (sd != null) {
boolean initialized = declarators[i] instanceof InitializedVariableDeclarator;
VariableData vdata = new VariableData(name, mav, sd, initialized, enclosing); //want hasBeenAssigned to be true if this variable declaration is initialized, and false otherwise.
vdata.setHasInitializer(initialized);
vds.addLast(vdata);
}
else {
_addAndIgnoreError("Class or Interface " + type.getName() + " not found", declarators[i].getType());
}
}
return vds.toArray(new VariableData[vds.size()]);
}
/**
* This method is called when an error should be added to the static LinkedList of errors.
* This version is called from the DoFirst methods in the LanguageLevelVisitors to halt
* parsing of the construct.
*/
protected static void _addError(String message, JExpressionIF that) {
_errorAdded = true;
errors.addLast(new Pair<String, JExpressionIF>(message, that));
}
/**
* This method is called when an error should be added, but tree-walking should continue
* on this construct. Generally, if the error is not added in the DoFirst, the _errorAdded
* flag is not checked anyway, so this version should be called.
*/
protected static void _addAndIgnoreError(String message, JExpressionIF that) {
if (_errorAdded) {
throw new RuntimeException("Internal Program Error: _addAndIgnoreError called while _errorAdded was true. Please report this bug.");
}
_errorAdded = false;
errors.addLast(new Pair<String, JExpressionIF>(message, that));
}
protected boolean prune(JExpressionIF node) {
return _checkError();
}
/**If _errorAdded is true, set it back to false and return true.
* This will cause the current construct to be skipped, but will allow this first pass
* to otherwise continue unimpeeded.
* Otherwise, return false, which will allow this first pass to continue normally.
*/
protected static boolean _checkError() {
if (_errorAdded) {
_errorAdded = false;
return true;
}
else {
return false;
}
}
/**Add an error explaining the modifiers' conflict.*/
private void _badModifiers(String first, String second, ModifiersAndVisibility that) {
_addError("Illegal combination of modifiers. Can't use " + first + " and " + second + " together.", that);
}
/**
* Check for problems with modifiers that are common to all language levels:
* duplicate modifiers and illegal combinations of modifiers.
*/
public void forModifiersAndVisibilityDoFirst(ModifiersAndVisibility that) {
String[] modifiersAndVisibility = that.getModifiers();
Arrays.sort(modifiersAndVisibility);
if (modifiersAndVisibility.length > 0) {
String s = modifiersAndVisibility[0];
// check for duplicate modifiers
for (int i = 1; i < modifiersAndVisibility.length; i++) {
if (s.equals(modifiersAndVisibility[i])) {
_addError("Duplicate modifier: " + s, that);
}
s = modifiersAndVisibility[i];
}
// check for illegal combination of modifiers
String visibility = "package";
boolean isAbstract = false;
boolean isStatic = false;
boolean isFinal = false;
boolean isSynchronized = false;
boolean isStrictfp = false;
boolean isTransient = false;
boolean isVolatile = false;
boolean isNative = false;
for (int i = 0; i < modifiersAndVisibility.length; i++) {
s = modifiersAndVisibility[i];
if (s.equals("public") || s.equals("protected") || s.equals("private")) {
if (!visibility.equals("package")) {
_badModifiers(visibility, s, that);
}
else if (s.equals("private") && isAbstract) {
_badModifiers("private", "abstract", that);
}
else {
visibility = s;
}
}
else if (s.equals("abstract")) {
isAbstract = true;
}
else if (s.equals("final")) {
isFinal = true;
if (isAbstract) {
_badModifiers("final", "abstract", that);
}
}
else if (s.equals("static")) {
isStatic = true;
if (isAbstract) {
_badModifiers("static", "abstract", that);
}
}
else if (s.equals("native")) {
isNative = true;
if (isAbstract) {
_badModifiers("native", "abstract", that);
}
}
else if (s.equals("synchronized")) {
isSynchronized = true;
if (isAbstract) {
_badModifiers("synchronized", "abstract", that);
}
}
else if (s.equals("volatile")) {
isVolatile = true;
if (isFinal) {
_badModifiers("final", "volatile", that);
}
}
}
forJExpressionDoFirst(that);
}
}
/**
* Check for problems with ClassDefs that are common to all Language Levels:
* Make sure that the top level class is not private, and that the class name has
* not already been imported.s
*/
public void forClassDefDoFirst(ClassDef that) {
String name = that.getName().getText();
Iterator<String> iter = _importedFiles.iterator();
while (iter.hasNext()) {
String s = iter.next();
if (s.endsWith(name) && !s.equals(getQualifiedClassName(name))) {
_addAndIgnoreError("The class " + name + " was already imported.", that);
}
}
//top level classes cannot be private.
String[] mavStrings = that.getMav().getModifiers();
if (!(that instanceof InnerClassDef)) {
for (int i = 0; i < mavStrings.length; i++) {
if (mavStrings[i].equals("private")) {
_addAndIgnoreError("Top level classes cannot be private", that);
}
}
}
forTypeDefBaseDoFirst(that);
}
/**
* Check for problems with InterfaceDefs that are common to all language levels:
* specifically, top level interfaces cannot be private or final.
*/
public void forInterfaceDefDoFirst(InterfaceDef that) {
//top level interfaces cannot be private or final.
String[] mavStrings = that.getMav().getModifiers();
for (int i = 0; i < mavStrings.length; i++) {
if (mavStrings[i].equals("private")) {
_addAndIgnoreError("Top level interfaces cannot be private", that);
}
if (mavStrings[i].equals("final")) {
_addAndIgnoreError("Interfaces cannot be final", that);
}
}
forTypeDefBaseDoFirst(that);
}
/**
* Check for problems with InnerInterfaceDefs that are common to all language levels:
* specifically, they cannot be final.
*/
public void forInnerInterfaceDefDoFirst(InnerInterfaceDef that) {
String[] mavStrings = that.getMav().getModifiers();
for (int i = 0; i < mavStrings.length; i++) {
if (mavStrings[i].equals("final")) {
_addAndIgnoreError("Interfaces cannot be final", that);
}
}
forTypeDefBaseDoFirst(that);
}
/**
* This sets the package name field in order to find other classes
* in the same package.
*/
public void forPackageStatementOnly(PackageStatement that) {
CompoundWord cWord = that.getCWord();
Word[] words = cWord.getWords();
String newPackage;
String separator = System.getProperty("file.separator");
if (words.length > 0) {
_package = words[0].getText();
newPackage = _package;
for (int i = 1; i < words.length; i++) {
String temp = words[i].getText();
newPackage = newPackage + separator + temp;
_package = _package + "." + temp;
}
String directory = _file.getParent();
if (directory == null || !directory.endsWith(newPackage)) {
_addAndIgnoreError("The package name must mirror your file's directory.", that);
}
}
//call getSymbolData to see if this is actually a class as well as a Package Name. If it is, an error
//will be given in the TypeChecking step
//If file is a .java file and not compiled, won't find it. This is not consistent with the JLS.
//if file is a ll file and not compiled, will find it, though this is not consistent with the JLS.
getSymbolData(_package, that.getSourceInfo(), false, false, false);
forJExpressionOnly(that);
}
/**Make sure the class being imported has not already been imported and that it doesn
* not duplicate the packagie--i.e. import something that is already in the package.
* If there are no errors, add it to the list of imported files, and create a continuation for it.
* The class will be resolved later.
*/
public void forClassImportStatementOnly(ClassImportStatement that) {
CompoundWord cWord = that.getCWord();
Word[] words = cWord.getWords();
//Make sure that this specific imported class has not already been specifically imported
for (int i = 0; i<_importedFiles.size(); i++) {
String name = _importedFiles.get(i);
int indexOfLastDot = name.lastIndexOf(".");
if (indexOfLastDot != -1 && (words[words.length-1].getText()).equals(name.substring(indexOfLastDot + 1, name.length()))) {
_addAndIgnoreError("The class " + words[words.length-1].getText() + " has already been imported.", that);
return;
}
}
StringBuffer tempBuff = new StringBuffer(words[0].getText());
for (int i = 1; i < words.length; i++) {tempBuff.append("." + words[i].getText());}
String temp = tempBuff.toString();
//Make sure that this imported class does not dupliate the package.
//Although this is allowed in full java, we decided to not allow it at any LanguageLevel.
int indexOfLastDot = temp.lastIndexOf(".");
if (indexOfLastDot != -1) {
if (_package.equals(temp.substring(0, indexOfLastDot))) {
_addAndIgnoreError("You do not need to import " + temp + ". It is in your package so it is already visible", that);
return;
}
}
//Now add the class to the list of imported files
_importedFiles.addLast(temp);
SymbolData sd = symbolTable.get(temp);
if (sd == null) {
// Create a continuation for the imported class and put it into the symbol table so
// that on lookup, we can check imported classes before classes in the same package.
sd = new SymbolData(temp);
continuations.put(temp, new Pair<SourceInfo, LanguageLevelVisitor>(that.getSourceInfo(), this));
symbolTable.put(temp, sd);
}
forImportStatementOnly(that);
}
/**Check to make sure that this package import statement is not trying to import the current pacakge. */
public void forPackageImportStatementOnly(PackageImportStatement that) {
CompoundWord cWord = that.getCWord();
Word[] words = cWord.getWords();
StringBuffer tempBuff = new StringBuffer(words[0].getText());
for (int i = 1; i < words.length; i++) {tempBuff.append("." + words[i].getText());}
String temp = tempBuff.toString();
//make sure this imported package does not match the current package
if (_package.equals(temp)) {
_addAndIgnoreError("You do not need to import package " + temp + ". It is your package so all public classes in it are already visible.", that);
return;
}
_importedPackages.addLast(temp);
forImportStatementOnly(that);
}
/**Bitwise operators are not allowed at any language level...*/
public void forShiftAssignmentExpressionDoFirst(ShiftAssignmentExpression that) {
_addAndIgnoreError("Shift assignment operators cannot be used at any language level", that);
}
public void forBitwiseAssignmentExpressionDoFirst(BitwiseAssignmentExpression that) {
_addAndIgnoreError("Bitwise operators cannot be used at any language level", that);
}
public void forBitwiseBinaryExpressionDoFirst(BitwiseBinaryExpression that) {
_addAndIgnoreError("Bitwise binary expressions cannot be used at any language level", that);
}
public void forBitwiseOrExpressionDoFirst(BitwiseOrExpression that) {
_addAndIgnoreError("Bitwise or expressions cannot be used at any language level. Perhaps you meant to compare two values using regular or (||)", that);
}
public void forBitwiseXorExpressionDoFirst(BitwiseXorExpression that) {
_addAndIgnoreError("Bitwise xor expressions cannot be used at any language level", that);
}
public void forBitwiseAndExpressionDoFirst(BitwiseAndExpression that) {
_addAndIgnoreError("Bitwise and expressions cannot be used at any language level. Perhaps you meant to compare two values using regular and (&&)", that);
}
public void forBitwiseNotExpressionDoFirst(BitwiseNotExpression that) {
_addAndIgnoreError("Bitwise not expressions cannot be used at any language level. Perhaps you meant to negate this value using regular not (!)", that);
}
public void forShiftBinaryExpressionDoFirst(ShiftBinaryExpression that) {
_addAndIgnoreError("Bit shifting operators cannot be used at any language level", that);
}
public void forBitwiseNotExpressionDoFirst(ShiftBinaryExpression that) {
_addAndIgnoreError("Bitwise operators cannot be used at any language level", that);
}
/** The EmptyExpression is a sign of an error. It means that we were missing something
* we needed when the parser built the AST*/
public void forEmptyExpressionDoFirst(EmptyExpression that) {
_addAndIgnoreError("You appear to be missing an expression here", that);
}
/** The NoOp expression signifies a missing binary operator that was encountered when the
* parser built the AST. */
public void forNoOpExpressionDoFirst(NoOpExpression that) {
_addAndIgnoreError("You are missing a binary operator here", that);
}
/**
* If one of the ClassDefs defined in this source file is a
* TestCase class, make sure it is the only thing in the file.
*/
public void forSourceFileDoFirst(SourceFile that) {
for (int i = 0; i< that.getTypes().length; i++) {
if (that.getTypes()[i] instanceof ClassDef) {
ClassDef c = (ClassDef) that.getTypes()[i];
String superName = c.getSuperclass().getName();
if (superName.equals("TestCase") || superName.equals("junit.framework.TestCase")) {
if (that.getTypes().length > 1) {
_addAndIgnoreError("TestCases must appear in files by themselves at all language levels", c);
}
}
}
}
}
/**
* Check to make sure there aren't any immediate errors in this SourceFile by calling the
* doFirst method. Then, check to make sure that java.lang is imported, and if it is not, add
* it to the list of importedpackages, since it is imported by default. Make a list of all classes
* defined in this file.
* Then, visit them one by one.
*/
public void forSourceFile(SourceFile that) {
forSourceFileDoFirst(that);
if (prune(that)) { return; }
// The parser enforces that there is either zero or one PackageStatement.
for (int i = 0; i < that.getPackageStatements().length; i++) that.getPackageStatements()[i].visit(this);
for (int i = 0; i < that.getImportStatements().length; i++) that.getImportStatements()[i].visit(this);
if (! _importedPackages.contains("java.lang"))
_importedPackages.addFirst("java.lang");
TypeDefBase[] types = that.getTypes();
// store the qualified names of all classes defined in this file
_classNamesInThisFile = new LinkedList<String>();
for (int i = 0; i < types.length; i++) {
// TODO: Add static inner classes in the file to classes (for advanced level)
String qualifiedClassName = getQualifiedClassName(types[i].getName().getText());
_classNamesInThisFile.addFirst(qualifiedClassName);
_classesToBeParsed.put(qualifiedClassName,
new Pair<TypeDefBase, LanguageLevelVisitor>(types[i], this));
}
for (int i = 0; i < types.length; i++) {
// Remove the class that is about to be visited from the list of ClassDefs in this file.
String qualifiedClassName = getQualifiedClassName(types[i].getName().getText());
// Only visit a class if _classesToBeParsed contains it. Otherwise, this class has
// already been resolved since it was a superclass of a previous class.
if (_classesToBeParsed.containsKey(qualifiedClassName)) {
types[i].visit(this);
}
}
forSourceFileOnly(that);
}
/** Call the ResolveNameVisitor to see if this is a reference to a Type name. */
public void forSimpleNameReference(SimpleNameReference that) {
that.visit(new ResolveNameVisitor());
}
/** Call the ResolveNameVisitor to see if this is a reference to a Type name. */
public void forComplexNameReference(ComplexNameReference that) {
that.visit(new ResolveNameVisitor());
}
/**Do nothing. This is handled in the forVariableDeclarationOnly case.*/
public void forVariableDeclaration(VariableDeclaration that) {
forVariableDeclarationDoFirst(that);
if (prune(that)) { return; }
that.getMav().visit(this);
forVariableDeclarationOnly(that);
}
/**
* If the method being generated already exists in the SymbolData,
* throw an error, because generated methods cannot be overwritten.
*/
protected static void addGeneratedMethod(SymbolData sd, MethodData md) {
MethodData rmd = SymbolData.repeatedSignature(sd.getMethods(), md);
if (rmd == null) {
sd.addMethod(md, true);
md.setGenerated(true);
}
else if (!(getUnqualifiedClassName(sd.getName()).equals(md.getName()))) {
//if it is not a constructor, it cannot be overridden--give an error
_addAndIgnoreError("The method " + md.getName() + " is automatically generated, and thus you cannot override it", rmd.getJExpression());
}
}
/**
* Creates the automatically generated constructor for this class. It needs
* to take in the same arguments as its super class' constructor as well as
* its fields. If there are multiple constructors in the super class, pick the
* one with the least number of parameters.
* No constructor is created if this is an advanced level file (overridden at advanced level), because
* no code augmentation is done.
*/
public void createConstructor(SymbolData sd) {
if (LanguageLevelConverter.isAdvancedFile(_file)) {return;}
SymbolData superSd = sd.getSuperClass();
//there was an error somewhere else. just return.
if (sd.isContinuation()) {return;}
LinkedList<MethodData> superMethods = superSd.getMethods();
String superUnqualifiedName = getUnqualifiedClassName(superSd.getName());
LanguageLevelVisitor sslv = _newSDs.remove(superSd);
if (sslv != null) {sslv.createConstructor(superSd);}
// Find the super's smallest constructor.
MethodData superConstructor = null;
Iterator<MethodData> iter = superMethods.iterator();
while (iter.hasNext()) {
MethodData superMd = iter.next();
if (superMd.getName().equals(superUnqualifiedName)) {
if (superConstructor == null || superMd.getParams().length < superConstructor.getParams().length) {
superConstructor = superMd;
}
}
}
String name = getUnqualifiedClassName(sd.getName());
MethodData md = new MethodData(name,
new ModifiersAndVisibility(JExprParser.NO_SOURCE_INFO, new String[] {"public"}),
new TypeParameter[0],
sd,
new VariableData[0], // Parameters to be filled in later.
new String[0],
sd,
null);
LinkedList<VariableData> params = new LinkedList<VariableData>();
if (superConstructor != null) {
for (VariableData superParam : superConstructor.getParams()) {
String paramName = md.createUniqueName("super_" + superParam.getName());
VariableData newParam = new VariableData(paramName, new ModifiersAndVisibility(JExprParser.NO_SOURCE_INFO, new String[0]), superParam.getType().getSymbolData(), true, md);
newParam.setGenerated(true);
params.add(newParam);
md.addVar(newParam); // We do this on each iteration so that createUniqueName will handle nameless super parameters (from class files)
}
}
//only add in those fields that do not have a value and are not static.
boolean hasOtherConstructor = sd.hasMethod(name);
for (VariableData field : sd.getVars()) {
if (!field.hasInitializer() && !field.hasModifier("static")) {
if (!hasOtherConstructor) { field.gotValue(); } // Set hasValue if no other constructors need to be visited
// Rather than creating a new parameter, we use the field, since all the important data is the same in both of them.
params.add(field);
}
}
md.setParams(params.toArray(new VariableData[params.size()]));
md.setVars(params);
addGeneratedMethod(sd, md);
_newSDs.remove(sd); //this won't do anything if sd is not in _newSDs.
}
/**
* Overridden at the Advanced Level, because no code augmentation is done there.
* Create a method that is an accessor for each field in the class.
* File file is passed in so this can remain a static method
*/
protected static void createAccessors(SymbolData sd, File file) {
if (LanguageLevelConverter.isAdvancedFile(file)) {return;}
LinkedList<VariableData> fields = sd.getVars();
Iterator<VariableData> iter = fields.iterator();
while (iter.hasNext()) {
VariableData vd = iter.next();
if (!vd.hasModifier("static")) {
String name = getFieldAccessorName(vd.getName());
String[] mavStrings;
mavStrings = new String[] {"public"};
MethodData md = new MethodData(name,
new ModifiersAndVisibility(JExprParser.NO_SOURCE_INFO, mavStrings),
new TypeParameter[0],
vd.getType().getSymbolData(),
new VariableData[0],
new String[0],
sd,
null); // no SourceInfo
addGeneratedMethod(sd, md);
}
}
}
/**
* Create a method called toString that returns type String.
* Overridden at the Advanced Level files, because no code augmentation is done for
* them so you don't want to create this method.
*/
protected void createToString(SymbolData sd) {
String name = "toString";
String[] mavStrings;
mavStrings = new String[] {"public"};
// }
MethodData md = new MethodData(name,
new ModifiersAndVisibility(JExprParser.NO_SOURCE_INFO, mavStrings),
new TypeParameter[0],
getSymbolData("String", _makeSourceInfo("java.lang.String")),
new VariableData[0],
new String[0],
sd,
null); // no SourceInfo
addGeneratedMethod(sd, md);
}
/**
* Create a method called hashCode that returns an int.
* Overriden for Advanced Level files, because no code augmentation is done for
* them, so we don't want to create this method.
*/
protected void createHashCode(SymbolData sd) {
String name = "hashCode";
String[] mavStrings;
mavStrings = new String[] {"public"};
MethodData md = new MethodData(name,
new ModifiersAndVisibility(JExprParser.NO_SOURCE_INFO, mavStrings),
new TypeParameter[0],
SymbolData.INT_TYPE,
new VariableData[0],
new String[0],
sd,
null); // no SourceInfo
addGeneratedMethod(sd, md);
}
/**
* Create a method called equals() that takes in an Object argument and returns a boolean.
* Overriden for Advanced Level files, because no code augmentation is done for
* them, so we don't want to create this method.
*/
protected void createEquals(SymbolData sd) {
String name = "equals";
String[] mavStrings;
mavStrings = new String[] {"public"};
SymbolData type = getSymbolData("java.lang.Object", _makeSourceInfo("java.lang.Object"));
VariableData param = new VariableData(type);
MethodData md = new MethodData(name,
new ModifiersAndVisibility(JExprParser.NO_SOURCE_INFO, mavStrings),
new TypeParameter[0],
SymbolData.BOOLEAN_TYPE,
new VariableData[] {param},
new String[0],
sd,
null); // no SourceInfo
param.setEnclosingData(md);
addGeneratedMethod(sd, md);
}
/**
* This is overwritten because we don't want to visit each half of
* MemberType recursively. Just take the whole thing and look for it in
* forMemberTypeOnly (calls forTypeOnly eventually to get looked up).
*/
public void forMemberType(MemberType that) {
forMemberTypeDoFirst(that);
if (prune(that)) { return; }
forMemberTypeOnly(that);
}
/**Return the SymbolData for java.lang.String by default*/
public void forStringLiteralOnly(StringLiteral that) {
getSymbolData("String", that.getSourceInfo(), true);
}
/** Try to resolve the type of the instantiation, and make sure there are no errors*/
public void forSimpleNamedClassInstantiation(SimpleNamedClassInstantiation that) {
forSimpleNamedClassInstantiationDoFirst(that);
if (prune(that)) { return; }
that.getType().visit(this);
that.getArguments().visit(this);
// Put the allocated type into the symbol table
/* TODO!: Shouldn't this happen for all Instantiations?
* Even for all Types, regardless of where they show up?
*/
getSymbolData(that.getType().getName(), that.getSourceInfo());
forSimpleNamedClassInstantiationOnly(that);
}
/**
* Checks that the 2 provided arrays are equal. If they are both null,
* they are equal. If one is null and one is not, they are not equal. If
* they do not have the same length, they are not equal. Otherwise, iterate
* through the arrays comparing corresponding elements. The two arrays are
* equal if all of their corresponding elements are equal.
* @param array1 The first array to check.
* @param array2 The array to compare array1 to.
* @return true if the 2 arrays are equal.
*/
public static boolean arrayEquals(Object[] array1, Object[] array2) {
if (array1 == null && array2 == null) { return true; }
if (array1 == null || array2 == null) { return false; }
if (array1.length != array2.length) { return false; }
for (int i = 0; i < array1.length; i++) {
if (!array1[i].equals(array2[i])) { return false;}
}
return true;
}
/**
* Use this to see if a name references a type that needs to be added to the symbolTable.
*/
private class ResolveNameVisitor extends JExpressionIFAbstractVisitor<TypeData> {
public ResolveNameVisitor() {
}
/**
* Most expressions are not relevant for this check--visit them with outer visitor.
*/
public TypeData defaultCase(JExpressionIF that) {
that.visit(LanguageLevelVisitor.this);
return null;
}
/**
* Try to look up this simple name reference and match it to a symbol data.
* If it could not be matched, return a package data.
* @param that The thing we're trying to match to a type
*/
public TypeData forSimpleNameReference(SimpleNameReference that) {
SymbolData result = getSymbolData(that.getName().getText(), that.getSourceInfo());
//it could not be resolved: return a Package Data
if (result==SymbolData.KEEP_GOING) {
return new PackageData(that.getName().getText());
}
return result;
}
/**
* Try to look up the enclosing of this complex name reference and then try to
* match the name on the right within that context to a type.
* If it could not be matched, return a package data.
* @param that The thing we're trying to match to a type
*/
public TypeData forComplexNameReference(ComplexNameReference that) {
TypeData lhs = that.getEnclosing().visit(this);
SymbolData result = getSymbolData(lhs, that.getName().getText(), that.getSourceInfo(), true);
if (result == SymbolData.KEEP_GOING) {
if (lhs instanceof PackageData) {
return new PackageData((PackageData) lhs, that.getName().getText());
}
return null;
}
return result;
}
}
/**
* Test the methods defined in the above class.
*/
public static class LanguageLevelVisitorTest extends TestCase {
private LanguageLevelVisitor _llv;
private SymbolData _sd1;
private SymbolData _sd2;
private SymbolData _sd3;
private SymbolData _sd4;
private SymbolData _sd5;
private SymbolData _sd6;
private ModifiersAndVisibility _publicMav = new ModifiersAndVisibility(JExprParser.NO_SOURCE_INFO, new String[] {"public"});
private ModifiersAndVisibility _protectedMav = new ModifiersAndVisibility(JExprParser.NO_SOURCE_INFO, new String[] {"protected"});
private ModifiersAndVisibility _privateMav = new ModifiersAndVisibility(JExprParser.NO_SOURCE_INFO, new String[] {"private"});
private ModifiersAndVisibility _packageMav = new ModifiersAndVisibility(JExprParser.NO_SOURCE_INFO, new String[0]);
private ModifiersAndVisibility _finalMav = new ModifiersAndVisibility(JExprParser.NO_SOURCE_INFO, new String[]{"final"});
public LanguageLevelVisitorTest() {
this("");
}
public LanguageLevelVisitorTest(String name) {
super(name);
_llv = new LanguageLevelVisitor(new File(""), "", new LinkedList<String>(), new LinkedList<String>(),
new LinkedList<String>(), new Hashtable<String, Pair<SourceInfo, LanguageLevelVisitor>>());
}
public void setUp() {
_llv = new LanguageLevelVisitor(new File(""), "", new LinkedList<String>(), new LinkedList<String>(),
new LinkedList<String>(), new Hashtable<String, Pair<SourceInfo, LanguageLevelVisitor>>());
errors = new LinkedList<Pair<String, JExpressionIF>>();
_errorAdded=false;
symbolTable = new Symboltable();
_llv.continuations = new Hashtable<String, Pair<SourceInfo, LanguageLevelVisitor>>();
visitedFiles = new LinkedList<Pair<LanguageLevelVisitor, SourceFile>>();
_hierarchy = new Hashtable<String, TypeDefBase>();
_classesToBeParsed = new Hashtable<String, Pair<TypeDefBase, LanguageLevelVisitor>>();
_llv._resetNonStaticFields();
_llv._importedPackages.add("java.lang");
_llv._newSDs = new Hashtable<SymbolData, LanguageLevelVisitor>();
_sd1 = new SymbolData("i.like.monkey");
_sd2 = new SymbolData("i.like.giraffe");
_sd3 = new SymbolData("zebra");
_sd4 = new SymbolData("u.like.emu");
_sd5 = new SymbolData("");
_sd6 = new SymbolData("cebu");
}
/**
* Tests the getUnqualifiedClassName method.
*/
public void testGetUnqualifiedClassName() {
assertEquals("getUnqualifiedClassName with a qualified name with an inner class", "innermonkey", _llv.getUnqualifiedClassName("i.like.monkey$innermonkey"));
assertEquals("getUnqualifiedClassName with a qualified name", "monkey", _llv.getUnqualifiedClassName("i.like.monkey"));
assertEquals("getUnqualifiedClassName with an unqualified name", "monkey", _llv.getUnqualifiedClassName("monkey"));
assertEquals("getUnqualifiedClassName with an empty string", "", _llv.getUnqualifiedClassName(""));
}
public void testClassFile2SymbolData() {
//test a java.lang symbol data
SymbolData objectSD = _llv._classFile2SymbolData("java.lang.Object", "");
SymbolData stringSD = _llv._classFile2SymbolData("java.lang.String", "");
MethodData md = new MethodData("substring", _publicMav, new TypeParameter[0], stringSD,
new VariableData[] {new VariableData(SymbolData.INT_TYPE)},
new String[0], stringSD, null);
assertTrue("java.lang.String should have been converted successfully",
stringSD.getName().equals("java.lang.String"));
assertEquals("java.lang.String's superClass should should be java.lang.Object",
objectSD,
stringSD.getSuperClass());
LinkedList<MethodData> methods = stringSD.getMethods();
Iterator<MethodData> iter = methods.iterator();
boolean found = false;
while (iter.hasNext()) {
MethodData currMd = iter.next();
if (currMd.getName().equals("substring") && currMd.getParams().length == 1 && currMd.getParams()[0].getType()==SymbolData.INT_TYPE.getInstanceData()) {
found = true;
md.getParams()[0].setEnclosingData(currMd);
break;
}
}
assertTrue("Should have found method substring(int) in java.lang.String", found);
assertEquals("java.lang.String should be packaged correctly", "java.lang", _llv.getSymbolData("java.lang.String", JExprParser.NO_SOURCE_INFO).getPackage());
//now, test that a second call to the same method won't replace the symbol data that is already there.
SymbolData newStringSD = _llv._classFile2SymbolData("java.lang.String", "");
assertTrue("Second call to classFileToSymbolData should not change sd in hash table.", stringSD == _llv.symbolTable.get("java.lang.String"));
assertTrue("Second call to classFileToSymbolData should return same SD.", newStringSD == _llv.symbolTable.get("java.lang.String"));
//now, test one of our own small class files.
SymbolData bartSD = _llv._classFile2SymbolData("Bart", "testFiles");
assertFalse("bartSD should not be null", bartSD == null);
assertFalse("bartSD should not be a continuation", bartSD.isContinuation());
MethodData md1 = new MethodData("myMethod", _privateMav, new TypeParameter[0], SymbolData.BOOLEAN_TYPE,
new VariableData[] {new VariableData(SymbolData.INT_TYPE)}, new String[] {"java.lang.Exception"}, bartSD, null);
md1.getParams()[0].setEnclosingData(bartSD.getMethods().getLast());
MethodData md2 = new MethodData("Bart", _publicMav, new TypeParameter[0], bartSD,
new VariableData[0], new String[0], bartSD, null);
VariableData vd1 = new VariableData("i", _publicMav, SymbolData.INT_TYPE, true, bartSD);
LinkedList<MethodData> bartsMD = new LinkedList<MethodData>();
bartsMD.addFirst(md1);
bartsMD.addFirst(md2);
LinkedList<VariableData> bartsVD = new LinkedList<VariableData>();
bartsVD.addLast(vd1);
assertEquals("Bart's super class should be java.lang.Object: errors = " + errors, objectSD, bartSD.getSuperClass());
assertEquals("Bart's Variable Data should be a linked list containing only vd1", bartsVD, bartSD.getVars());
assertEquals("The first method data of bart's should be correct", md2, bartSD.getMethods().getFirst());
assertEquals("The second method data of bart's should be correct", md1, bartSD.getMethods().getLast());
assertEquals("Bart's Method Data should be a linked list containing only md1", bartsMD, bartSD.getMethods());
}
public void testLookupFromClassesToBeParsed() {
// Create a ClassDef. Recreate the ClassOrInterfaceType for Object instead of using
// JExprParser.NO_TYPE since otherwise the ElementaryVisitor will complain that the
// user must explicitly extend Object.
ClassDef cd = new ClassDef(JExprParser.NO_SOURCE_INFO, _publicMav, new Word(JExprParser.NO_SOURCE_INFO, "Lisa"),
new TypeParameter[0], new ClassOrInterfaceType(JExprParser.NO_SOURCE_INFO, "Object", new Type[0]), new ReferenceType[0],
new BracedBody(JExprParser.NO_SOURCE_INFO, new BodyItemI[0]));
// Use a ElementaryVisitor so lookupFromClassesToBeParsed will actually visit the ClassDef.
ElementaryVisitor bv = new ElementaryVisitor(new File(""), errors, symbolTable, continuations, new LinkedList<Pair<LanguageLevelVisitor, SourceFile>>(), new Hashtable<SymbolData, LanguageLevelVisitor>(), targetVersion);
// Test that passing resolve equals false returns a continuation.
assertTrue("Should return a continuation", _llv._lookupFromClassesToBeParsed("Lisa", JExprParser.NO_SOURCE_INFO, false).isContinuation());
// Put Lisa in the hierarchy and test that there is one error and that the message
// says that there is cyclic inheritance.
// _hierarchy.put("Lisa", cd);
// _classesToBeParsed.put("Lisa", new Pair<TypeDefBase, LanguageLevelVisitor>(cd, bv));
// assertEquals("Should return null because Lisa is in the hierarchy",
// null,
// _llv._lookupFromClassesToBeParsed("Lisa", JExprParser.NO_SOURCE_INFO, true));
// assertEquals("Should be one error", 1, errors.size());
// assertEquals("Error message should be correct", "Cyclic inheritance involving Lisa", errors.get(0).getFirst());
// _hierarchy.remove("Lisa");
//Re-add Lisa because the first call with resolve set to true removed it and
// test that Lisa is actually visited and added to the symbolTable.
_classesToBeParsed.put("Lisa", new Pair<TypeDefBase, LanguageLevelVisitor>(cd, bv));
assertFalse("Should return a non-continuation",
_llv._lookupFromClassesToBeParsed("Lisa",
JExprParser.NO_SOURCE_INFO,
true).isContinuation());
}
public void testGetSymbolDataForClassFile() {
// Test that passing a legal class return a non-continuation.
assertFalse("Should return a non-continuation", _llv.getSymbolDataForClassFile("java.lang.String",
JExprParser.NO_SOURCE_INFO).isContinuation());
// Test that passing a java class that can't be found returns null but doesn't add an error.
assertEquals("Should return null with a java class that can't be found",
null,
_llv.getSymbolDataForClassFile("java.lang.Marge", JExprParser.NO_SOURCE_INFO));
assertEquals("There should be no errors", 0, errors.size());
// Test that passing a userclass that can't be found returns null and adds an error.
assertEquals("Should return null with a user class that can't be found",
null,
_llv.getSymbolDataForClassFile("Marge", JExprParser.NO_SOURCE_INFO));
assertEquals("There should be one error", 1, errors.size());
assertEquals("The error message should be correct",
"Class Marge not found.",
errors.get(0).getFirst());
}
public void testGetSymbolData_Primitive() {
assertEquals("should be boolean type", SymbolData.BOOLEAN_TYPE, _llv._getSymbolData_Primitive("boolean"));
assertEquals("should be char type", SymbolData.CHAR_TYPE, _llv._getSymbolData_Primitive("char"));
assertEquals("should be byte type", SymbolData.BYTE_TYPE, _llv._getSymbolData_Primitive("byte"));
assertEquals("should be short type", SymbolData.SHORT_TYPE, _llv._getSymbolData_Primitive("short"));
assertEquals("should be int type", SymbolData.INT_TYPE, _llv._getSymbolData_Primitive("int"));
assertEquals("should be long type", SymbolData.LONG_TYPE, _llv._getSymbolData_Primitive("long"));
assertEquals("should be float type", SymbolData.FLOAT_TYPE, _llv._getSymbolData_Primitive("float"));
assertEquals("should be double type", SymbolData.DOUBLE_TYPE, _llv._getSymbolData_Primitive("double"));
assertEquals("should be void type", SymbolData.VOID_TYPE, _llv._getSymbolData_Primitive("void"));
assertEquals("should be null type", SymbolData.NULL_TYPE, _llv._getSymbolData_Primitive("null"));
assertEquals("should return null--not a primitive", null, _llv._getSymbolData_Primitive("java.lang.String"));
}
public void testGetSymbolData_IsQualified() {
_llv._file = new File("testFiles/Fake.dj0");
SymbolData sd = new SymbolData("testPackage.File");
_llv._package = "testPackage";
symbolTable.put("testPackage.File", sd);
SymbolData sd1 = new SymbolData("java.lang.String");
symbolTable.put("java.lang.String", sd1);
//Test that classes not in the symbol table are handled correctly.
assertEquals("should return null--does not exist", null, _llv._getSymbolData_IsQualified("testPackage.File", JExprParser.NO_SOURCE_INFO, true, false, true));
assertEquals("should be one error so far.", 1, errors.size());
SymbolData sd2 = _llv._getSymbolData_IsQualified("java.lang.Integer", JExprParser.NO_SOURCE_INFO, true, true, true);
assertEquals("should return non-continuation java.lang.Integer", "java.lang.Integer", sd2.getName());
assertFalse("should not be a continuation.", sd2.isContinuation());
SymbolData sd3 = _llv._getSymbolData_IsQualified("Wow", JExprParser.NO_SOURCE_INFO, true, true, true);
assertEquals("should return Wow", "Wow", sd3.getName());
assertFalse("Should not be a continuation.", sd3.isContinuation());
//Test that classes in the symbol table are handled correctly
assertEquals("should return null sd--does not exist", null, _llv._getSymbolData_IsQualified("testPackage.File", JExprParser.NO_SOURCE_INFO, false, false, true));
assertEquals("Should be 2 errors", 2, errors.size());
sd.setIsContinuation(false);
assertEquals("should return non-continuation sd", sd, _llv._getSymbolData_IsQualified("testPackage.File", JExprParser.NO_SOURCE_INFO, true, false, true));
assertEquals("Should return sd1.", sd1, _llv._getSymbolData_IsQualified("java.lang.String", JExprParser.NO_SOURCE_INFO, true, false, true));
assertFalse("sd1 should no longer be a continuation.", sd1.isContinuation());
//check that stuff not in symbol table and packaged incorrectly is handled right.
assertEquals("should return null-because it's not a valid class", null, _llv._getSymbolData_IsQualified("testPackage.not.in.symboltable", JExprParser.NO_SOURCE_INFO, true, false, true));
assertEquals("should be three errors so far.", 3, errors.size());
assertNull("should return null", _llv._getSymbolData_IsQualified("testPackage.not.in.symboltable", JExprParser.NO_SOURCE_INFO, false, false, false));
assertNull("should return null.", _llv._getSymbolData_IsQualified("notRightPackage", JExprParser.NO_SOURCE_INFO, false, false, false));
assertEquals("should still be three errors.", 3, errors.size());
}
public void testGetSymbolData_ArrayType() {
//Initially, force the inner sd of this array type to be null, to test that.
assertEquals("Should return null, because inner sd is null.", null, _llv._getSymbolData_ArrayType("TestFile[]", JExprParser.NO_SOURCE_INFO, false, false, false, false));
/**Now, put a real SymbolData base in the table.*/
SymbolData sd = new SymbolData("Iexist");
symbolTable.put("Iexist", sd);
_llv._getSymbolData_ArrayType("Iexist[]", JExprParser.NO_SOURCE_INFO, false, false, false, false).getName();
assertTrue("Should have created an array data and add it to symbol table.", symbolTable.containsKey("Iexist[]"));
SymbolData ad = symbolTable.get("Iexist[]");
//make sure that ad has the appropriate fields and super classes and interfaces and methods
assertEquals("Should only have field 'length'", 1, ad.getVars().size());
assertNotNull("Should contain field 'length'", ad.getVar("length"));
assertEquals("Should only have one method-clone", 1, ad.getMethods().size());
assertTrue("Should contain method clone", ad.hasMethod("clone"));
assertEquals("Should have Object as super class", symbolTable.get("java.lang.Object"), ad.getSuperClass());
assertEquals("Should have 2 interfaces", 2, ad.getInterfaces().size());
assertEquals("Interface 1 should be java.lang.Cloneable", "java.lang.Cloneable", ad.getInterfaces().get(0).getName());
assertEquals("Interface 2 should be java.io.Serializable", "java.io.Serializable", ad.getInterfaces().get(1).getName());
/**Now try it with the full thing already in the symbol table.*/
assertEquals("Since it's already in symbol table now, should just return it.", ad, _llv._getSymbolData_ArrayType("Iexist[]", JExprParser.NO_SOURCE_INFO, false, false, false, false));
/**Now, try it with a multiple dimension array.*/
_llv._getSymbolData_ArrayType("Iexist[][]", JExprParser.NO_SOURCE_INFO, false, false, false, false);
assertTrue("Should have added a multidimensional array to the table.", symbolTable.containsKey("Iexist[][]"));
SymbolData sd2 = new SymbolData("String");
symbolTable.put("String", sd2);
_llv._getSymbolData_ArrayType("String[][]", JExprParser.NO_SOURCE_INFO, false, false, false, false);
assertTrue("Should have added String[] to table", symbolTable.containsKey("String[]"));
assertTrue("Should have added String[][] to table", symbolTable.containsKey("String[][]"));
}
public void testGetSymbolData_FromCurrFile() {
_sd4.setIsContinuation(false);
_sd6.setIsContinuation(true);
symbolTable.put("u.like.emu", _sd4);
symbolTable.put("cebu", _sd6);
//test if it's already in the symbol table and doesn't need to be resolved
//not stopping when it should. get error b/c not in classes to be parsed assertEquals("symbol data is not a continuation, so should just be returned.", _sd6, _llv._getSymbolData_FromCurrFile("cebu", JExprParser.NO_SOURCE_INFO, true));
assertEquals("symbol data is a continuation, but resolve is false, so should just be returned.", _sd4, _llv._getSymbolData_FromCurrFile("u.like.emu", JExprParser.NO_SOURCE_INFO, false));
//test if it needs to be resolved:
ClassDef cd = new ClassDef(JExprParser.NO_SOURCE_INFO, _publicMav, new Word(JExprParser.NO_SOURCE_INFO, "Lisa"),
new TypeParameter[0], new ClassOrInterfaceType(JExprParser.NO_SOURCE_INFO, "Object", new Type[0]), new ReferenceType[0],
new BracedBody(JExprParser.NO_SOURCE_INFO, new BodyItemI[0]));
// Use a ElementaryVisitor so lookupFromClassesToBeParsed will actually visit the ClassDef.
ElementaryVisitor bv = new ElementaryVisitor(new File(""));
_classesToBeParsed.put("Lisa", new Pair<TypeDefBase, LanguageLevelVisitor>(cd, bv));
assertFalse("Should return a non-continuation", _llv._getSymbolData_FromCurrFile("Lisa", JExprParser.NO_SOURCE_INFO,
true).isContinuation());
}
public void testGetSymbolData_FromFileSystem() {
//what if it is in classes to be parsed?
//and what if the class we're looking up is in the same package as the current file?
//qualified
_llv._package="fully.qualified";
_llv._file = new File("testFiles/fully/qualified/Fake.dj0");
SymbolData sd2 = new SymbolData("fully.qualified.Woah");
_llv.symbolTable.put("fully.qualified.Woah", sd2);
SymbolData result = _llv._getSymbolData_FromFileSystem("fully.qualified.Woah", JExprParser.NO_SOURCE_INFO, false, true);
assertEquals("Should return sd2, unresolved.", sd2, result);
assertTrue("sd2 should still be unresolved", sd2.isContinuation());
assertEquals("Should be no errors", 0, errors.size());
result = _llv._getSymbolData_FromFileSystem("fully.qualified.Woah", JExprParser.NO_SOURCE_INFO, false, true);
assertEquals("Should return sd2, now unresolved.", sd2, result);
assertTrue("sd2 should not be resolved", sd2.isContinuation());
assertEquals("Should be no errors", 0, errors.size());
result = _llv._getSymbolData_FromFileSystem("fully.qualified.Woah", JExprParser.NO_SOURCE_INFO, true, true);
assertEquals("Should return sd2, now resolved.", sd2, result);
assertFalse("sd2 should now be resolved", sd2.isContinuation());
assertEquals("Should be no errors", 0, errors.size());
//what if the files are in different packages
_llv.symbolTable.remove("fully.qualified.Woah");
_llv.visitedFiles.clear();
_llv._package="another.package";
_llv._file = new File("testFiles/another/package/Wowsers.dj0");
sd2 = new SymbolData("fully.qualified.Woah");
_llv.symbolTable.put("fully.qualified.Woah", sd2);
result = _llv._getSymbolData_FromFileSystem("fully.qualified.Woah", JExprParser.NO_SOURCE_INFO, false, true);
assertEquals("Should return sd2, unresolved.", sd2, result);
assertTrue("sd2 should still be unresolved", sd2.isContinuation());
assertEquals("Should be no errors", 0, errors.size());
result = _llv._getSymbolData_FromFileSystem("fully.qualified.Woah", JExprParser.NO_SOURCE_INFO, true, true);
assertEquals("Should return sd2, now resolved.", sd2, result);
assertFalse("sd2 should be resolved", sd2.isContinuation());
assertEquals("Should be no errors", 0, errors.size());
//what if there is no package
//Now, check cases when desired SymbolData is in the symbol table.
_llv._package = "";
_llv._file = new File ("testFiles/Cool.dj0");
//unqualified
SymbolData sd1 = new SymbolData("Wow");
SymbolData obj = _llv._getSymbolData_FromFileSystem("java.lang.Object", JExprParser.NO_SOU