dataMetaDom 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.
@@ -0,0 +1,1056 @@
1
+ $:.unshift(File.dirname(__FILE__)) unless $:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
2
+
3
+ require 'set'
4
+ require 'fileutils'
5
+ require 'dataMetaDom/help'
6
+ require 'dataMetaDom/field'
7
+ require 'dataMetaDom/util'
8
+ require 'erb'
9
+ require 'ostruct'
10
+
11
+ module DataMetaDom
12
+
13
+ =begin rdoc
14
+ Definition for generating Plain Old Java Objects (POJOs) and everything related that depends on JDK only
15
+ witout any other dependencies.
16
+
17
+ TODO this isn't a bad way, but beter use templating next time such as {ERB}[http://ruby-doc.org/stdlib-1.9.3/libdoc/erb/rdoc/ERB.html].
18
+
19
+ For command line details either check the new method's source or the README.rdoc file, the usage section.
20
+ =end
21
+ module PojoLexer
22
+ include DataMetaDom
23
+
24
+ =begin rdoc
25
+ Maps DataMeta DOM datatypes to the matching Java classes, for those that need to be imported.
26
+ The Java source generator will import these if they are used in the class.
27
+ =end
28
+ JAVA_IMPORTS = {
29
+ DATETIME => 'java.time.ZonedDateTime',
30
+ NUMERIC => 'java.math.BigDecimal'
31
+ }
32
+
33
+ =begin rdoc
34
+ DataMeta DOM aggregated field type spec mapped to matching Java class:
35
+ =end
36
+ AGGR_CLASSES = {
37
+ Field::SET => 'java.util.Set',
38
+ Field::LIST => 'java.util.List',
39
+ Field::DEQUE => 'java.util.LinkedList',
40
+ }
41
+
42
+ # Augment the class with Java specifics
43
+ class JavaRegExRoster < RegExRoster
44
+
45
+ # converts the registry to the java declarations for the class
46
+ def to_patterns
47
+ i_to_r.keys.map { |ix|
48
+ r = i_to_r[ix]
49
+ rx = r.r.to_s
50
+ %<#{INDENT}private static final java.util.regex.Pattern #{RegExRoster.ixToVarName(ix)} = // #{r.vars.to_a.sort.join(', ')}
51
+ #{INDENT*2}java.util.regex.Pattern.compile(#{rx.inspect});>
52
+ }.join("\n\n")
53
+ end
54
+
55
+ # converts the registry to the verification code for the verify() method
56
+ def to_verifications
57
+ result = (canned.keys.map { |r|
58
+ r = canned[r]
59
+ vs = r.vars.to_a.sort
60
+ vs.map { |v|
61
+ rx = r.r.to_s
62
+ %<#{INDENT*2}if(#{r.req? ? '' : "#{v} != null && "}!getCannedRegEx(#{rx.inspect}).matcher(#{v}).matches())
63
+ #{INDENT*3}throw new VerificationException("Variable \\"#{v}\\" == {{" + #{v} + "}} didn't match canned expression \\"#{rx}\\"" );>
64
+ }
65
+ }).flatten
66
+ (result << i_to_r.keys.map { |ix|
67
+ r = i_to_r[ix]
68
+ vs = r.vars.to_a.sort
69
+ rv = RegExRoster.ixToVarName(ix)
70
+ vs.map { |v|
71
+ %<#{INDENT*2}if(#{r.req? ? '' : "#{v} != null && "}!#{rv}.matcher(#{v}).matches())
72
+ #{INDENT*3}throw new VerificationException("Variable \\"#{v}\\" == {{" + #{v} + "}} didn't match custom expression {{" + #{rv} + "}}");>
73
+ }
74
+ }).flatten
75
+ result.join("\n")
76
+
77
+ end
78
+ end
79
+
80
+ =begin rdoc
81
+ Special import for special case -- the map
82
+ =end
83
+ MAP_IMPORT = 'java.util.Map'
84
+
85
+ # URL data type projection into Java
86
+ URL_CLASS = 'java.net.URL'
87
+
88
+ =begin rdoc
89
+ Field types for which Java primitivs can be used along with == equality.
90
+
91
+ Note that CHAR is not primitivable, we do not expect any
92
+ single character fields in our metadata that should be treated differently than multichar fields.
93
+
94
+ Deprecated. With the advent of the Verifiable interface, we must have all objects, no primitives.
95
+ =end
96
+ PRIMITIVABLE_TYPES = Set.new # [DataMetaDom::INT, BOOL, FLOAT]
97
+
98
+ =begin
99
+ Primitives that need to be converted to wrappers for aggregate types.
100
+ =end
101
+ PRIMS_TO_WRAP = {
102
+ :int => 'Integer',
103
+ :long => 'Long',
104
+ :boolean => 'Boolean',
105
+ :float => 'Float',
106
+ :double => 'Double',
107
+ }
108
+
109
+ # Methods to fetch primitives values:
110
+ def primValMethod(dt)
111
+ case dt.type
112
+ when INT
113
+ dt.length < 5 ? 'intValue' : 'longValue'
114
+ when FLOAT
115
+ dt.length < 5 ? 'floatValue' : 'doubleValue'
116
+ else
117
+ raise ArgumentError, %<Can't determine primitive value method for the data type: #{dt}>
118
+ end
119
+ end
120
+ =begin rdoc
121
+ Wraps type into <tt>com.google.common.base.Optional<></tt> if it is required
122
+ <tt>
123
+ def wrapOpt(isReq, t); isReq ? t : "Optional<#{t}>" end
124
+ </tt>
125
+
126
+ After a bit of thinking, decided not to employ the Optional idiom by Guava then of the JDK 8 for the following reasons:
127
+ * the pros don't look a clear winner vs the cons
128
+ * if an optional field is made non-optional, lots of refactoring would be needed that is hard to automate
129
+ * Java developers are used to deal with nulls, not so with Optionals
130
+ * it requires dragging another dependency - Guava with all the generated files.
131
+ * wrapping objects into Optional would create a lot of extra objects in the heap
132
+ potentially with long lifespan
133
+ =end
134
+ def wrapOpt(isReq, t); t end # - left the work done just in case
135
+
136
+ =begin rdoc
137
+ Renderer for the String type.
138
+ =end
139
+ TEXTUAL_TYPER = lambda{|t| 'String'}
140
+
141
+ =begin rdoc
142
+ A map from DataMeta DOM standard types to the lambdas that render correspondent Java types per Java syntax.
143
+
144
+ We used to render the primitives for the required types but the Verifiable interface made it impractical.
145
+ =end
146
+ JAVA_TYPES = {
147
+ DataMetaDom::INT => lambda{ |t|
148
+ len = t.length
149
+ case
150
+ when len <= 4; 'Integer' #req ? 'int' : 'Integer'
151
+ when len <=8; 'Long' # req ? 'long' : 'Long'
152
+ else; raise "Invalid integer length #{len}"
153
+ end
154
+ },
155
+ STRING => TEXTUAL_TYPER,
156
+ DATETIME => lambda{|t| 'ZonedDateTime'},
157
+ BOOL => lambda{|t| 'Boolean'}, # req ? 'boolean' : 'Boolean'},
158
+ CHAR => TEXTUAL_TYPER,
159
+ FLOAT => lambda{|t|
160
+ len = t.length
161
+ case
162
+ when len <= 4; 'Float' # req ? 'float' : 'Float'
163
+ when len <=8; 'Double' #req ? 'double' : 'Double'
164
+ else; raise "Invalid float length #{len}"
165
+ end
166
+ },
167
+ RAW => lambda{|t| 'byte[]'},
168
+ URL => lambda{|t| URL_CLASS},
169
+ NUMERIC => lambda{|t| 'BigDecimal'}
170
+ }
171
+
172
+ =begin rdoc
173
+ A hash from a character sequences to their Java escapes. Used by escapeJava method which is deprecated, see the docs,
174
+ there are better ways to escape Java string in Ruby.
175
+ =end
176
+ JAVA_ESCAPE_HASH = { '\\'.to_sym => '\\\\',
177
+ "\n".to_sym => '\\n',
178
+ '"'.to_sym => '\\"',
179
+ "\t".to_sym => '\\t',
180
+ }
181
+
182
+ =begin rdoc
183
+ Maximum size of a Mapping (Map), rather aribtrary choice, not backed by any big idea.
184
+ =end
185
+ MAX_MAPPING_SIZE = 10000
186
+
187
+ class << self
188
+ =begin rdoc
189
+ Figures out type adjusted for aggregates and maps.
190
+ =end
191
+ def aggrType(aggr, trg, rawType, javaPackage)
192
+ if aggr
193
+ k = rawType.to_sym
194
+ subType = PRIMS_TO_WRAP.has_key?(k) ? PRIMS_TO_WRAP[k] : rawType
195
+ "#{aggr}<#{subType}>"
196
+ elsif trg
197
+ k = rawType.to_sym
198
+ srcType = PRIMS_TO_WRAP.has_key?(k) ? PRIMS_TO_WRAP[k] : rawType
199
+ typeRenderer = JAVA_TYPES[trg.type]
200
+ rawTrg = typeRenderer ? typeRenderer.call(trg) : condenseType(trg.type, javaPackage)
201
+ k = rawTrg.to_sym
202
+ trgType = PRIMS_TO_WRAP.has_key?(k) ? PRIMS_TO_WRAP[k] : rawTrg
203
+ "Map<#{srcType}, #{trgType}>"
204
+ else
205
+ rawType
206
+ end
207
+ end
208
+
209
+ end
210
+
211
+ =begin rdoc
212
+ Given the property +docs+ of Documentable, return the JAVA_DOC_TARGET if it is present,
213
+ PLAIN_DOC_TARGET otherwise. Returns empty string if the argument is nil.
214
+ =end
215
+ def javaDocs(docs)
216
+ return '' unless docs
217
+ case
218
+ when docs[JAVA_DOC_TARGET]
219
+ docs[JAVA_DOC_TARGET].text
220
+ when docs[PLAIN_DOC_TARGET]
221
+ docs[PLAIN_DOC_TARGET].text
222
+ else
223
+ ''
224
+ end
225
+ end
226
+ =begin rdoc
227
+ Java Class JavaDoc text with the Wiki reference.
228
+ =end
229
+ def classJavaDoc(docs)
230
+ return <<CLASS_JAVADOC
231
+ /**
232
+ #{PojoLexer.javaDocs(docs)}
233
+ * This class is generated by
234
+ * #{WIKI_REF_HTML}.
235
+ */
236
+ CLASS_JAVADOC
237
+ end
238
+
239
+ =begin rdoc
240
+ Java Enum class-level JavaDoc text with the Wiki reference.
241
+ =end
242
+ def enumJavaDoc(docs)
243
+ return <<ENUM_JAVADOC
244
+ /**
245
+ #{PojoLexer.javaDocs(docs)}
246
+ * This enum is generated by
247
+ * #{WIKI_REF_HTML}.
248
+ */
249
+ ENUM_JAVADOC
250
+ end
251
+
252
+ =begin rdoc
253
+ For the given DataMeta DOM data type and the isRequired flag, builds and returns the matching Java data type declaration.
254
+ For standard types, uses the JAVA_TYPES map
255
+ =end
256
+ def getJavaType(dmDomType)
257
+ typeRenderer = JAVA_TYPES[dmDomType.type]
258
+ typeRenderer ? typeRenderer.call(dmDomType) : dmDomType.type
259
+ end
260
+
261
+ =begin rdoc
262
+ Renders the value for the given DataType according to Java syntax, for all standard data types.
263
+ See STANDARD_TYPES.
264
+ =end
265
+ def getJavaVal(dataType, val)
266
+ case
267
+ when dataType.type == DATETIME
268
+ %Q< java.time.ZonedDateTime.from(java.time.format.DateTimeFormatter.ISO_DATE_TIME.parse("#{val.to_s}")) >
269
+ when dataType.type == NUMERIC
270
+ %Q< new BigDecimal(#{val.inspect}) >
271
+ when val.kind_of?(Symbol)
272
+ val.to_s.inspect
273
+ when dataType.type == FLOAT && dataType.length <= 4
274
+ "#{val.inspect}F"
275
+ when dataType.type == INT && dataType.length > 4
276
+ "#{val.inspect}L"
277
+ when dataType.type == URL
278
+ %Q< new java.net.URL(#{val.inspect}) >
279
+ else
280
+ val.inspect
281
+ end
282
+ end
283
+
284
+ =begin rdoc
285
+ Used to escape the given string according to the Java syntax, now *deprecated*, use Ruby Object.inspect
286
+ or the getJavaVal method.
287
+ =end
288
+ def escapeJava(what)
289
+ result = ''
290
+ what.each_char { |c|
291
+ replacement = JAVA_ESCAPE_HASH[c.to_sym]
292
+ result << ( replacement ? replacement : c )
293
+ }
294
+ result
295
+ end
296
+
297
+ =begin rdoc
298
+ Builds Java imports for the given fields if any, per JAVA_IMPORTS.
299
+ Returns the text of imports to insert straight into the Java source file
300
+ =end
301
+ def javaImports(fields)
302
+ imports = Set.new
303
+ fields.each { |f|
304
+ importable = JAVA_IMPORTS[f.dataType.type]
305
+ imports << importable.to_sym if importable
306
+ if f.aggr?
307
+ imports << AGGR_CLASSES[f.aggr].to_sym
308
+ elsif f.map?
309
+ imports << MAP_IMPORT.to_sym
310
+ srcImport = JAVA_IMPORTS[f.trgType.type]
311
+ imports << srcImport.to_sym if srcImport
312
+ end
313
+ }
314
+ imports
315
+
316
+ # remnant of the Optional effort for non-required fields
317
+ #hasOpt = fields.values.map{|f| !f.isRequired }.reduce(:|) # true if there is at least one optional field
318
+ #imports << 'com.google.common.base.Optional' << 'static com.google.common.base.Optional.fromNullable' if hasOpt
319
+ end
320
+
321
+ # Converts an import set to the matching Java source snippet.
322
+ def importSetToSrc(importSet)
323
+ importSet.to_a.map{|k| "import #{k};"}.sort.join("\n") + "\n"
324
+ end
325
+
326
+ =begin rdoc
327
+ Generates Java source code, the Java class for a DataMeta DOM Record
328
+
329
+ Parameters:
330
+ * +model+ - the source model to export from
331
+ * +out+ - open output file to write the result to.
332
+ * +record+ - instance of DataMetaDom::Record to export
333
+ * +javaPackage+ - Java package to export to
334
+ * +baseName+ - the name of the class to generate.
335
+ =end
336
+ def genEntity(model, out, record, javaPackage, baseName)
337
+ fields = record.fields
338
+ # scan for imports needed
339
+
340
+ out.puts <<ENTITY_CLASS_HEADER
341
+ package #{javaPackage};
342
+ #{importSetToSrc(javaImports fields.values)}
343
+ import org.ebay.datameta.dom.Verifiable;
344
+ import java.util.Objects;
345
+ import java.util.StringJoiner;
346
+ import org.ebay.datameta.dom.VerificationException;
347
+ import org.ebay.datameta.util.jdk.SemanticVersion;
348
+ import static org.ebay.datameta.dom.CannedRegexUtil.getCannedRegEx;
349
+
350
+ #{PojoLexer.classJavaDoc record.docs}public class #{baseName} implements Verifiable {
351
+
352
+ ENTITY_CLASS_HEADER
353
+ if record.ver
354
+ out.puts %Q<#{INDENT}public static final SemanticVersion VERSION = SemanticVersion.parse("#{record.ver.full}");
355
+
356
+ >
357
+ end
358
+ fieldDeclarations = ''
359
+ gettersSetters = ''
360
+ eqHashFields = record.identity ? record.identity.args : fields.keys.sort
361
+ reqFields = fields.values.select{|f| f.isRequired }.map{|f| f.name}
362
+ rxRoster = JavaRegExRoster.new
363
+ fieldVerifications = ''
364
+ fields.each_key { |k|
365
+ f = fields[k]
366
+ dt = f.dataType
367
+ rxRoster.register(f) if f.regex
368
+
369
+ typeDef = aggrJavaType(f, javaPackage)
370
+
371
+ if f.trgType # Maps: if either the key or the value is verifiable, do it
372
+ mainVf = model.records[dt.type] # main data type is verifiable
373
+ trgVf = model.records[f.trgType.type] # target type is verifiable
374
+ if mainVf || trgVf
375
+ fieldVerifications << "#{INDENT*2}#{!f.isRequired ? "if(#{f.name} != null) " : '' }#{f.name}.forEach((k,v) -> {#{mainVf ? 'k.verify();' : ''} #{trgVf ? 'v.verify();' : ''}});\n"
376
+ end
377
+ end
378
+
379
+ if model.records[dt.type] && !f.trgType # maps handled separately
380
+ fieldVerifications << "#{INDENT*2}#{!f.isRequired ? "if(#{f.name} != null) " : '' }#{f.name}#{f.aggr ? '.forEach(Verifiable::verify)' : '.verify()'};\n"
381
+ # the Verifiable::verify method reference works just fine, tested it: Java correctly calls the method on the object
382
+ end
383
+
384
+ fieldDeclarations << "\n#{INDENT}private #{wrapOpt(f.isRequired, typeDef)} #{f.name};"
385
+ if f.isRequired
386
+ gettersSetters << <<CHECKING_SETTER
387
+ #{INDENT}public void #{DataMetaDom.setterName(f)}(final #{typeDef} newValue) {
388
+ #{INDENT*2}if(newValue == null) throw new IllegalArgumentException(
389
+ #{INDENT*4}"NULL passed to the setter of the required field '#{f.name}' on the class #{record.name}.");
390
+ #{INDENT*2}this.#{f.name} = newValue;
391
+ #{INDENT}}
392
+ CHECKING_SETTER
393
+ else # not required, can not be primitive - wrap into Optional<>
394
+ gettersSetters << "\n#{INDENT}public void #{DataMetaDom.setterName(f)}(final #{wrapOpt(f.isRequired, typeDef)} newValue) {this.#{f.name} = newValue; }\n"
395
+ end
396
+ if f.docs.empty?
397
+ gettersSetters << "\n"
398
+ else
399
+ gettersSetters << <<FIELD_JAVADOC
400
+ #{INDENT}/**
401
+ #{PojoLexer.javaDocs f.docs}#{INDENT} */
402
+ FIELD_JAVADOC
403
+ end
404
+ gettersSetters << "#{INDENT}public #{wrapOpt(f.isRequired, typeDef)} #{DataMetaDom.getterName(f)}() {return this.#{f.name}; }\n"
405
+ }
406
+ out.puts(rxRoster.to_patterns)
407
+ out.puts fieldDeclarations
408
+ out.puts
409
+ out.puts gettersSetters
410
+ out.puts %|
411
+ #{INDENT}/**
412
+ #{INDENT}* If there is class type mismatch, somehow we are comparing apples to oranges, this is an error, not
413
+ #{INDENT}* a not-equal condition.
414
+ #{INDENT}*/
415
+ #{INDENT}@SuppressWarnings("EqualsWhichDoesntCheckParameterClass") @Override public boolean equals(Object other) {
416
+ #{INDENT * 2}return Objects.deepEquals(new Object[]{#{eqHashFields.map{|q| "this.#{q}"}.join(', ')}},
417
+ #{INDENT * 2} new Object[]{#{eqHashFields.map{|q| "((#{baseName}) other).#{q}"}.join(', ')}});
418
+ #{INDENT}}
419
+ |
420
+ out.puts %|
421
+ #{INDENT}@Override public int hashCode() {// null - safe: result = 31 * result + (element == null ? 0 : element.hashCode());
422
+ #{INDENT * 2}return Objects.hash(#{eqHashFields.map{|q| "this.#{q}"}.join(', ')});
423
+ #{INDENT}}
424
+ |
425
+ verCalls = reqFields.map{|r| %<if(#{r} == null) missingFields.add("#{r}");>}.join("\n#{INDENT * 2}")
426
+ out.puts %|
427
+ #{INDENT}public void verify() {
428
+ |
429
+ unless verCalls.empty?
430
+ out.puts %|
431
+ #{INDENT * 2}StringJoiner missingFields = new StringJoiner(", ");
432
+ #{INDENT * 2}#{verCalls}
433
+ #{INDENT * 2}if(missingFields.length() != 0) throw new VerificationException(getClass().getSimpleName() + ": required fields not set: " + missingFields);
434
+ |
435
+
436
+ end
437
+
438
+ out.puts %|
439
+
440
+ #{rxRoster.to_verifications}
441
+ #{fieldVerifications}
442
+ #{INDENT}}
443
+ |
444
+ out.puts %<
445
+ #{INDENT}public final SemanticVersion getVersion() { return VERSION; }
446
+ }>
447
+ end
448
+
449
+ # Unaggregated Java type
450
+ def unaggrJavaType(dt, javaPackage)
451
+ typeRenderer = JAVA_TYPES[dt.type]
452
+ typeRenderer ? typeRenderer.call(dt) : condenseType(dt.type, javaPackage)
453
+ end
454
+
455
+ # aggregated Java type
456
+ def aggrJavaType(f, javaPackage)
457
+ rawType = unaggrJavaType(f.dataType, javaPackage)
458
+ aggr = f.aggr? ? DataMetaDom.splitNameSpace(AGGR_CLASSES[f.aggr])[1] : nil
459
+ PojoLexer.aggrType(aggr, f.trgType, rawType, javaPackage)
460
+ end
461
+
462
+ =begin
463
+ DataMetaSame condition generated for the given field. This applies only for a single instance.
464
+ All aggregation specifics are hhandled elsewhere (see genDataMetaSame)
465
+ =end
466
+ def lsCondition(parser, f, javaPackage, suffix, imports, one, another)
467
+ dt = f.dataType
468
+ g = "#{DataMetaDom.getterName(f)}()"
469
+ if false # Ruby prints the warning that the var is unused, unable to figure out that it is used in the ERB file
470
+ # and adding insult to injury, the developers didn't think of squelching the false warnings
471
+ p g
472
+ end
473
+ typeRec = parser.records[dt.type]
474
+ enumType = parser.enums[dt.type]
475
+ case
476
+ when typeRec
477
+ ftNs, ftClassBase = DataMetaDom.splitNameSpace(typeRec.name)
478
+ # the name of the DataMetaSame implementor of the Field's type, assuming it is available during compile time
479
+ ftLsClassBase = "#{ftClassBase}#{suffix}"
480
+ # import the class if it belogns to a different package
481
+ imports << "#{combineNsBase(ftNs, ftLsClassBase)}" unless javaPackage == ftNs
482
+ %Q<#{ftLsClassBase}.I.isSame(#{one}, #{another})>
483
+
484
+ when (f.isRequired && PRIMITIVABLE_TYPES.member?(dt.type)) || (enumType && enumType.kind_of?(DataMetaDom::Enum))
485
+ %Q<(#{one} == #{another})>
486
+
487
+ when enumType && enumType.kind_of?(DataMetaDom::Mappings)
488
+ %Q<MAP_EQ.isSame(#{one}, #{another})>
489
+
490
+ else # leverage the equals method, that works for the BitMaps too
491
+ %Q<EQ.isSame(#{one}, #{another})>
492
+ end
493
+ end
494
+ =begin rdoc
495
+ Generates Java source code for the DataMetaSame implementor in Java to compare by all the fields in the class for the
496
+ given Record.
497
+
498
+ No attempt made to pretty-format the output. Pretty-formatting makes sense only when human eyes look at the
499
+ generated code in which case one keyboard shortcut gets the file pretty-formatted. Beside IDEs, there are
500
+ Java pretty-formatters that can be plugged in into the build process:
501
+
502
+ * {Jalopy}[http://jalopy.sourceforge.net]
503
+ * {JxBeauty}[http://members.aon.at/johann.langhofer/jxb.htm]
504
+ * {BeautyJ}[http://beautyj.berlios.de]
505
+
506
+ To name a few.
507
+
508
+ Parameters:
509
+ * +parser+ - the instance of Model
510
+ * +destDir+ - destination root.
511
+ * +javaPackage+ - Java package to export to
512
+ * +suffix+ - The suffix to append to the DataMeta DOM Class to get the DataMetaSame implementor's name.
513
+ * +dmClass+ - the name of DataMeta DOM class to generate for
514
+ * +record+ - the DataMeta DOM record to generate the DataMetaSame implementors for.
515
+ * +fields+ - a collection of fields to compare
516
+ =end
517
+ def genDataMetaSame(parser, destDir, javaPackage, suffix, dmClass, record, fields)
518
+ conditions = []
519
+ aggrChecks = ''
520
+ imports = javaImports(fields)
521
+ javaClass = "#{dmClass}#{suffix}"
522
+ fields.each { |f|
523
+ g = "#{DataMetaDom.getterName(f)}()"
524
+ if f.aggr?
525
+ if f.set?
526
+ =begin
527
+ # no option with the Set for full compare -- a Set is a Set, must use equals and hashCode
528
+ but, if in future a need arises, could use the following pattern -- tested, the stream shortcut works fine:
529
+ final Set<String> personas___1__ = one.getPersonas();
530
+ final Set<String> personas___2__ = another.getPersonas();
531
+ if(personas___1__ != personas___2__) {
532
+ if(personas___1__ == null || personas___2__ == null ) return false; // one of them is null but not both -- not equal short-circuit
533
+ if(personas___1__.size() != personas___2__.size()) return false;
534
+ // this should run in supposedly O(N), since Set.contains(v) is supposedly O(1)
535
+ final Optional<String> firstMismatch = personas___1__.stream().filter(v -> !personas___2__.contains(v)).findFirst();
536
+ if(firstMismatch.isPresent()) return false;
537
+ }
538
+ =end
539
+ conditions << %|(one.#{g} != null && one.#{g}.equals(another.#{g}))|
540
+ else
541
+ a1 = "#{f.name}___1__"
542
+ a2 = "#{f.name}___2__"
543
+ li1 = "#{f.name}___li1__"
544
+ li2 = "#{f.name}___li2__"
545
+ jt = unaggrJavaType(f.dataType, javaPackage)
546
+ aggrChecks << %|
547
+ #{INDENT * 2}final #{aggrJavaType(f, javaPackage)} #{a1} = one.#{g};
548
+ #{INDENT * 2}final #{aggrJavaType(f, javaPackage)} #{a2} = another.#{g};
549
+ #{INDENT * 2}if(#{a1} != #{a2} ) {
550
+ #{INDENT * 3}if(#{a1} == null #{'||'} #{a2} == null ) return false; // one of them is null but not both -- not equal short-circuit
551
+ #{INDENT * 3}java.util.ListIterator<#{jt}> #{li1} = #{a1}.listIterator();
552
+ #{INDENT * 3}java.util.ListIterator<#{jt}> #{li2} = #{a2}.listIterator();
553
+ #{INDENT * 3}while(#{li1}.hasNext() && #{li2}.hasNext()) {
554
+ #{INDENT * 4}final #{jt} o1 = #{li1}.next(), o2 = #{li2}.next();
555
+ #{INDENT * 4}if(!(o1 == null ? o2 == null : #{lsCondition(parser, f, javaPackage, suffix, imports, 'o1', 'o2')})) return false; // shortcircuit to false
556
+ #{INDENT * 3}}
557
+ #{INDENT * 3}if(#{li1}.hasNext() #{'||'} #{li2}.hasNext()) return false; // leftover elements in one
558
+ #{INDENT * 2}}
559
+ |
560
+ end
561
+ elsif f.map?
562
+ a1 = "#{f.name}___1__"
563
+ a2 = "#{f.name}___2__"
564
+ aggrChecks << %|
565
+ #{INDENT * 2}final java.util.Map<#{unaggrJavaType(f.dataType, javaPackage)}, #{unaggrJavaType(f.trgType, javaPackage)}> #{a1} = one.#{g};
566
+ #{INDENT * 2}final java.util.Map<#{unaggrJavaType(f.dataType, javaPackage)}, #{unaggrJavaType(f.trgType, javaPackage)}> #{a2} = another.#{g};
567
+ #{INDENT * 2}if(#{a1} != #{a2} ) {
568
+ #{INDENT * 3}if(#{a1} == null #{'||'} #{a2} == null ) return false; // one of them is null but not both -- not equal short-circuit
569
+ #{INDENT * 3}if(!#{a1}.equals(#{a2})) return false; // Maps are shallow-compared, otherwise logic and spread of semantics are too complex
570
+ #{INDENT * 2}}
571
+ |
572
+ else # regular field
573
+ conditions << lsCondition(parser, f, javaPackage, suffix, imports, "one.#{g}", "another.#{g}")
574
+ end
575
+ }
576
+ out = File.open(File.join(destDir, "#{javaClass}.java"), 'wb')
577
+ out.puts <<DM_SAME_CLASS
578
+ package #{javaPackage};
579
+
580
+ #{importSetToSrc(imports)}
581
+
582
+ import org.ebay.datameta.dom.DataMetaSame;
583
+ import org.ebay.datameta.util.jdk.SemanticVersion;
584
+
585
+ #{PojoLexer.classJavaDoc record.docs}public class #{javaClass} implements DataMetaSame<#{dmClass}>{
586
+ #{INDENT}/**
587
+ #{INDENT}* Convenience instance.
588
+ #{INDENT}*/
589
+ #{INDENT}public final static #{javaClass} I = new #{javaClass}();
590
+ #{INDENT}@Override public boolean isSame(final #{dmClass} one, final #{dmClass} another) {
591
+ #{INDENT * 2}if(one == another) return true; // same object or both are null
592
+ #{INDENT * 2}//noinspection SimplifiableIfStatement
593
+ #{INDENT * 2}if(one == null || another == null) return false; // whichever of them is null but the other is not
594
+ #{INDENT * 2}#{aggrChecks}
595
+ #{INDENT * 2}return #{conditions.join(' && ')};
596
+ #{INDENT}}
597
+ DM_SAME_CLASS
598
+ if record.ver
599
+ out.puts %Q<#{INDENT}public static final SemanticVersion VERSION = SemanticVersion.parse("#{record.ver.full}");>
600
+ end
601
+ out.puts '}'
602
+ out.close
603
+ end
604
+
605
+
606
+ =begin rdoc
607
+ Runs generation of Java source code for DataMetaSame implementors for the given parser into the given output path.
608
+
609
+ Parameters:
610
+ * +parser+ - an instance of DataMetaDom::Model
611
+ * +outRoot+ - the path to output the generated Java packages into
612
+ * +style+ - can pass one of the following values:
613
+ * ID_ONLY_COMPARE - see the docs to it
614
+ * FULL_COMPARE - see the docs to it
615
+ =end
616
+ def genDataMetaSames(parser, outRoot, style = FULL_COMPARE)
617
+ parser.records.values.each { |record|
618
+ javaPackage, base, packagePath = assertNamespace(record.name)
619
+ destDir = File.join(outRoot, packagePath)
620
+ FileUtils.mkdir_p destDir
621
+ case style
622
+ when FULL_COMPARE
623
+ suffix = SAME_FULL_SFX
624
+ fields = record.fields.values
625
+ when ID_ONLY_COMPARE
626
+ unless record.identity
627
+ L.warn "#{record.name} does not have identity defined"
628
+ next
629
+ end
630
+ suffix = SAME_ID_SFX
631
+ fields = record.fields.keys.select{|k| record.identity.hasArg?(k)}.map{|k| record.fields[k]}
632
+ else; raise %Q<Unsupported DataMetaSame POJO style "#{style}">
633
+ end
634
+ if fields.empty?
635
+ L.warn "#{record.name} does not have any fields to compare by"
636
+ next
637
+ end
638
+ genDataMetaSame parser, destDir, javaPackage, suffix, base, record, fields
639
+ }
640
+ end
641
+
642
+ =begin rdoc
643
+ Generates Java source code for the worded enum, DataMeta DOM keyword "<tt>enum</tt>".
644
+ =end
645
+ def genEnumWorded(out, enum, javaPackage, baseName)
646
+ values = enum.keys.map{|k| enum[k]} # sort by ordinals to preserve the order
647
+ out.puts <<ENUM_CLASS_HEADER
648
+ package #{javaPackage};
649
+ import javax.annotation.Nullable;
650
+ import java.util.HashMap;
651
+ import java.util.Map;
652
+
653
+ import org.ebay.datameta.dom.DataMetaEntity;
654
+ import org.ebay.datameta.util.jdk.SemanticVersion;
655
+ import static java.util.Collections.unmodifiableMap;
656
+
657
+ #{enumJavaDoc(enum.docs)}public enum #{baseName} implements DataMetaEntity {
658
+ #{values.join(', ')};
659
+ /**
660
+ * Staple Java lazy init idiom.
661
+ * See <a href="http://en.wikipedia.org/wiki/Initialization-on-demand_holder_idiom">this article</a>.
662
+ */
663
+ private static class LazyInit {
664
+ final static Map<String, #{baseName}> NAME_TO_ENUM;
665
+ final static #{baseName}[] ORD_TO_ENUM = new #{baseName}[values().length];
666
+
667
+ static {
668
+ final Map<String, #{baseName}> map = new HashMap<>(values().length * 3 / 2 + 1);
669
+ for (int ix = 0; ix < values().length; ix++) {
670
+ final #{baseName} val = values()[ix];
671
+ ORD_TO_ENUM[ix] = val;
672
+ map.put(val.name(), val);
673
+ }
674
+ NAME_TO_ENUM = unmodifiableMap(map);
675
+ }
676
+ }
677
+
678
+ /**
679
+ * Retrieve a value for the given textual form.
680
+ * Lenient replacement for {@link Enum#valueOf(Class, java.lang.String)} that returns null
681
+ * instead of throwing an exception.
682
+ */
683
+ @Nullable public static #{baseName} forName(final String textual) {
684
+ return LazyInit.NAME_TO_ENUM.get(textual);
685
+ }
686
+
687
+ /**
688
+ * Fast instance retrieval for the given ordinal, works super fast because it uses an array
689
+ * indexing, not a map.
690
+ */
691
+ @Nullable public static #{baseName} forOrd(final int ordinal) {
692
+ return LazyInit.ORD_TO_ENUM[ordinal];
693
+ }
694
+
695
+ public static interface Visitor<IN, OUT> {
696
+ ENUM_CLASS_HEADER
697
+
698
+ values.each { |v|
699
+ out.puts " OUT visit#{v}(IN input);"
700
+ }
701
+ out.puts <<VISITOR_SWITCH_HEAD
702
+ }
703
+
704
+ /** Use this switch with your {@link Visitor} implementation,
705
+ * There should be no other switches of this kind in your program.
706
+ * If the enum changes, all implementations will break and will need to be fixed.
707
+ * This will ensure that no unhandled cases will be left in the program.
708
+ */
709
+ public static <IN, OUT> OUT visit(final #{baseName} value, final Visitor<IN, OUT> visitor, final IN input) {
710
+ switch(value) {
711
+ VISITOR_SWITCH_HEAD
712
+
713
+ values.each { |v|
714
+ out.puts " case #{v}:\n return visitor.visit#{v}(input);"
715
+ }
716
+ out.puts <<VISITOR_SWITCH_TAIL
717
+ default:
718
+ throw new IllegalArgumentException("Unsupported enum value: " + value);
719
+ }
720
+ }
721
+ VISITOR_SWITCH_TAIL
722
+ if enum.ver
723
+ out.puts %Q< public static final SemanticVersion VERSION = SemanticVersion.parse("#{enum.ver.full}");>
724
+ end
725
+
726
+ out.puts %<
727
+ #{INDENT}public final SemanticVersion getVersion() { return VERSION; }
728
+ }>
729
+ end
730
+
731
+ =begin rdoc
732
+ Generates Java source code for the DataMeta DOM Mapping, DataMeta DOM keyword "<tt>mapping</tt>".
733
+ =end
734
+ def genMapping(out, mapping, javaPackage, baseName)
735
+ keys = mapping.keys
736
+ raise "Mapping too big, size = #{keys.length}, max size #{MAX_MAPPING_SIZE}" if keys.length > MAX_MAPPING_SIZE
737
+ fromType = getJavaType(mapping.fromT)
738
+ toType = getJavaType(mapping.toT)
739
+ imports = {}
740
+ importable = JAVA_IMPORTS[mapping.fromT.type]; imports[importable.to_sym] = 1 if importable
741
+ importable = JAVA_IMPORTS[mapping.toT.type]; imports[importable.to_sym] = 1 if importable
742
+ importText = imports.keys.to_a.map{|i| "import #{i};"}.join("\n")
743
+ mapGeneric = "#{fromType}, #{toType}"
744
+ out.puts <<MAPPING_CLASS_HEADER
745
+ package #{javaPackage};
746
+
747
+ import org.ebay.datameta.core.Mapping;
748
+ #{importText}
749
+ import java.util.Collection;
750
+ import java.util.Collections;
751
+ import java.util.HashMap;
752
+ import java.util.Map;
753
+ import java.util.Set;
754
+
755
+ import org.ebay.datameta.util.jdk.SemanticVersion;
756
+
757
+ #{PojoLexer.classJavaDoc mapping.docs}public final class #{baseName} implements Mapping<#{mapGeneric}>{
758
+
759
+ private final static Map<#{mapGeneric}> mapping;
760
+ protected final static int count = #{keys.length};
761
+
762
+ static {
763
+ final Map<#{mapGeneric}> m = new HashMap<#{mapGeneric}>(count * 3 / 2 + 1);
764
+ MAPPING_CLASS_HEADER
765
+ keys.sort.each { |k|
766
+ out.puts %Q<#{INDENT*2}m.put(#{getJavaVal(mapping.fromT, k)}, #{getJavaVal(mapping.toT, mapping[k])});>
767
+ }
768
+ out.puts <<MAPPING_CLASS_FOOTER
769
+ mapping = Collections.unmodifiableMap(m);
770
+ }
771
+ public static int size() { return mapping.size(); }
772
+ public static boolean containsKey(#{fromType} key) { return mapping.containsKey(key); }
773
+ public static #{toType} get(#{fromType} key) { return mapping.get(key); }
774
+ public static Set<#{fromType}> keySet() { return mapping.keySet(); }
775
+ public static Collection<#{toType}> values() { return mapping.values(); }
776
+ private static void assertKey(#{fromType} key) {
777
+ if(!mapping.containsKey(key)) throw new IllegalArgumentException("The key " + key
778
+ + " does not belong to this mapping");
779
+ }
780
+
781
+ private #{fromType} key;
782
+
783
+ public #{baseName}(){}
784
+ public #{baseName}(#{fromType} key){ assertKey(key); this.key = key;}
785
+
786
+ public void setKey(#{fromType} key) {assertKey(key); this.key = key; }
787
+ public #{fromType} getKey() { return key; }
788
+ public #{toType} getValue() { return mapping.get(key); }
789
+ @Override public String toString() { return getClass().getSimpleName() + '{' + key + "=>" + mapping.get(key) + '}'; }
790
+ MAPPING_CLASS_FOOTER
791
+ if mapping.ver
792
+ out.puts %Q< public static final SemanticVersion VERSION = SemanticVersion.parse("#{mapping.ver.full}");>
793
+ end
794
+
795
+ out.puts %<
796
+ #{INDENT}public final SemanticVersion getVersion() { return VERSION; }
797
+ }>
798
+
799
+ end
800
+
801
+ =begin rdoc
802
+ Generates Java source code for the DataMeta DOM BitSet, DataMeta DOM keyword "<tt>bitset</tt>".
803
+ =end
804
+ def genBitSet(out, bitSet, javaPackage, baseName)
805
+ keys = bitSet.keys
806
+ toType = getJavaType(bitSet.toT)
807
+ importable = JAVA_IMPORTS[bitSet.toT.type]
808
+ importTxt = importable ? "import #{importable};" : ''
809
+ maxBit = bitSet.keys.max
810
+ raise "Mapping too big, size = #{maxBit}, max size #{MAX_MAPPING_SIZE}" if maxBit > MAX_MAPPING_SIZE
811
+ out.puts <<BIT_SET_HEADER
812
+ package #{javaPackage};
813
+
814
+ import org.ebay.datameta.core.BitSetImpl;
815
+ #{importTxt}
816
+ #{PojoLexer.classJavaDoc bitSet.docs}public final class #{baseName} extends BitSetImpl<#{toType}>{
817
+ public static final int MAX_BIT = #{maxBit};
818
+ public static final int COUNT = MAX_BIT + 1;
819
+ // we do not expect huge arrays here, the sizes should be very limited and likely continuous.
820
+ private static final #{toType}[] mapping = new #{toType}[COUNT];
821
+ static {
822
+ BIT_SET_HEADER
823
+
824
+ keys.sort.each { |k|
825
+ out.puts %Q<#{INDENT*2}mapping[#{k}] = #{getJavaVal(bitSet.toT, bitSet[k])};>
826
+ }
827
+
828
+ out.puts <<BIT_SET_FOOTER
829
+ }
830
+
831
+ public #{baseName}() {
832
+ }
833
+
834
+ public #{baseName}(long[] image) {
835
+ super(image);
836
+ }
837
+
838
+ public final int getCount() { return COUNT; }
839
+ public final #{toType}[] getMap() { return mapping;}
840
+ BIT_SET_FOOTER
841
+ if bitSet.ver
842
+ out.puts %Q< public static final SemanticVersion VERSION = SemanticVersion.parse("#{bitSet.ver.full}");>
843
+ end
844
+
845
+ out.puts %<
846
+ #{INDENT}public final SemanticVersion getVersion() { return VERSION; }
847
+ }>
848
+
849
+ end
850
+
851
+ =begin rdoc
852
+ Extracts 3 pieces of information from the given full name:
853
+ * The namespace if any, i.e. Java package, empty string if none
854
+ * The base name for the type, without the namespace
855
+ * Java package's relative path, the dots replaced by the file separator.
856
+
857
+ Returns an array of these pieces of info in this exact order as described here.
858
+ =end
859
+ def assertNamespace(name)
860
+ ns, base = DataMetaDom.splitNameSpace(name)
861
+ javaPackage = DataMetaDom.validNs?(ns, base) ? ns : ''
862
+ packagePath = javaPackage.empty? ? '' : javaPackage.gsub('.', File::SEPARATOR)
863
+
864
+ [javaPackage, base, packagePath]
865
+ end
866
+
867
+ =begin rdoc
868
+ Generates java sources for the model, the POJOs.
869
+ * Parameters
870
+ * +parser+ - instance of Model
871
+ * +outRoot+ - output directory
872
+ =end
873
+ def genPojos(model, outRoot)
874
+ (model.enums.values + model.records.values).each { |e|
875
+ javaPackage, base, packagePath = assertNamespace(e.name)
876
+ destDir = File.join(outRoot, packagePath)
877
+ FileUtils.mkdir_p destDir
878
+ out = File.open(File.join(destDir, "#{base}.java"), 'wb')
879
+ begin
880
+ case
881
+ when e.kind_of?(DataMetaDom::Record)
882
+ genEntity model, out, e, javaPackage, base
883
+ when e.kind_of?(DataMetaDom::Mappings)
884
+ genMapping out, e, javaPackage, base
885
+ when e.kind_of?(DataMetaDom::Enum)
886
+ genEnumWorded out, e, javaPackage, base
887
+ when e.kind_of?(DataMetaDom::BitSet)
888
+ genBitSet out, e, javaPackage, base
889
+ else
890
+ raise "Unsupported Entity: #{e.inspect}"
891
+ end
892
+ ensure
893
+ out.close
894
+ end
895
+ }
896
+ end
897
+
898
+ =begin
899
+ Generates migration guides from the given model to the given model
900
+ =end
901
+ def genMigrations(mo1, mo2, outRoot)
902
+ v1 = mo1.records.values.first.ver.full
903
+ v2 = mo2.records.values.first.ver.full
904
+ destDir = outRoot
905
+ javaPackage = '' # set the scope for the var
906
+ vars = OpenStruct.new # for template's local variables. ERB does not make them visible to the binding
907
+ if false # Ruby prints the warning that the var is unused, unable to figure out that it is used in the ERB file
908
+ # and adding insult to injury, the developers didn't think of squelching the false warnings
909
+ p vars
910
+ # it's interesting that there is no warning about the unused destDir and javaPackage. Duh!
911
+ end
912
+ # sort the models by versions out, 2nd to be the latest:
913
+ raise ArgumentError, "Versions on the model are the same: #{v1}, nothing to migrate" if v1 == v2
914
+ if v1 > v2
915
+ model2 = mo1
916
+ model1 = mo2
917
+ ver1 = v2
918
+ ver2 = v1
919
+ else
920
+ model2 = mo2
921
+ model1 = mo1
922
+ ver1 = v1
923
+ ver2 = v2
924
+ end
925
+
926
+ puts "Migrating from ver #{ver1} to #{ver2}"
927
+ ctxs = []
928
+ droppedRecs = []
929
+ addedRecs = []
930
+ (model1.enums.values + model1.records.values).each { |srcE|
931
+ trgRecName = flipVer(srcE.name, ver1.toVarName, ver2.toVarName)
932
+ trgE = model2.records[trgRecName] || model2.enums[trgRecName]
933
+ droppedRecs << srcE.name unless trgE
934
+ }
935
+
936
+ (model2.enums.values + model2.records.values).each { |trgE|
937
+ srcRecName = flipVer(trgE.name, ver2.toVarName, ver1.toVarName)
938
+ srcE = model1.records[srcRecName] || model1.enums[srcRecName]
939
+ unless srcE
940
+ addedRecs << trgE.name
941
+ next
942
+ end
943
+ javaPackage, baseName, packagePath = assertNamespace(trgE.name)
944
+ javaClassName = migrClass(baseName, ver1, ver2)
945
+ destDir = File.join(outRoot, packagePath)
946
+ migrCtx = MigrCtx.new trgE.name
947
+ ctxs << migrCtx
948
+ FileUtils.mkdir_p destDir
949
+ javaDestFile = File.join(destDir, "#{javaClassName}.java")
950
+ case
951
+ when trgE.kind_of?(DataMetaDom::Record)
952
+ if File.file?(javaDestFile)
953
+ migrCtx.isSkipped = true
954
+ $stderr.puts %<Migration target "#{javaDestFile} present, therefore skipped">
955
+ else
956
+ IO::write(javaDestFile,
957
+ ERB.new(IO.read(File.join(File.dirname(__FILE__), '../../tmpl/java/migrationEntityEnums.erb')),
958
+ $SAFE, '%<>').result(binding), mode: 'wb')
959
+ end
960
+ when trgE.kind_of?(DataMetaDom::Mappings)
961
+ $stderr.puts "WARN: Migration guides for the mapping #{trgE.name} are not generated; migration is not implemented for mappings"
962
+ when trgE.kind_of?(DataMetaDom::Enum)
963
+ # handled by the POJO migrator above, i.e. the case when trgE.kind_of?(DataMetaDom::Record)
964
+ when trgE.kind_of?(DataMetaDom::BitSet)
965
+ $stderr.puts "WARN: Migration guides for the bitset #{trgE.name} are not generated; migration is not implemented for bitsets"
966
+ else
967
+ raise "Unsupported Entity: #{trgE.inspect}"
968
+ end
969
+ }
970
+ noAutos = ctxs.reject{|c| c.canAuto}
971
+ skipped = ctxs.select{|c| c.isSkipped}
972
+ unless skipped.empty?
973
+ $stderr.puts %<Skipped: #{skipped.size}>
974
+ end
975
+ unless noAutos.empty?
976
+ $stderr.puts %<#{noAutos.size} class#{noAutos.size > 1 ? 'es' : ''} out of #{ctxs.size} can not be migrated automatically:
977
+ #{noAutos.map{|c| c.rec}.sort.join("\n")}
978
+ Please edit the Migrate_ code for #{noAutos.size > 1 ? 'these' : 'this one'} manually.
979
+ >
980
+ end
981
+ unless droppedRecs.empty?
982
+
983
+ $stderr.puts %<#{droppedRecs.size} class#{droppedRecs.size > 1 ? 'es were' : ' was'} dropped from your model:
984
+ #{droppedRecs.sort.join("\n")}
985
+ -- you may want to review if #{droppedRecs.size > 1 ? 'these were' : 'this one was'} properly handled.
986
+ >
987
+ end
988
+ unless addedRecs.empty?
989
+ $stderr.puts %<#{addedRecs.size} class#{addedRecs.size > 1 ? 'es were' : ' was'} added to your model:
990
+ #{addedRecs.sort.join("\n")}
991
+ -- no migration guides were generated for #{addedRecs.size > 1 ? 'these' : 'this one'}.
992
+ >
993
+ end
994
+
995
+ end
996
+
997
+ =begin rdoc
998
+ Runs DataMetaSame generator for the given style.
999
+
1000
+ Parameters:
1001
+ * +style+ - FULL_COMPARE or ID_ONLY_COMPARE
1002
+ * +runnable+ - the name of the executable script that called this method, used for help
1003
+ =end
1004
+ def dataMetaSameRun(style, runnable, source, target, options={autoVerNs: true})
1005
+ @source, @target = source, target
1006
+ helpDataMetaSame runnable, style unless @source && @target
1007
+ helpDataMetaSame(runnable, style, "DataMeta DOM source #{@source} is not a file") unless File.file?(@source)
1008
+ helpDataMetaSame(runnable, style, "DataMetaSame destination directory #{@target} is not a dir") unless File.directory?(@target)
1009
+
1010
+ @parser = Model.new
1011
+ begin
1012
+ @parser.parse(@source, options)
1013
+ genDataMetaSames(@parser, @target, style)
1014
+ rescue Exception => e
1015
+ puts "ERROR #{e.message}; #{@parser.diagn}"
1016
+ puts e.backtrace.inspect
1017
+ end
1018
+ end
1019
+
1020
+ # Shortcut to help for the Full Compare DataMetaSame generator.
1021
+ def helpDataMetaSame(file, style, errorText=nil)
1022
+ styleWording = case style
1023
+ when FULL_COMPARE; "Full"
1024
+ when ID_ONLY_COMPARE; "Identity Only"
1025
+ else raise %Q<Unsupported identity style "#{style}">
1026
+ end
1027
+ help(file, "#{styleWording} Compare DataMetaSame generator", '<DataMeta DOM source> <target directory>', errorText)
1028
+ end
1029
+
1030
+ # Switches Namespace version part on a versioned DataMeta DOM entity.
1031
+ def flipVer(fullName, from, to)
1032
+ fullName.to_s.gsub(".v#{from}.", ".v#{to}.").to_sym
1033
+ end
1034
+
1035
+ # The field must be an aggregate. The method puts together an argument for the proper collection setter
1036
+ def setAggrPrims(trgFld)
1037
+ #new LinkedList<>(src.getInts().stream().map(Integer::longValue).collect(Collectors.toList()))
1038
+ case trgFld.aggr
1039
+ when Field::SET
1040
+ %|src.#{DataMetaDom.getterName(trgFld)}().stream().map(e -> e.#{primValMethod(trgFld.dataType)}()).collect(toSet())|
1041
+ when Field::LIST
1042
+ %|new ArrayList<>(src.#{DataMetaDom.getterName(trgFld)}().stream().map(e -> e.#{primValMethod(trgFld.dataType)}()).collect(toList()))|
1043
+ when Field::DEQUE
1044
+ %|new LinkedList<>(src.#{DataMetaDom.getterName(trgFld)}().stream().map(e -> e.#{primValMethod(trgFld.dataType)}()).collect(toList()))|
1045
+ else
1046
+ raise ArgumentError, %<Unsupported aggregation type on the field:
1047
+ #{trgFld}>
1048
+ end
1049
+ end
1050
+ module_function :getJavaType, :getJavaVal, :wrapOpt, :classJavaDoc,
1051
+ :assertNamespace, :dataMetaSameRun, :genDataMetaSames, :genDataMetaSame, :helpDataMetaSame, :javaImports,
1052
+ :javaDocs, :genPojos, :genEntity, :flipVer, :primValMethod, :importSetToSrc, :aggrJavaType,
1053
+ :unaggrJavaType, :lsCondition
1054
+ end
1055
+
1056
+ end