package org.jetbrains.plugins.ruby.motion.bridgesupport;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.util.io.StreamUtil;
import com.intellij.openapi.util.text.StringUtil;
import org.jdom.Element;
import org.jdom.input.SAXBuilder;
import org.jetbrains.org.objectweb.asm.Opcodes;
import org.jetbrains.org.objectweb.asm.signature.SignatureReader;
import org.jetbrains.org.objectweb.asm.signature.SignatureVisitor;
import org.jetbrains.plugins.ruby.ruby.lang.psi.impl.RNameUtilCore;
import java.io.InputStream;
import java.util.List;
/**
* @author Dennis.Ushakov
*/
public class BridgeSupportReader {
private static final Logger LOG = Logger.getInstance(BridgeSupportReader.class);
private static final String DECLARED_TYPE = "declared_type";
private static final String DECLARED_TYPE64 = "declared_type64";
private static final String NAME = "name";
public static Framework read(final String name, final String version, final InputStream text, final boolean osx) {
final Framework framework = new Framework(name, version, osx);
try {
final Element root = new SAXBuilder().build(text).getRootElement();
readFramework(root, framework);
framework.mergeClasses();
} catch (Exception e) {
LOG.error("Can't load framework", e, name, version, osx ? "osx" : "");
}
finally {
StreamUtil.closeStream(text);
}
framework.seal();
return framework;
}
private static void readFramework(Element root, Framework framework) {
final List children = root.getChildren();
for (Object child : children) {
final Element e = (Element)child;
final String name = e.getName();
if ("class".equals(name) || "interface".equals(name)) {
framework.addClass(readClass(e));
} else if ("informal_protocol".equals(name)) {
framework.addProtocol(readClass(e));
} else if ("constant".equals(name)) {
readConstant(framework, e);
} else if ("string_constant".equals(name)) {
readStringConstant(framework, e);
} else if ("enum".equals(name)) {
readEnum(framework, e);
} else if ("function".equals(name)) {
readFunction(framework, e);
} else if ("function_alias".equals(name)) {
readFunctionAlias(framework, e);
} else if ("struct".equals(name)) {
readStruct(framework, e);
}
}
}
private static void readStruct(Framework framework, Element e) {
final Struct struct = new Struct(e.getAttributeValue(NAME));
for (Object o : e.getChildren()) {
final Element child = (Element)o;
if ("field".equals(child.getName())) {
struct.addField(child.getAttributeValue(NAME), getDeclaredType(child));
}
}
struct.seal();
framework.addStruct(struct);
}
private static void readFunctionAlias(Framework framework, Element e) {
framework.addFunctionAlias(e.getAttributeValue("name"), e.getAttributeValue("original"));
}
private static void readFunction(FunctionHolder holder, Element e) {
String name = e.getAttributeValue("selector");
name = name == null ? e.getAttributeValue(NAME) : name;
final Function function = new Function(name, "true".equals(e.getAttributeValue("variadic")),
"true".equals(e.getAttributeValue("class_method")));
final String type = e.getAttributeValue("type");
for (Object o : e.getChildren()) {
final Element child = (Element)o;
if ("arg".equals(child.getName())) {
function.addArgument(child.getAttributeValue(NAME), getDeclaredType(child));
} else if ("retval".equals(child.getName())) {
function.setReturnValue(getDeclaredType(child));
}
}
if (function.getReturnValue() == null && type != null) {
readAndroidTypeAndArguments(function, type);
}
holder.addFunction(function);
}
private static void readAndroidTypeAndArguments(final Function function, String argsAndType) {
final SignatureReader reader = new SignatureReader(argsAndType);
final AndroidSignatureVisitor visitor = new AndroidSignatureVisitor(function);
reader.accept(visitor);
function.setReturnValue(visitor.getReturnType());
}
private static void readConstant(Framework framework, Element e) {
framework.addConstant(new Constant(e.getAttributeValue(NAME), getDeclaredType(e)));
}
private static void readStringConstant(Framework framework, Element e) {
final String nsstring = e.getAttributeValue("nsstring");
framework.addConstant(new StringConstant(e.getAttributeValue(NAME), e.getAttributeValue("value"), "true".equals(nsstring)));
}
private static void readEnum(Framework framework, Element e) {
framework.addConstant(new Enum(e.getAttributeValue(NAME), e.getAttributeValue("value")));
}
private static Class readClass(Element e) {
final String name = buildClassName(e.getAttributeValue(NAME));
final Class clazz = new Class(name);
for (Object o : e.getChildren()) {
final Element child = (Element)o;
if ("method".equals(child.getName())) {
readFunction(clazz, child);
}
}
clazz.seal();
return clazz;
}
private static String buildClassName(final String name) {
final String[] components = name.split("(/|\\$)");
for (int i = 0; i < components.length; i++) {
components[i] = StringUtil.capitalize(components[i]);
}
return StringUtil.join(components, RNameUtilCore.SYMBOL_DELIMITER);
}
private static String getDeclaredType(Element e) {
String declaredType = e.getAttributeValue(DECLARED_TYPE);
declaredType = declaredType == null ? e.getAttributeValue(DECLARED_TYPE64) : declaredType;
if (declaredType == null) {
LOG.warn("No declared type for " + ((Element)e.getParent()).getAttributeValue(NAME));
return "void";
}
declaredType = StringUtil.trimEnd(declaredType, " _Nullable");
declaredType = StringUtil.trimEnd(declaredType, " _Nonnull");
return declaredType.intern();
}
private static class AndroidSignatureVisitor extends SignatureVisitor {
private final Function myFunction;
private StringBuilder typeBuilder;
private int arrayLevel;
public AndroidSignatureVisitor(Function function) {
super(Opcodes.API_VERSION);
myFunction = function;
typeBuilder = new StringBuilder();
}
@Override
public void visitFormalTypeParameter(String name) {
}
@Override
public SignatureVisitor visitClassBound() {
return this;
}
@Override
public SignatureVisitor visitInterfaceBound() {
return this;
}
@Override
public SignatureVisitor visitSuperclass() {
return this;
}
@Override
public SignatureVisitor visitInterface() {
return this;
}
@Override
public SignatureVisitor visitParameterType() {
addArgument();
return this;
}
private void addArgument() {
if (typeBuilder.length() > 0) {
finishArrayType();
myFunction.addArgument(null, typeBuilder.toString());
typeBuilder.setLength(0);
}
}
@Override
public SignatureVisitor visitReturnType() {
addArgument();
return this;
}
@Override
public SignatureVisitor visitExceptionType() {
return this;
}
@Override
public void visitBaseType(char descriptor) {
final String type;
switch (descriptor) {
case 'Z':
type = "bool";
break;
case 'B':
type = "Byte";
break;
case 'C':
type = "char";
break;
case 'D':
type = "double";
break;
case 'F':
type = "float";
break;
case 'I':
type = "int";
break;
case 'J':
type = "long";
break;
default:
type = "void";
}
typeBuilder.append(type);
}
@Override
public void visitTypeVariable(String name) {
}
@Override
public SignatureVisitor visitArrayType() {
typeBuilder.append("Array<");
arrayLevel++;
return this;
}
@Override
public void visitClassType(String name) {
typeBuilder.append(buildClassName(name));
}
@Override
public void visitInnerClassType(String name) {
}
@Override
public void visitTypeArgument() {
}
@Override
public SignatureVisitor visitTypeArgument(char wildcard) {
return this;
}
@Override
public void visitEnd() {
}
public String getReturnType() {
finishArrayType();
return typeBuilder.toString();
}
private void finishArrayType() {
for (; arrayLevel > 0; arrayLevel--) {
typeBuilder.append(">");
}
}
}
}