divine 0.0.2 → 0.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. data/.gitignore +30 -0
  2. data/README.md +57 -0
  3. data/Rakefile +45 -18
  4. data/lib/divine.rb +3 -3
  5. data/lib/divine/code_generators/code_generator.rb +112 -14
  6. data/lib/divine/code_generators/java.rb +144 -80
  7. data/lib/divine/code_generators/javascript.rb +192 -104
  8. data/lib/divine/code_generators/ruby.rb +133 -60
  9. data/lib/divine/dsl.rb +70 -12
  10. data/lib/divine/version.rb +1 -1
  11. data/test/basic_complex_test/js_test/js_testBasic.js +1 -1
  12. data/test/basic_complex_test/js_test/js_testComplex.js +1 -1
  13. data/test/basic_complex_test/ruby_test/ruby_test.rb +10 -10
  14. data/test/binaryTree_test/js_test/js_test.js +1 -1
  15. data/test/binaryTree_test/ruby_test/ruby_test.rb +4 -4
  16. data/test/complex_test/js_test/js_test.js +3 -2
  17. data/test/ipv6_test/java_test/JavaTest.java +4 -6
  18. data/test/ipv6_test/js_test/js_test.js +1 -1
  19. data/test/signed_float_test/ruby_test/ruby_test.rb +36 -0
  20. data/test/signed_float_test/signed_float_test.rb +14 -0
  21. data/test/signed_int_test/java_test/JavaTest.java +83 -0
  22. data/test/signed_int_test/js_test/js_test.js +55 -0
  23. data/test/signed_int_test/ruby_test/ruby_test.rb +37 -0
  24. data/test/signed_int_test/signed_int_test.rb +15 -0
  25. data/test/unify_test/unify_test.rb +24 -13
  26. metadata +14 -38
  27. data/test/basic_complex_test/java_test/test_babel.java +0 -368
  28. data/test/basic_complex_test/js_test/test_babel.js +0 -523
  29. data/test/basic_complex_test/ruby_test/test_babel.rb +0 -368
  30. data/test/binaryTree_test/java_test/test_binaryTree.java +0 -301
  31. data/test/binaryTree_test/js_test/test_binaryTree.js +0 -447
  32. data/test/binaryTree_test/ruby_test/test_binaryTree.rb +0 -303
  33. data/test/complex_test/java_test/test_complex.java +0 -331
  34. data/test/complex_test/js_test/test_complex.js +0 -478
  35. data/test/complex_test/ruby_test/test_complex.rb +0 -330
  36. data/test/ipv6_test/java_test/junit.jar +0 -0
  37. data/test/ipv6_test/java_test/test_ipv6.java +0 -270
  38. data/test/ipv6_test/js_test/test_ipv6.js +0 -409
  39. data/test/ipv6_test/ruby_test/test_ipv6.rb +0 -267
  40. data/test/unify_test/java_test/test_unify.java +0 -171
  41. data/test/unify_test/js_test/js_test.js +0 -56
  42. data/test/unify_test/js_test/test_unify.js +0 -326
  43. data/test/unify_test/ruby_test/ruby_test.rb +0 -35
  44. data/test/unify_test/ruby_test/test_unify.rb +0 -187
data/.gitignore CHANGED
@@ -15,3 +15,33 @@ spec/reports
15
15
  test/tmp
16
16
  test/version_tmp
17
17
  tmp
18
+ *.class
19
+ test/basic_complex_test/java_test/bin.babel
20
+ test/basic_complex_test/js_test/bin.babel.js
21
+ test/basic_complex_test/ruby_test/bin.babel.rb
22
+ test/binaryTree_test/java_test/bin.babel
23
+ test/binaryTree_test/js_test/bin.babel.js
24
+ test/binaryTree_test/ruby_test/bin.babel.rb
25
+ test/complex_test/java_test/bin.babel
26
+ test/complex_test/js_test/bin.babel.js
27
+ test/complex_test/ruby_test/bin.babel.rb
28
+ test/ipv6_test/java_test/bin.babel
29
+ test/ipv6_test/js_test/bin.babel.js
30
+ test/ipv6_test/ruby_test/bin.babel.rb
31
+ test/basic_complex_test/ruby_test/test_babel.rb
32
+ test/binaryTree_test/ruby_test/test_binaryTree.rb
33
+ test/complex_test/ruby_test/test_complex.rb
34
+ test/ipv6_test/ruby_test/test_ipv6.rb
35
+ test/signed_int_test/ruby_test/test_signed_int.rb
36
+ test/signed_int_test/js_test/bin.babel.js
37
+ test/signed_int_test/ruby_test/bin.babel.rb
38
+ test/basic_complex_test/java_test/test_babel.java
39
+ test/binaryTree_test/java_test/test_binaryTree.java
40
+ test/complex_test/java_test/test_complex.java
41
+ test/ipv6_test/java_test/test_ipv6.java
42
+ test/signed_int_test/java_test/test_signed_int.java
43
+ test/basic_complex_test/js_test/test_babel.js
44
+ test/binaryTree_test/js_test/test_binaryTree.js
45
+ test/complex_test/js_test/test_complex.js
46
+ test/ipv6_test/js_test/test_ipv6.js
47
+ test/signed_int_test/js_test/test_signed_int.js
data/README.md CHANGED
@@ -94,6 +94,63 @@ c2.deserialize(new BabelDataReader(ca));
94
94
  console.log(c2);
95
95
  ```
96
96
 
97
+ ### Versioning
98
+ ```ruby
99
+ require 'divine'
100
+
101
+ struct 'Foobar' do # Default version is '1'
102
+ int8 :foo
103
+ end
104
+
105
+
106
+ struct 'Foobar', version: 2 do
107
+ int8 :foo
108
+ int8 :bar # We added a new field named 'bar' in version 2 of Foobar
109
+ end
110
+
111
+ Divine::CodeGenerator.new.generate(:ruby, file: 'test_babel.rb')
112
+ Divine::CodeGenerator.new.generate(:java, file: 'test_babel.java')
113
+ ```
114
+
115
+ There are some basic rules regarding versioning of structs
116
+
117
+ * A versioned struct defines all fields you want to serialize/deserialize
118
+ * You can delete and add fields as you whish between versions
119
+ * You are not allowed to change type of a defined variable between versions
120
+ * You cannot have a bigger version number than 255
121
+ * The class that represents the struct also defines a 'struct_version' that keeps the current version of the struct
122
+
123
+
124
+ ### Freezing
125
+ When starting to use generated code in production, the defined structs cannot be changed without breaking everything (unless you really know what you're doing).
126
+ To prevent that you change a struct by accident, you can 'freeze' a struct. The easiest way to get started is to add a empty _freeze_ field to the struct, like below
127
+
128
+ ```ruby
129
+ require 'divine'
130
+
131
+ struct 'Foobar', freeze: '' do
132
+ int8 :foo
133
+ int8 :bar
134
+ end
135
+
136
+ Divine::CodeGenerator.new.generate(:ruby, file: 'test_babel.rb')
137
+ ```
138
+
139
+ The compiler will throw a runtime exception saying that the MD5 sum differs. Take the MD5 sum from this exception and put into the _freeze_ field as below
140
+
141
+ ```ruby
142
+ require 'divine'
143
+
144
+ struct 'Foobar', freeze: '3e59aa582d3137a2d0cdba174e5aa6b18beb649b' do
145
+ int8 :foo
146
+ int8 :bar
147
+ end
148
+
149
+ Divine::CodeGenerator.new.generate(:ruby, file: 'test_babel.rb')
150
+ ```
151
+
152
+ It is now not possible to alter _Foobar_ by accident. If you make a change to the struct, you will also need to provide a correct MD5 sum.
153
+
97
154
 
98
155
  ## Installation
99
156
 
data/Rakefile CHANGED
@@ -1,27 +1,48 @@
1
1
  require "bundler/gem_tasks"
2
2
 
3
+
4
+ task :default => ["test:all"]
5
+
6
+
3
7
  namespace :test do
8
+ desc "Run all tests"
9
+ task :all => [:ruby, :js, :java, :unify] # Make sure that the unify test be the last one in the list.
4
10
 
5
11
  desc "Run Ruby code test suite"
6
- task :ruby => :environment do
7
- ruby "test/basic_complex_test/ruby_test/ruby_test.rb"
8
- ruby "test/binaryTree_test/ruby_test/ruby_test.rb"
12
+ task :ruby do
13
+ generate_source('ruby')
14
+ ruby "test/signed_int_test/ruby_test/ruby_test.rb"
9
15
  ruby "test/ipv6_test/ruby_test/ruby_test.rb"
10
16
  ruby "test/complex_test/ruby_test/ruby_test.rb"
17
+ ruby "test/binaryTree_test/ruby_test/ruby_test.rb"
18
+ ruby "test/basic_complex_test/ruby_test/ruby_test.rb"
19
+
20
+ system("find . -name 'test*.rb' | xargs rm") # Remove generated source code files
11
21
  end
12
22
 
13
23
 
14
24
  desc "Run JS code test suite"
15
- task :js => :environment do
16
- system("node test/basic_complex_test/js_test/js_testBasic.js")
17
- system("node test/basic_complex_test/js_test/js_testComplex.js")
18
- system("node test/binaryTree_test/js_test/js_test.js")
25
+ task :js do
26
+ generate_source('js')
27
+ system("node test/signed_int_test/js_test/js_test.js")
19
28
  system("node test/ipv6_test/js_test/js_test.js")
20
29
  system("node test/complex_test/js_test/js_test.js")
30
+ system("node test/binaryTree_test/js_test/js_test.js")
31
+ system("node test/basic_complex_test/js_test/js_testBasic.js")
32
+ system("node test/basic_complex_test/js_test/js_testComplex.js")
33
+
34
+ system("find . -name 'test*.js' | xargs rm") # Remove generated source code files
21
35
  end
22
36
 
23
37
  desc "Run java code test suite"
24
- task :java => :environment do
38
+ task :java do
39
+ generate_source('java')
40
+ #system("javac -cp test/java_lib/junit.jar: test/signed_float_test/java_test/*.java")
41
+ #system("java -cp test/signed_float_test/java_test/:test/java_lib/junit.jar:. org.junit.runner.JUnitCore JavaTest")
42
+
43
+ system("javac -cp test/java_lib/junit.jar: test/signed_int_test/java_test/*.java")
44
+ system("java -cp test/signed_int_test/java_test/:test/java_lib/junit.jar:. org.junit.runner.JUnitCore JavaTest")
45
+
25
46
  system("javac -cp test/java_lib/junit.jar: test/ipv6_test/java_test/*.java")
26
47
  system("java -cp test/ipv6_test/java_test/:test/java_lib/junit.jar:. org.junit.runner.JUnitCore JavaTest")
27
48
 
@@ -33,18 +54,24 @@ namespace :test do
33
54
 
34
55
  system("javac -cp test/java_lib/junit.jar: test/basic_complex_test/java_test/*.java")
35
56
  system("java -cp test/basic_complex_test/java_test/:test/java_lib/junit.jar:. org.junit.runner.JUnitCore JavaTest")
36
- end
37
-
38
57
 
39
- desc "Generate Source Code"
40
- task :environment do
41
- puts "Divine Version #{Divine::VERSION}"
42
- lang = Rake.application.top_level_tasks[0].match(/:(.*)/)[1]
58
+ system("find . -name 'test*.java' | xargs rm") # Remove generated source code files
59
+ system("find . -name '*.class' | xargs rm") # Remove generated .class files
60
+ end
43
61
 
44
- ruby "test/basic_complex_test/basic_complex_test.rb #{lang}"
45
- ruby "test/ipv6_test/ipv6_test.rb #{lang}"
46
- ruby "test/complex_test/complex_test.rb #{lang}"
47
- ruby "test/binaryTree_test/binaryTree_test.rb #{lang}"
62
+ desc "Unify test to compare the produced binary files. Prerequisite Other tests must be run first to generate bin files to be compared"
63
+ task :unify do
64
+ ruby "test/unify_test/unify_test.rb"
65
+ system("find . -name 'bin.babel*' | xargs rm") # Remove generated bin files
48
66
  end
49
67
  end
50
68
 
69
+
70
+ def generate_source(lang)
71
+ puts "Divine Version #{Divine::VERSION}"
72
+ ruby "test/signed_int_test/signed_int_test.rb #{lang}"
73
+ ruby "test/ipv6_test/ipv6_test.rb #{lang}"
74
+ ruby "test/complex_test/complex_test.rb #{lang}"
75
+ ruby "test/binaryTree_test/binaryTree_test.rb #{lang}"
76
+ ruby "test/basic_complex_test/basic_complex_test.rb #{lang}"
77
+ end
@@ -14,9 +14,9 @@ end
14
14
  #
15
15
  # Toplevel definition of struct'
16
16
  #
17
- def struct(name, version=1, &block)
18
- puts "struct #{name}"
19
- builder = Divine::StructBuilder.new(name, version) # Defined in divine/dsl.rb
17
+ def struct(name, properties=nil, &block)
18
+ #puts "struct #{name}"
19
+ builder = Divine::StructBuilder.new(name, properties) # Defined in divine/dsl.rb
20
20
  ::Docile.dsl_eval(builder, &block)
21
21
  builder
22
22
  end
@@ -1,6 +1,29 @@
1
+ require 'digest/sha1'
2
+ require 'fileutils'
3
+
4
+
1
5
  module Divine
2
6
  $language_generators = {}
3
7
 
8
+ class StructHandler
9
+ attr_reader :name, :latest_version, :structs
10
+
11
+ def initialize(structs)
12
+ @structs = structs
13
+ @name = structs.first.name
14
+ @latest_version = structs.last.version
15
+ @field_hash = structs.map(&:fields).flatten.group_by(&:name)
16
+ end
17
+
18
+ def field(name)
19
+ @field_hash[name]
20
+ end
21
+
22
+ def field_names
23
+ @field_hash.keys.sort
24
+ end
25
+ end
26
+
4
27
  class BabelHelperMethods
5
28
  def format_src(first_indent, following_indent, is, spc = " ")
6
29
  indent = "#{spc * first_indent}"
@@ -27,40 +50,115 @@ module Divine
27
50
  ss = str.map(&:to_s).join("_").split(/_/).flatten
28
51
  "#{ss.first.downcase}#{ss[1..-1].map(&:downcase).map(&:capitalize).join}"
29
52
  end
53
+
54
+ def get_header_comment_text
55
+ return [
56
+ "",
57
+ "-- DO NOT EDIT THIS FILE --", "",
58
+ "This file was generated by Divine #{Divine::VERSION} (#{Time.now.to_s})", "",
59
+ "-- DO NOT EDIT THIS FILE --", ""
60
+ ]
61
+ end
62
+
63
+ #
64
+ # Sanity check the different revisions of the struct
65
+ #
66
+ def sanity_check(ss)
67
+ vos = ss.sort do |x,y|
68
+ v1,v2 = [x,y].map(&:version)
69
+ v1 <=> v2
70
+ end
71
+
72
+ # Check version numbers
73
+ unless vos.map(&:version).uniq.size == ss.size
74
+ raise "Inconsistent version numbering for '#{ss.first.name}': #{vos.map(&:version).join(', ')}"
75
+ end
76
+
77
+ # Check that we don't have multiple variables with same name
78
+ vos.each do |s|
79
+ names = s.fields.group_by(&:name)
80
+ names.each_pair do |k, v|
81
+ raise "Multiple fields with same name '#{k}' is defined in '#{s.name}', version #{s.version}" unless v.size == 1
82
+ end
83
+ end
84
+
85
+ # Check types between versions
86
+ check_field_types(vos)
87
+
88
+ # Check for changed definitions
89
+ check_freezed_structs(vos)
90
+
91
+ # Return them ordered by version
92
+ vos
93
+ end
94
+
95
+ private
96
+ #
97
+ # Check types through all versions, we're not allowed to change the type of a variable between different versions
98
+ #
99
+ def check_field_types(ss)
100
+ types = {}
101
+ ss.each do |x|
102
+ x.fields.each do |f|
103
+ if types[f.name]
104
+ unless same_type(types[f.name], x, f.name)
105
+ raise "Cannot change the field type for struct #{x.name}.#{f.name} between version #{types[f.name].version} and #{x.version}"
106
+ end
107
+ else
108
+ types[f.name] = x
109
+ end
110
+ end
111
+ end
112
+ end
113
+
114
+ def same_type(t1, t2, field)
115
+ t1.get_field(field).referenced_types == t2.get_field(field).referenced_types
116
+ end
117
+
118
+ def check_freezed_structs(ss)
119
+ ss.each do |s|
120
+ if s.freezed?
121
+ sig = calculate_signature(s)
122
+ unless sig == s.freeze_signature
123
+ raise "Struct '#{s.name}', version #{s.version} has changed! Got signature '#{sig}', expected '#{s.freeze_signature}'"
124
+ end
125
+ end
126
+ end
127
+ end
128
+
129
+ def calculate_signature(s)
130
+ str = s.fields.map do |f|
131
+ "#{f.name}:#{f.referenced_types.inspect}"
132
+ end.join(",")
133
+ Digest::SHA1.hexdigest("#{s.name}:#{s.version},#{str}")
134
+ end
30
135
  end
31
136
 
137
+
138
+
32
139
  class CodeGenerator
33
140
  def generate(target, opts)
34
141
  gen = $language_generators[target.to_sym]
35
- raise "Unknown taget language: #{target}" unless gen
142
+ raise "Unknown target language: #{target}" unless gen
36
143
  puts "Generating code for #{target}"
37
144
  src = gen.generate_code($all_structs, opts)
38
- target_dir = getTargetDir(opts[:target_dir])
145
+ target_dir = (opts[:target_dir] || ".") + "/"
39
146
 
40
147
  if opts[:package]
41
- path = target_dir + opts[:package]
42
- Dir.mkdir(path) unless File.exists?(path)
148
+ path = target_dir + opts[:package].gsub(/\./, "/")
149
+ FileUtils.mkdir_p(path) unless File.exists?(path)
43
150
  for cls in src
44
151
  file_name = path+"/"+cls[:file]
45
152
  writeFile(file_name, cls[:src])
46
153
  end
47
154
  elsif opts[:file]
155
+ FileUtils.mkdir_p(target_dir) unless File.exists?(target_dir)
48
156
  path = target_dir + opts[:file]
49
157
  writeFile(path, src[0][:src])
50
158
  else
51
159
  puts src
52
160
  end
53
161
  end
54
-
55
- def getTargetDir(dir)
56
- # check if the path is relative or absolute if exist
57
- if dir && File.directory?(dir)
58
- return dir + "/"
59
- elsif dir && File.directory?(Dir.pwd + "/" + dir)
60
- return Dir.pwd + dir + "/"
61
- end
62
- ""
63
- end
64
162
 
65
163
  def writeFile(path, content)
66
164
  File.open(path, 'w+') do |f|
@@ -1,6 +1,12 @@
1
1
  module Divine
2
2
 
3
3
  class JavaHelperMethods < BabelHelperMethods
4
+ def get_header_comment
5
+ get_header_comment_text.map do |s|
6
+ "// #{s}"
7
+ end.join("\n")
8
+ end
9
+
4
10
  def java_base_class_template_str
5
11
  <<EOS
6
12
  abstract class BabelBase <%= toplevel_class %> {
@@ -33,6 +39,14 @@ abstract class BabelBase <%= toplevel_class %> {
33
39
  return (data.read() << 24) | readInt24(data);
34
40
  }
35
41
 
42
+ protected int readSint32(ByteArrayInputStream data) {
43
+ return (data.read() << 24) | readInt24(data);
44
+ }
45
+
46
+ protected long readSint64(ByteArrayInputStream data) {
47
+ return (readInt32(data) << 32) | (readInt32(data) & 0xFFFFFFFFL);
48
+ }
49
+
36
50
  protected boolean readBool(ByteArrayInputStream data) {
37
51
  return readInt8(data) == 1;
38
52
  }
@@ -135,6 +149,26 @@ abstract class BabelBase <%= toplevel_class %> {
135
149
  writeInt24((int) (v & 0xFFFFFF), out);
136
150
  }
137
151
 
152
+ protected void writeSint32(int v, ByteArrayOutputStream out) {
153
+ if (v > Integer.MAX_VALUE) { // Max 2.147.483.647
154
+ raiseError("Too large sInt32 number: " + v + ", Max = " + Integer.MAX_VALUE);
155
+ }else if(v < Integer.MIN_VALUE){ // Min -2.147.483.648
156
+ raiseError("Too small sInt32 number: " + v + ", Min = " + Integer.MIN_VALUE);
157
+ }
158
+ writeInt8((int) ((v >> 24) & 0xFF), out);
159
+ writeInt24((int) (v & 0xFFFFFF), out);
160
+ }
161
+
162
+ protected void writeSint64(long v, ByteArrayOutputStream out) {
163
+ if (v > Long.MAX_VALUE) { // Max 9,223,372,036,854,775,807
164
+ raiseError("Too large sInt64 number: " + v + ", Max = " + Integer.MAX_VALUE);
165
+ }else if(v < Long.MIN_VALUE){ // Min -9,223,372,036,854,775,807
166
+ raiseError("Too small sInt64 number: " + v + ", Min = " + Integer.MIN_VALUE);
167
+ }
168
+ writeInt32(((v >> 32) & 0xFFFFFFFFL), out);
169
+ writeInt32( (v & 0xFFFFFFFFL) , out);
170
+ }
171
+
138
172
  protected void writeBool(boolean v, ByteArrayOutputStream out) {
139
173
  writeInt8(v ? 1 : 0, out);
140
174
  }
@@ -242,43 +276,90 @@ abstract class BabelBase <%= toplevel_class %> {
242
276
  EOS
243
277
  end
244
278
 
245
- def java_class_template_str
246
- <<EOS2
247
- class <%= c.name %> extends BabelBase {
248
- <% unless c.fields.empty? %>
249
- <% c.fields.each do |f| %>
250
- public <%= this.java_get_type_declaration(f) %> <%= f.name %> = <%= this.java_get_empty_declaration(f) %>;
251
- <% end %>
252
- <% end %>
253
-
254
- @Override
255
- void serializeInternal(ByteArrayOutputStream baos) throws IOException {
256
- <% c.simple_fields.each do |f| %>
257
- <%= this.camelize("write", f.type) %>(this.<%= f.name %>, baos);
258
- <% end %>
259
- <% c.complex_fields.each do |f| %>
260
- <%= this.java_serialize_complex f %>
261
- <% end %>
262
- }
279
+ def java_class_template(sh)
280
+ code = [
281
+ "class #{sh.name} extends BabelBase {",
282
+ :indent,
283
+ "",
284
+
285
+ # PROPERTIES
286
+ "public int struct_version = #{sh.latest_version};",
287
+ sh.field_names.map do |fn|
288
+ f = sh.field(fn).last
289
+ "public #{java_get_type_declaration(f)} #{fn} = #{java_get_empty_declaration(f)};"
290
+ end, "",
291
+
263
292
 
264
- @Override
265
- public void deserialize(ByteArrayInputStream bais) throws IOException {
266
- <% c.simple_fields.each do |f| %>
267
- this.<%= f.name %> = <%= this.camelize("read", f.type) %>(bais);
268
- <% end %>
269
- <% c.complex_fields.each do |f| %>
270
- <%= this.java_deserialize_complex f %>
271
- <% end %>
272
- }
273
- }
274
- EOS2
293
+ # SERiALIZE INTERNAL
294
+ "@Override",
295
+ "void serializeInternal(ByteArrayOutputStream baos) throws IOException {",
296
+ :indent,
297
+ "writeInt8(this.struct_version, baos);",
298
+ sh.structs.map do |s|
299
+ [
300
+ "if(this.struct_version == #{s.version}) {",
301
+ :indent,
302
+ s.simple_fields.map do |f|
303
+ "#{camelize("write", f.type)}(this.#{f.name}, baos);"
304
+ end,
305
+ s.complex_fields.map do |f|
306
+ [
307
+ "", "// Serialize #{f.type} '#{f.name}'",
308
+ java_serialize_internal("this.#{f.name}", f.referenced_types)
309
+ ]
310
+ end,
311
+ "return;",
312
+ :deindent,
313
+ "}", ""
314
+ ]
315
+ end, "",
316
+ "throw new UnsupportedOperationException(\"Unsupported version \" + this.struct_version + \" for type '#{sh.name}'\");",
317
+ :deindent,
318
+ "}", "",
319
+
320
+
321
+ # DESERIALIZE
322
+ "@Override",
323
+ "public void deserialize(ByteArrayInputStream bais) throws IOException {",
324
+ :indent,
325
+ "this.struct_version = readInt8(bais);",
326
+ sh.structs.map do |s|
327
+ [
328
+ "if(this.struct_version == #{s.version}) {",
329
+ :indent,
330
+ s.simple_fields.map do |f|
331
+ "this.#{f.name} = #{camelize("read", f.type)}(bais);"
332
+ end,
333
+ s.complex_fields.map do |f|
334
+ [
335
+ "", "// Read #{f.type} '#{f.name}'",
336
+ java_deserialize_internal("this.#{f.name}", f.referenced_types)
337
+ ]
338
+ end,
339
+ "return;",
340
+ :deindent,
341
+ "}"
342
+ ]
343
+ end, "",
344
+ "throw new UnsupportedOperationException(\"Unsupported version \" + this.struct_version + \" for type '#{sh.name}'\");",
345
+ :deindent,
346
+ "}", "",
347
+
348
+
349
+ # END OF CLASS
350
+ :deindent,
351
+ "}"
352
+ ]
353
+
354
+ format_src(3, 3, code)
275
355
  end
356
+
276
357
 
277
358
  def java_get_empty_declaration(types, is_reference_type = false)
278
359
  if types.respond_to? :referenced_types
279
360
  java_get_empty_declaration(types.referenced_types)
280
361
 
281
- elsif types.respond_to? :first
362
+ elsif types.respond_to?(:first) && types.size > 1
282
363
  case types.first
283
364
  when :list
284
365
  "new #{java_get_type_declaration(types, true)}()"
@@ -288,13 +369,16 @@ EOS2
288
369
  raise "Missing empty declaration for #{types}"
289
370
  end
290
371
 
372
+ elsif types.respond_to?(:first) && types.size == 1
373
+ java_get_empty_declaration(types.first, is_reference_type)
374
+
291
375
  else
292
376
  case types
293
377
  when :binary, :short_binary
294
378
  is_reference_type ? "new Byte[0]" : "new byte[0]"
295
- when :int8, :int16
379
+ when :int8, :int16, :sint32
296
380
  "0"
297
- when :int32
381
+ when :int32, :sint64
298
382
  "0L"
299
383
  when :string, :ip_number
300
384
  "\"\""
@@ -312,7 +396,7 @@ EOS2
312
396
  if types.respond_to? :referenced_types
313
397
  java_get_type_declaration(types.referenced_types, is_reference_type)
314
398
 
315
- elsif types.respond_to? :first
399
+ elsif types.respond_to?(:first) && types.size > 1
316
400
  case types.first
317
401
  when :list
318
402
  subtypes = java_get_type_declaration(types[1], true)
@@ -324,14 +408,17 @@ EOS2
324
408
  else
325
409
  raise "Missing serialization for #{types}"
326
410
  end
411
+
412
+ elsif types.respond_to?(:first) && types.size == 1
413
+ java_get_type_declaration(types.first, is_reference_type)
327
414
 
328
415
  else
329
416
  case types
330
417
  when :binary, :short_binary
331
418
  is_reference_type ? "Byte[]" : "byte[]"
332
- when :int8, :int16
419
+ when :int8, :int16, :sint32
333
420
  is_reference_type ? "Integer" : "int"
334
- when :int32
421
+ when :int32, :sint64
335
422
  is_reference_type ? "Long" : "long"
336
423
  when :string, :ip_number
337
424
  "String"
@@ -345,15 +432,6 @@ EOS2
345
432
  end
346
433
  end
347
434
 
348
- def java_serialize_complex(field)
349
- types = field.referenced_types
350
- as = [
351
- "// Serialize #{field.type} '#{field.name}'",
352
- java_serialize_internal(field.name, types)
353
- ]
354
- format_src(2, 1, as, "\t")
355
- end
356
-
357
435
  def java_serialize_internal(var, types)
358
436
  if types.respond_to? :first
359
437
  case types.first
@@ -398,15 +476,6 @@ EOS2
398
476
  end
399
477
  end
400
478
 
401
- def java_deserialize_complex(field)
402
- types = field.referenced_types
403
- as = [
404
- "// Deserialize #{field.type} '#{field.name}'",
405
- java_deserialize_internal("this.#{field.name}", types)
406
- ]
407
- format_src(2, 1, as, "\t")
408
- end
409
-
410
479
  def java_deserialize_internal(var, types)
411
480
  if types.respond_to? :first
412
481
  case types.first
@@ -465,16 +534,14 @@ EOS2
465
534
 
466
535
  class JavaGenerator < JavaHelperMethods
467
536
  def generate_code(structs, opts)
468
- pp opts
469
537
  $debug_java = true if opts[:debug]
470
538
  base_template = Erubis::Eruby.new(java_base_class_template_str)
471
- class_template = Erubis::Eruby.new(java_class_template_str)
472
539
  keys = structs.keys.sort
473
540
  src = keys.map do |k|
474
541
  ss = structs[k]
475
- # TODO: Should we merge different versions and deduce deprecated methods, warn for incompatible changes, etc?
476
- raise "Duplicate definitions of struct #{k}" if ss.size > 1
477
- class_template.result( c: ss.first, this: self )
542
+ # Check different aspects the the structs
543
+ vss = sanity_check(ss)
544
+ java_class_template(StructHandler.new(vss))
478
545
  end
479
546
 
480
547
  # User defined super class?
@@ -492,28 +559,25 @@ EOS2
492
559
  end
493
560
 
494
561
  def java_get_begin_module(opts)
495
- if opts[:package]
496
- [
497
- "package #{opts[:package]};\n",
498
- "import java.io.ByteArrayInputStream;",
499
- "import java.io.ByteArrayOutputStream;",
500
- "import java.io.IOException;",
501
- "import java.util.ArrayList;",
502
- "import java.util.HashMap;",
503
- "import java.util.regex.Pattern;",
504
- "import java.nio.charset.Charset;\n\n"
505
- ].join("\n")
506
- else
507
- [
508
- "import java.io.ByteArrayInputStream;",
509
- "import java.io.ByteArrayOutputStream;",
510
- "import java.io.IOException;",
511
- "import java.util.ArrayList;",
512
- "import java.util.HashMap;",
513
- "import java.util.regex.Pattern;",
514
- "import java.nio.charset.Charset;\n\n"
515
- ].join("\n")
516
- end
562
+ str = "#{get_header_comment}\n\n"
563
+ str << "package #{opts[:package]};" if opts[:package]
564
+ str << get_java_imports
565
+ str << "\n\n"
566
+ return str
567
+ end
568
+
569
+ def get_java_imports
570
+ [
571
+ "java.io.ByteArrayInputStream",
572
+ "java.io.ByteArrayOutputStream",
573
+ "java.io.IOException",
574
+ "java.util.ArrayList",
575
+ "java.util.HashMap",
576
+ "java.util.regex.Pattern",
577
+ "java.nio.charset.Charset"
578
+ ].map do |i|
579
+ "import #{i};"
580
+ end.join("\n")
517
581
  end
518
582
  end
519
583