blob: efc22fe7de6f68722fbe966bef2c5b060f07e52b [file] [log] [blame]
// 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"
"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 []*Func
Methods []*Func
// All methods, including methods from
// supers.
AllMethods []*Func
Vars []*Var
Supers []string
Final bool
Abstract bool
Interface bool
Throwable bool
}
// Func is a Java static function or method or constructor.
type Func struct {
FuncSig
ArgDesc string
GoName 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 {
clspath string
clsMap map[string]*Class
}
const (
Int TypeKind = iota
Boolean
Short
Char
Byte
Long
Float
Double
String
Array
Object
)
var (
errClsNotFound = errors.New("class not found")
)
// IsAvailable returns 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 Import(classpath string, refs *importers.References) ([]*Class, error) {
imp := &importer{
clspath: classpath,
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)
}
}
}
classes, err := imp.importClasses(names, true)
if err != nil {
return nil, err
}
for _, cls := range classes {
imp.fillAllMethods(cls)
}
imp.fillThrowables()
for _, cls := range classes {
imp.mangleOverloads(cls.AllMethods)
imp.mangleOverloads(cls.Funcs)
}
imp.filterReferences(classes, refs)
return classes, nil
}
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 (c *Class) HasSuper() bool {
return !c.Final && !c.Interface
}
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) {
refFuncs := make(map[[2]string]struct{})
for _, ref := range refs.Refs {
pkgName := strings.Replace(ref.Pkg, "/", ".", -1)
cls := j.clsMap[pkgName]
if cls == nil {
continue
}
refFuncs[[...]string{pkgName, ref.Name}] = struct{}{}
}
for _, cls := range classes {
var filtered []*Func
for _, f := range cls.Funcs {
if _, exists := refFuncs[[...]string{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
filtered = nil
for _, m := range cls.AllMethods {
if _, exists := refs.Names[m.GoName]; exists {
filtered = append(filtered, m)
}
}
cls.AllMethods = filtered
}
}
func (j *importer) importClasses(names []string, allowMissingClasses bool) ([]*Class, error) {
if len(names) == 0 {
return nil, nil
}
args := []string{"-s", "-protected", "-constants"}
if j.clspath != "" {
args = append(args, "-bootclasspath", j.clspath)
}
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: %s", 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 {
if allowMissingClasses && err == errClsNotFound {
continue
}
return nil, err
}
classes = append(classes, cls)
j.clsMap[name] = cls
}
// Include the methods from classes extended or implemented
unkCls := classes
for {
var unknown []string
for _, cls := range unkCls {
unknown = j.unknownSuperClasses(cls, unknown)
}
if len(unknown) == 0 {
break
}
newCls, err := j.importClasses(unknown, false)
if err != nil {
return nil, err
}
for _, cls := range newCls {
j.clsMap[cls.Name] = cls
}
unkCls = newCls
}
return classes, nil
}
func (j *importer) unknownSuperClasses(cls *Class, unk []string) []string {
loop:
for _, n := range cls.Supers {
if s, exists := j.clsMap[n]; exists {
unk = j.unknownSuperClasses(s, unk)
} else {
for _, u := range unk {
if u == n {
continue loop
}
}
unk = append(unk, n)
}
}
return unk
}
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
}
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
}
if len(cls.Supers) == 0 && name != "java.lang.Object" {
cls.Supers = append(cls.Supers, "java.lang.Object")
}
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 {
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 {
if f.Static || f.Constructor {
cls.Funcs = append(cls.Funcs, f)
} else {
cls.Methods = append(cls.Methods, f)
}
}
return cls, nil
}
func (j *importer) scanClassDecl(name string, decl string) (*Class, error) {
cls := &Class{
Name: name,
}
const (
stMod = iota
stName
stExt
stImpl
)
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 '{':
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:
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() {
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 j.clsMap {
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 (j *importer) fillAllMethods(cls *Class) {
if len(cls.AllMethods) > 0 {
return
}
if len(cls.Supers) == 0 {
cls.AllMethods = cls.Methods
return
}
for _, supName := range cls.Supers {
super := j.clsMap[supName]
j.fillAllMethods(super)
}
methods := make(map[FuncSig]struct{})
for _, supName := range cls.Supers {
super := j.clsMap[supName]
for _, f := range super.AllMethods {
if _, exists := methods[f.FuncSig]; !exists {
methods[f.FuncSig] = struct{}{}
// Copy function so each class can have its own
// JNI name mangling.
cpf := *f
cls.AllMethods = append(cls.AllMethods, &cpf)
}
}
}
for _, f := range cls.Methods {
if _, exists := methods[f.FuncSig]; !exists {
cls.AllMethods = append(cls.AllMethods, f)
}
}
}
// mangleOverloads assigns unique names to overloaded Java functions by appending
// the argument count. If multiple methods have the same name and argument count,
// the method signature is appended in JNI mangled form.
func (j *importer) mangleOverloads(allFuncs []*Func) {
overloads := make(map[string][]*Func)
for _, f := range allFuncs {
overloads[f.Name] = append(overloads[f.Name], f)
}
for _, funcs := range overloads {
for _, f := range funcs {
f.GoName = initialUpper(f.Name)
f.JNIName = JNIMangle(f.Name)
}
if len(funcs) == 1 {
continue
}
lengths := make(map[int]int)
for _, f := range funcs {
f.JNIName += "__" + JNIMangle(f.ArgDesc)
lengths[len(f.Params)]++
}
for _, f := range funcs {
n := len(f.Params)
if lengths[n] > 1 {
f.GoName += "_" + JNIMangle(f.ArgDesc)
continue
}
if n > 0 {
f.GoName = fmt.Sprintf("%s%d", f.GoName, n)
}
}
}
}
func (j *importer) parseJavaValue(v string) (string, bool) {
v = strings.TrimRight(v, "df")
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)
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:]
}