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.
- checksums.yaml +7 -0
- data/.yardopts +1 -0
- data/History.md +6 -0
- data/PostInstall.txt +1 -0
- data/README.md +137 -0
- data/Rakefile +13 -0
- data/bin/dataMetaGvExport.rb +97 -0
- data/bin/dataMetaMySqlDdl.rb +27 -0
- data/bin/dataMetaOracleDdl.rb +26 -0
- data/bin/dataMetaPojo.rb +27 -0
- data/bin/dataMetaReVersion.rb +66 -0
- data/bin/dataMetaSameFullJ.rb +13 -0
- data/bin/dataMetaSameIdJ.rb +12 -0
- data/lib/dataMetaDom/converter.rb +70 -0
- data/lib/dataMetaDom/dataType.rb +112 -0
- data/lib/dataMetaDom/docs.rb +122 -0
- data/lib/dataMetaDom/enum.rb +125 -0
- data/lib/dataMetaDom/field.rb +182 -0
- data/lib/dataMetaDom/help.rb +41 -0
- data/lib/dataMetaDom/model.rb +274 -0
- data/lib/dataMetaDom/mySql.rb +256 -0
- data/lib/dataMetaDom/ora.rb +295 -0
- data/lib/dataMetaDom/pojo.rb +1056 -0
- data/lib/dataMetaDom/python.rb +168 -0
- data/lib/dataMetaDom/recAttr.rb +271 -0
- data/lib/dataMetaDom/record.rb +397 -0
- data/lib/dataMetaDom/ref.rb +127 -0
- data/lib/dataMetaDom/sourceFile.rb +150 -0
- data/lib/dataMetaDom/sources.rb +68 -0
- data/lib/dataMetaDom/util.rb +279 -0
- data/lib/dataMetaDom/ver.rb +244 -0
- data/lib/dataMetaDom.rb +141 -0
- data/test/test_dataMetaDom.rb +130 -0
- data/test/test_helper.rb +6 -0
- data/tmpl/java/migrationEntityEnums.erb +172 -0
- data/tmpl/python/entity.erb +50 -0
- metadata +126 -0
@@ -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
|