dataMetaDom 1.0.0

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