1183 lines
27 KiB
Go
1183 lines
27 KiB
Go
// Copyright 2016 The Go Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style
|
|
// license that can be found in the LICENSE file.
|
|
|
|
// The java package takes the result of an AST traversal by the
|
|
// importers package and queries the java command for the type
|
|
// information for the referenced Java classes and interfaces.
|
|
//
|
|
// It is the of go/types for Java types and is used by the bind
|
|
// package to generate Go wrappers for Java API on Android.
|
|
package java
|
|
|
|
import (
|
|
"bufio"
|
|
"bytes"
|
|
"errors"
|
|
"fmt"
|
|
"os/exec"
|
|
"reflect"
|
|
"strings"
|
|
"unicode"
|
|
"unicode/utf8"
|
|
|
|
"golang.org/x/mobile/internal/importers"
|
|
)
|
|
|
|
// Class is the bind representation of a Java class or
|
|
// interface.
|
|
// Use Import to convert class references to Class.
|
|
type Class struct {
|
|
// "java.pkg.Class.Inner"
|
|
Name string
|
|
// "java.pkg.Class$Inner"
|
|
FindName string
|
|
// JNI mangled name
|
|
JNIName string
|
|
// "Inner"
|
|
PkgName string
|
|
Funcs []*FuncSet
|
|
Methods []*FuncSet
|
|
// funcMap maps function names.
|
|
funcMap map[string]*FuncSet
|
|
// FuncMap maps method names.
|
|
methodMap map[string]*FuncSet
|
|
// All methods, including methods from
|
|
// supers.
|
|
AllMethods []*FuncSet
|
|
Vars []*Var
|
|
Supers []string
|
|
Final bool
|
|
Abstract bool
|
|
Interface bool
|
|
Throwable bool
|
|
// Whether the class has a no-arg constructor
|
|
HasNoArgCon bool
|
|
}
|
|
|
|
// FuncSet is the set of overloaded variants of a function.
|
|
// If the function is not overloaded, its FuncSet contains
|
|
// one entry.
|
|
type FuncSet struct {
|
|
Name string
|
|
GoName string
|
|
Funcs []*Func
|
|
CommonSig
|
|
}
|
|
|
|
// CommonSig is a signature compatible with every
|
|
// overloaded variant of a FuncSet.
|
|
type CommonSig struct {
|
|
// Variadic is set if the signature covers variants
|
|
// with varying number of parameters.
|
|
Variadic bool
|
|
// HasRet is true if at least one variant returns a
|
|
// value.
|
|
HasRet bool
|
|
Throws bool
|
|
Params []*Type
|
|
Ret *Type
|
|
}
|
|
|
|
// Func is a Java static function or method or constructor.
|
|
type Func struct {
|
|
FuncSig
|
|
ArgDesc string
|
|
// Mangled JNI name
|
|
JNIName string
|
|
Static bool
|
|
Abstract bool
|
|
Final bool
|
|
Public bool
|
|
Constructor bool
|
|
Params []*Type
|
|
Ret *Type
|
|
Decl string
|
|
Throws string
|
|
}
|
|
|
|
// FuncSig uniquely identifies a Java Func.
|
|
type FuncSig struct {
|
|
Name string
|
|
// The method descriptor, in JNI format.
|
|
Desc string
|
|
}
|
|
|
|
// Var is a Java member variable.
|
|
type Var struct {
|
|
Name string
|
|
Static bool
|
|
Final bool
|
|
Val string
|
|
Type *Type
|
|
}
|
|
|
|
// Type is a Java type.
|
|
type Type struct {
|
|
Kind TypeKind
|
|
Class string
|
|
Elem *Type
|
|
}
|
|
|
|
type TypeKind int
|
|
|
|
type Importer struct {
|
|
Bootclasspath string
|
|
Classpath string
|
|
// JavaPkg is java package name for generated classes.
|
|
JavaPkg string
|
|
|
|
clsMap map[string]*Class
|
|
}
|
|
|
|
// funcRef is a reference to a Java function (static method).
|
|
// It is used as a key to filter unused Java functions.
|
|
type funcRef struct {
|
|
clsName string
|
|
goName string
|
|
}
|
|
|
|
type errClsNotFound struct {
|
|
name string
|
|
}
|
|
|
|
const (
|
|
Int TypeKind = iota
|
|
Boolean
|
|
Short
|
|
Char
|
|
Byte
|
|
Long
|
|
Float
|
|
Double
|
|
String
|
|
Array
|
|
Object
|
|
)
|
|
|
|
func (e *errClsNotFound) Error() string {
|
|
return "class not found: " + e.name
|
|
}
|
|
|
|
// IsAvailable reports whether the required tools are available for
|
|
// Import to work. In particular, IsAvailable checks the existence
|
|
// of the javap binary.
|
|
func IsAvailable() bool {
|
|
_, err := javapPath()
|
|
return err == nil
|
|
}
|
|
|
|
func javapPath() (string, error) {
|
|
return exec.LookPath("javap")
|
|
}
|
|
|
|
// Import returns Java Class descriptors for a list of references.
|
|
//
|
|
// The javap command from the Java SDK is used to dump
|
|
// class information. Its output looks like this:
|
|
//
|
|
// Compiled from "System.java"
|
|
// public final class java.lang.System {
|
|
//
|
|
// public static final java.io.InputStream in;
|
|
// descriptor: Ljava/io/InputStream;
|
|
// public static final java.io.PrintStream out;
|
|
// descriptor: Ljava/io/PrintStream;
|
|
// public static final java.io.PrintStream err;
|
|
// descriptor: Ljava/io/PrintStream;
|
|
// public static void setIn(java.io.InputStream);
|
|
// descriptor: (Ljava/io/InputStream;)V
|
|
//
|
|
// ...
|
|
//
|
|
// }
|
|
func (j *Importer) Import(refs *importers.References) ([]*Class, error) {
|
|
if j.clsMap == nil {
|
|
j.clsMap = make(map[string]*Class)
|
|
}
|
|
clsSet := make(map[string]struct{})
|
|
var names []string
|
|
for _, ref := range refs.Refs {
|
|
// The reference could be to some/pkg.Class or some/pkg/Class.Identifier. Include both.
|
|
pkg := strings.Replace(ref.Pkg, "/", ".", -1)
|
|
for _, cls := range []string{pkg, pkg + "." + ref.Name} {
|
|
if _, exists := clsSet[cls]; !exists {
|
|
clsSet[cls] = struct{}{}
|
|
names = append(names, cls)
|
|
}
|
|
}
|
|
}
|
|
// Make sure toString() is included; it is called when wrapping Java exception types to Go
|
|
// errors.
|
|
refs.Names["ToString"] = struct{}{}
|
|
funcRefs := make(map[funcRef]struct{})
|
|
for _, ref := range refs.Refs {
|
|
pkgName := strings.Replace(ref.Pkg, "/", ".", -1)
|
|
funcRefs[funcRef{pkgName, ref.Name}] = struct{}{}
|
|
}
|
|
classes, err := j.importClasses(names, true)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
j.filterReferences(classes, refs, funcRefs)
|
|
supers, err := j.importReferencedClasses(classes)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
j.filterReferences(supers, refs, funcRefs)
|
|
// Embedders refer to every exported Go struct that will have its class
|
|
// generated. Allow Go code to reverse bind to those classes by synthesizing
|
|
// their class descriptors.
|
|
for _, emb := range refs.Embedders {
|
|
n := emb.Pkg + "." + emb.Name
|
|
if j.JavaPkg != "" {
|
|
n = j.JavaPkg + "." + n
|
|
}
|
|
if _, exists := j.clsMap[n]; exists {
|
|
continue
|
|
}
|
|
clsSet[n] = struct{}{}
|
|
cls := &Class{
|
|
Name: n,
|
|
FindName: n,
|
|
JNIName: JNIMangle(n),
|
|
PkgName: emb.Name,
|
|
HasNoArgCon: true,
|
|
}
|
|
for _, ref := range emb.Refs {
|
|
jpkg := strings.Replace(ref.Pkg, "/", ".", -1)
|
|
super := jpkg + "." + ref.Name
|
|
if _, exists := j.clsMap[super]; !exists {
|
|
return nil, fmt.Errorf("failed to find Java class %s, embedded by %s", super, n)
|
|
}
|
|
cls.Supers = append(cls.Supers, super)
|
|
}
|
|
classes = append(classes, cls)
|
|
j.clsMap[cls.Name] = cls
|
|
}
|
|
// Include implicit classes that are used in parameter or return values.
|
|
for _, cls := range classes {
|
|
for _, fsets := range [][]*FuncSet{cls.Funcs, cls.Methods} {
|
|
for _, fs := range fsets {
|
|
for _, f := range fs.Funcs {
|
|
names := j.implicitFuncTypes(f)
|
|
for _, name := range names {
|
|
if _, exists := clsSet[name]; exists {
|
|
continue
|
|
}
|
|
clsSet[name] = struct{}{}
|
|
classes = append(classes, j.clsMap[name])
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
for _, cls := range j.clsMap {
|
|
j.fillFuncSigs(cls.Funcs)
|
|
j.fillFuncSigs(cls.Methods)
|
|
for _, m := range cls.Methods {
|
|
j.fillSuperSigs(cls, m)
|
|
}
|
|
}
|
|
for _, cls := range j.clsMap {
|
|
j.fillAllMethods(cls)
|
|
}
|
|
// Include classes that appear as ancestor types for overloaded signatures.
|
|
for _, cls := range classes {
|
|
for _, funcs := range [][]*FuncSet{cls.Funcs, cls.AllMethods} {
|
|
for _, f := range funcs {
|
|
for _, p := range f.Params {
|
|
if p == nil || p.Kind != Object {
|
|
continue
|
|
}
|
|
if _, exists := clsSet[p.Class]; !exists {
|
|
clsSet[p.Class] = struct{}{}
|
|
classes = append(classes, j.clsMap[p.Class])
|
|
}
|
|
}
|
|
if t := f.Ret; t != nil && t.Kind == Object {
|
|
if _, exists := clsSet[t.Class]; !exists {
|
|
clsSet[t.Class] = struct{}{}
|
|
classes = append(classes, j.clsMap[t.Class])
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
for _, cls := range classes {
|
|
j.fillJNINames(cls.Funcs)
|
|
j.fillJNINames(cls.AllMethods)
|
|
}
|
|
j.fillThrowables(classes)
|
|
return classes, nil
|
|
}
|
|
|
|
func (j *Importer) fillJNINames(funcs []*FuncSet) {
|
|
for _, fs := range funcs {
|
|
for _, f := range fs.Funcs {
|
|
f.JNIName = JNIMangle(f.Name)
|
|
if len(fs.Funcs) > 1 {
|
|
f.JNIName += "__" + JNIMangle(f.ArgDesc)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// commonType finds the most specific type common to t1 and t2.
|
|
// If t1 and t2 are both Java classes, the most specific ancestor
|
|
// class is returned.
|
|
// Else if the types are equal, their type is returned.
|
|
// Finally, nil is returned, indicating no common type.
|
|
func commonType(clsMap map[string]*Class, t1, t2 *Type) *Type {
|
|
if t1 == nil || t2 == nil {
|
|
return nil
|
|
}
|
|
if reflect.DeepEqual(t1, t2) {
|
|
return t1
|
|
}
|
|
if t1.Kind != Object || t2.Kind != Object {
|
|
// The types are fundamentally incompatible
|
|
return nil
|
|
}
|
|
superSet := make(map[string]struct{})
|
|
supers := []string{t1.Class}
|
|
for len(supers) > 0 {
|
|
var newSupers []string
|
|
for _, s := range supers {
|
|
cls := clsMap[s]
|
|
superSet[s] = struct{}{}
|
|
newSupers = append(newSupers, cls.Supers...)
|
|
}
|
|
supers = newSupers
|
|
}
|
|
supers = []string{t2.Class}
|
|
for len(supers) > 0 {
|
|
var newSupers []string
|
|
for _, s := range supers {
|
|
if _, exists := superSet[s]; exists {
|
|
return &Type{Kind: Object, Class: s}
|
|
}
|
|
cls := clsMap[s]
|
|
newSupers = append(newSupers, cls.Supers...)
|
|
}
|
|
supers = newSupers
|
|
}
|
|
return &Type{Kind: Object, Class: "java.lang.Object"}
|
|
}
|
|
|
|
// combineSigs finds the most specific function signature
|
|
// that covers all its overload variants.
|
|
// If a function has only one variant, its common signature
|
|
// is the signature of that variant.
|
|
func combineSigs(clsMap map[string]*Class, sigs ...CommonSig) CommonSig {
|
|
var common CommonSig
|
|
minp := len(sigs[0].Params)
|
|
for i := 1; i < len(sigs); i++ {
|
|
sig := sigs[i]
|
|
n := len(sig.Params)
|
|
common.Variadic = common.Variadic || sig.Variadic || n != minp
|
|
if n < minp {
|
|
minp = n
|
|
}
|
|
}
|
|
for i, sig := range sigs {
|
|
for j, p := range sig.Params {
|
|
idx := j
|
|
// If the common signature is variadic, combine all parameters in the
|
|
// last parameter type of the shortest parameter list.
|
|
if idx > minp {
|
|
idx = minp
|
|
}
|
|
if idx < len(common.Params) {
|
|
common.Params[idx] = commonType(clsMap, common.Params[idx], p)
|
|
} else {
|
|
common.Params = append(common.Params, p)
|
|
}
|
|
}
|
|
common.Throws = common.Throws || sig.Throws
|
|
common.HasRet = common.HasRet || sig.HasRet
|
|
if i > 0 {
|
|
common.Ret = commonType(clsMap, common.Ret, sig.Ret)
|
|
} else {
|
|
common.Ret = sig.Ret
|
|
}
|
|
}
|
|
return common
|
|
}
|
|
|
|
// fillSuperSigs combines methods signatures with super class signatures,
|
|
// to preserve the assignability of classes to their super classes.
|
|
//
|
|
// For example, the class
|
|
//
|
|
// class A {
|
|
// void f();
|
|
// }
|
|
//
|
|
// is by itself represented by the Go interface
|
|
//
|
|
// type A interface {
|
|
// f()
|
|
// }
|
|
//
|
|
// However, if class
|
|
//
|
|
// class B extends A {
|
|
// void f(int);
|
|
// }
|
|
//
|
|
// is also imported, it will be represented as
|
|
//
|
|
// type B interface {
|
|
// f(...int32)
|
|
// }
|
|
//
|
|
// To make Go B assignable to Go A, the signature of A's f must
|
|
// be updated to f(...int32) as well.
|
|
func (j *Importer) fillSuperSigs(cls *Class, m *FuncSet) {
|
|
for _, s := range cls.Supers {
|
|
sup := j.clsMap[s]
|
|
if sm, exists := sup.methodMap[m.GoName]; exists {
|
|
sm.CommonSig = combineSigs(j.clsMap, sm.CommonSig, m.CommonSig)
|
|
}
|
|
j.fillSuperSigs(sup, m)
|
|
}
|
|
}
|
|
|
|
func (v *Var) Constant() bool {
|
|
return v.Static && v.Final && v.Val != ""
|
|
}
|
|
|
|
// Mangle a name according to
|
|
// http://docs.oracle.com/javase/6/docs/technotes/guides/jni/spec/design.html#wp16696
|
|
//
|
|
// TODO: Support unicode characters
|
|
func JNIMangle(s string) string {
|
|
var m []byte
|
|
for i := 0; i < len(s); i++ {
|
|
switch c := s[i]; c {
|
|
case '.', '/':
|
|
m = append(m, '_')
|
|
case '$':
|
|
m = append(m, "_00024"...)
|
|
case '_':
|
|
m = append(m, "_1"...)
|
|
case ';':
|
|
m = append(m, "_2"...)
|
|
case '[':
|
|
m = append(m, "_3"...)
|
|
default:
|
|
m = append(m, c)
|
|
}
|
|
}
|
|
return string(m)
|
|
}
|
|
|
|
func (t *Type) Type() string {
|
|
switch t.Kind {
|
|
case Int:
|
|
return "int"
|
|
case Boolean:
|
|
return "boolean"
|
|
case Short:
|
|
return "short"
|
|
case Char:
|
|
return "char"
|
|
case Byte:
|
|
return "byte"
|
|
case Long:
|
|
return "long"
|
|
case Float:
|
|
return "float"
|
|
case Double:
|
|
return "double"
|
|
case String:
|
|
return "String"
|
|
case Array:
|
|
return t.Elem.Type() + "[]"
|
|
case Object:
|
|
return t.Class
|
|
default:
|
|
panic("invalid kind")
|
|
}
|
|
}
|
|
|
|
func (t *Type) JNIType() string {
|
|
switch t.Kind {
|
|
case Int:
|
|
return "jint"
|
|
case Boolean:
|
|
return "jboolean"
|
|
case Short:
|
|
return "jshort"
|
|
case Char:
|
|
return "jchar"
|
|
case Byte:
|
|
return "jbyte"
|
|
case Long:
|
|
return "jlong"
|
|
case Float:
|
|
return "jfloat"
|
|
case Double:
|
|
return "jdouble"
|
|
case String:
|
|
return "jstring"
|
|
case Array:
|
|
return "jarray"
|
|
case Object:
|
|
return "jobject"
|
|
default:
|
|
panic("invalid kind")
|
|
}
|
|
}
|
|
|
|
func (t *Type) CType() string {
|
|
switch t.Kind {
|
|
case Int, Boolean, Short, Char, Byte, Long, Float, Double:
|
|
return t.JNIType()
|
|
case String:
|
|
return "nstring"
|
|
case Array:
|
|
if t.Elem.Kind != Byte {
|
|
panic("unsupported array type")
|
|
}
|
|
return "nbyteslice"
|
|
case Object:
|
|
return "jint"
|
|
default:
|
|
panic("invalid kind")
|
|
}
|
|
}
|
|
|
|
func (t *Type) JNICallType() string {
|
|
switch t.Kind {
|
|
case Int:
|
|
return "Int"
|
|
case Boolean:
|
|
return "Boolean"
|
|
case Short:
|
|
return "Short"
|
|
case Char:
|
|
return "Char"
|
|
case Byte:
|
|
return "Byte"
|
|
case Long:
|
|
return "Long"
|
|
case Float:
|
|
return "Float"
|
|
case Double:
|
|
return "Double"
|
|
case String, Object, Array:
|
|
return "Object"
|
|
default:
|
|
panic("invalid kind")
|
|
}
|
|
}
|
|
|
|
func (j *Importer) filterReferences(classes []*Class, refs *importers.References, funcRefs map[funcRef]struct{}) {
|
|
for _, cls := range classes {
|
|
var filtered []*FuncSet
|
|
for _, f := range cls.Funcs {
|
|
if _, exists := funcRefs[funcRef{cls.Name, f.GoName}]; exists {
|
|
filtered = append(filtered, f)
|
|
}
|
|
}
|
|
cls.Funcs = filtered
|
|
filtered = nil
|
|
for _, m := range cls.Methods {
|
|
if _, exists := refs.Names[m.GoName]; exists {
|
|
filtered = append(filtered, m)
|
|
}
|
|
}
|
|
cls.Methods = filtered
|
|
}
|
|
}
|
|
|
|
// importClasses imports the named classes from the classpaths of the Importer.
|
|
func (j *Importer) importClasses(names []string, allowMissingClasses bool) ([]*Class, error) {
|
|
if len(names) == 0 {
|
|
return nil, nil
|
|
}
|
|
args := []string{"-J-Duser.language=en", "-s", "-protected", "-constants"}
|
|
args = append(args, "-classpath", j.Classpath)
|
|
if j.Bootclasspath != "" {
|
|
args = append(args, "-bootclasspath", j.Bootclasspath)
|
|
}
|
|
args = append(args, names...)
|
|
javapPath, err := javapPath()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
javap := exec.Command(javapPath, args...)
|
|
out, err := javap.CombinedOutput()
|
|
if err != nil {
|
|
if _, ok := err.(*exec.ExitError); !ok {
|
|
return nil, fmt.Errorf("javap failed: %v", err)
|
|
}
|
|
// Not every name is a Java class so an exit error from javap is not
|
|
// fatal.
|
|
}
|
|
s := bufio.NewScanner(bytes.NewBuffer(out))
|
|
var classes []*Class
|
|
for _, name := range names {
|
|
cls, err := j.scanClass(s, name)
|
|
if err != nil {
|
|
_, notfound := err.(*errClsNotFound)
|
|
if notfound && allowMissingClasses {
|
|
continue
|
|
}
|
|
if notfound && name != "android.databinding.DataBindingComponent" {
|
|
return nil, err
|
|
}
|
|
// The Android Databinding library generates android.databinding.DataBindingComponent
|
|
// too late in the build process for the gobind plugin to import it. Synthesize a class
|
|
// for it instead.
|
|
cls = &Class{
|
|
Name: name,
|
|
FindName: name,
|
|
Interface: true,
|
|
PkgName: "databinding",
|
|
JNIName: JNIMangle(name),
|
|
}
|
|
}
|
|
classes = append(classes, cls)
|
|
j.clsMap[name] = cls
|
|
}
|
|
return classes, nil
|
|
}
|
|
|
|
// importReferencedClasses imports all implicit classes (super types, parameter and
|
|
// return types) for the given classes not already imported.
|
|
func (j *Importer) importReferencedClasses(classes []*Class) ([]*Class, error) {
|
|
var allCls []*Class
|
|
// Include methods from extended or implemented classes.
|
|
for {
|
|
set := make(map[string]struct{})
|
|
for _, cls := range classes {
|
|
j.unknownImplicitClasses(cls, set)
|
|
}
|
|
if len(set) == 0 {
|
|
break
|
|
}
|
|
var names []string
|
|
for n := range set {
|
|
names = append(names, n)
|
|
}
|
|
newCls, err := j.importClasses(names, false)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
allCls = append(allCls, newCls...)
|
|
classes = newCls
|
|
}
|
|
return allCls, nil
|
|
}
|
|
|
|
func (j *Importer) implicitFuncTypes(f *Func) []string {
|
|
var unk []string
|
|
if rt := f.Ret; rt != nil && rt.Kind == Object {
|
|
unk = append(unk, rt.Class)
|
|
}
|
|
for _, t := range f.Params {
|
|
if t.Kind == Object {
|
|
unk = append(unk, t.Class)
|
|
}
|
|
}
|
|
return unk
|
|
}
|
|
|
|
func (j *Importer) unknownImplicitClasses(cls *Class, set map[string]struct{}) {
|
|
for _, fsets := range [][]*FuncSet{cls.Funcs, cls.Methods} {
|
|
for _, fs := range fsets {
|
|
for _, f := range fs.Funcs {
|
|
names := j.implicitFuncTypes(f)
|
|
for _, name := range names {
|
|
if _, exists := j.clsMap[name]; !exists {
|
|
set[name] = struct{}{}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
for _, n := range cls.Supers {
|
|
if s, exists := j.clsMap[n]; exists {
|
|
j.unknownImplicitClasses(s, set)
|
|
} else {
|
|
set[n] = struct{}{}
|
|
}
|
|
}
|
|
}
|
|
|
|
func (j *Importer) implicitFuncClasses(funcs []*FuncSet, impl []string) []string {
|
|
var l []string
|
|
for _, fs := range funcs {
|
|
for _, f := range fs.Funcs {
|
|
if rt := f.Ret; rt != nil && rt.Kind == Object {
|
|
l = append(l, rt.Class)
|
|
}
|
|
for _, t := range f.Params {
|
|
if t.Kind == Object {
|
|
l = append(l, t.Class)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return impl
|
|
}
|
|
|
|
func (j *Importer) scanClass(s *bufio.Scanner, name string) (*Class, error) {
|
|
if !s.Scan() {
|
|
return nil, fmt.Errorf("%s: missing javap header", name)
|
|
}
|
|
head := s.Text()
|
|
if errPref := "Error: "; strings.HasPrefix(head, errPref) {
|
|
msg := head[len(errPref):]
|
|
if strings.HasPrefix(msg, "class not found: "+name) {
|
|
return nil, &errClsNotFound{name}
|
|
}
|
|
return nil, errors.New(msg)
|
|
}
|
|
if !strings.HasPrefix(head, "Compiled from ") {
|
|
return nil, fmt.Errorf("%s: unexpected header: %s", name, head)
|
|
}
|
|
if !s.Scan() {
|
|
return nil, fmt.Errorf("%s: missing javap class declaration", name)
|
|
}
|
|
clsDecl := s.Text()
|
|
cls, err := j.scanClassDecl(name, clsDecl)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
cls.JNIName = JNIMangle(cls.Name)
|
|
clsElems := strings.Split(cls.Name, ".")
|
|
cls.PkgName = clsElems[len(clsElems)-1]
|
|
var funcs []*Func
|
|
for s.Scan() {
|
|
decl := strings.TrimSpace(s.Text())
|
|
if decl == "}" {
|
|
break
|
|
} else if decl == "" {
|
|
continue
|
|
}
|
|
if !s.Scan() {
|
|
return nil, fmt.Errorf("%s: missing descriptor for member %q", name, decl)
|
|
}
|
|
desc := strings.TrimSpace(s.Text())
|
|
desc = strings.TrimPrefix(desc, "descriptor: ")
|
|
var static, final, abstract, public bool
|
|
// Trim modifiders from the declaration.
|
|
loop:
|
|
for {
|
|
idx := strings.Index(decl, " ")
|
|
if idx == -1 {
|
|
break
|
|
}
|
|
keyword := decl[:idx]
|
|
switch keyword {
|
|
case "public":
|
|
public = true
|
|
case "protected", "native":
|
|
// ignore
|
|
case "static":
|
|
static = true
|
|
case "final":
|
|
final = true
|
|
case "abstract":
|
|
abstract = true
|
|
default:
|
|
// Hopefully we reached the declaration now.
|
|
break loop
|
|
}
|
|
decl = decl[idx+1:]
|
|
}
|
|
// Trim ending ;
|
|
decl = decl[:len(decl)-1]
|
|
if idx := strings.Index(decl, "("); idx != -1 {
|
|
f, err := j.scanMethod(decl, desc, idx)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("%s: %v", name, err)
|
|
}
|
|
if f != nil {
|
|
f.Static = static
|
|
f.Abstract = abstract
|
|
f.Public = public || cls.Interface
|
|
f.Final = final
|
|
f.Constructor = f.Name == cls.FindName
|
|
if f.Constructor {
|
|
cls.HasNoArgCon = cls.HasNoArgCon || len(f.Params) == 0
|
|
f.Public = f.Public && !cls.Abstract
|
|
f.Name = "new"
|
|
f.Ret = &Type{Class: name, Kind: Object}
|
|
}
|
|
funcs = append(funcs, f)
|
|
}
|
|
} else {
|
|
// Member is a variable
|
|
v, err := j.scanVar(decl, desc)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("%s: %v", name, err)
|
|
}
|
|
if v != nil && public {
|
|
v.Static = static
|
|
v.Final = final
|
|
cls.Vars = append(cls.Vars, v)
|
|
}
|
|
}
|
|
}
|
|
for _, f := range funcs {
|
|
var m map[string]*FuncSet
|
|
var l *[]*FuncSet
|
|
goName := initialUpper(f.Name)
|
|
if f.Static || f.Constructor {
|
|
m = cls.funcMap
|
|
l = &cls.Funcs
|
|
} else {
|
|
m = cls.methodMap
|
|
l = &cls.Methods
|
|
}
|
|
fs, exists := m[goName]
|
|
if !exists {
|
|
fs = &FuncSet{
|
|
Name: f.Name,
|
|
GoName: goName,
|
|
}
|
|
m[goName] = fs
|
|
*l = append(*l, fs)
|
|
}
|
|
fs.Funcs = append(fs.Funcs, f)
|
|
}
|
|
return cls, nil
|
|
}
|
|
|
|
func (j *Importer) scanClassDecl(name string, decl string) (*Class, error) {
|
|
isRoot := name == "java.lang.Object"
|
|
cls := &Class{
|
|
Name: name,
|
|
funcMap: make(map[string]*FuncSet),
|
|
methodMap: make(map[string]*FuncSet),
|
|
HasNoArgCon: isRoot,
|
|
}
|
|
const (
|
|
stMod = iota
|
|
stName
|
|
stExt
|
|
stImpl
|
|
)
|
|
superClsDecl := isRoot
|
|
st := stMod
|
|
var w []byte
|
|
// if > 0, we're inside a generics declaration
|
|
gennest := 0
|
|
for i := 0; i < len(decl); i++ {
|
|
c := decl[i]
|
|
switch c {
|
|
default:
|
|
if gennest == 0 {
|
|
w = append(w, c)
|
|
}
|
|
case '>':
|
|
gennest--
|
|
case '<':
|
|
gennest++
|
|
case '{':
|
|
if !superClsDecl && !cls.Interface {
|
|
cls.Supers = append(cls.Supers, "java.lang.Object")
|
|
}
|
|
return cls, nil
|
|
case ' ', ',':
|
|
if gennest > 0 {
|
|
break
|
|
}
|
|
switch w := string(w); w {
|
|
default:
|
|
switch st {
|
|
case stName:
|
|
if strings.Replace(w, "$", ".", -1) != strings.Replace(name, "$", ".", -1) {
|
|
return nil, fmt.Errorf("unexpected name %q in class declaration: %q", w, decl)
|
|
}
|
|
cls.FindName = w
|
|
case stExt:
|
|
superClsDecl = true
|
|
cls.Supers = append(cls.Supers, w)
|
|
case stImpl:
|
|
if !cls.Interface {
|
|
cls.Supers = append(cls.Supers, w)
|
|
}
|
|
default:
|
|
return nil, fmt.Errorf("unexpected %q in class declaration: %q", w, decl)
|
|
}
|
|
case "":
|
|
// skip
|
|
case "public":
|
|
if st != stMod {
|
|
return nil, fmt.Errorf("unexpected %q in class declaration: %q", w, decl)
|
|
}
|
|
case "abstract":
|
|
if st != stMod {
|
|
return nil, fmt.Errorf("unexpected %q in class declaration: %q", w, decl)
|
|
}
|
|
cls.Abstract = true
|
|
case "final":
|
|
if st != stMod {
|
|
return nil, fmt.Errorf("unexpected %q in class declaration: %q", w, decl)
|
|
}
|
|
cls.Final = true
|
|
case "interface":
|
|
cls.Interface = true
|
|
fallthrough
|
|
case "class":
|
|
if st != stMod {
|
|
return nil, fmt.Errorf("unexpected %q in class declaration: %q", w, decl)
|
|
}
|
|
st = stName
|
|
case "extends":
|
|
if st != stName {
|
|
return nil, fmt.Errorf("unexpected %q in class declaration: %q", w, decl)
|
|
}
|
|
st = stExt
|
|
case "implements":
|
|
if st != stName && st != stExt {
|
|
return nil, fmt.Errorf("unexpected %q in class declaration: %q", w, decl)
|
|
}
|
|
st = stImpl
|
|
}
|
|
w = w[:0]
|
|
}
|
|
}
|
|
return nil, fmt.Errorf("missing ending { in class declaration: %q", decl)
|
|
}
|
|
|
|
func (j *Importer) scanVar(decl, desc string) (*Var, error) {
|
|
v := new(Var)
|
|
const eq = " = "
|
|
idx := strings.Index(decl, eq)
|
|
if idx != -1 {
|
|
val, ok := j.parseJavaValue(decl[idx+len(eq):])
|
|
if !ok {
|
|
// Skip constants that cannot be represented in Go
|
|
return nil, nil
|
|
}
|
|
v.Val = val
|
|
} else {
|
|
idx = len(decl)
|
|
}
|
|
for i := idx - 1; i >= 0; i-- {
|
|
if i == 0 || decl[i-1] == ' ' {
|
|
v.Name = decl[i:idx]
|
|
break
|
|
}
|
|
}
|
|
if v.Name == "" {
|
|
return nil, fmt.Errorf("unable to parse member name from declaration: %q", decl)
|
|
}
|
|
typ, _, err := j.parseJavaType(desc)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("invalid type signature for %s: %q", v.Name, desc)
|
|
}
|
|
v.Type = typ
|
|
return v, nil
|
|
}
|
|
|
|
func (j *Importer) scanMethod(decl, desc string, parenIdx int) (*Func, error) {
|
|
// Member is a method
|
|
f := new(Func)
|
|
f.Desc = desc
|
|
for i := parenIdx - 1; i >= 0; i-- {
|
|
if i == 0 || decl[i-1] == ' ' {
|
|
f.Name = decl[i:parenIdx]
|
|
break
|
|
}
|
|
}
|
|
if f.Name == "" {
|
|
return nil, fmt.Errorf("unable to parse method name from declaration: %q", decl)
|
|
}
|
|
if desc[0] != '(' {
|
|
return nil, fmt.Errorf("invalid descriptor for method %s: %q", f.Name, desc)
|
|
}
|
|
const throws = " throws "
|
|
if idx := strings.Index(decl, throws); idx != -1 {
|
|
f.Throws = decl[idx+len(throws):]
|
|
}
|
|
i := 1
|
|
for desc[i] != ')' {
|
|
typ, n, err := j.parseJavaType(desc[i:])
|
|
if err != nil {
|
|
return nil, fmt.Errorf("invalid descriptor for method %s: %v", f.Name, err)
|
|
}
|
|
i += n
|
|
f.Params = append(f.Params, typ)
|
|
}
|
|
f.ArgDesc = desc[1:i]
|
|
i++ // skip ending )
|
|
if desc[i] != 'V' {
|
|
typ, _, err := j.parseJavaType(desc[i:])
|
|
if err != nil {
|
|
return nil, fmt.Errorf("invalid descriptor for method %s: %v", f.Name, err)
|
|
}
|
|
f.Ret = typ
|
|
}
|
|
return f, nil
|
|
}
|
|
|
|
func (j *Importer) fillThrowables(classes []*Class) {
|
|
thrCls, ok := j.clsMap["java.lang.Throwable"]
|
|
if !ok {
|
|
// If Throwable isn't in the class map
|
|
// no imported class inherits from Throwable
|
|
return
|
|
}
|
|
for _, cls := range classes {
|
|
j.fillThrowableFor(cls, thrCls)
|
|
}
|
|
}
|
|
|
|
func (j *Importer) fillThrowableFor(cls, thrCls *Class) {
|
|
if cls.Interface || cls.Throwable {
|
|
return
|
|
}
|
|
cls.Throwable = cls == thrCls
|
|
for _, name := range cls.Supers {
|
|
sup := j.clsMap[name]
|
|
j.fillThrowableFor(sup, thrCls)
|
|
cls.Throwable = cls.Throwable || sup.Throwable
|
|
}
|
|
}
|
|
|
|
func commonSig(f *Func) CommonSig {
|
|
return CommonSig{
|
|
Params: f.Params,
|
|
Ret: f.Ret,
|
|
HasRet: f.Ret != nil,
|
|
Throws: f.Throws != "",
|
|
}
|
|
}
|
|
|
|
func (j *Importer) fillFuncSigs(funcs []*FuncSet) {
|
|
for _, fs := range funcs {
|
|
var sigs []CommonSig
|
|
for _, f := range fs.Funcs {
|
|
sigs = append(sigs, commonSig(f))
|
|
}
|
|
fs.CommonSig = combineSigs(j.clsMap, sigs...)
|
|
}
|
|
}
|
|
|
|
func (j *Importer) fillAllMethods(cls *Class) {
|
|
if len(cls.AllMethods) > 0 {
|
|
return
|
|
}
|
|
for _, supName := range cls.Supers {
|
|
super := j.clsMap[supName]
|
|
j.fillAllMethods(super)
|
|
}
|
|
var fsets []*FuncSet
|
|
fsets = append(fsets, cls.Methods...)
|
|
for _, supName := range cls.Supers {
|
|
super := j.clsMap[supName]
|
|
fsets = append(fsets, super.AllMethods...)
|
|
}
|
|
sigs := make(map[FuncSig]struct{})
|
|
methods := make(map[string]*FuncSet)
|
|
for _, fs := range fsets {
|
|
clsFs, exists := methods[fs.Name]
|
|
if !exists {
|
|
clsFs = &FuncSet{
|
|
Name: fs.Name,
|
|
GoName: fs.GoName,
|
|
CommonSig: fs.CommonSig,
|
|
}
|
|
cls.AllMethods = append(cls.AllMethods, clsFs)
|
|
methods[fs.Name] = clsFs
|
|
} else {
|
|
// Combine the (overloaded) signature with the other variants.
|
|
clsFs.CommonSig = combineSigs(j.clsMap, clsFs.CommonSig, fs.CommonSig)
|
|
}
|
|
for _, f := range fs.Funcs {
|
|
if _, exists := sigs[f.FuncSig]; exists {
|
|
continue
|
|
}
|
|
sigs[f.FuncSig] = struct{}{}
|
|
clsFs.Funcs = append(clsFs.Funcs, f)
|
|
}
|
|
}
|
|
}
|
|
|
|
func (j *Importer) parseJavaValue(v string) (string, bool) {
|
|
v = strings.TrimRight(v, "ldf")
|
|
switch v {
|
|
case "", "NaN", "Infinity", "-Infinity":
|
|
return "", false
|
|
default:
|
|
if v[0] == '\'' {
|
|
// Skip character constants, since they can contain invalid code points
|
|
// that are unacceptable to Go.
|
|
return "", false
|
|
}
|
|
return v, true
|
|
}
|
|
}
|
|
|
|
func (j *Importer) parseJavaType(desc string) (*Type, int, error) {
|
|
t := new(Type)
|
|
var n int
|
|
if desc == "" {
|
|
return t, n, errors.New("empty type signature")
|
|
}
|
|
n++
|
|
switch desc[0] {
|
|
case 'Z':
|
|
t.Kind = Boolean
|
|
case 'B':
|
|
t.Kind = Byte
|
|
case 'C':
|
|
t.Kind = Char
|
|
case 'S':
|
|
t.Kind = Short
|
|
case 'I':
|
|
t.Kind = Int
|
|
case 'J':
|
|
t.Kind = Long
|
|
case 'F':
|
|
t.Kind = Float
|
|
case 'D':
|
|
t.Kind = Double
|
|
case 'L':
|
|
var clsName string
|
|
for i := n; i < len(desc); i++ {
|
|
if desc[i] == ';' {
|
|
clsName = strings.Replace(desc[n:i], "/", ".", -1)
|
|
clsName = strings.Replace(clsName, "$", ".", -1)
|
|
n += i - n + 1
|
|
break
|
|
}
|
|
}
|
|
if clsName == "" {
|
|
return t, n, errors.New("missing ; in class type signature")
|
|
}
|
|
if clsName == "java.lang.String" {
|
|
t.Kind = String
|
|
} else {
|
|
t.Kind = Object
|
|
t.Class = clsName
|
|
}
|
|
case '[':
|
|
et, n2, err := j.parseJavaType(desc[n:])
|
|
if err != nil {
|
|
return t, n, err
|
|
}
|
|
n += n2
|
|
t.Kind = Array
|
|
t.Elem = et
|
|
default:
|
|
return t, n, fmt.Errorf("invalid type signature: %s", desc)
|
|
}
|
|
return t, n, nil
|
|
}
|
|
|
|
func initialUpper(s string) string {
|
|
if s == "" {
|
|
return ""
|
|
}
|
|
r, n := utf8.DecodeRuneInString(s)
|
|
return string(unicode.ToUpper(r)) + s[n:]
|
|
}
|