divine 0.0.1
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.
- 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
|