contrib/jwrapper: add an option to search for existing wrappers in custom libs
[nit.git] / contrib / jwrapper / src / jwrapper.nit
1 # This file is part of NIT (http://www.nitlanguage.org).
2 #
3 # Copyright 2014 Frédéric Vachon <fredvac@gmail.com>
4 # Copyright 2015 Alexis Laferrière <alexis.laf@xymus.net>
5 #
6 # Licensed under the Apache License, Version 2.0 (the "License");
7 # you may not use this file except in compliance with the License.
8 # You may obtain a copy of the License at
9 #
10 # http://www.apache.org/licenses/LICENSE-2.0
11 #
12 # Unless required by applicable law or agreed to in writing, software
13 # distributed under the License is distributed on an "AS IS" BASIS,
14 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 # See the License for the specific language governing permissions and
16 # limitations under the License.
17
18 # jwrapper wraps Java classes in extern Nit classes
19 #
20 # This tool takes class file, Jar archives and javap files as input,
21 # and it outputs a Nit source file.
22 # For further details on installation and usage, refer to the README file.
23 #
24 # Here's an overview of the project design :
25 # * Grammar and lexer : `grammar/javap.sablecc`
26 # * The `javap_visitor` implements the visitor that extracts data from the AST
27 # * The `code_generator` takes these data and converts it to Nit code via the `jtype_converter` module and generate the output Nit file.
28 # * The `model` contains data structures used to represent the data
29 # * The `jwrapper` module implements the user interface
30 module jwrapper
31
32 import opts
33 import performance_analysis
34
35 import javap_test_parser
36 import code_generator
37 import javap_visitor
38
39 var opts = new OptionContext
40
41 var opt_unknown = new OptionEnum(["comment", "stub", "ignore"], "How to deal with unknown types", 0, "-u")
42 var opt_verbose = new OptionCount("Verbosity", "-v")
43 var opt_output = new OptionString("Output file", "-o")
44 var opt_regex = new OptionString("Regex pattern to filter classes in Jar archives", "-r")
45 var opt_help = new OptionBool("Show this help message", "-h", "--help")
46
47 opts.add_option(opt_output, opt_unknown, opt_extern_class_prefix, opt_libs, opt_regex, opt_verbose, opt_help)
48 opts.parse args
49
50 if opts.errors.not_empty or opts.rest.is_empty or opt_help.value then
51 print """
52 Usage: jwrapper [options] file [other_file [...]]
53 Input files: bytecode Java class (.class), Jar archive (.jar) or javap output (.javap)
54 Options:"""
55 opts.usage
56
57 if opt_help.value then exit 0
58 exit 1
59 end
60
61 var out_file = opt_output.value
62 if out_file == null then out_file = "out.nit"
63
64 if not "javap".program_is_in_path then
65 print "Error: 'javap' must be in PATH"
66 exit 1
67 end
68
69 var regex = null
70 var regex_code = opt_regex.value
71 if regex_code != null then
72 regex = regex_code.to_re
73 var error = regex.compile
74 if error != null then
75 print_error "Regex Error: {error}"
76 exit 1
77 end
78 end
79
80 # List of bytecode Java classes and javap output files
81 var class_files = new Array[String]
82 var javap_files = new Array[String]
83 var clock = new Clock
84
85 # Sort through input files passed as arguments
86 for input in opts.rest do
87 var ext = input.file_extension
88 if ext == "class" then
89 class_files.add input
90 else if ext == "javap" then
91 javap_files.add input
92 else if ext == "jar" then
93 var out_dir = "tmp"
94 clock.lapse
95
96 if opt_verbose.value > 0 then print "# Extracting {input}"
97
98 # Extract all files
99 var cmd = "cd {out_dir}; jar -xf {input}"
100 var status = system(cmd)
101 if status != 0 then
102 print_error "Warning: Failed to extract Jar archive '{input}'"
103 continue
104 end
105
106 # List .class files
107 var javap = new ProcessReader("jar", "-tf", input)
108 var output = javap.read_all
109 javap.wait
110 for path in output.split("\n") do
111 if path.file_extension == "class" then
112
113 # Filter out the classes that do not answer to the Regex
114 if regex != null and not path.has(regex) then continue
115
116 class_files.add out_dir / path
117 end
118 end
119 javap.close
120
121 sys.perfs["jar extract"].add clock.lapse
122 else
123 print_error "Warning: Unsupported file extension for input file '{input}'"
124 end
125 end
126
127 if class_files.is_empty and javap_files.is_empty then
128 print_error "Error: No valid input files, quitting"
129 exit 1
130 end
131
132 var model = new JavaModel
133 var converter = new JavaTypeConverter
134
135 # Concatenated javap output for all the target files
136 var javap_output = ""
137
138 # Parse and analyze all the classes at once
139 if class_files.not_empty then
140
141 if opt_verbose.value > 0 then print "# Running javap on {class_files.length} Java classes"
142 clock.lapse
143
144 # Run javap of the class file
145 class_files.unshift "-public"
146 var javap = new ProcessReader("javap", class_files...)
147 javap_output += javap.read_all
148 javap.wait
149 javap.close
150 var status = javap.status
151
152 if status != 0 then
153 print_error "Warning: javap failed to parse all class files, is it a valid class file?"
154 exit 1
155 end
156
157 sys.perfs["javap"].add clock.lapse
158 end
159
160 # Concatenate the preprocessed javap outputs
161 for class_file in javap_files do
162 if opt_verbose.value > 0 then print "# Using the preprocessed file {class_file}"
163
164 var ext = class_file.file_extension
165 assert ext == "javap"
166
167 javap_output += class_file.to_path.read_all
168 end
169
170 if opt_verbose.value > 0 then print "# Parsing javap output"
171 if opt_verbose.value > 1 then javap_output.write_to_file "tests/javap.javap"
172
173 # Lexer
174 var lexer = new Lexer_javap(javap_output)
175 var parser = new Parser_javap
176 var tokens = lexer.lex
177 parser.tokens.add_all tokens
178 sys.perfs["core lexer"].add clock.lapse
179
180 # Parser
181 var root_node = parser.parse
182 if root_node isa NError then
183 print_error "Warning: Parsing failed with {root_node.message}:{root_node.position or else ""}"
184 exit 1
185 end
186 sys.perfs["core parser"].add clock.lapse
187
188 # Build model
189 if opt_verbose.value > 0 then print "# Building model"
190 assert root_node isa NStart
191 var visitor = new JavaVisitor(model)
192 visitor.enter_visit root_node
193 sys.perfs["core model"].add clock.lapse
194
195 if opt_verbose.value > 0 then print "# Generating Nit code"
196
197 var use_comment = opt_unknown.value == 0
198 var use_stub = opt_unknown.value == 1
199 var generator = new CodeGenerator(out_file, model, use_comment, use_stub)
200 generator.generate
201 sys.perfs["code generator"].add clock.lapse
202
203 if opt_verbose.value > 1 then
204 print "# Performance Analysis:"
205 print sys.perfs
206 end