# This file is part of NIT (http://www.nitlanguage.org).
#
# Copyright 2014 Frédéric Vachon <fredvac@gmail.com>
+# Copyright 2015 Alexis Laferrière <alexis.laf@xymus.net>
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# See the License for the specific language governing permissions and
# limitations under the License.
-# jwrapper is a Nit extern class generator `in "Java"`.
-# It takes a .class file and output a Nit file. For further details on installation and usage, refer to the README file.
+# jwrapper wraps Java classes in extern Nit classes
+#
+# This tool takes class file, Jar archives and javap files as input,
+# and it outputs a Nit source file.
+# For further details on installation and usage, refer to the README file.
#
# Here's an overview of the project design :
# * Grammar and lexer : `grammar/javap.sablecc`
# * The `javap_visitor` implements the visitor that extracts data from the AST
# * The `code_generator` takes these data and converts it to Nit code via the `jtype_converter` module and generate the output Nit file.
-# * The `types` contains data structures used to represent the data
+# * The `model` contains data structures used to represent the data
# * The `jwrapper` module implements the user interface
module jwrapper
import opts
+import performance_analysis
+
import javap_test_parser
import code_generator
import javap_visitor
var opts = new OptionContext
-var opt_attr = new OptionBool("Generate attributes", "-a", "--with-attributes")
-var opt_comment = new OptionBool("Comment methods/attributes containing unknown types", "-c", "--comment")
-var opt_wrap = new OptionBool("Create extern classes wrapping unknown types (Default)", "-w", "--wrap")
+var opt_unknown = new OptionEnum(["comment", "stub", "ignore"], "How to deal with unknown types", 0, "-u")
+var opt_verbose = new OptionCount("Verbosity", "-v")
+var opt_output = new OptionString("Output file", "-o")
+var opt_regex = new OptionString("Regex pattern to filter classes in Jar archives", "-r")
var opt_help = new OptionBool("Show this help message", "-h", "--help")
-opts.add_option(opt_attr, opt_comment, opt_wrap, opt_help)
-
-opts.parse(args)
-
-if opt_wrap.value and opt_comment.value then
- print "Error: Can't use both '-c' and '-w'"
- exit 1
-end
+opts.add_option(opt_output, opt_unknown, opt_extern_class_prefix, opt_libs, opt_regex, opt_cast_objects, opt_arrays, opt_verbose, opt_help)
+opts.parse args
-if not opts.errors.is_empty or opts.rest.length != 2 or opt_help.value then
- print "USAGE: jwrapper [OPTIONS] class_file nit_file"
- print "Options:"
+if opts.errors.not_empty or opts.rest.is_empty or opt_help.value then
+ print """
+Usage: jwrapper [options] file [other_file [...]]
+Input files: bytecode Java class (.class), Jar archive (.jar) or javap output (.javap)
+Options:"""
opts.usage
if opt_help.value then exit 0
exit 1
end
-var dot_class = opts.rest[0]
-var out_file = opts.rest[1]
+var out_file = opt_output.value
+if out_file == null then out_file = "out.nit"
if not "javap".program_is_in_path then
- print "ERROR: 'javap' not found."
+ print "Error: 'javap' must be in PATH"
exit 1
end
-var javap = new IProcess("javap", "-public", dot_class)
+var regex = null
+var regex_code = opt_regex.value
+if regex_code != null then
+ regex = regex_code.to_re
+ var error = regex.compile
+ if error != null then
+ print_error "Regex Error: {error}"
+ exit 1
+ end
+end
-var p = new TestParser_javap
-var tree = p.work(javap.read_all)
+# List of bytecode Java classes and javap output files
+var class_files = new Array[String]
+var javap_files = new Array[String]
+var clock = new Clock
+
+# Sort through input files passed as arguments
+for input in opts.rest do
+ var ext = input.file_extension
+ if ext == "class" then
+ class_files.add input
+ else if ext == "javap" then
+ javap_files.add input
+ else if ext == "jar" then
+ var out_dir = "tmp"
+ clock.lapse
+
+ if opt_verbose.value > 0 then print "# Extracting {input}"
+
+ # Extract all files
+ var cmd = "cd {out_dir}; jar -xf {input}"
+ var status = system(cmd)
+ if status != 0 then
+ print_error "Warning: Failed to extract Jar archive '{input}'"
+ continue
+ end
+
+ # List .class files
+ var javap = new ProcessReader("jar", "-tf", input)
+ var output = javap.read_all
+ javap.wait
+ for path in output.split("\n") do
+ if path.file_extension == "class" then
+
+ # Filter out the classes that do not answer to the Regex
+ if regex != null and not path.has(regex) then continue
+
+ class_files.add out_dir / path
+ end
+ end
+ javap.close
+
+ sys.perfs["jar extract"].add clock.lapse
+ else
+ print_error "Warning: Unsupported file extension for input file '{input}'"
+ end
+end
+if class_files.is_empty and javap_files.is_empty then
+ print_error "Error: No valid input files, quitting"
+ exit 1
+end
+
+var model = new JavaModel
var converter = new JavaTypeConverter
-var visitor = new JavaVisitor(converter)
-visitor.enter_visit(tree)
+# Concatenated javap output for all the target files
+var javap_output = ""
+
+# Parse and analyze all the classes at once
+if class_files.not_empty then
+
+ if opt_verbose.value > 0 then print "# Running javap on {class_files.length} Java classes"
+ clock.lapse
+
+ # Run javap of the class file
+ class_files.unshift "-public"
+ var javap = new ProcessReader("javap", class_files...)
+ javap_output += javap.read_all
+ javap.wait
+ javap.close
+ var status = javap.status
+
+ if status != 0 then
+ print_error "Warning: javap failed to parse all class files, is it a valid class file?"
+ exit 1
+ end
-var generator = new CodeGenerator(out_file, visitor.java_class, opt_attr.value, opt_comment.value)
+ sys.perfs["javap"].add clock.lapse
+end
+
+# Concatenate the preprocessed javap outputs
+for class_file in javap_files do
+ if opt_verbose.value > 0 then print "# Using the preprocessed file {class_file}"
+
+ var ext = class_file.file_extension
+ assert ext == "javap"
+
+ javap_output += class_file.to_path.read_all
+end
+
+if opt_verbose.value > 0 then print "# Parsing javap output"
+if opt_verbose.value > 1 then javap_output.write_to_file "tests/javap.javap"
+
+# Lexer
+var lexer = new Lexer_javap(javap_output)
+var parser = new Parser_javap
+var tokens = lexer.lex
+parser.tokens.add_all tokens
+sys.perfs["core lexer"].add clock.lapse
+
+# Parser
+var root_node = parser.parse
+if root_node isa NError then
+ print_error "Warning: Parsing failed with {root_node.message}:{root_node.position or else ""}"
+ exit 1
+end
+sys.perfs["core parser"].add clock.lapse
+
+# Build model
+if opt_verbose.value > 0 then print "# Building model"
+assert root_node isa NStart
+var visitor = new JavaVisitor(model)
+visitor.enter_visit root_node
+sys.perfs["core model"].add clock.lapse
+
+if opt_verbose.value > 0 then print "# Generating Nit code"
+
+var use_comment = opt_unknown.value == 0
+var use_stub = opt_unknown.value == 1
+var generator = new CodeGenerator(out_file, model, use_comment, use_stub)
generator.generate
+sys.perfs["code generator"].add clock.lapse
+
+if opt_verbose.value > 1 then
+ print "# Performance Analysis:"
+ print sys.perfs
+
+ print "# {model.unknown_types.length} unknown types:"
+ var c = 0
+ for id, ntype in model.unknown_types do
+ print "* {id}"
+ c += 1
+ if c > 100 then
+ print "* ..."
+ break
+ end
+ end
+end