Merge: doc: fixed some typos and other misc. corrections
[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_cast_objects, opt_arrays, opt_save_model, opt_load_models, opt_no_properties, 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 clock.lapse
94
95 var out_dir = "tmp"
96 if not out_dir.file_exists then out_dir.mkdir
97
98 if opt_verbose.value > 0 then print "# Extracting {input}"
99
100 # Extract all files
101 var cmd = "cd {out_dir}; jar -xf {input}"
102 var status = system(cmd)
103 if status != 0 then
104 print_error "Warning: Failed to extract Jar archive '{input}'"
105 continue
106 end
107
108 # List .class files
109 var javap = new ProcessReader("jar", "-tf", input)
110 var output = javap.read_all
111 javap.wait
112 for path in output.split("\n") do
113 if path.file_extension == "class" then
114
115 # Filter out the classes that do not answer to the Regex
116 if regex != null and not path.has(regex) then continue
117
118 class_files.add out_dir / path
119 end
120 end
121 javap.close
122
123 sys.perfs["jar extract"].add clock.lapse
124 else
125 print_error "Warning: Unsupported file extension for input file '{input}'"
126 end
127 end
128
129 if class_files.is_empty and javap_files.is_empty then
130 print_error "Error: No valid input files, quitting"
131 exit 1
132 end
133
134 var model = new JavaModel
135 var converter = new JavaTypeConverter
136
137 # Concatenated javap output for all the target files
138 var javap_output = ""
139
140 # Parse and analyze all the classes at once
141 if class_files.not_empty then
142
143 if opt_verbose.value > 0 then print "# Running javap on {class_files.length} Java classes"
144 clock.lapse
145
146 # Run javap of the class file
147 class_files.unshift "-public"
148 var javap = new ProcessReader("javap", class_files...)
149 javap_output += javap.read_all
150 javap.wait
151 javap.close
152 var status = javap.status
153
154 if status != 0 then
155 print_error "Warning: javap failed to parse all class files, is it a valid class file?"
156 exit 1
157 end
158
159 sys.perfs["javap"].add clock.lapse
160 end
161
162 # Concatenate the preprocessed javap outputs
163 for class_file in javap_files do
164 if opt_verbose.value > 0 then print "# Using the preprocessed file {class_file}"
165
166 var ext = class_file.file_extension
167 assert ext == "javap"
168
169 javap_output += class_file.to_path.read_all
170 end
171
172 if opt_verbose.value > 0 then print "# Parsing javap output"
173 if opt_verbose.value > 1 then javap_output.write_to_file "tests/javap.javap"
174
175 # Lexer
176 var lexer = new Lexer_javap(javap_output)
177 var parser = new Parser_javap
178 var tokens = lexer.lex
179 parser.tokens.add_all tokens
180 sys.perfs["core lexer"].add clock.lapse
181
182 # Parser
183 var root_node = parser.parse
184 if root_node isa NError then
185 print_error "Warning: Parsing failed with {root_node.message}:{root_node.position or else ""}"
186 exit 1
187 end
188 sys.perfs["core parser"].add clock.lapse
189
190 # Build model
191 if opt_verbose.value > 0 then print "# Building model"
192 assert root_node isa NStart
193 var visitor = new JavaVisitor(model)
194 visitor.enter_visit root_node
195 sys.perfs["core model"].add clock.lapse
196
197 # Resolve types
198 model.resolve_types
199 sys.perfs["core resolve"].add clock.lapse
200
201 # Build class hierarchy
202 model.build_class_hierarchy
203 sys.perfs["core hierarchy"].add clock.lapse
204
205 if opt_verbose.value > 0 then print "# Generating Nit code"
206
207 # Generate the Nit module
208 var use_comment = opt_unknown.value == 0
209 var use_stub = opt_unknown.value == 1
210 var generator = new CodeGenerator(out_file, model, use_comment, use_stub)
211 generator.generate
212 sys.perfs["code generator"].add clock.lapse
213
214 # Write the model to a file, for use by subsequent passes
215 generator.write_model_to_file
216 sys.perfs["writing model"].add clock.lapse
217
218 if opt_verbose.value > 1 then
219 print "# Performance Analysis:"
220 print sys.perfs
221
222 print "# {model.unknown_types.length} unknown types:"
223 var c = 0
224 for id, ntype in model.unknown_types do
225 print "* {id}"
226 c += 1
227 if c > 100 then
228 print "* ..."
229 break
230 end
231 end
232 end