divine 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in babelphish.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,23 @@
1
+ Copyright (c) 2012 Nils Franzen
2
+ Copyright (c) 2009-2010, Patrick Reiter Horn (Javascript UTF8 string encode/decode methods taken from the ProtoJS project)
3
+
4
+ MIT License
5
+
6
+ Permission is hereby granted, free of charge, to any person obtaining
7
+ a copy of this software and associated documentation files (the
8
+ "Software"), to deal in the Software without restriction, including
9
+ without limitation the rights to use, copy, modify, merge, publish,
10
+ distribute, sublicense, and/or sell copies of the Software, and to
11
+ permit persons to whom the Software is furnished to do so, subject to
12
+ the following conditions:
13
+
14
+ The above copyright notice and this permission notice shall be
15
+ included in all copies or substantial portions of the Software.
16
+
17
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
18
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
19
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
20
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
21
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
22
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
23
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,122 @@
1
+ # Divine
2
+
3
+ Divine provides a compact data interchange format for Ruby, Java and Javascript. Divine is similar to [Thrift](http://thrift.apache.org/) and [Protobuf](http://code.google.com/p/protobuf/), but I try to overcome of some of the shortcommings I think the other libraries have.
4
+
5
+
6
+ ## Example
7
+
8
+
9
+ ```ruby
10
+ require 'divine'
11
+
12
+ struct 'TestBasic' do
13
+ int8 :i8
14
+ int16 :i16
15
+ int32 :i32
16
+ string :str
17
+ ip_number :ip
18
+ binary :guid
19
+ end
20
+
21
+ struct 'Entry' do
22
+
23
+ end
24
+
25
+
26
+ struct(:TestComplex) {
27
+ list :list1, :int32
28
+ list :list2, :int8
29
+ map :map1, :int8, :int32
30
+ map(:map2, :string) {
31
+ list 'Entry'
32
+ }
33
+ }
34
+
35
+ Divine::CodeGenerator.new.generate(:ruby, file: 'test_babel.rb', module: 'BabelTest', parent_class: "Object")
36
+ Divine::CodeGenerator.new.generate(:javascript, file: 'test_babel.js')
37
+ ```
38
+
39
+ The resulting _test_babel.rb_ contains the generated source code for the defined structs. Below is an example on how to use the generated code in ruby
40
+
41
+ ```ruby
42
+ require_relative 'test_babel.rb'
43
+
44
+ t1 = BabelTest::TestBasic.new
45
+ t1.i8 = 0x0F
46
+ t1.i16 = 0X1234
47
+ t1.i32 = 0x567890AB
48
+ t1.str = "abc\n123\tåäö´+'\""
49
+ t1.ip = "192.168.0.1"
50
+ t1.guid = (0..255).to_a
51
+
52
+ p1 = BabelTest::TestComplex.new
53
+ p1.list1 = [0, 1, 255, 0x7FFFFFFF, 0x7FFFFFFF+1, 0xFFFFFFFE, 0xFFFFFFFF]
54
+ p1.list2 = [0,1, 15,16, 127,128, 254,255]
55
+ p1.map1[0] = 10
56
+ p1.map1[10] = 100
57
+ p1.map2["Hello"] = [BabelTest::Entry.new, BabelTest::Entry.new]
58
+
59
+
60
+ data1 = t1.serialize
61
+ data2 = p1.serialize
62
+ File.open("bin.babel", "w+b") do |f|
63
+ f.write(data1)
64
+ f.write(data2)
65
+ end
66
+
67
+ mem_buf = File.new('bin.babel').binmode
68
+ t2 = BabelTest::TestBasic.new
69
+ t2.deserialize mem_buf
70
+
71
+ p2 = BabelTest::TestComplex.new
72
+ p2.deserialize mem_buf
73
+ ```
74
+
75
+ And a javascript example that uses the generated file _test_babel.js_
76
+
77
+ ```javascript
78
+ var c1 = new TestComplex();
79
+ c1.list1 = [0, 1, 255, 0x7FFFFFFF, 0x7FFFFFFF+1, 0xFFFFFFFE, 0xFFFFFFFF];
80
+ c1.list2 = [0,1, 15,16, 127,128, 254,255];
81
+
82
+ c1.map1[0] = 10;
83
+ c1.map1[10] = 100;
84
+
85
+ c1.map2["FooBar"] = [new Entry(), new Entry()];
86
+
87
+
88
+ console.log("SERIALIZE");
89
+ var ca = c1.serialize();
90
+
91
+ console.log("DESERIALIZE");
92
+ var c2 = new TestComplex();
93
+ c2.deserialize(new BabelDataReader(ca));
94
+ console.log(c2);
95
+ ```
96
+
97
+
98
+ ## Installation
99
+
100
+ Add this line to your application's Gemfile:
101
+
102
+ gem 'divine'
103
+
104
+ And then execute:
105
+
106
+ $ bundle
107
+
108
+ Or install it yourself as:
109
+
110
+ $ gem install divine
111
+
112
+ ## Usage
113
+
114
+ TODO: Write usage instructions here
115
+
116
+ ## Contributing
117
+
118
+ 1. Fork it
119
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
120
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
121
+ 4. Push to the branch (`git push origin my-new-feature`)
122
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
data/divine.gemspec ADDED
@@ -0,0 +1,22 @@
1
+ # -*- encoding: utf-8 -*-
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'divine/version'
5
+
6
+ Gem::Specification.new do |gem|
7
+ gem.name = "divine"
8
+ gem.version = Divine::VERSION
9
+ gem.authors = ["Nils Franzen"]
10
+ gem.email = ["nils@franzens.org"]
11
+ gem.description = %q{A simple data serialization generator for java, ruby and javascript}
12
+ gem.summary = %q{A simple data serialization generator for java, ruby and javascript}
13
+ gem.homepage = "https://github.com/franzen/babelphish"
14
+
15
+ gem.files = `git ls-files`.split($/)
16
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
17
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
18
+ gem.require_paths = ["lib"]
19
+
20
+ gem.add_dependency(%q<docile>, [">= 1.0.0"])
21
+ gem.add_dependency(%q<erubis>, [">= 2.7.0"])
22
+ end
data/lib/divine.rb ADDED
@@ -0,0 +1,22 @@
1
+ require 'erubis'
2
+ require 'docile'
3
+
4
+ require "divine/version"
5
+ require "divine/dsl"
6
+ require "divine/code_generators/code_generator"
7
+ require "divine/code_generators/ruby"
8
+ require "divine/code_generators/java"
9
+ require "divine/code_generators/javascript"
10
+
11
+ module Divine
12
+ end
13
+
14
+ #
15
+ # Toplevel definition of struct'
16
+ #
17
+ def struct(name, version=1, &block)
18
+ puts "struct #{name}"
19
+ builder = Divine::StructBuilder.new(name, version) # Defined in divine/dsl.rb
20
+ ::Docile.dsl_eval(builder, &block)
21
+ builder
22
+ end
@@ -0,0 +1,49 @@
1
+ module Divine
2
+ $language_generators = {}
3
+
4
+ class BabelHelperMethods
5
+ def format_src(first_indent, following_indent, is, spc = " ")
6
+ indent = "#{spc * first_indent}"
7
+ is.flatten.compact.map do |i|
8
+ case i
9
+ when :indent
10
+ indent << spc * following_indent
11
+ nil
12
+ when :deindent
13
+ indent = indent[0..-(following_indent+1)]
14
+ nil
15
+ else
16
+ "#{indent}#{i}"
17
+ end
18
+ end.compact.join("\n")
19
+ end
20
+
21
+ def get_fresh_variable_name
22
+ @vindex = (@vindex || 0xFF) + 1
23
+ return "var_#{@vindex.to_s(16)}"
24
+ end
25
+
26
+ def camelize(*str)
27
+ ss = str.map(&:to_s).join("_").split(/_/).flatten
28
+ "#{ss.first.downcase}#{ss[1..-1].map(&:downcase).map(&:capitalize).join}"
29
+ end
30
+ end
31
+
32
+ class CodeGenerator
33
+ def generate(target, opts)
34
+ gen = $language_generators[target.to_sym]
35
+ raise "Unknown taget language: #{target}" unless gen
36
+ puts "Generating code for #{target}"
37
+ src = gen.generate_code($all_structs, opts)
38
+ if opts[:file]
39
+ filename = opts[:file]
40
+ File.open(filename, 'w+') do |f|
41
+ puts "... writing #{filename}"
42
+ f.write(src)
43
+ end
44
+ else
45
+ puts src
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,414 @@
1
+ module Divine
2
+
3
+ class JavaHelperMethods < BabelHelperMethods
4
+ def java_base_class_template_str
5
+ <<EOS
6
+ abstract class BabelBase <%= toplevel_class %> {
7
+ private static final Charset UTF8 = Charset.forName("UTF-8");
8
+
9
+ public byte[] serialize() throws IOException {
10
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
11
+ serializeInternal(baos);
12
+ baos.close();
13
+ return baos.toByteArray();
14
+ }
15
+
16
+ abstract void serializeInternal(ByteArrayOutputStream baos) throws IOException;
17
+
18
+ abstract void deserialize(ByteArrayInputStream baos) throws IOException;
19
+
20
+ protected int readInt8(ByteArrayInputStream data) {
21
+ return data.read() & 0xff;
22
+ }
23
+
24
+ protected int readInt16(ByteArrayInputStream data) {
25
+ return (data.read() << 8) | readInt8(data);
26
+ }
27
+
28
+ protected int readInt24(ByteArrayInputStream data) {
29
+ return (data.read() << 16) | readInt16(data);
30
+ }
31
+
32
+ protected long readInt32(ByteArrayInputStream data) {
33
+ return (data.read() << 24) | readInt24(data);
34
+ }
35
+
36
+ protected boolean readBool(ByteArrayInputStream data) {
37
+ return readInt8(data) == 1;
38
+ }
39
+
40
+ protected String readString(ByteArrayInputStream data) throws IOException {
41
+ // Force utf8
42
+ return new String(readBytes(readInt16(data), data), UTF8);
43
+ }
44
+
45
+ private byte[] readBytes(int size, ByteArrayInputStream data) throws IOException {
46
+ byte[] bs = new byte[size];
47
+ data.read(bs);
48
+ return bs;
49
+ }
50
+
51
+ protected byte[] readBinary(ByteArrayInputStream data) throws IOException {
52
+ long c = readInt32(data);
53
+ if (c > Integer.MAX_VALUE) {
54
+ throw new IndexOutOfBoundsException("Binary data to big for java");
55
+ }
56
+ return readBytes((int) readInt32(data), data);
57
+ }
58
+
59
+ protected byte[] readShortBinary(ByteArrayInputStream data) throws IOException {
60
+ return readBytes(readInt8(data), data);
61
+ }
62
+
63
+ protected String readIpNumber(ByteArrayInputStream data) throws IOException {
64
+ byte[] ips = readShortBinary(data);
65
+ String ip = "";
66
+ for (byte b : ips) {
67
+ if (ip.length() > 0) {
68
+ ip += ".";
69
+ }
70
+ ip += (b & 0xFF);
71
+ }
72
+ return ip;
73
+ }
74
+
75
+ protected void writeInt8(int v, ByteArrayOutputStream out) {
76
+ if (v > 0xFF) { // Max 255
77
+ raiseError("Too large int8 number: " + v);
78
+ }
79
+ out.write(v);
80
+ }
81
+
82
+ protected void writeInt16(int v, ByteArrayOutputStream out) {
83
+ if (v > 0xFFFF) { // Max 65.535
84
+ raiseError("Too large int16 number: " + v);
85
+ }
86
+ writeInt8(v >> 8 & 0xFF, out);
87
+ writeInt8(v & 0xFF, out);
88
+ }
89
+
90
+ protected void writeInt24(int v, ByteArrayOutputStream out) {
91
+ if (v > 0xFFFFFF) { // Max 16.777.215
92
+ raiseError("Too large int24 number: " + v);
93
+ }
94
+ writeInt8(v >> 16 & 0xFF, out);
95
+ writeInt16(v & 0xFFFF, out);
96
+ }
97
+
98
+ protected void writeInt32(long v, ByteArrayOutputStream out) {
99
+ if (v > 0xFFFFFFFF) { // Max 4.294.967.295
100
+ raiseError("Too large int32 number: " + v);
101
+ }
102
+ writeInt8((int) ((v >> 24) & 0xFF), out);
103
+ writeInt24((int) (v & 0xFFFFFF), out);
104
+ }
105
+
106
+ protected void writeBool(boolean v, ByteArrayOutputStream out) {
107
+ writeInt8(v ? 1 : 0, out);
108
+ }
109
+
110
+ protected void writeString(String v, ByteArrayOutputStream out) throws IOException {
111
+ byte[] bs = v.getBytes(UTF8);
112
+ if (bs.length > 0xFFFF) {
113
+ raiseError("Too large string: " + bs.length + " bytes");
114
+ }
115
+ writeInt16(bs.length, out);
116
+ out.write(bs);
117
+ }
118
+
119
+ protected void writeBinary(byte[] v, ByteArrayOutputStream out) throws IOException {
120
+ if (v.length > 0xFFFFFFFF) {
121
+ raiseError("Too large binary: " + v.length + " bytes");
122
+ }
123
+ writeInt32(v.length, out);
124
+ out.write(v);
125
+ }
126
+
127
+ protected void writeShortBinary(byte[] v, ByteArrayOutputStream out) throws IOException {
128
+ if (v.length > 0xFF) {
129
+ raiseError("Too large short_binary: " + v.length + " bytes");
130
+ }
131
+ writeInt8(v.length, out);
132
+ out.write(v);
133
+ }
134
+
135
+ protected void writeIpNumber(String v, ByteArrayOutputStream out) throws IOException {
136
+ String[] bs = v.split(".");
137
+ byte[] ss = new byte[bs.length];
138
+ for (int i = 0; i < bs.length; i++) {
139
+ ss[i] = (byte) (Integer.parseInt(bs[i]) & 0xFF);
140
+ }
141
+ if (ss.length == 0 || ss.length == 4) {
142
+ writeShortBinary(ss, out);
143
+ } else {
144
+ raiseError("Unknown IP v4 number " + v); // Only IPv4 for now
145
+ }
146
+ }
147
+
148
+ protected void raiseError(String msg) {
149
+ throw new IllegalArgumentException("[" + this.getClass().getCanonicalName() + "] " + msg);
150
+ }
151
+ }
152
+ EOS
153
+ end
154
+
155
+ def java_class_template_str
156
+ <<EOS2
157
+ public class <%= c.name %> extends BabelBase {
158
+ <% unless c.fields.empty? %>
159
+ <% c.fields.each do |f| %>
160
+ public <%= this.java_get_type_declaration(f) %> <%= f.name %> = <%= this.java_get_empty_declaration(f) %>;
161
+ <% end %>
162
+ <% end %>
163
+
164
+ @Override
165
+ void serializeInternal(ByteArrayOutputStream baos) throws IOException {
166
+ <% c.simple_fields.each do |f| %>
167
+ <%= this.camelize("write", f.type) %>(this.<%= f.name %>, baos);
168
+ <% end %>
169
+ <% c.complex_fields.each do |f| %>
170
+ <%= this.java_serialize_complex f %>
171
+ <% end %>
172
+ }
173
+
174
+ @Override
175
+ void deserialize(ByteArrayInputStream bais) throws IOException {
176
+ <% c.simple_fields.each do |f| %>
177
+ this.<%= f.name %> = <%= this.camelize("read", f.type) %>(bais);
178
+ <% end %>
179
+ <% c.complex_fields.each do |f| %>
180
+ <%= this.java_deserialize_complex f %>
181
+ <% end %>
182
+ }
183
+ }
184
+ EOS2
185
+ end
186
+
187
+ def java_get_empty_declaration(types, is_reference_type = false)
188
+ if types.respond_to? :referenced_types
189
+ java_get_empty_declaration(types.referenced_types)
190
+
191
+ elsif types.respond_to? :first
192
+ case types.first
193
+ when :list
194
+ "new #{java_get_type_declaration(types, true)}()"
195
+ when :map
196
+ "new #{java_get_type_declaration(types, true)}()"
197
+ else
198
+ raise "Missing empty declaration for #{types}"
199
+ end
200
+
201
+ else
202
+ case types
203
+ when :binary, :short_binary
204
+ is_reference_type ? "new Byte[0]" : "new byte[0]"
205
+ when :int8, :int16
206
+ "0"
207
+ when :int32
208
+ "0L"
209
+ when :string, :ip_number
210
+ "\"\""
211
+ else
212
+ if $all_structs[types]
213
+ types
214
+ else
215
+ raise "Unkown field type #{types}"
216
+ end
217
+ end
218
+ end
219
+ end
220
+
221
+ def java_get_type_declaration(types, is_reference_type = false)
222
+ if types.respond_to? :referenced_types
223
+ java_get_type_declaration(types.referenced_types, is_reference_type)
224
+
225
+ elsif types.respond_to? :first
226
+ case types.first
227
+ when :list
228
+ subtypes = java_get_type_declaration(types[1], true)
229
+ return "ArrayList<#{subtypes}>"
230
+ when :map
231
+ key_type = java_get_type_declaration(types[1], true)
232
+ value_type = java_get_type_declaration(types[2], true)
233
+ return "HashMap<#{key_type}, #{value_type}>"
234
+ else
235
+ raise "Missing serialization for #{types}"
236
+ end
237
+
238
+ else
239
+ case types
240
+ when :binary, :short_binary
241
+ is_reference_type ? "Byte[]" : "byte[]"
242
+ when :int8, :int16
243
+ is_reference_type ? "Integer" : "int"
244
+ when :int32
245
+ is_reference_type ? "Long" : "long"
246
+ when :string, :ip_number
247
+ "String"
248
+ else
249
+ if $all_structs[types]
250
+ types
251
+ else
252
+ raise "Unkown field type #{types}"
253
+ end
254
+ end
255
+ end
256
+ end
257
+
258
+ def java_serialize_complex(field)
259
+ types = field.referenced_types
260
+ as = [
261
+ "// Serialize #{field.type} '#{field.name}'",
262
+ java_serialize_internal(field.name, types)
263
+ ]
264
+ format_src(2, 1, as, "\t")
265
+ end
266
+
267
+ def java_serialize_internal(var, types)
268
+ if types.respond_to? :first
269
+ case types.first
270
+ when :list
271
+ nv = get_fresh_variable_name
272
+ idx = get_fresh_variable_name
273
+ return [
274
+ "writeInt32(#{var}.size(), baos);",
275
+ "for(int #{idx}=0; #{idx}<#{var}.size(); #{idx}++) {",
276
+ :indent,
277
+ "#{java_get_type_declaration types[1]} #{nv} = #{var}.get(#{idx});",
278
+ java_serialize_internal(nv, types[1]),
279
+ :deindent,
280
+ "}"
281
+ ]
282
+ when :map
283
+ nv1 = get_fresh_variable_name
284
+ nv2 = get_fresh_variable_name
285
+ return [
286
+ "writeInt32(#{var}.size(), baos);",
287
+ "for(#{java_get_type_declaration types[1]} #{nv1} : #{var}.keySet()) {",
288
+ :indent,
289
+ "#{java_get_type_declaration types[2]} #{nv2} = #{var}.get(#{nv1});",
290
+ java_serialize_internal(nv1, types[1]),
291
+ java_serialize_internal(nv2, types[2]),
292
+ :deindent,
293
+ "}"
294
+ ]
295
+ else
296
+ raise "Missing serialization for #{var}"
297
+ end
298
+ else
299
+ if $all_structs[types]
300
+ "#{var}.serializeInternal(baos);"
301
+
302
+ elsif $available_types[types] && $available_types[types].ancestors.include?(SimpleDefinition)
303
+ "#{self.camelize "write", types}(#{var}, baos);"
304
+
305
+ else
306
+ raise "Missing code generation case #{types}"
307
+ end
308
+ end
309
+ end
310
+
311
+ def java_deserialize_complex(field)
312
+ types = field.referenced_types
313
+ as = [
314
+ "// Deserialize #{field.type} '#{field.name}'",
315
+ java_deserialize_internal("this.#{field.name}", types)
316
+ ]
317
+ format_src(2, 1, as, "\t")
318
+ end
319
+
320
+ def java_deserialize_internal(var, types)
321
+ if types.respond_to? :first
322
+ case types.first
323
+ when :list
324
+ count = get_fresh_variable_name
325
+ nv = get_fresh_variable_name
326
+ iter = get_fresh_variable_name
327
+ return [
328
+ "#{"#{java_get_type_declaration(types)} " unless var.include? "this."}#{var} = #{java_get_empty_declaration(types)};",
329
+ "int #{count} = (int)this.readInt32(bais);",
330
+ "for(int #{iter}=0; #{iter}<#{count}; #{iter}++) {",
331
+ :indent,
332
+ java_deserialize_internal(nv, types[1]),
333
+ "#{var}.add(#{nv});",
334
+ :deindent,
335
+ "}"
336
+ ]
337
+ when :map
338
+ count = get_fresh_variable_name
339
+ nv1 = get_fresh_variable_name
340
+ nv2 = get_fresh_variable_name
341
+ iter = get_fresh_variable_name
342
+ return ["#{"#{java_get_type_declaration(types)} " unless var.include? "this."}#{var} = #{java_get_empty_declaration(types)};",
343
+ "int #{count} = (int)readInt32(bais);",
344
+ "for(int #{iter}=0; #{iter}<#{count}; #{iter}++) {",
345
+ :indent,
346
+ java_deserialize_internal(nv1, types[1]),
347
+ java_deserialize_internal(nv2, types[2]),
348
+ "#{var}.put(#{nv1}, #{nv2});",
349
+ :deindent,
350
+ "}"
351
+ ]
352
+ else
353
+ raise "Missing serialization for #{var}"
354
+ end
355
+ else
356
+ # case types
357
+ # when :map
358
+ # "#{var} = #{java_get_empty_declaration(types)}"
359
+ # when :list
360
+ # "#{var} = #{java_get_empty_declaration(types)}"
361
+ # else
362
+ if $all_structs.key? types
363
+ [
364
+ "#{types} #{var} = new #{types}();",
365
+ "#{var}.deserialize(bais);"
366
+ ]
367
+ else
368
+ "#{"#{java_get_type_declaration(types)} " unless var.include? "this."}#{var} = #{self.camelize("read", types)}(bais);"
369
+ end
370
+ # end
371
+ end
372
+ end
373
+ end
374
+
375
+
376
+ class JavaGenerator < JavaHelperMethods
377
+ def generate_code(structs, opts)
378
+ pp opts
379
+ $debug_java = true if opts[:debug]
380
+ base_template = Erubis::Eruby.new(java_base_class_template_str)
381
+ class_template = Erubis::Eruby.new(java_class_template_str)
382
+ keys = structs.keys.sort
383
+ src = keys.map do |k|
384
+ ss = structs[k]
385
+ # TODO: Should we merge different versions and deduce deprecated methods, warn for incompatible changes, etc?
386
+ raise "Duplicate definitions of struct #{k}" if ss.size > 1
387
+ class_template.result( c: ss.first, this: self )
388
+ end
389
+
390
+ # User defined super class?
391
+ toplevel = opts[:parent_class] || nil
392
+ toplevel = " extends #{toplevel}" if toplevel
393
+ return "#{java_get_begin_module(opts)}#{base_template.result({ toplevel_class: toplevel })}\n\n#{src.join("\n\n")}"
394
+ end
395
+
396
+ def java_get_begin_module(opts)
397
+ if opts[:package]
398
+ [
399
+ "package #{opts[:package]};\n",
400
+ "import java.io.ByteArrayInputStream;",
401
+ "import java.io.ByteArrayOutputStream;",
402
+ "import java.io.IOException;",
403
+ "import java.util.ArrayList;",
404
+ "import java.util.HashMap;",
405
+ "import java.nio.charset.Charset;\n\n"
406
+ ].join("\n")
407
+ else
408
+ nil
409
+ end
410
+ end
411
+ end
412
+
413
+ $language_generators[:java] = JavaGenerator.new
414
+ end