contrib/jwrapper: print unsupported types in verbose mode
[nit.git] / contrib / jwrapper / src / jwrapper.nit
index 56128b7..06e8f44 100644 (file)
@@ -1,6 +1,7 @@
 # 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