/*
* AutoImportor.java
*
* Copyright (c) 2006-2007 David Holroyd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package uk.co.badgersinfoil.metaas.impl;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import org.asdt.core.internal.antlr.AS3Parser;
import uk.co.badgersinfoil.metaas.dom.ASArg;
import uk.co.badgersinfoil.metaas.dom.ASClassType;
import uk.co.badgersinfoil.metaas.dom.ASInterfaceType;
import uk.co.badgersinfoil.metaas.dom.ASMember;
import uk.co.badgersinfoil.metaas.dom.ASMethod;
import uk.co.badgersinfoil.metaas.dom.ASPackage;
import uk.co.badgersinfoil.metaas.dom.ASType;
import uk.co.badgersinfoil.metaas.impl.antlr.LinkedListTree;
/**
* Modify all CompilationUnits in an ASProject so that fully qualified names
* in the code are replaced with unqualified names, and the appropriate
* import statements are added to the package declaration.
*/
public class AutoImporter {
private List visibleQNames = new ArrayList();
// TODO: maintaining a list of names that *could* clash is not as nice
// as actually working out which names *do* clash in a particular
// compilation unit. However, that would require two passes over the
// compilation unit, so we're taking the simpler route for the mo.
private Set possiblyAmbigLocalNames = new HashSet();
public void performAutoImport(ASTActionScriptProject project) {
loadAccessableLanguageElements(project);
processSources(project);
}
private void loadAccessableLanguageElements(ASTActionScriptProject project) {
final Set seenLocalNames = new HashSet();
// look for definitions within the current project code itself
ASVisitor visitor = new ASVisitor.Null() {
public void visit(ASPackage pkg) {
// TODO: will need to look for other top-level
// elements, once metaas has support for them
ASType type = pkg.getType();
ASQName name = new ASQName(pkg.getName(), type.getName());
visibleQNames.add(name);
if (seenLocalNames.contains(name.getLocalName())) {
possiblyAmbigLocalNames.add(name.getLocalName());
} else {
seenLocalNames.add(name.getLocalName());
}
}
};
new ASWalker(visitor).walk(project);
// handle project classpath definitions
Iterator i = project.getResourceRoots().iterator();
while (i.hasNext()) {
ResourceRoot resourceRoot = (ResourceRoot)i.next();
Iterator j = resourceRoot.getDefinitionQNames().iterator();
while (j.hasNext()) {
ASQName name = (ASQName)j.next();
visibleQNames.add(name);
if (seenLocalNames.contains(name.getLocalName())) {
possiblyAmbigLocalNames.add(name.getLocalName());
} else {
seenLocalNames.add(name.getLocalName());
}
}
}
}
private void processSources(ASTActionScriptProject project) {
new ASWalker(new ImportSourceProcessor()).walk(project);
}
private class ImportSourceProcessor extends ASVisitor.Null {
private String currentPackageName;
private ASQName currentTypeName;
private ASPackage currentPackage;
private Set currentPackageImports = new HashSet();
private ASType currentType;
// TODO: handle local names that would be ambiguous if not
// fully-qualified.
public void visit(ASPackage pkg) {
currentPackage = pkg;
currentPackageName = pkg.getName();
inspectImports(pkg);
}
private void inspectImports(ASPackage pkg) {
currentPackageImports.clear();
List imports = pkg.findImports();
for (Iterator i=imports.iterator(); i.hasNext(); ) {
ASQName imp = new ASQName((String)i.next());
if (imp.getLocalName().equals("*")) {
// expand, to list all the imported names
importNamesFromPackage(imp.getPackagePrefix());
} else {
currentPackageImports.add(imp);
}
}
}
private void importNamesFromPackage(String packagePrefix) {
for (Iterator i=visibleQNames.iterator(); i.hasNext(); ) {
ASQName name = (ASQName)i.next();
if (packagePrefix.equals(name.getPackagePrefix())) {
currentPackageImports.add(name);
}
}
}
public void visit(ASType type) {
currentTypeName = new ASQName(currentPackageName, type.getName());
currentType = type;
}
public void visit(ASClassType clazz) {
String superClassName = clazz.getSuperclass();
String processed = processTypeName(superClassName);
if (processed != null) {
clazz.setSuperclass(processed);
}
List interfaceNames = clazz.getImplementedInterfaces();
for (Iterator i=interfaceNames.iterator(); i.hasNext(); ) {
String interfaceName = (String)i.next();
processed = processTypeName(interfaceName);
if (processed != null) {
clazz.removeImplementedInterface(interfaceName);
clazz.addImplementedInterface(processed);
}
}
}
public void visit(ASInterfaceType iface) {
List interfaceNames = iface.getSuperInterfaces();
for (Iterator i=interfaceNames.iterator(); i.hasNext(); ) {
String interfaceName = (String)i.next();
String processed = processTypeName(interfaceName);
if (processed != null) {
iface.removeSuperInterface(interfaceName);
iface.addSuperInterface(processed);
}
}
}
public void visit(ASMember member) {
String processedName = processTypeName(member.getType());
if (processedName != null) {
member.setType(processedName);
}
}
private String processTypeName(String typeName) {
if (typeName != null) {
ASQName qname = new ASQName(typeName);
if (qname.isQualified()
&& visibleQNames.contains(qname))
{
if (importRequired(qname)) {
addImport(qname);
}
if (!possiblyAmbigLocalNames.contains(qname.getLocalName())) {
return qname.getLocalName();
}
}
}
return null;
}
private boolean importRequired(ASQName qname) {
if (currentPackageName != null && currentPackageName.equals(qname.getPackagePrefix())) {
return false;
}
return !currentPackageImports.contains(qname);
}
private void addImport(ASQName qname) {
currentPackage.addImport(qname.toString());
currentPackageImports.add(qname);
}
public void visit(ASMethod method) {
// there's no visit(ASArg),
processAllArgs(method);
if (currentType instanceof ASClassType) {
// the methods in an interface lack a body,
processBody(method);
}
}
private void processAllArgs(ASMethod method) {
List args = method.getArgs();
for (Iterator i=args.iterator(); i.hasNext(); ) {
processArg((ASArg)i.next());
}
}
private void processArg(ASArg arg) {
String processedName = processTypeName(arg.getType());
if (processedName != null) {
arg.setType(processedName);
}
}
private void processBody(ASMethod method) {
LinkedListTree ast = ((ASTASMethod)method).getAST();
LinkedListTree body = (LinkedListTree)ast.getFirstChildWithType(AS3Parser.BLOCK);
processTreeForImports(body);
}
// metaas doesn't have any representations for things at the
// statement / expression level, so now we have to walk the AST,
private void processTreeForImports(LinkedListTree ast) {
if (processNodeForImports(ast)) {
for (int i=0; i<ast.getChildCount(); i++) {
LinkedListTree child = (LinkedListTree)ast.getChild(i);
processTreeForImports(child);
}
}
}
private boolean processNodeForImports(LinkedListTree ast) {
switch (ast.getType()) {
case AS3Parser.TYPE_SPEC:
processTypeSpecForImports(ast);
return false;
case AS3Parser.IDENTIFIER:
processIdentifierForImports(ast);
return false;
case AS3Parser.PROPERTY_OR_IDENTIFIER:
processPropertyOrIdentifierForImports(ast);
return false;
}
return true;
}
private void processTypeSpecForImports(LinkedListTree ast) {
String typeName = ASTUtils.typeSpecText(ast);
String processedName = processTypeName(typeName);
if (processedName != null) {
LinkedListTree newTypeSpec = AS3FragmentParser.parseTypeSpec(processedName);
ast.setChildWithTokens(0, newTypeSpec.getFirstChild());
}
}
private void processIdentifierForImports(LinkedListTree ast) {
System.out.println(ASTUtils.identText(ast));
}
private ASQName processPropertyOrIdentifierForImports(LinkedListTree ast) {
//System.out.println("processPropertyOrIdentifierForImports("+ast.toStringTree()+")");
LinkedListTree target = ast.getFirstChild();
LinkedListTree name = ast.getLastChild();
if (name.getType() != AS3Parser.IDENT) {
return null;
}
ASQName qname = null;
if (target.getType() == AS3Parser.PROPERTY_OR_IDENTIFIER) {
ASQName tmp = processPropertyOrIdentifierForImports(target);
if (tmp != null) {
qname = new ASQName(tmp.toString(), name.getText());
}
} else if (target.getType() == AS3Parser.IDENT) {
qname = new ASQName(target.getText(), name.getText());
}
if (qname == null) {
ast.token.setType(AS3Parser.PROPERTY_ACCESS);
} else {
String processedName = processTypeName(qname.toString());
//System.out.println(" : processTypeName("+qname+") => "+processedName);
if (processedName != null) {
//System.out.println(" : ast.start="+ast.getStartToken()+" ast.stop="+ast.getStopToken());
int myIndex = ast.getParent().getIndexOfChild(ast);
ast.getParent().setChildWithTokens(myIndex, ASTUtils.newAST(AS3Parser.IDENT, processedName));
qname = null;
}
}
return qname;
}
}
}