org.robovm.compiler.plugin.objc.ObjCProtocolProxyPlugin Maven / Gradle / Ivy
The newest version!
/*
* Copyright (C) 2014 RoboVM AB
*
* This program 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 2
* of the License, or (at your option) any later version.
*
* This program 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 this program. If not, see .
*/
package org.robovm.compiler.plugin.objc;
import static org.objectweb.asm.Opcodes.*;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.apache.commons.io.FileUtils;
import org.objectweb.asm.AnnotationVisitor;
import org.objectweb.asm.Attribute;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.FieldVisitor;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import org.robovm.compiler.ModuleBuilder;
import org.robovm.compiler.clazz.Clazz;
import org.robovm.compiler.clazz.Clazzes;
import org.robovm.compiler.config.Config;
import org.robovm.compiler.plugin.AbstractCompilerPlugin;
import org.robovm.compiler.plugin.CompilerPlugin;
import soot.SootClass;
import soot.SootResolver;
/**
* {@link CompilerPlugin} which generates proxy classes for Objective-C protocol
* interfaces. The proxy class implements all @Method and @Property methods of
* an interface and all its superinterfaces. We need these proxy classes as
* peers for Objective-C protocols which don't have a dedicated Java peer
* class (e.g. when the class implementing the Objective-C protocol isn't
* exposed).
*/
public class ObjCProtocolProxyPlugin extends AbstractCompilerPlugin {
public static final String OBJC_PROTOCOL = "org.robovm.objc.ObjCProtocol";
public static final String PROXY_CLASS_NAME_SUFFIX = "$ObjCProxy";
private SootClass org_robovm_objc_ObjCProtocol = null;
private boolean initialized = false;
private void init() {
if (initialized) {
return;
}
SootResolver r = SootResolver.v();
org_robovm_objc_ObjCProtocol = r.makeClassRef(OBJC_PROTOCOL);
initialized = true;
}
public static boolean isObjCProxy(SootClass cls) {
return (cls.getModifiers() & Opcodes.ACC_SYNTHETIC) > 0
&& cls.getName().endsWith(PROXY_CLASS_NAME_SUFFIX);
}
private boolean isObjCProtocol(SootClass cls) {
if (org_robovm_objc_ObjCProtocol.isPhantom() || !cls.isInterface()) {
return false;
}
for (SootClass interfaze : cls.getInterfaces()) {
if (interfaze == org_robovm_objc_ObjCProtocol || isObjCProtocol(interfaze)) {
return true;
}
}
return false;
}
/**
* Returns the class the proxy should have as superclass, either
* org.robovm.apple.foundation.NSObject
,
* org.robovm.cocoatouch.foundation.NSObject
or
* org.robovm.objc.ObjCObject
.
*/
private String getProxySuperclassInternalName(SootClass cls) {
if (cls.getName().equals("org.robovm.apple.foundation.NSObjectProtocol")) {
return "org/robovm/apple/foundation/NSObject";
}
if (cls.getName().equals("org.robovm.cocoatouch.foundation.NSObjectProtocol")) {
return "org/robovm/cocoatouch/foundation/NSObject";
}
if (cls == org_robovm_objc_ObjCProtocol) {
return "org/robovm/objc/ObjCObject";
}
for (SootClass interfaze : cls.getInterfaces()) {
String name = getProxySuperclassInternalName(interfaze);
if (name != null) {
return name;
}
}
return null;
}
private void collectProxyInterfaceInternalNames(SootClass cls, List result) {
if (result.isEmpty()) {
result.add(cls.getName().replace('.', '/'));
}
for (SootClass interfaze : cls.getInterfaces()) {
String internalName = interfaze.getName().replace('.', '/');
if (!result.contains(internalName)) {
result.add(internalName);
}
}
for (SootClass interfaze : cls.getInterfaces()) {
collectProxyInterfaceInternalNames(interfaze, result);
}
}
private void generateProxyMethods(Config config, List interfazes,
ClassWriter cw) throws IOException {
Clazzes clazzes = config.getClazzes();
final Set addedMethods = new HashSet<>();
for (String interfaze : interfazes) {
Clazz clazz = clazzes.load(interfaze);
if (clazz == null) {
continue;
}
// Copy all abstract method (we skip default methods) to the proxy
// and make them native instead of abstract.
ClassReader classReader = new ClassReader(clazz.getBytes());
classReader.accept(new ClassVisitor(ASM4, cw) {
@Override
public MethodVisitor visitMethod(int access, String name, String desc, String signature,
String[] exceptions) {
String key = name + desc;
if ((access & ACC_ABSTRACT) > 0 && !addedMethods.contains(key)) {
access &= ~ACC_ABSTRACT;
access |= ACC_NATIVE;
addedMethods.add(key);
return super.visitMethod(access, name, desc, signature, exceptions);
}
return null;
}
@Override
public void visit(int version, int access, String name, String signature, String superName,
String[] interfaces) {
// Ignored
}
@Override
public void visitEnd() {
// Ignored
}
@Override
public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
// Ignored
return null;
}
@Override
public void visitAttribute(Attribute attr) {
// Ignored
}
@Override
public FieldVisitor visitField(int access, String name, String desc, String signature, Object value) {
// Ignored
return null;
}
@Override
public void visitInnerClass(String name, String outerName, String innerName, int access) {
// Ignored
}
@Override
public void visitOuterClass(String owner, String name, String desc) {
// Ignored
}
@Override
public void visitSource(String source, String debug) {
// Ignored
}
}, 0);
}
}
@Override
public void beforeClass(Config config, Clazz clazz, ModuleBuilder moduleBuilder) {
init();
SootClass sootClass = clazz.getSootClass();
if (isObjCProtocol(sootClass)) {
try {
String proxyInternalName = clazz.getInternalName() + PROXY_CLASS_NAME_SUFFIX;
ArrayList interfazes = new ArrayList<>();
collectProxyInterfaceInternalNames(sootClass, interfazes);
ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS);
cw.visit(51, ACC_SUPER + ACC_FINAL + ACC_SYNTHETIC + ACC_PUBLIC,
proxyInternalName, null,
getProxySuperclassInternalName(sootClass),
new String[] {clazz.getInternalName()});
generateProxyMethods(config, interfazes, cw);
cw.visitEnd();
File f = clazz.getPath().getGeneratedClassFile(proxyInternalName);
FileUtils.writeByteArrayToFile(f, cw.toByteArray());
// The proxy class is created after the interface is compiled.
// This prevents the triggering of a recompile of the interface.
f.setLastModified(clazz.lastModified());
// Add the proxy class as a dependency for the protocol interface.
// Important! This must be done AFTER the class file has been written.
clazz.getClazzInfo().addClassDependency(proxyInternalName, false);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy