internal/importers: introduce package to analyze Java classes

The importers package adds functions to traverse the AST of a Go
file or set of packages and extract references to Java packages and
types.

The java package adds a parser that uses the javap command to extract
type information about Java classes and interfaces.

The resulting type information is needed to generate Java API wrappers
in Go.

This is the first part of the implementation of proposal golang/go#16876.

For golang/go#16876

Change-Id: Ic844472a1101354d61401d9e8c120acdee2519df
Reviewed-on: https://go-review.googlesource.com/28595
Reviewed-by: David Crawshaw <crawshaw@golang.org>
diff --git a/internal/importers/ast.go b/internal/importers/ast.go
new file mode 100644
index 0000000..e9c4ee1
--- /dev/null
+++ b/internal/importers/ast.go
@@ -0,0 +1,160 @@
+// 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 importers package uses go/ast to analyze Go packages or Go files
+// and collect references to types whose package has a package prefix.
+// It is used by the language specific importers to determine the set of
+// wrapper types to be generated.
+//
+// For example, in the Go file
+//
+// package javaprogram
+//
+// import "Java/java/lang"
+//
+// func F() {
+//     o := lang.Object.New()
+//     ...
+// }
+//
+// the java importer uses this package to determine that the "java/lang"
+// package and the wrapper interface, lang.Object, needs to be generated.
+// After calling AnalyzeFile or AnalyzePackages, the References result
+// contains the reference to lang.Object and the names set will contain
+// "New".
+package importers
+
+import (
+	"errors"
+	"go/ast"
+	"go/build"
+	"go/parser"
+	"go/token"
+	"path"
+	"path/filepath"
+	"strconv"
+	"strings"
+)
+
+// References is the result of analyzing a Go file or set of Go packages.
+//
+// For example, the Go file
+//
+// package pkg
+//
+// import "Prefix/some/Package"
+//
+// var A = Package.Identifier
+//
+// Will result in a single PkgRef with the "some/Package" package and
+// the Identifier name. The Names set will contain the single name,
+// "Identifier".
+type References struct {
+	// The list of references to identifiers in packages that are
+	// identified by a package prefix.
+	Refs []PkgRef
+	// The list of names used in at least one selector expression.
+	// Useful as a conservative upper bound on the set of identifiers
+	// referenced from a set of packages.
+	Names map[string]struct{}
+}
+
+// PkgRef is a reference to an identifier in a package.
+type PkgRef struct {
+	Pkg  string
+	Name string
+}
+
+type refsSaver struct {
+	pkgPrefix string
+	References
+	refMap map[PkgRef]struct{}
+}
+
+// AnalyzeFile scans the provided file for references to packages with the given
+// package prefix. The list of unique (package, identifier) pairs is returned
+func AnalyzeFile(file *ast.File, pkgPrefix string) (*References, error) {
+	visitor := newRefsSaver(pkgPrefix)
+	fset := token.NewFileSet()
+	files := map[string]*ast.File{file.Name.Name: file}
+	// Ignore errors (from unknown packages)
+	pkg, _ := ast.NewPackage(fset, files, visitor.importer(), nil)
+	ast.Walk(visitor, pkg)
+	return &visitor.References, nil
+}
+
+// AnalyzePackages scans the provided packages for references to packages with the given
+// package prefix. The list of unique (package, identifier) pairs is returned
+func AnalyzePackages(pkgs []*build.Package, pkgPrefix string) (*References, error) {
+	visitor := newRefsSaver(pkgPrefix)
+	imp := visitor.importer()
+	fset := token.NewFileSet()
+	for _, pkg := range pkgs {
+		fileNames := append(append([]string{}, pkg.GoFiles...), pkg.CgoFiles...)
+		files := make(map[string]*ast.File)
+		for _, name := range fileNames {
+			f, err := parser.ParseFile(fset, filepath.Join(pkg.Dir, name), nil, 0)
+			if err != nil {
+				return nil, err
+			}
+			files[name] = f
+		}
+		// Ignore errors (from unknown packages)
+		astpkg, _ := ast.NewPackage(fset, files, imp, nil)
+		ast.Walk(visitor, astpkg)
+	}
+	return &visitor.References, nil
+}
+
+func newRefsSaver(pkgPrefix string) *refsSaver {
+	s := &refsSaver{
+		pkgPrefix: pkgPrefix,
+		refMap:    make(map[PkgRef]struct{}),
+	}
+	s.Names = make(map[string]struct{})
+	return s
+}
+
+func (v *refsSaver) importer() ast.Importer {
+	return func(imports map[string]*ast.Object, pkgPath string) (*ast.Object, error) {
+		if pkg, exists := imports[pkgPath]; exists {
+			return pkg, nil
+		}
+		if !strings.HasPrefix(pkgPath, v.pkgPrefix) {
+			return nil, errors.New("ignored")
+		}
+		pkg := ast.NewObj(ast.Pkg, path.Base(pkgPath))
+		imports[pkgPath] = pkg
+		return pkg, nil
+	}
+}
+
+func (v *refsSaver) Visit(n ast.Node) ast.Visitor {
+	switch n := n.(type) {
+	case *ast.SelectorExpr:
+		v.Names[n.Sel.Name] = struct{}{}
+		if x, ok := n.X.(*ast.Ident); ok && x.Obj != nil {
+			if imp, ok := x.Obj.Decl.(*ast.ImportSpec); ok {
+				pkgPath, err := strconv.Unquote(imp.Path.Value)
+				if err != nil {
+					return nil
+				}
+				if strings.HasPrefix(pkgPath, v.pkgPrefix) {
+					pkgPath = pkgPath[len(v.pkgPrefix):]
+					ref := PkgRef{Pkg: pkgPath, Name: n.Sel.Name}
+					if _, exists := v.refMap[ref]; !exists {
+						v.refMap[ref] = struct{}{}
+						v.Refs = append(v.Refs, ref)
+					}
+				}
+				return nil
+			}
+		}
+	case *ast.FuncDecl:
+		if n.Recv != nil { // Methods
+			v.Names[n.Name.Name] = struct{}{}
+		}
+	}
+	return v
+}
diff --git a/internal/importers/ast_test.go b/internal/importers/ast_test.go
new file mode 100644
index 0000000..92ba531
--- /dev/null
+++ b/internal/importers/ast_test.go
@@ -0,0 +1,35 @@
+package importers
+
+import (
+	"go/parser"
+	"go/token"
+	"testing"
+)
+
+func TestAnalyzer(t *testing.T) {
+	file := `package ast_test
+
+import "Prefix/some/pkg/Name"
+
+const c = Name.Constant
+`
+	fset := token.NewFileSet()
+	f, err := parser.ParseFile(fset, "ast_test.go", file, parser.AllErrors)
+	if err != nil {
+		t.Fatal(err)
+	}
+	refs, err := AnalyzeFile(f, "Prefix/")
+	if err != nil {
+		t.Fatal(err)
+	}
+	if len(refs.Refs) != 1 {
+		t.Fatalf("expected 1 reference; got %d", len(refs.Refs))
+	}
+	got := refs.Refs[0]
+	if exp := (PkgRef{"some/pkg/Name", "Constant"}); exp != got {
+		t.Errorf("expected ref %v; got %v", exp, got)
+	}
+	if _, exists := refs.Names["Constant"]; !exists {
+		t.Errorf("expected \"Constant\" in the names set")
+	}
+}
diff --git a/internal/importers/java/java.go b/internal/importers/java/java.go
new file mode 100644
index 0000000..efc22fe
--- /dev/null
+++ b/internal/importers/java/java.go
@@ -0,0 +1,863 @@
+// 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:]
+}
diff --git a/internal/importers/java/java_test.go b/internal/importers/java/java_test.go
new file mode 100644
index 0000000..cc1ed7e
--- /dev/null
+++ b/internal/importers/java/java_test.go
@@ -0,0 +1,67 @@
+// 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.
+
+package java
+
+import (
+	"reflect"
+	"testing"
+
+	"golang.org/x/mobile/internal/importers"
+)
+
+func TestImport(t *testing.T) {
+	if !IsAvailable() {
+		t.Skipf("javap not available")
+	}
+	tests := []struct {
+		ref     importers.PkgRef
+		name    string
+		methods []*Func
+	}{
+		{
+			ref:  importers.PkgRef{"java/lang/Object", "equals"},
+			name: "java.lang.Object",
+			methods: []*Func{
+				&Func{FuncSig: FuncSig{Name: "equals", Desc: "(Ljava/lang/Object;)Z"}, ArgDesc: "Ljava/lang/Object;", GoName: "Equals", JNIName: "equals", Public: true, Params: []*Type{&Type{Kind: Object, Class: "java.lang.Object"}}, Ret: &Type{Kind: Boolean}},
+			},
+		},
+		{
+			ref:  importers.PkgRef{"java/lang/Runnable", "run"},
+			name: "java.lang.Runnable",
+			methods: []*Func{
+				&Func{FuncSig: FuncSig{Name: "run", Desc: "()V"}, ArgDesc: "", GoName: "Run", JNIName: "run", Public: true, Abstract: true},
+			},
+		},
+	}
+	for _, test := range tests {
+		refs := &importers.References{
+			Refs:  []importers.PkgRef{test.ref},
+			Names: make(map[string]struct{}),
+		}
+		for _, m := range test.methods {
+			refs.Names[m.GoName] = struct{}{}
+		}
+		classes, err := Import("", refs)
+		if err != nil {
+			t.Fatal(err)
+		}
+		if len(classes) != 1 {
+			t.Fatalf("got %d classes, expected 1", len(classes))
+		}
+		cls := classes[0]
+		if cls.Name != test.name {
+			t.Errorf("got class name %s, expected %s", cls.Name, test.name)
+		}
+	loop:
+		for _, exp := range test.methods {
+			for _, got := range cls.Methods {
+				if reflect.DeepEqual(exp, got) {
+					continue loop
+				}
+			}
+			t.Errorf("failed to find method: %+v", exp)
+		}
+	}
+}