/**
This file is part of allspice.
allspice is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
allspice is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with allspice. If not, see <http://www.gnu.org/licenses/>.
**/
package org.allspice.structuredtobc;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.TreeSet;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import org.allspice.bytecode.TypeCode;
import org.allspice.bytecode.TypeName;
import org.allspice.structured.ClassDecl;
import org.allspice.structured.ClassType;
import org.allspice.structured.FieldDecl;
import org.allspice.structured.FieldOrMethod;
import org.allspice.structured.FileUnit;
import org.allspice.structured.MethodDecl;
import org.allspice.structured.VarDecl;
import org.allspice.structured.Visibility;
import org.allspice.structured.expr.ConstExpr;
import org.allspice.structured.expr.Expr;
import org.allspice.util.FIFO;
import org.allspice.util.ImmutableCollection;
import com.sun.org.apache.bcel.internal.classfile.Constant;
import com.sun.org.apache.bcel.internal.classfile.ConstantObject;
import com.sun.org.apache.bcel.internal.classfile.ConstantValue;
import com.sun.org.apache.bcel.internal.classfile.JavaClass;
import com.sun.org.apache.bcel.internal.generic.Type;
import com.sun.org.apache.bcel.internal.util.ClassPath;
import com.sun.org.apache.bcel.internal.util.SyntheticRepository;
public class ClassPool {
public final Map<FileUnit,FileUnitInfo> units = new HashMap<FileUnit,FileUnitInfo>() ;
public final Map<TypeName,ClassStub> classStubs = new HashMap<TypeName,ClassStub>() ;
public final Entry myClasses ;
public static final ClassPath classPath = new ClassPath(ClassPath.getClassPath());
public static final SyntheticRepository repo = SyntheticRepository.getInstance(classPath);
public static Entry allClasses = new Entry() ;
public static class FileUnitInfo {
public final FileUnit unit ;
public final Map<String,TypeName> aliases = new HashMap<String,TypeName>() ;
public FileUnitInfo(FileUnit unit) {
super();
this.unit = unit;
}
public TypeName getFullQualified(String type) {
if (type.endsWith("[]")) {
return new TypeName(getFullQualified(type.substring(0,type.length() - 2))+"[]") ;
}
TypeName fq = aliases.get(type) ;
if (fq != null) {
return fq ;
}
return new TypeName(type) ;
}
}
public ClassPool(Collection<FileUnit> fileUnits) {
myClasses = allClasses.clone();
for(FileUnit fu: fileUnits) {
final FileUnitInfo fuinfo = new FileUnitInfo(fu);
this.units.put(fu,fuinfo) ;
for(ClassDecl cd: fu.classDecl) {
TypeName cname = getClassName(fu,cd);
myClasses.add(cname.toString().replace(".","/")) ;
}
}
for(FileUnit fu: fileUnits) {
final FileUnitInfo fuinfo = units.get(fu) ;
for(String imp: fuinfo.unit.imports) {
myClasses.addAliases(imp, fuinfo.aliases, "",false) ;
}
myClasses.addAliases("java.lang.*",fuinfo.aliases, "",true) ;
if (fu.packageName != null) {
myClasses.addAliases(fu.packageName+".*",fuinfo.aliases, "",true) ;
}
}
}
public boolean isPackageOrClass(String s) {
for(FileUnitInfo fui: units.values()) {
if (myClasses.isPackageOrClass(s) || fui.aliases.get(s) != null) {
return true ;
}
}
return allClasses.isPackageOrClass(s) ;
}
public boolean find(StubResolver resolver,TypeName s) throws CompilerException {
if (s.equals(resolver.getTypeName())) {
return true ;
}
ClassStub cl = resolver.getStub() ;
if (cl.superClass != null) {
if (find(cl.superClass,s)) {
return true ;
}
}
for(StubResolver iface: cl.ifaces) {
if (find(iface,s)) {
return true ;
}
}
return false ;
}
public boolean isAutoConvertible(StubResolver resolver,TypeName s) {
switch(TypeCode.getType(s)) {
case DOUBLE:
switch(resolver.getTypeCode()) {
case DOUBLE:
case FLOAT:
case LONG:
case SHORT:
case INT:
case CHAR:
case BYTE:
return true ;
default:
return false ;
}
case FLOAT:
switch(resolver.getTypeCode()) {
case FLOAT:
case LONG:
case INT:
case SHORT:
case CHAR:
case BYTE:
return true ;
default:
return false ;
}
case LONG:
switch(resolver.getTypeCode()) {
case LONG:
case INT:
case SHORT:
case CHAR:
case BYTE:
return true ;
default:
return false ;
}
case INT:
switch(resolver.getTypeCode()) {
case INT:
case SHORT:
case CHAR:
case BYTE:
return true ;
default:
return false ;
}
case BYTE:
switch(resolver.getTypeCode()) {
case BYTE:
return true ;
default:
return false ;
}
case CHAR:
switch(resolver.getTypeCode()) {
case CHAR:
return true ;
default:
return false ;
}
case BOOLEAN:
switch(resolver.getTypeCode()) {
case BOOLEAN:
return true ;
default:
return false ;
}
case SHORT:
switch(resolver.getTypeCode()) {
case BYTE:
case SHORT:
return true ;
default:
return false ;
}
case OBJECT:
break ;
default :
return false ;
}
return false ;
}
public ClassDecl getClassDecl(TypeName name) {
for(FileUnit fu: units.keySet()) {
for(ClassDecl cd: fu.classDecl) {
TypeName cname = getClassName(fu,cd) ;
if (cname.equals(name)) {
return cd ;
}
}
}
return null ;
}
public static class Entry implements Cloneable {
public Set<String> items = new TreeSet<String>() ;
public Map<String,Entry> entries = new HashMap<String,Entry>() ;
public void addAliases(String s,Map<String,TypeName> aliases,String prefix,boolean ignoreIfPresent) {
int pos = s.indexOf(".") ;
if (pos >= 0) {
String t = s.substring(0,pos) ;
String u = s.substring(pos + 1) ;
Entry e = entries.get(t) ;
if (e == null) {
return ;
}
final String newPrefix = prefix+t+".";
e.addAliases(u,aliases,newPrefix,ignoreIfPresent) ;
}
else {
if (s.equals("*")) {
for(String item: items) {
if (aliases.containsKey(item)) {
if (!ignoreIfPresent) {
aliases.put(item,null) ;
}
}
else {
aliases.put(item,new TypeName(prefix+item)) ;
}
}
}
else if (items.contains(s)) {
if (aliases.containsKey(s)) {
if (!ignoreIfPresent) {
aliases.put(s,null) ;
}
}
else {
aliases.put(s,new TypeName(prefix+s)) ;
}
}
}
}
public boolean isPackageOrClass(String s) {
int pos = s.indexOf(".") ;
if (pos >= 0) {
String t = s.substring(0,pos) ;
String u = s.substring(pos + 1) ;
Entry e = entries.get(t) ;
if (e == null) {
return false ;
}
return e.isPackageOrClass(u) ;
}
else {
return entries.containsKey(s) || items.contains(s) ;
}
}
public void add(String s) {
int pos = s.indexOf("/") ;
if (pos >= 0) {
String t = s.substring(0,pos) ;
String u = s.substring(pos + 1) ;
Entry e = entries.get(t) ;
if (e == null) {
e = new Entry() ;
entries.put(t, e) ;
}
e.add(u) ;
}
else {
items.add(s) ;
}
}
@Override
public String toString() {
return "["+items+","+entries+"]" ;
}
public void spew() {
System.out.println("items="+items) ;
for(Map.Entry<String, Entry> e: entries.entrySet()) {
System.out.println(e.getKey()+"{") ;
e.getValue().spew();
System.out.println("}") ;
}
}
@Override
protected Entry clone() {
Entry cloned = new Entry() ;
cloned.items = new HashSet<String>(items) ;
for(Map.Entry<String, Entry> e: entries.entrySet()) {
cloned.entries.put(e.getKey(),e.getValue().clone()) ;
}
return cloned ;
}
}
public static void fill(File dir,String prefix) {
for(File f: dir.listFiles()) {
if (f.isDirectory()) {
fill(f,prefix+f.getName()+"/") ;
}
else {
if (f.getName().endsWith(".class")) {
allClasses.add(prefix+f.getName().substring(0,f.getName().length() - 6)) ;
}
}
}
}
static {
String cp = ClassPath.getClassPath() ;
StringTokenizer tok = new StringTokenizer(cp,System.getProperty("path.separator"));
while(tok.hasMoreTokens()) {
String path = tok.nextToken();
File f = new File(path) ;
if (f.isDirectory()) {
fill(f,"") ;
}
else {
try {
ZipInputStream zis = new ZipInputStream(new FileInputStream(f)) ;
try {
for(;;) {
ZipEntry entry = zis.getNextEntry();
if (entry == null) {
break ;
}
final String name = entry.getName();
if (name.endsWith(".class")) {
allClasses.add(name.substring(0,name.length() - 6)) ;
}
}
}
finally {
zis.close() ;
}
} catch (IOException e) {
continue ;
}
}
}
}
public FileUnitInfo findFileUnit(FileUnit fu) {
return units.get(fu) ;
}
private ClassStub getClassStub(TypeName name) throws CompilerException {
if (classStubs.containsKey(name)) {
return classStubs.get(name) ;
}
if (name.toString().endsWith("[]")) {
ClassStub stub = new ClassStub(name,new SimpleResolver(TypeName.OBJECT),false) ;
classStubs.put(name,stub) ;
return stub ;
}
for(FileUnit fu: units.keySet()) {
for(ClassDecl cd: fu.classDecl) {
TypeName cname = ClassPool.getClassName(fu,cd) ;
if (cname.equals(name)) {
ClassStub stub = makeClassDeclStub(units.get(fu),cd);
classStubs.put(name,stub) ;
return stub ;
}
}
}
JavaClass jc = repo.findClass(name.toString()) ;
if (jc == null) {
try {
jc = repo.loadClass(name.toString()) ;
} catch (ClassNotFoundException e) {
/* jc = null ; */
}
}
if (jc != null) {
ClassStub stub = makeJavaClassStub(jc) ;
classStubs.put(name,stub) ;
return stub ;
}
switch(TypeCode.getType(name)) {
case VOID:
case BOOLEAN:
case BYTE:
case CHAR:
case DOUBLE:
case FLOAT:
case LONG:
case INT:
case SHORT:
ClassStub stub = new ClassStub(name,null,false) ;
classStubs.put(name,stub) ;
return stub ;
default:
break ;
}
throw new UndefinedClass(name) ;
}
public class SimpleResolver implements StubResolver {
final TypeName cname ;
public SimpleResolver(TypeName cname) {
super();
this.cname = cname;
}
@Override
public ClassStub getStub() throws CompilerException {
return getClassStub(cname) ;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + getOuterType().hashCode();
result = prime * result + ((cname == null) ? 0 : cname.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
SimpleResolver other = (SimpleResolver) obj;
if (!getOuterType().equals(other.getOuterType()))
return false;
if (cname == null) {
if (other.cname != null)
return false;
} else if (!cname.equals(other.cname))
return false;
return true;
}
private ClassPool getOuterType() {
return ClassPool.this;
}
@Override
public TypeName getTypeName() {
return cname;
}
@Override
public StubResolver getArrayBase() {
return new SimpleResolver(cname.getArrayBase()) ;
}
@Override
public TypeCode getTypeCode() {
return TypeCode.getType(cname) ;
}
}
private ClassStub makeJavaClassStub(JavaClass realClass) {
ClassStub stub;
StubResolver superc ;
if (realClass.getSuperclassName() != null && !realClass.getSuperclassName().equals(realClass.getClassName())) {
superc = new SimpleResolver(new TypeName(realClass.getSuperclassName())) ;
}
else {
superc = null ;
}
final TypeName cname = new TypeName(realClass.getClassName());
stub = new ClassStub(cname,superc,realClass.isInterface()) ;
StubResolver myStub = new SimpleResolver(cname) ;
final com.sun.org.apache.bcel.internal.classfile.Method[] methods = realClass.getMethods();
for(com.sun.org.apache.bcel.internal.classfile.Method meth: methods) {
Type[] type = meth.getArgumentTypes();
FIFO<StubResolver> types = new FIFO<StubResolver>() ;
for(Type ptype: type) {
types = types.insert(new SimpleResolver(new TypeName(ptype.toString()))) ;
}
boolean isStatic = false ;
boolean isPrivate = false ;
if ((meth.getModifiers() & Modifier.STATIC) != 0) {
isStatic = true ;
}
if ((meth.getModifiers() & Modifier.PRIVATE) != 0) {
isPrivate = true ;
}
final TypeName mname = new TypeName(meth.getReturnType().toString()) ;
StubResolver retType = new SimpleResolver(mname) ;
stub = stub.addMethod(new MethodStub(meth.getName(),myStub,retType,types,isStatic,isPrivate)) ;
}
com.sun.org.apache.bcel.internal.classfile.Field[] fields = realClass.getFields();
for(com.sun.org.apache.bcel.internal.classfile.Field fld: fields) {
final boolean isStatic = (fld.getModifiers() & Modifier.STATIC) != 0;
final boolean isFinal = (fld.getModifiers() & Modifier.FINAL) != 0;
StubResolver fldstub = new SimpleResolver(new TypeName(fld.getType().toString())) ;
if (isStatic && isFinal) {
ConstantValue cv = fld.getConstantValue();
Object o ;
if (cv != null) {
Constant c = cv.getConstantPool().getConstant(cv.getConstantValueIndex());
if (c instanceof ConstantObject) {
o = ((ConstantObject)c).getConstantValue(cv.getConstantPool()) ;
try {
o = StdJavaExpressions.cast( this, fldstub, new ConstObj(o)) ;
} catch (CompilerException e) {
o = null ;
}
}
else {
o = null ;
}
}
else {
o = null ;
}
if (o != null) {
final FieldStub fldDef = new FieldStub(fld.getName(),myStub,fldstub,isStatic,isFinal,new ConstExpr(o,null));
stub = stub.addField(fldDef) ;
continue ;
}
}
stub = stub.addField(new FieldStub(fld.getName(),myStub,fldstub,isStatic,isFinal,null));
}
String[] ifaces = realClass.getInterfaceNames();
for(String iface: ifaces) {
stub = stub.addInterface(new SimpleResolver(new TypeName(iface))) ;
}
return stub;
}
private ClassStub makeClassDeclStub(FileUnitInfo finfo,ClassDecl cd) {
ClassStub stub;
TypeName cname = getClassName(finfo.unit,cd) ;
StubResolver enclosing = new SimpleResolver(cname) ;
stub = new ClassStub(cname,new SimpleResolver(finfo.getFullQualified(cd.superClass)),cd.classType == ClassType.INTERFACE) ;
classStubs.put(cname,stub) ;
boolean foundInit = false ;
for(FieldOrMethod fom: cd.decls) {
if (fom instanceof FieldDecl) {
FieldDecl fdec = (FieldDecl)fom ;
StubResolver fldstub = new SimpleResolver(finfo.getFullQualified(fdec.type)) ;
stub = stub.addField(new FieldStub(fdec.name,enclosing,fldstub,fdec.fieldAttrs.isStatic,false,fdec.intializer));
}
else if (fom instanceof MethodDecl) {
MethodDecl mdec = (MethodDecl)fom ;
FIFO<StubResolver> types = new FIFO<StubResolver>() ;
for(VarDecl vd: mdec.parameters) {
types = types.insert(new SimpleResolver(finfo.getFullQualified(vd.type))) ;
}
boolean isStatic = false ;
boolean isPrivate = false ;
if (mdec.fieldAttrs.isStatic) {
isStatic = true ;
}
if (mdec.fieldAttrs.visibility == Visibility.PRIVATE) {
isPrivate = true ;
}
StubResolver retType = new SimpleResolver(finfo.getFullQualified(mdec.returnType)) ;
stub = stub.addMethod(new MethodStub(mdec.name,enclosing,retType,types,isStatic,isPrivate)) ;
}
}
if (!foundInit) {
StubResolver retType = new SimpleResolver(TypeName.VOID) ;
stub = stub.addMethod(new MethodStub("<init>",enclosing,retType,new FIFO<StubResolver>(),false,false)) ;
}
for(String iface: cd.interfaces) {
stub = stub.addInterface(new SimpleResolver(finfo.getFullQualified(iface))) ;
}
return stub;
}
public boolean isAssignableFrom(TypeName t1,StubResolver s2) throws CompilerException {
return isAutoConvertible(s2, t1) || isSubClass(t1,s2) ;
}
public boolean isSubClass(TypeName t1,StubResolver s2) throws CompilerException {
if (find(s2,t1)) {
return true ;
}
if (t1.isArray() && TypeCode.getType(s2.getTypeName()) == TypeCode.ARRAY) {
return isSubClass(t1.getArrayBase(),new SimpleResolver(s2.getTypeName().getArrayBase())) ;
}
return false ;
}
public FieldStub getField(StubResolver realClass, String fname) throws CompilerException {
ClassStub stub = realClass.getStub() ;
{
FieldStub fd = stub.fields.get(fname) ;
if (fd != null) {
return fd ;
}
}
if (stub.superClass != null) {
FieldStub fd = getField(stub.superClass,fname) ;
if (fd != null) {
return fd ;
}
}
for(StubResolver iface: stub.ifaces) {
FieldStub fd = getField(iface,fname) ;
if (fd != null) {
return fd ;
}
}
return null ;
}
public boolean isSubSet(MethodStub ms1,MethodStub ms2) throws CompilerException {
Iterator<StubResolver> i = ms1.types.iterator();
Iterator<StubResolver> j = ms2.types.iterator();
while(i.hasNext()) {
StubResolver type1 = i.next();
StubResolver type2 = j.next();
if (!isAssignableFrom(type1.getTypeName(), type2)) {
return false ;
}
}
return true ;
}
public MethodStub getMethod(StubResolver realClassResolver, String mname,Collection<Expr> args,EvaluationContext context) throws CompilerException {
List<MethodStub> methods = new ArrayList<MethodStub>() ;
getMethods(realClassResolver,mname,args,context,methods) ;
List<MethodStub> winners = new ArrayList<MethodStub>() ;
for(MethodStub ms: methods) {
Iterator<MethodStub> i = winners.iterator() ;
boolean isLoser = false ;
while(i.hasNext()) {
MethodStub winner = i.next() ;
if (isSubSet(ms, winner)) {
isLoser = true ;
break ;
}
else if (isSubSet(winner,ms)) {
i.remove() ;
}
}
if (!isLoser) {
winners.add(ms) ;
}
}
if (winners.isEmpty()) {
return null ;
}
if (winners.size() > 1) {
throw new AmbiguousMethod(mname) ;
}
return winners.get(0) ;
}
public void getMethods(StubResolver realClassResolver, String mname,Collection<Expr> args,EvaluationContext context,
List<MethodStub> methodList) throws CompilerException {
ClassStub realClass = realClassResolver.getStub() ;
Collection<MethodStub> bucket = realClass.methods.get(ClassStub.getMethodBucketKey(mname,args.size())) ;
if (bucket != null) {
for(MethodStub fd: bucket) {
if (parameterMatch(args, context, fd.types)) {
methodList.add(fd) ;
}
}
}
if (mname.equals("<init>")) {
for(String key :realClass.methods.keySet()) {
if (key.startsWith("<init>:")) {
return ;
}
}
StubResolver retType = new SimpleResolver(TypeName.VOID) ;
methodList.add(new MethodStub("<init>",new SimpleResolver(realClass.name),retType,new FIFO<StubResolver>(),false,false)) ;
return ;
}
if (mname.equals("<clinit>")) {
return ;
}
if (realClass.superClass != null) {
getMethods(realClass.superClass,mname,args,context,methodList) ;
}
for(StubResolver iface: realClass.ifaces) {
getMethods(iface,mname,args,context,methodList) ;
}
}
private boolean parameterMatch(Collection<Expr> args,
EvaluationContext context, ImmutableCollection<StubResolver> types) throws CompilerException {
if (types.size() != args.size()) {
return false ;
}
Iterator<StubResolver> i = types.iterator();
Iterator<Expr> j = args.iterator();
while(i.hasNext()) {
StubResolver param = i.next();
Expr arg = j.next();
if (!context.isInstanceOf(param.getTypeName(),arg)) {
return false ;
}
}
return true;
}
public static TypeName getClassName(FileUnit fu,ClassDecl cd) {
return new TypeName((fu.packageName == null ? "" : (fu.packageName+"."))+cd.name);
}
}