Skip to content

Commit

Permalink
Add jmod describe command
Browse files Browse the repository at this point in the history
  • Loading branch information
zxh0 committed Oct 23, 2019
1 parent 23d6d54 commit 3d63d08
Show file tree
Hide file tree
Showing 6 changed files with 344 additions and 17 deletions.
134 changes: 128 additions & 6 deletions cmd/jmod/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,14 @@ package main

import (
"fmt"
"github.com/zxh0/jvm.go/vmutils"
"os"
"sort"
"strings"

"github.com/docopt/docopt-go"
"github.com/zxh0/jvm.go/classfile"
"github.com/zxh0/jvm.go/module"
"github.com/zxh0/jvm.go/vmutils"
)

const (
Expand All @@ -14,11 +18,13 @@ const (
Usage:
jmod list <file>
jmod describe <file>
jmod -h | --help
jmod --version
Commands:
list <file> Prints the names of all the entries.
list Prints the names of all the entries.
describe Prints the module details.
Options:
-h --help Print this help message
Expand All @@ -31,15 +37,131 @@ func main() {
fmt.Println(usage)
} else if opts["list"].(bool) {
list(opts["<file>"].(string))
} else if opts["describe"].(bool) {
describe(opts["<file>"].(string))
}
}

func list(filename string) {
if r, err := vmutils.OpenJModReader(filename); err != nil {
f, err := vmutils.OpenJModFile(filename)
if err != nil {
panic(err)
} else {
for _, f := range r.File {
fmt.Println(f.Name)
}
for _, f := range f.ListFiles() {
fmt.Println(f)
}
}

func describe(filename string) {
jmodFile, err := vmutils.OpenJModFile(filename)
if err != nil {
panic(err)
}

classData, err := jmodFile.ReadFile("classes/module-info.class")
if err != nil {
panic(err)
}

modInfo := module.NewModuleInfo(classData)
describeModule(jmodFile, modInfo)
}

func describeModule(jmodFile *vmutils.JModFile, modInfo module.Info) {
fmt.Printf("%s@%s\n", modInfo.Name, modInfo.Version)
// unqualified exports (sorted by package)
for _, export := range modInfo.Exports {
if len(export.To) == 0 {
fmt.Printf("exports %s\n", vmutils.SlashToDot(export.Package))
// TODO: flags
}
}
// dependencies
for _, require := range modInfo.Requires {
fmt.Printf("requires %s", require.Name)
flags := classfile.AccessFlags(require.Flags)
if flags.IsMandated() {
fmt.Print(" mandated")
} else if flags.IsTransitive() {
fmt.Print(" transitive")
}
fmt.Println()
}
// service uses
for _, use := range modInfo.Uses {
fmt.Printf("uses %s\n", vmutils.SlashToDot(use))
}
// service provides
for _, provide := range modInfo.Provides {
fmt.Printf("provides %s with", vmutils.SlashToDot(provide.Service))
for _, impl := range provide.Impls {
fmt.Printf(" %s", vmutils.SlashToDot(impl))
}
fmt.Println()
}
// qualified exports
for _, export := range modInfo.Exports {
if len(export.To) > 0 {
fmt.Printf("qualified exports %s to %s\n",
vmutils.SlashToDot(export.Package), strings.Join(export.To, " "))
}
}
// open packages
for _, open := range modInfo.Opens {
if len(open.To) == 0 {
fmt.Printf("opens %s\n", vmutils.SlashToDot(open.Package))
// TODO: flags
}
}
for _, open := range modInfo.Opens {
if len(open.To) > 0 {
fmt.Printf("qualified opens %s to %s\n",
vmutils.SlashToDot(open.Package), strings.Join(open.To, " "))
// TODO: flags
}
}
// non-exported/non-open packages
for _, pkg := range getPrivatePackages(jmodFile, modInfo) {
fmt.Printf("contains %s\n", vmutils.SlashToDot(pkg))
}
// TODO: platform & hashes
}

func getPrivatePackages(jmodFile *vmutils.JModFile, modInfo module.Info) []string {
nonPrivatePkgMap := map[string]bool{}
for _, export := range modInfo.Exports {
nonPrivatePkgMap[export.Package] = true
}
for _, open := range modInfo.Opens {
nonPrivatePkgMap[open.Package] = true
}

privatePkgMap := map[string]bool{}
for _, f := range jmodFile.ListFiles() {
pkg := getPackage(f)
if pkg != "" && !nonPrivatePkgMap[pkg] {
privatePkgMap[pkg] = true
}
}

privatePackages := make([]string, 0, len(privatePkgMap))
for pkg, _ := range privatePkgMap {
privatePackages = append(privatePackages, pkg)
}
sort.Strings(privatePackages)
return privatePackages
}

func getPackage(pathInJModFile string) string {
if strings.Index(pathInJModFile, ".class") < 0 {
return ""
}

slashIdx := strings.IndexByte(pathInJModFile, '/')
lastSlashIdx := strings.LastIndexByte(pathInJModFile, '/')
if lastSlashIdx > slashIdx {
return pathInJModFile[slashIdx+1 : lastSlashIdx]
} else {
return ""
}
}
152 changes: 152 additions & 0 deletions module/info.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
package module

import (
"github.com/zxh0/jvm.go/classfile"
)

type Info struct {
Name string
Flags uint16
Version string
Requires []Require
Exports []Export
Opens []Open
Uses []string
Provides []Provide
}

type Require struct {
Name string
Flags uint16
Version string
}

type Export struct {
Package string
Flags uint16
To []string
}

type Open struct {
Package string
Flags uint16
To []string
}

type Provide struct {
Service string
Impls []string
}

func NewModuleInfo(classData []byte) Info {
cf, err := classfile.Parse(classData)
if err != nil {
panic(err) // TODO
}

modAttr, ok := cf.GetModuleAttribute()
if !ok {
panic("no module attribute")
}

return Info{
Name: getModuleName(cf, modAttr.ModuleNameIndex),
Flags: modAttr.ModuleFlags,
Version: cf.GetUTF8(modAttr.ModuleVersionIndex),
Requires: getRequires(cf, modAttr),
Exports: getExports(cf, modAttr),
Opens: getOpens(cf, modAttr),
Uses: getClassNames(cf, modAttr.UsesIndexTable),
Provides: getProvides(cf, modAttr),
}
}

func getRequires(cf *classfile.ClassFile,
modAttr classfile.ModuleAttribute) []Require {

requires := make([]Require, len(modAttr.RequiresTable))
for i, cfRequire := range modAttr.RequiresTable {
requires[i] = Require{
Name: getModuleName(cf, cfRequire.RequiresIndex),
Flags: cfRequire.RequiresFlags,
Version: cf.GetUTF8(cfRequire.RequiresVersionIndex),
}
}

return requires
}

func getExports(cf *classfile.ClassFile,
modAttr classfile.ModuleAttribute) []Export {

exports := make([]Export, len(modAttr.ExportsTable))
for i, cfExport := range modAttr.ExportsTable {
exports[i] = Export{
Package: getPackageName(cf, cfExport.ExportsIndex),
Flags: cfExport.ExportsFlags,
To: getModuleNames(cf, cfExport.ExportsToIndexTable),
}
}

return exports
}

func getOpens(cf *classfile.ClassFile,
modAttr classfile.ModuleAttribute) []Open {

opens := make([]Open, len(modAttr.OpensTable))
for i, cfOpen := range modAttr.OpensTable {
opens[i] = Open{
Package: getPackageName(cf, cfOpen.OpensIndex),
Flags: cfOpen.OpensFlags,
To: getModuleNames(cf, cfOpen.OpensToIndexTable),
}
}

return opens
}

func getProvides(cf *classfile.ClassFile,
modAttr classfile.ModuleAttribute) []Provide {

provides := make([]Provide, len(modAttr.ProvidesTable))
for i, cfProvide := range modAttr.ProvidesTable {
provides[i] = Provide{
Service: getClassName(cf, cfProvide.ProvidesIndex),
Impls: getClassNames(cf, cfProvide.ProvidesWithIndexTable),
}
}

return provides
}

func getModuleNames(cf *classfile.ClassFile, cpIdxes []uint16) []string {
ss := make([]string, len(cpIdxes))
for i, cpIdx := range cpIdxes {
ss[i] = getModuleName(cf, cpIdx)
}
return ss
}

func getClassNames(cf *classfile.ClassFile, cpIdxes []uint16) []string {
ss := make([]string, len(cpIdxes))
for i, cpIdx := range cpIdxes {
ss[i] = getClassName(cf, cpIdx)
}
return ss
}

func getModuleName(cf *classfile.ClassFile, cpIdx uint16) string {
modInfo := cf.GetConstantInfo(cpIdx).(classfile.ConstantModuleInfo)
return cf.GetUTF8(modInfo.NameIndex)
}

func getPackageName(cf *classfile.ClassFile, cpIdx uint16) string {
pkgInfo := cf.GetConstantInfo(cpIdx).(classfile.ConstantPackageInfo)
return cf.GetUTF8(pkgInfo.NameIndex)
}

func getClassName(cf *classfile.ClassFile, cpIdx uint16) string {
clsInfo := cf.GetConstantInfo(cpIdx).(classfile.ConstantClassInfo)
return cf.GetUTF8(clsInfo.NameIndex)
}
37 changes: 37 additions & 0 deletions module/info_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package module

import (
"io/ioutil"
"testing"

"github.com/stretchr/testify/require"
)

func TestModuleInfo(t *testing.T) {
bytes, err := ioutil.ReadFile("../test/testdata/java13/module-info.class")
require.NoError(t, err)

info := NewModuleInfo(bytes)
require.Equal(t, info, Info{
Name: "hello.modules",
Flags: 0,
Version: "0.1",
Requires: []Require{
{
Name: "java.base",
Flags: 0x8000,
Version: "13.0.1",
},
},
Exports: []Export{
{
Package: "hello",
Flags: 0,
To: []string{},
},
},
Opens: []Open{},
Uses: []string{},
Provides: []Provide{},
})
}
8 changes: 5 additions & 3 deletions test/hw_module/test.sh
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
#!/bin/sh
set -e

alias javac='~/.sdkman/candidates/java/13.0.0-open/bin/javac'
alias jlink='~/.sdkman/candidates/java/13.0.0-open/bin/jlink'
alias javac='~/.sdkman/candidates/java/13.0.1-open/bin/javac'
alias jlink='~/.sdkman/candidates/java/13.0.1-open/bin/jlink'

javac -version | grep 'javac 13'
jlink --version | grep 13

OUT=myjre
rm -rf $OUT
javac --module-source-path src -d out -m hello.modules
javac --module-source-path src \
--module-version 0.1 \
-d out -m hello.modules
jlink --module-path out --add-modules hello.modules,java.base --output $OUT
./$OUT/bin/java -m hello.modules/hello.HelloWorld
Binary file modified test/testdata/java13/module-info.class
Binary file not shown.
Loading

0 comments on commit 3d63d08

Please sign in to comment.