gson-class-generator 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
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: []