gson-class-generator 1.0.0

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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 31edff5ae4bbb5516c78e6de582d195b766171b7
4
+ data.tar.gz: 9a52c4c0cecb7523f40cc91e4081a6708e5752e6
5
+ SHA512:
6
+ metadata.gz: efb5fbdcbd12fe87cd37cd0a26e51c6438375d03bed6e4bdeddd3376408a9ef109df6f496a516e5485bc91f78d7fdd47785c0027e74af492c16cb6cc1bb01c24
7
+ data.tar.gz: 41b5df16e9ba959c8b3c62978763ac4fd8105ab6a69a3b7400a39552af664fc8c6d251eaeda79d424c353a273430673e6dc23149353ff057b5b7274018f9ac7f
@@ -0,0 +1,75 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require_relative '../lib/gson-class-generator.rb'
4
+
5
+ module GsonClassGeneratorExecutor
6
+
7
+ private
8
+
9
+ def self.abort_with_help(message = nil)
10
+ puts "#{message}\n\n" if message
11
+ puts <<-HELP
12
+ Usage:
13
+ #{File.basename(__FILE__)} [-gfscb] <json_file> <class_name> <output_dir>
14
+
15
+ Flags:
16
+ g - Generate getters.
17
+ s - Generate setters.
18
+ f - All fields are final, this will also generate a public no arguement constructor for all classes and the 's' flag is ignored.
19
+ c - A public constructor is generated for all the classes with all the fields as arguments.
20
+ b - All primative types are boxed.
21
+
22
+ Arguments:
23
+ json_file - The file you want to generate the model class from.
24
+ class_name - The top level class name, and therefore the name of the output file.
25
+ output_directory - The location to save the generated model class. If this is not provided the current directory will be used.
26
+ HELP
27
+ abort
28
+ end
29
+
30
+ def self.check_flags(args)
31
+ if (args.match(/^[gsfcb]+$/) === nil)
32
+ abort_with_help("Unknown argument: " + args.gsub(/[gsfcb]/, ""))
33
+ end
34
+ end
35
+
36
+ def self.get_opts(flags)
37
+ {
38
+ :getters => !(flags.match(/^-.*g.*/) === nil),
39
+ :setters => !(flags.match(/^-.*s.*/) === nil),
40
+ :final_fields => !(flags.match(/^-.*f.*/) === nil),
41
+ :field_constructor => !(flags.match(/^-.*c.*/) === nil),
42
+ :boxed_primatives => !(flags.match(/^-.*b.*/) === nil)
43
+ }
44
+ end
45
+
46
+ public
47
+
48
+ def self.run(args)
49
+
50
+ if (args.length == 0)
51
+ abort_with_help("No arguments given")
52
+ end
53
+
54
+ args_offset = 0
55
+ flags = args[0]
56
+ if (flags.match(/^-.+$/))
57
+ check_flags(flags.gsub(/^-/, ""))
58
+ args_offset += 1
59
+ else
60
+ flags = ""
61
+ end
62
+
63
+ opts = self.get_opts(flags)
64
+
65
+ json_file = args[args_offset + 0]
66
+ class_name = args[args_offset + 1]
67
+ output_directory = args.fetch(args_offset + 2, "")
68
+
69
+ outputted_filename = GSONClassGenerator::generate(json_file, class_name, output_directory, opts)
70
+
71
+ puts "Generated '#{outputted_filename}'"
72
+ end
73
+ end
74
+
75
+ GsonClassGeneratorExecutor::run(ARGV)
@@ -0,0 +1,426 @@
1
+ require 'json'
2
+
3
+ module GSONClassGenerator
4
+
5
+ public
6
+
7
+ def self.generate(json_filename, class_name, output_directory, opts = {})
8
+
9
+ begin
10
+ json = JSON.parse(File.read(File.expand_path(json_filename)))
11
+ rescue Exception => e
12
+ abort "Invalid JSON: #{e.message}"
13
+ end
14
+
15
+ java_class = parse(json_filename, class_name)
16
+ output_options = OutputOptions.new(opts)
17
+
18
+ output(java_class, output_directory, output_options)
19
+ end
20
+
21
+ private
22
+
23
+ GSON_SERIALIZED_NAME_IMPORT = "import com.google.gson.annotations.SerializedName;\n"
24
+
25
+ def self.parse(json_filename, class_name)
26
+ json = JSON.parse(File.read(File.expand_path(json_filename)))
27
+ JavaClass.new(class_name, json)
28
+ end
29
+
30
+ def self.output(java_class, output_directory, opts)
31
+
32
+ filename = File.join(File.expand_path(output_directory), "#{java_class.class_name}.java")
33
+
34
+ File.open(filename, "w") do |file|
35
+
36
+ file << GSON_SERIALIZED_NAME_IMPORT
37
+ file << "\n"
38
+ file << "\n"
39
+
40
+ java_class.write(file, opts)
41
+ end
42
+
43
+ filename
44
+ end
45
+
46
+ class OutputOptions
47
+
48
+ def initialize(opts)
49
+ @getters = opts.fetch("getters".to_sym, false) === true
50
+ @setters = opts.fetch("setters".to_sym, false) === true
51
+ @final_fields = opts.fetch("final_fields".to_sym, false) === true
52
+ @field_constructor = opts.fetch("field_constructor".to_sym, false) === true
53
+ @boxed_primatives = opts.fetch("boxed_primatives".to_sym, false) === true
54
+
55
+ # Can't have setters for final fields
56
+ @setters = false if @final_fields
57
+ end
58
+
59
+ def getters?
60
+ @getters
61
+ end
62
+
63
+ def setters?
64
+ @setters
65
+ end
66
+
67
+ def final_fields?
68
+ @final_fields
69
+ end
70
+
71
+ def field_constructor?
72
+ @field_constructor
73
+ end
74
+
75
+ def boxed_primatives?
76
+ @boxed_primatives
77
+ end
78
+ end
79
+
80
+ class JavaClass
81
+
82
+ public
83
+
84
+ attr_reader :class_name, :fields
85
+
86
+ def initialize(class_name, json, depth = 0)
87
+ @class_name = class_name
88
+ @nested_classes = Array.new
89
+ @fields = Array.new
90
+ @depth = depth
91
+
92
+ if (json.is_a?(Array))
93
+
94
+ # Pull out first object to examine what it looks like
95
+ first_json = json.first
96
+ fields = self.class.new(nil, first_json).fields
97
+
98
+ @fields += fields
99
+
100
+ else
101
+
102
+ json.each do |k, v|
103
+ field = JavaField.new(k, v)
104
+
105
+ if (field.custom_class?)
106
+ nested_name = self.class.get_nested_class_name(field)
107
+ nested_class = self.class.new(nested_name, v, depth + 1)
108
+ @nested_classes << nested_class
109
+ end
110
+
111
+ @fields << field
112
+ end
113
+
114
+ end
115
+ end
116
+
117
+ def write(stream, opts)
118
+ tabs = self.class.indentation(@depth)
119
+ field_tabs = self.class.indentation(@depth + 1)
120
+
121
+ stream << "#{tabs}#{class_definition}\n"
122
+ stream << "#{tabs}{\n"
123
+ write_field_declarations(stream, field_tabs, opts)
124
+ write_constructor(stream, field_tabs, opts)
125
+ write_field_accessors(stream, field_tabs, opts)
126
+ write_nested_classes(stream, opts)
127
+ stream << "#{tabs}}\n"
128
+ end
129
+
130
+ private
131
+
132
+ def self.get_nested_class_name(field)
133
+ class_name = field.java_class
134
+ if (field.is_array?)
135
+ # Remove 's' if array
136
+ class_name.chomp!("s")
137
+ end
138
+ class_name
139
+ end
140
+
141
+ def self.indentation(depth)
142
+ Array.new(depth, "\t").join
143
+ end
144
+
145
+ def class_definition
146
+ prefix = "public"
147
+ if (@depth > 0)
148
+ prefix += " static"
149
+ end
150
+ "#{prefix} final class #{@class_name}"
151
+ end
152
+
153
+ def write_field_declarations(stream, tabs, opts)
154
+ @fields.each do |f|
155
+ stream << "\n"
156
+ f.write_declaration(stream, tabs, opts)
157
+ end
158
+ end
159
+
160
+ def write_constructor(stream, tabs, opts)
161
+
162
+ constructor_tabs = tabs + "\t"
163
+
164
+ if (opts.field_constructor?)
165
+ parameters = @fields.map { |f| "final #{f.type_definition(opts)} #{f.name}" }
166
+ stream << "\n"
167
+ stream << tabs
168
+ stream << "public #{class_name}(#{parameters.join(', ')})\n"
169
+ stream << tabs
170
+ stream << "{\n"
171
+ @fields.each do |f|
172
+ f.write_constructor_assignment(stream, constructor_tabs, opts)
173
+ end
174
+ stream << tabs
175
+ stream << "}\n"
176
+ end
177
+ if (opts.final_fields?)
178
+
179
+ stream << "\n"
180
+ stream << tabs
181
+ stream << "public #{class_name}()\n"
182
+ stream << tabs
183
+ stream << "{\n"
184
+ if (opts.field_constructor?)
185
+ arguments = @fields.map { |f| f.default_initalized_object(opts) }
186
+ # Call public constructor instead
187
+ stream << constructor_tabs
188
+ stream << "this(#{arguments.join(', ')});\n"
189
+ else
190
+ @fields.each do |f|
191
+ f.write_default_constructor_assignment(stream, constructor_tabs, opts)
192
+ end
193
+ end
194
+ stream << tabs
195
+ stream << "}\n"
196
+ end
197
+ end
198
+
199
+ def write_field_accessors(stream, tabs, opts)
200
+ @fields.each do |f|
201
+ f.write_accessors(stream, tabs, opts)
202
+ end
203
+ end
204
+
205
+ def write_nested_classes(stream, opts)
206
+ @nested_classes.each do |nested_class|
207
+ stream << "\n"
208
+ nested_class.write(stream, opts)
209
+ end
210
+ end
211
+ end
212
+
213
+ class JavaField
214
+
215
+ private
216
+
217
+ JAVA_TYPES = {
218
+ FalseClass => "boolean",
219
+ TrueClass => "boolean",
220
+ Integer => "int",
221
+ Float => "float",
222
+ String => "String",
223
+ NilClass => "Object"
224
+ }
225
+
226
+ JAVA_TYPES_BOXED = {
227
+ "boolean" => "Boolean",
228
+ "int" => "Integer",
229
+ "float" => "Float"
230
+ }
231
+
232
+ JAVA_DEFAULT_INITIALIZED_PRIMATIVES = {
233
+ "boolean" => "false",
234
+ "int" => "0",
235
+ "float" => "0.0f",
236
+ }
237
+
238
+ JAVA_KEYWORDS = ["public", "protected", "private", "abstract", "final", "static", "strictfp", "transient", "volatile", "class", "enum", "interface", "extends", "implements", "void", "while", "do", "for", "if", "switch", "case", "default", "break", "continue", "return", "synchronized", "try", "catch", "throw", "throws", "finally", "super", "this", "new", "false", "true", "null", "instanceof", "package", "import", "assert", "boolean", "byte", "char", "short", "int", "long", "float", "double", "const", "goto"]
239
+
240
+ public
241
+
242
+ attr_reader :name, :java_class
243
+
244
+ def initialize(key, value)
245
+ @array_dimens = self.class.array_dimens(value)
246
+ @json_key = key
247
+ @name = self.class.javaify_key(key, self.is_array?)
248
+ @java_class = self.class.class_for_value(key, value)
249
+ @custom_class = !JAVA_TYPES.value?(@java_class)
250
+ end
251
+
252
+ def is_array?
253
+ @array_dimens > 0
254
+ end
255
+
256
+ def custom_class?
257
+ @custom_class
258
+ end
259
+
260
+ def write_declaration(stream, tabs, opts)
261
+ stream << tabs
262
+ stream << serialized_name_definition
263
+ stream << tabs
264
+ stream << field_definition(opts)
265
+ end
266
+
267
+ def write_default_constructor_assignment(stream, tabs, opts)
268
+ stream << tabs
269
+ stream << "#{@name} = #{default_initalized_object(opts)};\n"
270
+ end
271
+
272
+ def write_constructor_assignment(stream, tabs, opts)
273
+ stream << tabs
274
+ stream << "this.#{@name} = #{@name};\n"
275
+ end
276
+
277
+ def write_accessors(stream, tabs, opts)
278
+ if (opts.getters?)
279
+ write_getter_definition(stream, tabs)
280
+ end
281
+ if (opts.setters?)
282
+ write_setter_definition(stream, tabs)
283
+ end
284
+ end
285
+
286
+ def type_definition(opts)
287
+ class_name = @java_class
288
+ if (opts.boxed_primatives?)
289
+ class_name = JAVA_TYPES_BOXED.fetch(class_name, @java_class)
290
+ end
291
+ array_def = Array.new(@array_dimens, "[]").join
292
+ "#{class_name}#{array_def}"
293
+ end
294
+
295
+ def default_initalized_object(opts)
296
+ if (opts.boxed_primatives?)
297
+ "null"
298
+ else
299
+ JAVA_DEFAULT_INITIALIZED_PRIMATIVES.fetch(@java_class, "null")
300
+ end
301
+ end
302
+
303
+ private
304
+
305
+ def serialized_name_definition
306
+ "@SerializedName(\"#{@json_key}\")\n"
307
+ end
308
+
309
+ def field_definition(opts)
310
+ definition = "private"
311
+ if (opts.final_fields?)
312
+ definition += " final"
313
+ end
314
+ type_definition = type_definition(opts)
315
+ "#{definition} #{type_definition} #{@name};\n"
316
+ end
317
+
318
+ def accessor_name
319
+ @name.gsub(/^(.)/) { $1.upcase }
320
+ end
321
+
322
+ def write_getter_definition(stream, tabs)
323
+ stream << "\n"
324
+ stream << tabs
325
+ stream << "public final #{@type_definition} get#{accessor_name}()\n"
326
+ stream << tabs
327
+ stream << "{\n"
328
+ stream << tabs
329
+ stream << "\treturn #{@name};\n"
330
+ stream << tabs
331
+ stream << "}\n"
332
+ end
333
+
334
+ def write_setter_definition(stream, tabs)
335
+ stream << "\n"
336
+ stream << tabs
337
+ stream << "public final void set#{accessor_name}(final #{@type_definition} #{@name})\n"
338
+ stream << tabs
339
+ stream << "{\n"
340
+ stream << tabs
341
+ stream << "\tthis.#{@name} = #{@name};\n"
342
+ stream << tabs
343
+ stream << "}\n"
344
+ end
345
+
346
+ # STATIC METHODS
347
+
348
+ def self.array_dimens(value)
349
+
350
+ dimens = 0
351
+
352
+ array = value
353
+ while (array.is_a?(Array))
354
+ dimens += 1
355
+ array = array.first
356
+ end
357
+
358
+ dimens
359
+ end
360
+
361
+ def self.class_for_value(key, value)
362
+
363
+ java_class = nil
364
+
365
+ if (value.is_a?(Hash))
366
+ # Use key as class name
367
+ java_class = key
368
+
369
+ # Clean up first character
370
+ java_class = java_class.gsub(/^[^\p{Pc}\p{Alpha}\p{Sc}]/, "")
371
+
372
+ # Clean up allowed characters
373
+ java_class = java_class.gsub(/[^\p{Pc}\p{Alnum}\p{Sc}]/, "")
374
+
375
+ # Upper case it
376
+ java_class = java_class.gsub(/^([\p{Pc}\p{Sc}]*)(\p{Alpha})/) { "#{$1}#{$2.upcase}" }
377
+
378
+ elsif (value.is_a?(Array))
379
+
380
+ # First check that the array is a native one
381
+ first_object = value.first
382
+
383
+ java_class = self.class_for_value(key, first_object)
384
+ else
385
+
386
+ JAVA_TYPES.each do |ruby_cls, java_cls|
387
+ if (value.is_a?(ruby_cls))
388
+ java_class = java_cls
389
+ break
390
+ end
391
+ end
392
+ end
393
+
394
+ abort "No class is able to be determined for JSON key #{key} (ruby class: #{value.class})" if !java_class
395
+
396
+ java_class
397
+ end
398
+
399
+ def self.javaify_key(json_key, is_array)
400
+ field_name = json_key
401
+
402
+ if (is_array)
403
+ if (!field_name.end_with?("s"))
404
+ # Append 's' if it doesn't have it
405
+ field_name += "s"
406
+ end
407
+ end
408
+
409
+ # Clean up first character
410
+ field_name = field_name.gsub(/^[^\p{Pc}\p{Alnum}\p{Sc}]+/, "")
411
+
412
+ # Clean up allowed characters
413
+ field_name = field_name.gsub(/[^\p{Pc}\p{Alnum}\p{Sc}]/, "_")
414
+
415
+ # Camel case underscores
416
+ field_name = field_name.gsub(/(?!^_)_+(.)/) { $1.upcase }
417
+
418
+ # Clean up field name in case its a Java keyword
419
+ if (!(JAVA_KEYWORDS.index(field_name) === nil))
420
+ field_name += "_"
421
+ end
422
+
423
+ field_name
424
+ end
425
+ end
426
+ end
metadata ADDED
@@ -0,0 +1,60 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: gson-class-generator
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Rich Hodgkins
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-02-01 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: json
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - '>='
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - '>='
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ description:
28
+ email: github@rhodgkins.co.uk
29
+ executables:
30
+ - gson-class-generator
31
+ extensions: []
32
+ extra_rdoc_files: []
33
+ files:
34
+ - lib/gson-class-generator.rb
35
+ - bin/gson-class-generator
36
+ homepage: https://github.com/rhodgkins/GSONClassGenerator
37
+ licenses:
38
+ - MIT
39
+ metadata: {}
40
+ post_install_message:
41
+ rdoc_options: []
42
+ require_paths:
43
+ - lib
44
+ required_ruby_version: !ruby/object:Gem::Requirement
45
+ requirements:
46
+ - - '>='
47
+ - !ruby/object:Gem::Version
48
+ version: '0'
49
+ required_rubygems_version: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - '>='
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ requirements: []
55
+ rubyforge_project:
56
+ rubygems_version: 2.0.6
57
+ signing_key:
58
+ specification_version: 4
59
+ summary: Ruby script generating JSON model objects in Java when using GSON
60
+ test_files: []