// ex: se sts=4 sw=4 expandtab:
/*
* Yeti language compiler java class type reader.
*
* Copyright (c) 2007-2013 Madis Janson
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. 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.
* 3. The name of the author may not be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR "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 AUTHOR 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.
*/
package yeti.lang.compiler;
import java.util.*;
import yeti.renamed.asmx.*;
import java.io.IOException;
import java.io.InputStream;
import yeti.lang.Core;
class JavaClassNotFoundException extends Exception {
public JavaClassNotFoundException(String what) {
super(what);
}
}
class JavaTypeReader extends ClassVisitor implements Opcodes {
Map vars = new HashMap();
Map fields = new HashMap();
Map staticFields = new HashMap();
List methods = new ArrayList();
List staticMethods = new ArrayList();
List constructors = new ArrayList();
JavaType parent;
String className;
String[] interfaces;
int access;
JavaTypeReader() {
super(ASM5);
}
public void visit(int version, int access, String name, String signature,
String superName, String[] interfaces) {
if (superName != null)
parent = JavaType.fromDescription('L' + superName + ';');
this.access = access;
this.interfaces = interfaces;
/* System.err.println("visit: ver=" + version + " | access=" + access
+ " | name=" + name + " | sig=" + signature + " super="
+ superName);*/
}
public void visitEnd() {
}
public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
return null;
}
public void visitAttribute(Attribute attr) {
}
private static int parseSig(Map vars, List res, int p, char[] s) {
int arrays = 0;
for (int l = s.length; p < l && s[p] != '>'; ++p) {
if (s[p] == '+' || s[p] == '*') {
continue;
}
if (s[p] == '[') {
++arrays;
continue;
}
if (s[p] == ')')
continue;
YType t = null;
if (s[p] == 'L') {
int p1 = p;
while (p < l && s[p] != ';' && s[p] != '<')
++p;
t = new YType(new String(s, p1, p - p1).concat(";"));
if (p < l && s[p] == '<') {
List param = new ArrayList();
p = parseSig(vars, param, p + 1, s) + 1;
/* XXX: workaround for broken generics support
// strips free type vars from classes...
for (int i = param.size(); --i >= 0;) {
if (((YType) param.get(i)).type
== YetiType.VAR) {
param.remove(i);
}
}*/
t.param = (YType[])
param.toArray(new YType[param.size()]);
}
} else if (s[p] == 'T') {
int p1 = p + 1;
while (++p < l && s[p] != ';' && s[p] != '<');
/*String varName = new String(s, p1, p - p1);
t = (YType) vars.get(varName);
if (t == null) {
t = new YType(1000000);
vars.put(varName, t);
}*/
t = YetiType.OBJECT_TYPE;
} else {
t = new YType(new String(s, p, 1));
}
for (; arrays > 0; --arrays) {
t = new YType(YetiType.JAVA_ARRAY,
new YType[] { t });
}
res.add(t);
}
return p;
}
private List parseSig(int start, String sig) {
List res = new ArrayList();
parseSig(vars, res, start, sig.toCharArray());
return res;
}
static YType[] parseSig1(int start, String sig) {
List res = new ArrayList();
parseSig(new HashMap(), res, start, sig.toCharArray());
return (YType[]) res.toArray(new YType[res.size()]);
}
public FieldVisitor visitField(int access, String name, String desc,
String signature, Object value) {
if ((access & ACC_PRIVATE) == 0) {
/* System.err.println("visitField: name=" + name + " | desc="
+ desc + " | sig=" + signature + " | val=" + value
+ " | access=" + access);*/
List l = parseSig(0, signature == null ? desc : signature);
JavaType.Field f =
new JavaType.Field(name, access, className, (YType) l.get(0));
if ((access & (ACC_FINAL | ACC_STATIC)) == (ACC_FINAL | ACC_STATIC))
f.constValue = value;
(((access & ACC_STATIC) == 0) ? fields : staticFields).put(name, f);
}
return null;
}
public void visitInnerClass(String name, String outerName,
String innerName, int access) {
/* System.err.println("visitInnerClass: name=" +
name + " | outer=" + outerName + " | inner=" + innerName);*/
}
public MethodVisitor visitMethod(int access, String name, String desc,
String signature, String[] exceptions) {
if ((access & ACC_PRIVATE) == 0) {
/* System.err.println("visitMethod: name=" + name + " | desc=" + desc
+ " | sig=" + signature + " | exc=" +
(exceptions == null ? "()"
: Arrays.asList(exceptions).toString())
+ " | access=" + access);*/
JavaType.Method m = new JavaType.Method();
// if (signature == null) {
signature = desc;
// }
List l = parseSig(1, signature);
m.sig = name + signature;
m.name = name.intern();
m.access = access;
int argc = l.size() - 1;
m.returnType = (YType) l.get(argc);
/* hack for broken generic support
if (m.returnType.type == YetiType.VAR) {
m.returnType = YetiType.OBJECT_TYPE;
}*/
m.arguments = (YType[])
l.subList(0, argc).toArray(new YType[argc]);
m.className = className;
if (m.name == "<init>") {
constructors.add(m);
} else if ((access & ACC_STATIC) == 0) {
methods.add(m);
} else {
staticMethods.add(m);
}
}
return null;
}
public void visitOuterClass(String owner, String name, String desc) {
/* System.err.println("visitOuterClass: owner=" + owner + " | name="
+ name + " | desc=" + desc);*/
}
public void visitSource(String source, String debug) {
// System.err.println("visitSource: src=" + source + " | debug=" + debug);
}
}
class JavaType implements Cloneable {
static class Field {
int access;
String name;
YType type;
YType classType;
String className;
Object constValue;
public Field(String name, int access,
String className, YType type) {
this.access = access;
this.type = type;
this.name = name;
this.className = className;
}
YType convertedType() {
return convertValueType(type);
}
void check(YetiParser.Node where, String packageName) {
classType.javaType.checkPackage(where, packageName);
if ((access & classType.javaType.publicMask) == 0)
checkPackage(where, packageName, className, "field", name);
}
}
static class Method {
int access;
String name;
YType[] arguments;
YType returnType;
YType classType;
String className; // name of the class the method actually belongs to
String sig;
String descr;
Method dup(Method[] arr, int n, YType classType) {
if (classType == this.classType ||
className.equals(classType.javaType.className())) {
this.classType = classType;
return this;
}
Method m = new Method();
m.access = access;
m.name = name;
m.arguments = arguments;
m.returnType = returnType;
m.classType = classType;
m.className = className;
m.sig = sig;
m.descr = descr;
arr[n] = m;
return m;
}
Method check(YetiParser.Node where, String packageName, int extraMask) {
classType.javaType.checkPackage(where, packageName);
if ((access & (classType.javaType.publicMask | extraMask)) == 0)
checkPackage(where, packageName, className, "method", name);
return this;
}
public String toString() {
StringBuffer s =
new StringBuffer(returnType.type == YetiType.UNIT
? "void" : returnType.toString());
s.append(' ');
s.append(name);
s.append('(');
for (int i = 0; i < arguments.length; ++i) {
if (i != 0)
s.append(", ");
s.append(arguments[i]);
}
s.append(")");
return s.toString();
}
YType convertedReturnType() {
return convertValueType(returnType);
}
String argDescr(int arg) {
return descriptionOf(arguments[arg]);
}
String descr(String extra) {
if (descr != null) {
return descr;
}
StringBuffer result = new StringBuffer("(");
for (int i = 0; i < arguments.length; ++i) {
result.append(argDescr(i));
}
if (extra != null)
result.append(extra);
result.append(')');
if (returnType.type == YetiType.UNIT) {
result.append('V');
} else {
result.append(descriptionOf(returnType));
}
return descr = result.toString();
}
}
private static final JavaType[] EMPTY_JTARR = {};
static final Map JAVA_PRIM = new HashMap();
final String description;
private boolean resolved;
private Map fields;
private Map staticFields;
private Method[] methods;
private Method[] staticMethods;
private Method[] constructors;
private JavaType parent;
private HashMap interfaces;
private static HashMap CACHE = new HashMap();
int publicMask = Opcodes.ACC_PUBLIC;
int access;
JavaClass implementation;
static void checkPackage(YetiParser.Node where, String packageName,
String name, String what, String item) {
if (!JavaType.packageOfClass(name).equals(packageName))
throw new CompileException(where,
"Non-public " + what + ' ' + name.replace('/', '.')
+ (item == null ? "" : "#".concat(item))
+ " cannot be accessed from different package ("
+ packageName.replace('/', '.') + ")");
}
void checkPackage(YetiParser.Node where, String packageName) {
if ((access & Opcodes.ACC_PUBLIC) == 0)
checkPackage(where, packageName, className(),
(access & Opcodes.ACC_INTERFACE) != 0
? "interface" : "class", null);
}
boolean isInterface() {
return (access & Opcodes.ACC_INTERFACE) != 0;
}
static String descriptionOf(YType t) {
if (t.type == YetiType.VAR) {
if (t.ref != null) {
return descriptionOf(t.ref);
}
return "Ljava/lang/Object;";
}
String r = "";
while (t.type == YetiType.JAVA_ARRAY) {
r = r.concat("[");
t = t.param[0];
}
if (t.type != YetiType.JAVA) {
return "Ljava/lang/Object;";
}
return r.concat(t.javaType.description);
}
static YType convertValueType(YType t) {
if (t.type != YetiType.JAVA) {
return t;
}
String descr = t.javaType.description;
if (descr == "Ljava/lang/String;" || descr == "C") {
return YetiType.STR_TYPE;
}
if (descr == "Ljava/lang/Boolean;" || descr == "Z") {
return YetiType.BOOL_TYPE;
}
if (descr == "Lyeti/lang/Num;" ||
descr.length() == 1 && "BDFIJS".indexOf(descr.charAt(0)) >= 0) {
return YetiType.NUM_TYPE;
}
if (descr == "V") {
return YetiType.UNIT_TYPE;
}
return t;
}
private static JavaType getClass(YType t) {
switch (t.type) {
case YetiType.JAVA:
return t.javaType;
case YetiType.NUM:
return fromDescription("Lyeti/lang/Num;");
case YetiType.STR:
return fromDescription("Ljava/lang/String;");
case YetiType.BOOL:
return fromDescription("Ljava/lang/Boolean;");
case YetiType.MAP:
switch (t.param[2].type) {
case YetiType.LIST_MARKER:
return fromDescription(t.param[1].type == YetiType.NUM
? "Lyeti/lang/MList;" : "Lyeti/lang/AList;");
case YetiType.MAP_MARKER:
return fromDescription("Ljava/util/Map;");
}
return fromDescription("Lyeti/lang/ByKey;");
case YetiType.FUN:
return fromDescription("Lyeti/lang/Fun;");
case YetiType.VARIANT:
return fromDescription("Lyeti/lang/Tag;");
case YetiType.STRUCT:
return fromDescription("Lyeti/lang/Struct;");
case YetiType.UNIT:
case YetiType.VAR:
return fromDescription("Ljava/lang/Object;");
}
return null;
}
static void checkUnsafeCast(YetiParser.Node cast,
YType from, YType to) {
if (from.type != YetiType.JAVA && to.type != YetiType.JAVA &&
to.type != YetiType.JAVA_ARRAY) {
throw new CompileException(cast,
"Illegal cast from " + from + " to " + to +
" (neither side is java object)");
}
JavaType src = getClass(from);
if (src == null)
throw new CompileException(cast, "Illegal cast from " + from);
JavaType dst = getClass(to);
if (from.type == YetiType.VAR && (dst == null ||
dst.description != "Ljava/lang/Object;")) {
from.type = YetiType.JAVA;
from.param = YetiType.NO_PARAM;
from.javaType = fromDescription("Ljava/lang/Object;");
}
if (dst == null) {
if (src.description == "Ljava/lang/Object;" &&
to.type == YetiType.JAVA_ARRAY)
return;
throw new CompileException(cast, "Illegal cast to " + to);
}
if (to.type == YetiType.JAVA &&
(src.access & Opcodes.ACC_INTERFACE) != 0) {
return;
}
try {
if (dst.isAssignable(src) < 0 &&
(from.type != YetiType.JAVA || src.isAssignable(dst) < 0)) {
throw new CompileException(cast,
"Illegal cast from " + from + " to " + to);
}
} catch (JavaClassNotFoundException ex) {
throw new CompileException(cast, ex);
}
}
private JavaType(String description) {
this.description = description.intern();
}
static JavaType createNewClass(String className, JavaClass impl) {
JavaType t = new JavaType('L' + className + ';');
t.implementation = impl;
return t;
}
boolean isCollection() {
return description == "Ljava/util/List;" ||
description == "Ljava/util/Collection;" ||
description == "Ljava/util/Set;";
}
String className() {
if (!description.startsWith("L")) {
throw new RuntimeException("No className for " + description);
}
return description.substring(1, description.length() - 1);
}
String dottedName() {
return className().replace('/', '.');
}
private static void putMethods(Map mm, Method[] methods) {
for (int i = methods.length; --i >= 0;) {
Method m = methods[i];
mm.put(m.sig, m);
}
}
private static void putMethods(Map mm, List methods) {
for (int i = methods.size(); --i >= 0;) {
Method m = (Method) methods.get(i);
mm.put(m.sig, m);
}
}
private static Method[] methodArray(Collection c) {
return (Method[]) c.toArray(new Method[c.size()]);
}
private synchronized void resolve() throws JavaClassNotFoundException {
if (resolved)
return;
if (!description.startsWith("L")) {
resolved = true;
return;
}
JavaTypeReader t = ((Compiler) Compiler.currentCompiler.get())
.classPath.readClass(className());
if (t == null) {
throw new JavaClassNotFoundException(dottedName());
}
resolve(t);
}
void resolve(JavaTypeReader t) throws JavaClassNotFoundException {
access = t.access;
interfaces = new HashMap();
if (t.interfaces != null) {
for (int i = t.interfaces.length; --i >= 0;) {
JavaType it = fromDescription('L' + t.interfaces[i] + ';');
it.resolve();
interfaces.putAll(it.interfaces);
interfaces.put(it.description, it);
}
}
fields = new HashMap();
staticFields = new HashMap();
HashMap mm = new HashMap();
HashMap smm = new HashMap();
if (t.parent != null) {
parent = t.parent;
parent.resolve();
}
for (Iterator i = interfaces.values().iterator(); i.hasNext();) {
JavaType ii = (JavaType) i.next();
staticFields.putAll(ii.staticFields);
putMethods(mm, ii.methods);
}
if (parent != null) {
interfaces.putAll(parent.interfaces);
fields.putAll(parent.fields);
staticFields.putAll(parent.staticFields);
putMethods(mm, parent.methods);
putMethods(smm, parent.staticMethods);
}
fields.putAll(t.fields);
staticFields.putAll(t.staticFields);
putMethods(mm, t.methods);
putMethods(smm, t.staticMethods);
constructors = methodArray(t.constructors);
methods = methodArray(mm.values());
staticMethods = methodArray(smm.values());
resolved = true;
}
void checkAbstract() {
if ((access & Opcodes.ACC_ABSTRACT) != 0)
return;
for (int i = methods.length; --i >= 0;) {
if ((methods[i].access & Opcodes.ACC_ABSTRACT) != 0) {
access |= Opcodes.ACC_ABSTRACT;
return;
}
}
}
static final String[] NUMBER_TYPES = {
"Ljava/lang/Byte;",
"Ljava/lang/Short;",
"Ljava/lang/Float;",
"Ljava/lang/Integer;",
"Ljava/lang/Long;",
"Ljava/math/BigInteger;",
"Ljava/math/BigDecimal;"
};
int isAssignable(JavaType from) throws JavaClassNotFoundException {
from.resolve();
if (this == from) {
return 0;
}
if (from.description.length() == 1) {
return -1;
}
if (from.interfaces.containsKey(description)) {
return 1; // I'm an interface implemented by from
}
from = from.parent;
// I'm from or one of from's parents?
for (int i = 1; from != null; ++i) {
if (this == from) {
return i;
}
from.resolve();
from = from.parent;
}
return -1;
}
YType[] TRY_SMART =
{ YetiType.BOOL_TYPE, YetiType.STR_TYPE, YetiType.NUM_TYPE };
// -1 not assignable. 0 - perfect match. > 0 convertable.
private int isAssignableJT(YType to, YType from, boolean smart)
throws JavaClassNotFoundException, TypeException {
int ass;
if (from.type != YetiType.JAVA && description == "Ljava/lang/Object;") {
return from.type == YetiType.VAR ? 1 : 10;
}
switch (from.type) {
case YetiType.STR:
return "Ljava/lang/String;" == description ? 0 :
"Ljava/lang/CharSequence;" == description ? 1 :
"Ljava/lang/StringBuffer;" == description ||
"Ljava/lang/StringBuilder;" == description ? 2 :
"C" == description ? 3 : -1;
case YetiType.NUM:
if (description == "D" || description == "Ljava/lang/Double;")
return 3;
if (description.length() == 1)
return "BFIJS".indexOf(description.charAt(0)) < 0 ? -1 : 4;
for (int i = NUMBER_TYPES.length; --i >= 0;)
if (NUMBER_TYPES[i] == description)
return 4;
return description == "Ljava/lang/Number;" ? 1 :
description == "Lyeti/lang/Num;" ? 0 : -1;
case YetiType.BOOL:
return description == "Z" || description == "Ljava/lang/Boolean;"
? 0 : -1;
case YetiType.FUN:
return description == "Lyeti/lang/Fun;" ? 0 : -1;
case YetiType.MAP: {
switch (from.param[2].deref().type) {
case YetiType.MAP_MARKER:
return "Ljava/util/Map;" == description &&
(to.param.length == 0 ||
isAssignable(to.param[1], from.param[0], smart) == 0 &&
isAssignable(from.param[1], to.param[0], smart) >= 0)
? 0 : -1;
case YetiType.LIST_MARKER:
if ("Ljava/util/List;" == description ||
"Ljava/util/Collection;" == description ||
"Ljava/util/Set;" == description ||
"Lyeti/lang/AList;" == description ||
"Lyeti/lang/AIter;" == description)
break;
default:
return -1;
}
return to.param.length == 0 ||
(ass = isAssignable(to.param[0], from.param[0], smart)) == 0
|| ass > 0 && from.param[1].type == YetiType.NONE ? 1 : -1;
}
case YetiType.STRUCT:
return description == "Lyeti/lang/Struct;" ? 0 : -1;
case YetiType.VARIANT:
return description == "Lyeti/lang/Tag;" ? 0 : -1;
case YetiType.JAVA:
return isAssignable(from.javaType);
case YetiType.JAVA_ARRAY:
return ("Ljava/util/Collection;" == description ||
"Ljava/util/List;" == description) &&
(to.param.length == 0 ||
isAssignable(to.param[0], from.param[0], smart) == 0)
? 1 : -1;
case YetiType.VAR:
if (smart) {
for (int i = 0; i < TRY_SMART.length; ++i) {
int r = isAssignableJT(to, TRY_SMART[i], false);
if (r >= 0) {
YetiType.unify(from, TRY_SMART[i]);
return r;
}
}
YetiType.unify(from, to);
return 1;
}
}
return description == "Ljava/lang/Object;" ? 10 : -1;
}
private static int isAssignable(YType to, YType from, boolean smart)
throws JavaClassNotFoundException, TypeException {
int ass;
// System.err.println(" --> isAssignable(" + to + ", " + from + ")");
to = to.deref();
from = from.deref();
if (to.type == YetiType.JAVA) {
return to.javaType.isAssignableJT(to, from, smart);
}
if (to.type == YetiType.JAVA_ARRAY) {
YType of = to.param[0];
switch (from.type) {
case YetiType.STR:
return of.type == YetiType.JAVA &&
of.javaType.description == "C" ? 1 : -1;
case YetiType.MAP: {
return from.param[2].type == YetiType.LIST_MARKER &&
(ass = isAssignable(to.param[0], from.param[0], smart))
>= 0 ? 1 : -1;
}
case YetiType.JAVA_ARRAY:
return isAssignable(to.param[0], from.param[0], smart);
}
}
if (to.type == YetiType.STR &&
from.type == YetiType.JAVA &&
from.javaType.description == "Ljava/lang/String;")
return 0;
return -1;
}
static int isAssignable(YetiParser.Node where, YType to,
YType from, boolean smart) {
from = from.deref();
if (smart && from.type == YetiType.UNIT) {
return 0;
}
try {
return isAssignable(to, from, smart);
} catch (JavaClassNotFoundException ex) {
throw new CompileException(where, ex);
} catch (TypeException ex) {
throw new CompileException(where, ex.getMessage());
}
}
static boolean isSafeCast(Scope scope, YetiParser.Node where,
YType to, YType from, boolean explicit) {
to = to.deref();
from = from.deref();
// automatic array wrapping
YType mapKind;
if (from.type == YetiType.JAVA_ARRAY && to.type == YetiType.MAP &&
((mapKind = to.param[2].deref()).type == YetiType.LIST_MARKER ||
mapKind.type == YetiType.VAR)) {
YType fp = from.param[0].deref();
YType tp = to.param[0].deref();
try {
if (fp.javaType != null &&
fp.javaType.description.length() == 1) {
char fromPrimitive = fp.javaType.description.charAt(0);
YetiType.unify(to.param[1], YetiType.NO_TYPE);
YetiType.unify(to.param[0],
fromPrimitive == 'Z' ? YetiType.BOOL_TYPE :
fromPrimitive == 'C' ? YetiType.STR_TYPE :
YetiType.NUM_TYPE);
} else if (tp.type == YetiType.VAR) {
if (fp != tp)
YetiType.unifyToVar(tp, fp);
} else if (isAssignable(where, tp, fp, false) < 0) {
return false;
}
} catch (TypeException ex) {
return false;
}
mapKind.type = YetiType.LIST_MARKER;
mapKind.param = YetiType.NO_PARAM;
YType index = to.param[1].deref();
if (index.type == YetiType.VAR) {
index.type = tp.type == YetiType.STR
? YetiType.NONE : YetiType.NUM;
index.param = YetiType.NO_PARAM;
}
if (index.type == YetiType.NUM && tp.type == YetiType.STR) {
scope.ctx.compiler.warn(new CompileException(where,
"Cast `as array<string>' is dangerous and deprecated." +
"\n Please use either `as list<string>' or" +
" `as array<~String>'"));
}
return true;
}
if (to.type == YetiType.JAVA && to.javaType.description.length() == 1)
return false;
if (explicit)
return isAssignable(where, to, from, true) >= 0;
boolean smart = true;
boolean mayExact = false;
while (from.type == YetiType.MAP &&
from.param[2].type == YetiType.LIST_MARKER &&
(to.type == YetiType.MAP &&
from.param[1].type == YetiType.NONE &&
to.param[2].type == YetiType.LIST_MARKER &&
to.param[1].type != YetiType.NUM ||
to.type == YetiType.JAVA_ARRAY)) {
if (to.type == YetiType.JAVA_ARRAY)
mayExact = true;
from = from.param[0].deref();
to = to.param[0].deref();
smart = false;
}
if (to.type == YetiType.STR && smart &&
(from.type == YetiType.JAVA &&
from.javaType.description == "Ljava/lang/String;"/* ||
from.type == YetiType.JAVA_ARRAY &&
from.param[0].deref().javaType.description == "C"*/))
return true;
if (from.type != YetiType.JAVA)
return false;
try {
return to.type == YetiType.JAVA &&
(to.javaType != from.javaType || mayExact) &&
(smart ? isAssignable(where, to, from, true)
: to.javaType.isAssignable(from.javaType)) >= 0;
} catch (JavaClassNotFoundException ex) {
throw new CompileException(where, ex);
}
}
private Method resolveByArgs(YetiParser.Node n, Method[] ma,
String name, Code[] args,
YType objType) {
name = name.intern();
int rAss = Integer.MAX_VALUE;
int res = -1;
int suitable[] = new int[ma.length];
int suitableCounter = 0;
for (int i = ma.length; --i >= 0;) {
Method m = ma[i];
if (m.name == name && m.arguments.length == args.length) {
suitable[suitableCounter++] = i;
}
}
boolean single = suitableCounter == 1;
find_match:
while (--suitableCounter >= 0) {
int index = suitable[suitableCounter];
Method m = ma[index];
int mAss = 0;
for (int j = 0; j < args.length; ++j) {
int ass = isAssignable(n, m.arguments[j], args[j].type, single);
if (ass < 0) {
continue find_match;
}
if (ass != 0) {
mAss += ass + 1;
}
}
if (m.returnType.javaType != null &&
(m.returnType.javaType.resolve(n).access &
Opcodes.ACC_PUBLIC) == 0)
mAss += 10;
if (mAss == 0) {
res = index;
break;
}
if (mAss < rAss) {
res = index;
rAss = mAss;
}
}
if (res != -1) {
return ma[res].dup(ma, res, objType);
}
StringBuffer err = new StringBuffer("No suitable method ")
.append(name).append('(');
for (int i = 0; i < args.length; ++i) {
if (i != 0)
err.append(", ");
err.append(args[i].type);
}
err.append(") found in ").append(dottedName());
boolean fst = true;
for (int i = ma.length; --i >= 0;) {
if (ma[i].name != name)
continue;
if (fst) {
err.append("\nMethods named ").append(name).append(':');
fst = false;
}
err.append("\n ").append(ma[i]);
}
throw new CompileException(n, err.toString());
}
JavaType resolve(YetiParser.Node where) {
try {
resolve();
} catch (JavaClassNotFoundException ex) {
throw new CompileException(where, ex);
}
return this;
}
private static JavaType javaTypeOf(YetiParser.Node where,
YType objType, String err) {
if (objType.type != YetiType.JAVA) {
throw new CompileException(where,
err + objType + ", java object expected");
}
return objType.javaType.resolve(where);
}
static Method resolveConstructor(YetiParser.Node call,
YType t, Code[] args,
boolean noAbstract) {
JavaType jt = t.javaType.resolve(call);
if ((jt.access & Opcodes.ACC_INTERFACE) != 0)
throw new CompileException(call, "Cannot instantiate interface "
+ jt.dottedName());
if (noAbstract && (jt.access & Opcodes.ACC_ABSTRACT) != 0) {
StringBuffer msg =
new StringBuffer("Cannot construct abstract class ");
msg.append(jt.dottedName());
int n = 0;
for (int i = 0; i < jt.methods.length; ++i)
if ((jt.methods[i].access & Opcodes.ACC_ABSTRACT) != 0) {
if (++n == 1) {
msg.append("\nAbstract methods found in ");
msg.append(jt.dottedName());
msg.append(':');
} else if (n > 2) {
msg.append("\n ...");
break;
}
msg.append("\n ");
msg.append(jt.methods[i]);
}
throw new CompileException(call, msg.toString());
}
return jt.resolveByArgs(call, jt.constructors, "<init>", args, t);
}
static Method resolveMethod(YetiParser.ObjectRefOp ref,
YType objType, Code[] args,
boolean isStatic) {
objType = objType.deref();
JavaType jt = javaTypeOf(ref, objType, "Cannot call method on ");
return jt.resolveByArgs(ref, isStatic ? jt.staticMethods : jt.methods,
ref.name, args, objType);
}
static Field resolveField(YetiParser.ObjectRefOp ref,
YType objType,
boolean isStatic) {
objType = objType.deref();
JavaType jt = javaTypeOf(ref, objType, "Cannot access field on ");
Map fm = isStatic ? jt.staticFields : jt.fields;
Field field = (Field) fm.get(ref.name);
if (field == null) {
throw new CompileException(ref,
(isStatic ? "Static field " : "Field ") +
ref.name + " not found in " + jt.dottedName());
}
if (field.classType != objType) {
if (!field.className.equals(objType.javaType.className())) {
field = new Field(field.name, field.access,
field.className, field.type);
fm.put(field.name, field);
}
field.classType = objType;
}
return field;
}
static JavaType fromDescription(String sig) {
synchronized (CACHE) {
JavaType t = (JavaType) CACHE.get(sig);
if (t == null) {
t = new JavaType(sig);
CACHE.put(sig, t);
}
return t;
}
}
JavaType dup() {
if (!resolved)
throw new IllegalStateException("Cannot clone unresolved class");
try {
return (JavaType) clone();
} catch (CloneNotSupportedException ex) {
throw new IllegalStateException(ex);
}
}
static YType typeOfClass(String packageName, String className) {
if (packageName != null && packageName.length() != 0) {
className = packageName + '/' + className;
}
return new YType("L" + className + ';');
}
public String str() {
switch (description.charAt(0)) {
case 'Z': return "boolean";
case 'B': return "byte";
case 'C': return "char";
case 'D': return "double";
case 'F': return "float";
case 'I': return "int";
case 'J': return "long";
case 'S': return "short";
case 'V': return "void";
case 'L': return "~".concat(dottedName());
}
return "~".concat(description);
}
static String packageOfClass(String className) {
if (className == null || className.length() == 0) {
return "";
}
int p = className.lastIndexOf('/');
return p < 0 ? "" : className.substring(0, p);
}
/* static YType toStructType(YType object) {
return null;
}*/
private static List parentList(JavaType t) {
List a = new ArrayList();
while (t != null) {
a.add(t);
t = t.parent;
}
return a;
}
static YType mergeTypes(YType a, YType b) {
a = a.deref();
b = b.deref();
if (a.type != YetiType.JAVA || b.type != YetiType.JAVA) {
// immutable lists can be recursively merged
if (a.type == YetiType.MAP && b.type == YetiType.MAP &&
a.param[1].type == YetiType.NONE &&
a.param[2].type == YetiType.LIST_MARKER &&
b.param[1].type == YetiType.NONE &&
b.param[2].type == YetiType.LIST_MARKER) {
YType t = mergeTypes(a.param[0], b.param[0]);
if (t != null) {
return new YType(YetiType.MAP, new YType[] {
t, YetiType.NO_TYPE, YetiType.LIST_TYPE });
}
}
if (a.type == YetiType.UNIT &&
(b.type == YetiType.JAVA &&
b.javaType.description.length() != 1 ||
b.type == YetiType.JAVA_ARRAY))
return b;
if (b.type == YetiType.UNIT &&
(a.type == YetiType.JAVA &&
a.javaType.description.length() != 1 ||
a.type == YetiType.JAVA_ARRAY))
return a;
return null;
}
if (a.javaType == b.javaType) {
return a;
}
List aa = parentList(a.javaType), ba = parentList(b.javaType);
JavaType common = null;
for (int i = aa.size(), j = ba.size();
--i >= 0 && --j >= 0 && aa.get(i) == ba.get(j);) {
common = (JavaType) aa.get(i);
}
if (common == null) {
return null;
}
JavaType aj = a.javaType, bj = b.javaType;
if (common.description == "Ljava/lang/Object;") {
int mc = -1;
if (bj.interfaces.containsKey(aj.description)) {
return a;
}
if (aj.interfaces.containsKey(bj.description)) {
return b;
}
Map m = bj.interfaces;
Iterator i = aj.interfaces.keySet().iterator();
while (i.hasNext()) {
Object o;
if ((o = m.get(i.next())) != null) {
JavaType jt = (JavaType) o;
int n = jt.methods.length;
if (n > mc) {
common = jt;
}
}
}
}
YType t = new YType(YetiType.JAVA, YetiType.NO_PARAM);
t.javaType = common;
return t;
}
static YType typeOfName(String name, Scope scope) {
int arrays = 0;
while (name.endsWith("[]")) {
++arrays;
name = name.substring(0, name.length() - 2);
}
String descr = (String) JAVA_PRIM.get(name);
YType t = descr != null ? new YType(descr) :
YetiType.resolveFullClass(arrays == 0 ? name : name.intern(),
scope);
while (--arrays >= 0)
t = new YType(YetiType.JAVA_ARRAY, new YType[] { t });
return t;
}
static {
Map p = JAVA_PRIM;
p.put("int", "I");
p.put("long", "J");
p.put("boolean", "Z");
p.put("byte", "B");
p.put("char", "C");
p.put("double", "D");
p.put("float", "F");
p.put("short", "S");
p.put("number", "Lyeti/lang/Num;");
}
}