/*
* Copyright (C) 2015 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 <http://www.gnu.org/licenses/gpl-2.0.html>.
*/
package org.robovm.compiler.plugin.objc;
import static org.robovm.compiler.Annotations.*;
import static org.robovm.compiler.Bro.*;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.TreeSet;
import org.robovm.compiler.Types;
import soot.PrimType;
import soot.RefType;
import soot.SootClass;
import soot.SootMethod;
import soot.Type;
import soot.VoidType;
/**
* Encodes Objective-C method signatures as specified by the <a href=
* "https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtTypeEncodings.html#//apple_ref/doc/uid/TP40008048-CH100"
* > Objective-C runtime Type Encodings documentation</a>. For now this just
* does enough to encode <code>IBOutlet</code>/<code>Property</code> annotated
* methods that take/return primitives and instances of types deriving from
* <code></code>.
*/
public class TypeEncoder {
public static final String SELECTOR = "org.robovm.objc.Selector";
public static final String OBJC_CLASS = "org.robovm.objc.ObjCClass";
public String encode(SootMethod method, boolean is64bit) {
StringBuilder sb = new StringBuilder();
sb.append(encodeOne(method, method.getReturnType(), -1, is64bit));
for (int i = 0; i < method.getParameterCount(); i++) {
sb.append(encodeOne(method, method.getParameterType(i), i, is64bit));
}
return sb.toString();
}
private boolean hasAnno(SootMethod method, int idx, String annotationType) {
if (idx == -1) {
return hasAnnotation(method, annotationType);
} else {
return hasParameterAnnotation(method, idx, annotationType);
}
}
private String encodeOne(SootMethod method, Type t, int idx, boolean is64bit) {
if (t instanceof VoidType) {
return encodeVoid((VoidType) t);
}
if (t instanceof PrimType) {
return encodePrimitive(method, (PrimType) t, idx, is64bit);
}
if (Types.isStruct(method.getDeclaringClass()) && hasAnno(method, idx, ARRAY)) {
throw new IllegalArgumentException("Cannot not determine type encoding for @Array annotated method "
+ method + ". @Array is not yet supported. Use an explicit @TypeEncoding annotation instead.");
}
if (t instanceof RefType) {
return encodeRef(method, (RefType) t, idx, is64bit);
}
throw new IllegalArgumentException("Unsupported type "
+ t.getClass().getName() + " " + t);
}
private String encodeVoid(VoidType t) {
return "v";
}
private String encodeRef(SootMethod method, RefType t, int idx, boolean is64bit) {
if (SELECTOR.equals(t.getClassName())) {
return ":";
}
if (OBJC_CLASS.equals(t.getClassName())) {
return "#";
}
if (Types.isStruct(t)) {
if (hasAnno(method, idx, BY_VAL)) {
// Encode as struct
return encodeStruct(method, t, idx, is64bit);
}
return "^v"; // Encode any type of pointer as void*
}
return "@";
}
private boolean isUnion(Collection<Member> members) {
for (Member m : members) {
if (m.offset > 0) {
return false;
}
}
return true;
}
private String encodeStruct(SootMethod method, RefType t, int idx, boolean is64bit) {
// We wrap in a TreeSet to filter out duplicate types that could come
// from getters/setters.
TreeSet<Member> members = new TreeSet<Member>(getStructMembers(t.getSootClass(), is64bit));
StringBuilder sb = new StringBuilder();
boolean union = isUnion(members);
sb.append(union ? '(' : '{');
sb.append("?=");
for (Member m : members) {
sb.append(m.type);
}
sb.append(union ? ')' : '}');
return sb.toString();
}
private String encodePrimitive(SootMethod method, PrimType t, int idx, boolean is64bit) {
if (t.equals(soot.BooleanType.v())) {
return "c";
} else if (t.equals(soot.ByteType.v())) {
return "c";
} else if (t.equals(soot.ShortType.v())) {
return "s";
} else if (t.equals(soot.CharType.v())) {
return "S";
} else if (t.equals(soot.IntType.v())) {
return "i";
} else if (t.equals(soot.LongType.v())) {
if (hasAnno(method, idx, POINTER)) {
return "^v"; // void*
}
if (hasAnno(method, idx, MACHINE_SIZED_S_INT)) {
return is64bit ? "q" : "i";
}
if (hasAnno(method, idx, MACHINE_SIZED_U_INT)) {
return is64bit ? "Q" : "I";
}
return "q";
} else if (t.equals(soot.FloatType.v())) {
if (hasAnno(method, idx, MACHINE_SIZED_FLOAT)) {
return is64bit ? "d" : "f";
}
return "f";
} else if (t.equals(soot.DoubleType.v())) {
if (hasAnno(method, idx, MACHINE_SIZED_FLOAT)) {
return is64bit ? "d" : "f";
}
return "d";
} else {
throw new IllegalArgumentException("Unknown Type: " + t);
}
}
static class Member implements Comparable<Member> {
int offset;
String type;
@Override
public int compareTo(Member o) {
int c = Integer.compare(this.offset, o.offset);
if (c == 0) {
c = type.compareTo(o.type);
}
return c;
}
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + offset;
result = prime * result + ((type == null) ? 0 : type.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;
}
Member other = (Member) obj;
if (offset != other.offset) {
return false;
}
if (type == null) {
if (other.type != null) {
return false;
}
} else if (!type.equals(other.type)) {
return false;
}
return true;
}
}
private List<Member> getStructMembers(SootClass clazz, boolean is64bit) {
List<Member> members = new ArrayList<>();
if (clazz.hasSuperclass()) {
SootClass superclass = clazz.getSuperclass();
if (!superclass.getName().equals("org.robovm.rt.bro.Struct")) {
members.addAll(getStructMembers(clazz, is64bit));
}
}
for (SootMethod method : clazz.getMethods()) {
int offset = getStructMemberOffset(method);
if (offset != -1) {
if (!method.isNative() && !method.isStatic()) {
// Invalid struct member. Just ignore. An exception will be
// thrown by the ClassCompiler later on.
continue;
}
Type type;
int idx;
if (method.getParameterCount() == 0) {
type = method.getReturnType();
idx = -1;
} else if (method.getParameterCount() == 1) {
type = method.getParameterType(0);
idx = 0;
} else {
// Invalid struct member. Just ignore. An exception will be
// throw by the ClassCompiler later on.
continue;
}
Member member = new Member();
member.offset = offset;
member.type = encodeOne(method, type, idx, is64bit);
members.add(member);
}
}
return members;
}
}