divine 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +17 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +23 -0
- data/README.md +122 -0
- data/Rakefile +1 -0
- data/divine.gemspec +22 -0
- data/lib/divine.rb +22 -0
- data/lib/divine/code_generators/code_generator.rb +49 -0
- data/lib/divine/code_generators/java.rb +414 -0
- data/lib/divine/code_generators/javascript.rb +512 -0
- data/lib/divine/code_generators/ruby.rb +360 -0
- data/lib/divine/dsl.rb +153 -0
- data/lib/divine/version.rb +3 -0
- metadata +90 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
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
|