Module: DataMetaDom::PojoLexer
- Includes:
- DataMetaDom
- Defined in:
- lib/dataMetaDom/pojo.rb
Overview
Definition for generating Plain Old Java Objects (POJOs) and everything related that depends on JDK only witout any other dependencies.
TODO this isn’t a bad way, but beter use templating next time such as ERB.
For command line details either check the new method’s source or the README.rdoc file, the usage section.
Defined Under Namespace
Classes: JavaRegExRoster
Constant Summary collapse
- JAVA_IMPORTS =
Maps DataMeta DOM datatypes to the matching Java classes, for those that need to be imported. The Java source generator will import these if they are used in the class.
{ DATETIME => 'java.time.ZonedDateTime', NUMERIC => 'java.math.BigDecimal' }
- AGGR_CLASSES =
DataMeta DOM aggregated field type spec mapped to matching Java class:
{ Field::SET => 'java.util.Set', Field::LIST => 'java.util.List', Field::DEQUE => 'java.util.LinkedList', }
- MAP_IMPORT =
Special import for special case – the map
'java.util.Map'
- URL_CLASS =
URL data type projection into Java
'java.net.URL'
- PRIMITIVABLE_TYPES =
Field types for which Java primitivs can be used along with == equality.
Note that CHAR is not primitivable, we do not expect any single character fields in our metadata that should be treated differently than multichar fields.
Deprecated. With the advent of the Verifiable interface, we must have all objects, no primitives.
Set.new
- PRIMS_TO_WRAP =
Primitives that need to be converted to wrappers for aggregate types.
{ :int => 'Integer', :long => 'Long', :boolean => 'Boolean', :float => 'Float', :double => 'Double', }
- TEXTUAL_TYPER =
Renderer for the String type.
lambda{|t| 'String'}
- JAVA_TYPES =
A map from DataMeta DOM standard types to the lambdas that render correspondent Java types per Java syntax.
We used to render the primitives for the required types but the Verifiable interface made it impractical.
{ DataMetaDom::INT => lambda{ |t| len = t.length case when len <= 4; 'Integer' #req ? 'int' : 'Integer' when len <=8; 'Long' # req ? 'long' : 'Long' else; raise "Invalid integer length #{len}" end }, STRING => TEXTUAL_TYPER, DATETIME => lambda{|t| 'ZonedDateTime'}, BOOL => lambda{|t| 'Boolean'}, # req ? 'boolean' : 'Boolean'}, CHAR => TEXTUAL_TYPER, FLOAT => lambda{|t| len = t.length case when len <= 4; 'Float' # req ? 'float' : 'Float' when len <=8; 'Double' #req ? 'double' : 'Double' else; raise "Invalid float length #{len}" end }, RAW => lambda{|t| 'byte[]'}, URL => lambda{|t| URL_CLASS}, NUMERIC => lambda{|t| 'BigDecimal'} }
- JAVA_ESCAPE_HASH =
A hash from a character sequences to their Java escapes. Used by escapeJava method which is deprecated, see the docs, there are better ways to escape Java string in Ruby.
{ '\\'.to_sym => '\\\\', "\n".to_sym => '\\n', '"'.to_sym => '\\"', "\t".to_sym => '\\t', }
- MAX_MAPPING_SIZE =
Maximum size of a Mapping (Map), rather aribtrary choice, not backed by any big idea.
10000
Constants included from DataMetaDom
BITSET, BOOL, BOOL_CONV, CANNED_RX, CHAR, CONVS, DATAMETA_LIB, DATETIME, DIMMED_TYPES, DOC, DOC_TARGETS, DTTM_CONV, DTTM_TYPE, END_KW, ENUM, FLOAT, FLOAT4, FLOAT8, FRACT_CONV, FULL_COMPARE, IDENTITY, ID_ONLY_COMPARE, ID_START, INCLUDE, INDENT, INDEX, INT, INT1, INT2, INT4, INT8, INTEGRAL_CONV, JAVA_DOC_TARGET, L, MAPPING, MATCHES, MODEL_LEVEL_TOKENS, NAMESPACE, NO_NAMESPACE, NUMERIC, OPTIONAL_PFX, OPT_DIMMABLE, PACK_SEPARATOR, PLAIN_DOC_TARGET, RAW, RECORD, RECORD_LEVEL_TOKENS, REC_ATTR_KEYWORDS, REQUIRED_PFX, SAME_FULL_SFX, SAME_ID_SFX, SCALE_TYPES, SOURCE_INDENT, STANDARD_TYPES, STRING, TEXT_CONV, TYPE_START, UNIQUE, URL, URL_TYPE, VERSION, VER_KW, WIKI, WIKI_REF_HTML
Class Method Summary collapse
-
.aggrJavaType(f, javaPackage) ⇒ Object
aggregated Java type.
-
.aggrType(aggr, trg, rawType, javaPackage) ⇒ Object
Figures out type adjusted for aggregates and maps.
-
.assertNamespace(name) ⇒ Object
Extracts 3 pieces of information from the given full name: * The namespace if any, i.e.
-
.classJavaDoc(docs) ⇒ Object
Java Class JavaDoc text with the Wiki reference.
-
.dataMetaSameRun(style, runnable, source, target, options = {autoVerNs: true}) ⇒ Object
Runs DataMetaSame generator for the given style.
-
.flipVer(fullName, from, to) ⇒ Object
Switches Namespace version part on a versioned DataMeta DOM entity.
-
.genDataMetaSame(parser, destDir, javaPackage, suffix, dmClass, record, fields) ⇒ Object
Generates Java source code for the DataMetaSame implementor in Java to compare by all the fields in the class for the given Record.
-
.genDataMetaSames(parser, outRoot, style = FULL_COMPARE) ⇒ Object
Runs generation of Java source code for DataMetaSame implementors for the given parser into the given output path.
-
.genEntity(model, out, record, javaPackage, baseName) ⇒ Object
Generates Java source code, the Java class for a DataMeta DOM Record.
-
.genPojos(model, outRoot) ⇒ Object
Generates java sources for the model, the POJOs.
-
.getJavaType(dmDomType) ⇒ Object
For the given DataMeta DOM data type and the isRequired flag, builds and returns the matching Java data type declaration.
-
.getJavaVal(dataType, val) ⇒ Object
Renders the value for the given DataType according to Java syntax, for all standard data types.
-
.helpDataMetaSame(file, style, errorText = nil) ⇒ Object
Shortcut to help for the Full Compare DataMetaSame generator.
-
.importSetToSrc(importSet) ⇒ Object
Converts an import set to the matching Java source snippet.
-
.javaDocs(docs) ⇒ Object
Given the property
docs
of Documentable, return the JAVA_DOC_TARGET if it is present, PLAIN_DOC_TARGET otherwise. -
.javaImports(fields) ⇒ Object
Builds Java imports for the given fields if any, per JAVA_IMPORTS.
-
.lsCondition(parser, f, javaPackage, suffix, imports, one, another) ⇒ Object
DataMetaSame condition generated for the given field.
-
.primValMethod(dt) ⇒ Object
Methods to fetch primitives values:.
-
.unaggrJavaType(dt, javaPackage) ⇒ Object
Unaggregated Java type.
-
.wrapOpt(isReq, t) ⇒ Object
Wraps type into
com.google.common.base.Optional<>
if it is requireddef wrapOpt(isReq, t); isReq ? t : "Optional<#{t}>" end
.
Instance Method Summary collapse
-
#enumJavaDoc(docs) ⇒ Object
Java Enum class-level JavaDoc text with the Wiki reference.
-
#escapeJava(what) ⇒ Object
Used to escape the given string according to the Java syntax, now deprecated, use Ruby Object.inspect or the getJavaVal method.
-
#genBitSet(out, bitSet, javaPackage, baseName) ⇒ Object
Generates Java source code for the DataMeta DOM BitSet, DataMeta DOM keyword “
bitset
”. -
#genEnumWorded(out, enum, javaPackage, baseName) ⇒ Object
Generates Java source code for the worded enum, DataMeta DOM keyword “
enum
”. -
#genMapping(out, mapping, javaPackage, baseName) ⇒ Object
Generates Java source code for the DataMeta DOM Mapping, DataMeta DOM keyword “
mapping
”. -
#genMigrations(mo1, mo2, outRoot) ⇒ Object
Generates migration guides from the given model to the given model.
-
#setAggrPrims(trgFld) ⇒ Object
The field must be an aggregate.
Methods included from DataMetaDom
combineNsBase, condenseType, fullTypeName, getParenDimInfo, getterName, help, helpAndVerFirstArg, helpMySqlDdl, #helpOracleDdl, helpPojoGen, helpScalaGen, #migrClass, nsAdjustment, #qualName, setterName, splitNameSpace, uniPath, validNs?
Class Method Details
.aggrJavaType(f, javaPackage) ⇒ Object
aggregated Java type
456 457 458 459 460 |
# File 'lib/dataMetaDom/pojo.rb', line 456 def aggrJavaType(f, javaPackage) rawType = unaggrJavaType(f.dataType, javaPackage) aggr = f.aggr? ? DataMetaDom.splitNameSpace(AGGR_CLASSES[f.aggr])[1] : nil PojoLexer.aggrType(aggr, f.trgType, rawType, javaPackage) end |
.aggrType(aggr, trg, rawType, javaPackage) ⇒ Object
Figures out type adjusted for aggregates and maps.
191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 |
# File 'lib/dataMetaDom/pojo.rb', line 191 def aggrType(aggr, trg, rawType, javaPackage) if aggr k = rawType.to_sym subType = PRIMS_TO_WRAP.has_key?(k) ? PRIMS_TO_WRAP[k] : rawType "#{aggr}<#{subType}>" elsif trg k = rawType.to_sym srcType = PRIMS_TO_WRAP.has_key?(k) ? PRIMS_TO_WRAP[k] : rawType typeRenderer = JAVA_TYPES[trg.type] rawTrg = typeRenderer ? typeRenderer.call(trg) : DataMetaDom.condenseType(trg.type, javaPackage) k = rawTrg.to_sym trgType = PRIMS_TO_WRAP.has_key?(k) ? PRIMS_TO_WRAP[k] : rawTrg "Map<#{srcType}, #{trgType}>" else rawType end end |
.assertNamespace(name) ⇒ Object
Extracts 3 pieces of information from the given full name:
-
The namespace if any, i.e. Java package, empty string if none
-
The base name for the type, without the namespace
-
Java package’s relative path, the dots replaced by the file separator.
Returns an array of these pieces of info in this exact order as described here.
861 862 863 864 865 866 867 |
# File 'lib/dataMetaDom/pojo.rb', line 861 def assertNamespace(name) ns, base = DataMetaDom.splitNameSpace(name) javaPackage = DataMetaDom.validNs?(ns, base) ? ns : '' packagePath = javaPackage.empty? ? '' : javaPackage.gsub('.', File::SEPARATOR) [javaPackage, base, packagePath] end |
.classJavaDoc(docs) ⇒ Object
Java Class JavaDoc text with the Wiki reference.
229 230 231 232 233 234 235 236 237 |
# File 'lib/dataMetaDom/pojo.rb', line 229 def classJavaDoc(docs) return <<CLASS_JAVADOC /** #{PojoLexer.javaDocs(docs)} * This class is generated by * #{WIKI_REF_HTML}. */ CLASS_JAVADOC end |
.dataMetaSameRun(style, runnable, source, target, options = {autoVerNs: true}) ⇒ Object
Runs DataMetaSame generator for the given style.
Parameters:
-
style
- FULL_COMPARE or ID_ONLY_COMPARE -
runnable
- the name of the executable script that called this method, used for help
1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 |
# File 'lib/dataMetaDom/pojo.rb', line 1006 def dataMetaSameRun(style, runnable, source, target, ={autoVerNs: true}) @source, @target = source, target helpDataMetaSame runnable, style unless @source && @target helpDataMetaSame(runnable, style, "DataMeta DOM source #{@source} is not a file") unless File.file?(@source) helpDataMetaSame(runnable, style, "DataMetaSame destination directory #{@target} is not a dir") unless File.directory?(@target) @parser = Model.new begin @parser.parse(@source, ) genDataMetaSames(@parser, @target, style) rescue Exception => e puts "ERROR #{e.}; #{@parser.diagn}" puts e.backtrace.inspect end end |
.flipVer(fullName, from, to) ⇒ Object
Switches Namespace version part on a versioned DataMeta DOM entity.
1033 1034 1035 |
# File 'lib/dataMetaDom/pojo.rb', line 1033 def flipVer(fullName, from, to) fullName.to_s.gsub(".v#{from}.", ".v#{to}.").to_sym end |
.genDataMetaSame(parser, destDir, javaPackage, suffix, dmClass, record, fields) ⇒ Object
Generates Java source code for the DataMetaSame implementor in Java to compare by all the fields in the class for the given Record.
No attempt made to pretty-format the output. Pretty-formatting makes sense only when human eyes look at the generated code in which case one keyboard shortcut gets the file pretty-formatted. Beside IDEs, there are Java pretty-formatters that can be plugged in into the build process:
To name a few.
Parameters:
-
parser
- the instance of Model -
destDir
- destination root. -
javaPackage
- Java package to export to -
suffix
- The suffix to append to the DataMeta DOM Class to get the DataMetaSame implementor’s name. -
dmClass
- the name of DataMeta DOM class to generate for -
record
- the DataMeta DOM record to generate the DataMetaSame implementors for. -
fields
- a collection of fields to compare
517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 |
# File 'lib/dataMetaDom/pojo.rb', line 517 def genDataMetaSame(parser, destDir, javaPackage, suffix, dmClass, record, fields) conditions = [] aggrChecks = '' imports = javaImports(fields) javaClass = "#{dmClass}#{suffix}" fields.each { |f| g = "#{DataMetaDom.getterName(f)}()" if f.aggr? if f.set? =begin # no option with the Set for full compare -- a Set is a Set, must use equals and hashCode but, if in future a need arises, could use the following pattern -- tested, the stream shortcut works fine: final Set<String> personas___1__ = one.getPersonas(); final Set<String> personas___2__ = another.getPersonas(); if(personas___1__ != personas___2__) { if(personas___1__ == null || personas___2__ == null ) return false; // one of them is null but not both -- not equal short-circuit if(personas___1__.size() != personas___2__.size()) return false; // this should run in supposedly O(N), since Set.contains(v) is supposedly O(1) final Optional<String> firstMismatch = personas___1__.stream().filter(v -> !personas___2__.contains(v)).findFirst(); if(firstMismatch.isPresent()) return false; } =end conditions << %|(one.#{g} != null && one.#{g}.equals(another.#{g}))| else a1 = "#{f.name}___1__" a2 = "#{f.name}___2__" li1 = "#{f.name}___li1__" li2 = "#{f.name}___li2__" jt = unaggrJavaType(f.dataType, javaPackage) aggrChecks << %| #{INDENT * 2}final #{aggrJavaType(f, javaPackage)} #{a1} = one.#{g}; #{INDENT * 2}final #{aggrJavaType(f, javaPackage)} #{a2} = another.#{g}; #{INDENT * 2}if(#{a1} != #{a2} ) { #{INDENT * 3}if(#{a1} == null #{'||'} #{a2} == null ) return false; // one of them is null but not both -- not equal short-circuit #{INDENT * 3}java.util.ListIterator<#{jt}> #{li1} = #{a1}.listIterator(); #{INDENT * 3}java.util.ListIterator<#{jt}> #{li2} = #{a2}.listIterator(); #{INDENT * 3}while(#{li1}.hasNext() && #{li2}.hasNext()) { #{INDENT * 4}final #{jt} o1 = #{li1}.next(), o2 = #{li2}.next(); #{INDENT * 4}if(!(o1 == null ? o2 == null : #{lsCondition(parser, f, javaPackage, suffix, imports, 'o1', 'o2')})) return false; // shortcircuit to false #{INDENT * 3}} #{INDENT * 3}if(#{li1}.hasNext() #{'||'} #{li2}.hasNext()) return false; // leftover elements in one #{INDENT * 2}} | end elsif f.map? a1 = "#{f.name}___1__" a2 = "#{f.name}___2__" aggrChecks << %| #{INDENT * 2}final java.util.Map<#{unaggrJavaType(f.dataType, javaPackage)}, #{unaggrJavaType(f.trgType, javaPackage)}> #{a1} = one.#{g}; #{INDENT * 2}final java.util.Map<#{unaggrJavaType(f.dataType, javaPackage)}, #{unaggrJavaType(f.trgType, javaPackage)}> #{a2} = another.#{g}; #{INDENT * 2}if(#{a1} != #{a2} ) { #{INDENT * 3}if(#{a1} == null #{'||'} #{a2} == null ) return false; // one of them is null but not both -- not equal short-circuit #{INDENT * 3}if(!#{a1}.equals(#{a2})) return false; // Maps are shallow-compared, otherwise logic and spread of semantics are too complex #{INDENT * 2}} | else # regular field conditions << lsCondition(parser, f, javaPackage, suffix, imports, "one.#{g}", "another.#{g}") end } out = File.open(File.join(destDir, "#{javaClass}.java"), 'wb') out.puts <<DM_SAME_CLASS package #{javaPackage}; #{importSetToSrc(imports)} import org.ebay.datameta.dom.DataMetaSame; import org.ebay.datameta.util.jdk.SemanticVersion; #{PojoLexer.classJavaDoc record.docs}public class #{javaClass} implements DataMetaSame<#{dmClass}>{ #{INDENT}/** #{INDENT}* Convenience instance. #{INDENT}*/ #{INDENT}public final static #{javaClass} I = new #{javaClass}(); #{INDENT}@Override public boolean isSame(final #{dmClass} one, final #{dmClass} another) { #{INDENT * 2}if(one == another) return true; // same object or both are null #{INDENT * 2}//noinspection SimplifiableIfStatement #{INDENT * 2}if(one == null || another == null) return false; // whichever of them is null but the other is not #{INDENT * 2}#{aggrChecks} #{INDENT * 2}return #{conditions.join(' && ')}; #{INDENT}} DM_SAME_CLASS if record.ver out.puts %Q<#{INDENT}public static final SemanticVersion VERSION = SemanticVersion.parse("#{record.ver.full}");> end out.puts '}' out.close end |
.genDataMetaSames(parser, outRoot, style = FULL_COMPARE) ⇒ Object
Runs generation of Java source code for DataMetaSame implementors for the given parser into the given output path.
Parameters:
-
parser
- an instance of DataMetaDom::Model -
outRoot
- the path to output the generated Java packages into -
style
- can pass one of the following values:* ID_ONLY_COMPARE - see the docs to it * FULL_COMPARE - see the docs to it
616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 |
# File 'lib/dataMetaDom/pojo.rb', line 616 def genDataMetaSames(parser, outRoot, style = FULL_COMPARE) parser.records.values.each { |record| javaPackage, base, packagePath = assertNamespace(record.name) destDir = File.join(outRoot, packagePath) FileUtils.mkdir_p destDir case style when FULL_COMPARE suffix = SAME_FULL_SFX fields = record.fields.values when ID_ONLY_COMPARE unless record.identity L.warn "#{record.name} does not have identity defined" next end suffix = SAME_ID_SFX fields = record.fields.keys.select{|k| record.identity.hasArg?(k)}.map{|k| record.fields[k]} else; raise %Q<Unsupported DataMetaSame POJO style "#{style}"> end if fields.empty? L.warn "#{record.name} does not have any fields to compare by" next end genDataMetaSame parser, destDir, javaPackage, suffix, base, record, fields } end |
.genEntity(model, out, record, javaPackage, baseName) ⇒ Object
Generates Java source code, the Java class for a DataMeta DOM Record
Parameters:
-
model
- the source model to export from -
out
- open output file to write the result to. -
record
- instance of DataMetaDom::Record to export -
javaPackage
- Java package to export to -
baseName
- the name of the class to generate.
336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 |
# File 'lib/dataMetaDom/pojo.rb', line 336 def genEntity(model, out, record, javaPackage, baseName) fields = record.fields # scan for imports needed out.puts <<ENTITY_CLASS_HEADER package #{javaPackage}; #{importSetToSrc(javaImports fields.values)} import org.ebay.datameta.dom.Verifiable; import java.util.Objects; import java.util.StringJoiner; import org.ebay.datameta.dom.VerificationException; import org.ebay.datameta.util.jdk.SemanticVersion; import static org.ebay.datameta.dom.CannedRegexUtil.getCannedRegEx; #{PojoLexer.classJavaDoc(record.docs)}public class #{baseName} implements Verifiable { ENTITY_CLASS_HEADER if record.ver out.puts %Q<#{INDENT}public static final SemanticVersion VERSION = SemanticVersion.parse("#{record.ver.full}"); > end fieldDeclarations = '' gettersSetters = '' eqHashFields = record.identity ? record.identity.args : fields.keys.sort reqFields = fields.values.select{|f| f.isRequired }.map{|f| f.name} rxRoster = JavaRegExRoster.new fieldVerifications = '' fields.each_key { |k| f = fields[k] dt = f.dataType rxRoster.register(f) if f.regex typeDef = aggrJavaType(f, javaPackage) if f.trgType # Maps: if either the key or the value is verifiable, do it mainVf = model.records[dt.type] # main data type is verifiable trgVf = model.records[f.trgType.type] # target type is verifiable if mainVf || trgVf fieldVerifications << "#{INDENT*2}#{!f.isRequired ? "if(#{f.name} != null) " : '' }#{f.name}.forEach((k,v) -> {#{mainVf ? 'k.verify();' : ''} #{trgVf ? 'v.verify();' : ''}});\n" end end if model.records[dt.type] && !f.trgType # maps handled separately fieldVerifications << "#{INDENT*2}#{!f.isRequired ? "if(#{f.name} != null) " : '' }#{f.name}#{f.aggr ? '.forEach(Verifiable::verify)' : '.verify()'};\n" # the Verifiable::verify method reference works just fine, tested it: Java correctly calls the method on the object end fieldDeclarations << "\n#{INDENT}private #{wrapOpt(f.isRequired, typeDef)} #{f.name};" if f.isRequired gettersSetters << <<CHECKING_SETTER #{INDENT}public void #{DataMetaDom.setterName(f)}(final #{typeDef} newValue) { #{INDENT*2}if(newValue == null) throw new IllegalArgumentException( #{INDENT*4}"NULL passed to the setter of the required field '#{f.name}' on the class #{record.name}."); #{INDENT*2}this.#{f.name} = newValue; #{INDENT}} CHECKING_SETTER else # not required, can not be primitive - wrap into Optional<> gettersSetters << "\n#{INDENT}public void #{DataMetaDom.setterName(f)}(final #{wrapOpt(f.isRequired, typeDef)} newValue) {this.#{f.name} = newValue; }\n" end if f.docs.empty? gettersSetters << "\n" else gettersSetters << <<FIELD_JAVADOC #{INDENT}/** #{PojoLexer.javaDocs f.docs}#{INDENT} */ FIELD_JAVADOC end gettersSetters << "#{INDENT}public #{wrapOpt(f.isRequired, typeDef)} #{DataMetaDom.getterName(f)}() {return this.#{f.name}; }\n" } out.puts(rxRoster.to_patterns) out.puts fieldDeclarations out.puts out.puts gettersSetters out.puts %| #{INDENT}/** #{INDENT}* If there is class type mismatch, somehow we are comparing apples to oranges, this is an error, not #{INDENT}* a not-equal condition. #{INDENT}*/ #{INDENT}@SuppressWarnings("EqualsWhichDoesntCheckParameterClass") @Override public boolean equals(Object other) { #{INDENT * 2}return Objects.deepEquals(new Object[]{#{eqHashFields.map{|q| "this.#{q}"}.join(', ')}}, #{INDENT * 2} new Object[]{#{eqHashFields.map{|q| "((#{baseName}) other).#{q}"}.join(', ')}}); #{INDENT}} | out.puts %| #{INDENT}@Override public int hashCode() {// null - safe: result = 31 * result + (element == null ? 0 : element.hashCode()); #{INDENT * 2}return Objects.hash(#{eqHashFields.map{|q| "this.#{q}"}.join(', ')}); #{INDENT}} | verCalls = reqFields.map{|r| %<if(#{r} == null) missingFields.add("#{r}");>}.join("\n#{INDENT * 2}") out.puts %| #{INDENT}public void verify() { | unless verCalls.empty? out.puts %| #{INDENT * 2}StringJoiner missingFields = new StringJoiner(", "); #{INDENT * 2}#{verCalls} #{INDENT * 2}if(missingFields.length() != 0) throw new VerificationException(getClass().getSimpleName() + ": required fields not set: " + missingFields); | end out.puts %| #{rxRoster.to_verifications} #{fieldVerifications} #{INDENT}} | out.puts %< #{INDENT}public final SemanticVersion getVersion() { return VERSION; } }> end |
.genPojos(model, outRoot) ⇒ Object
Generates java sources for the model, the POJOs.
-
Parameters
-
parser
- instance of Model -
outRoot
- output directory
-
875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 |
# File 'lib/dataMetaDom/pojo.rb', line 875 def genPojos(model, outRoot) (model.enums.values + model.records.values).each { |e| javaPackage, base, packagePath = assertNamespace(e.name) destDir = File.join(outRoot, packagePath) FileUtils.mkdir_p destDir out = File.open(File.join(destDir, "#{base}.java"), 'wb') begin case when e.kind_of?(DataMetaDom::Record) genEntity model, out, e, javaPackage, base when e.kind_of?(DataMetaDom::Mappings) genMapping out, e, javaPackage, base when e.kind_of?(DataMetaDom::Enum) genEnumWorded out, e, javaPackage, base when e.kind_of?(DataMetaDom::BitSet) genBitSet out, e, javaPackage, base else raise "Unsupported Entity: #{e.inspect}" end ensure out.close end } end |
.getJavaType(dmDomType) ⇒ Object
For the given DataMeta DOM data type and the isRequired flag, builds and returns the matching Java data type declaration. For standard types, uses the JAVA_TYPES map
256 257 258 259 |
# File 'lib/dataMetaDom/pojo.rb', line 256 def getJavaType(dmDomType) typeRenderer = JAVA_TYPES[dmDomType.type] typeRenderer ? typeRenderer.call(dmDomType) : dmDomType.type end |
.getJavaVal(dataType, val) ⇒ Object
Renders the value for the given DataType according to Java syntax, for all standard data types. See STANDARD_TYPES.
265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 |
# File 'lib/dataMetaDom/pojo.rb', line 265 def getJavaVal(dataType, val) case when dataType.type == DATETIME %Q< java.time.ZonedDateTime.from(java.time.format.DateTimeFormatter.ISO_DATE_TIME.parse("#{val.to_s}")) > when dataType.type == NUMERIC %Q< new BigDecimal(#{val.inspect}) > when val.kind_of?(Symbol) val.to_s.inspect when dataType.type == FLOAT && dataType.length <= 4 "#{val.inspect}F" when dataType.type == INT && dataType.length > 4 "#{val.inspect}L" when dataType.type == URL %Q< new java.net.URL(#{val.inspect}) > else val.inspect end end |
.helpDataMetaSame(file, style, errorText = nil) ⇒ Object
Shortcut to help for the Full Compare DataMetaSame generator.
1023 1024 1025 1026 1027 1028 1029 1030 |
# File 'lib/dataMetaDom/pojo.rb', line 1023 def helpDataMetaSame(file, style, errorText=nil) styleWording = case style when FULL_COMPARE; "Full" when ID_ONLY_COMPARE; "Identity Only" else raise %Q<Unsupported identity style "#{style}"> end help(file, "#{styleWording} Compare DataMetaSame generator", '<DataMeta DOM source> <target directory>', errorText) end |
.importSetToSrc(importSet) ⇒ Object
Converts an import set to the matching Java source snippet.
322 323 324 |
# File 'lib/dataMetaDom/pojo.rb', line 322 def importSetToSrc(importSet) importSet.to_a.map{|k| "import #{k};"}.sort.join("\n") + "\n" end |
.javaDocs(docs) ⇒ Object
Given the property docs
of Documentable, return the JAVA_DOC_TARGET if it is present, PLAIN_DOC_TARGET otherwise. Returns empty string if the argument is nil.
215 216 217 218 219 220 221 222 223 224 225 |
# File 'lib/dataMetaDom/pojo.rb', line 215 def javaDocs(docs) return '' unless docs case when docs[JAVA_DOC_TARGET] docs[JAVA_DOC_TARGET].text when docs[PLAIN_DOC_TARGET] docs[PLAIN_DOC_TARGET].text else '' end end |
.javaImports(fields) ⇒ Object
Builds Java imports for the given fields if any, per JAVA_IMPORTS. Returns the text of imports to insert straight into the Java source file
301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 |
# File 'lib/dataMetaDom/pojo.rb', line 301 def javaImports(fields) imports = Set.new fields.each { |f| importable = JAVA_IMPORTS[f.dataType.type] imports << importable.to_sym if importable if f.aggr? imports << AGGR_CLASSES[f.aggr].to_sym elsif f.map? imports << MAP_IMPORT.to_sym srcImport = JAVA_IMPORTS[f.trgType.type] imports << srcImport.to_sym if srcImport end } imports # remnant of the Optional effort for non-required fields #hasOpt = fields.values.map{|f| !f.isRequired }.reduce(:|) # true if there is at least one optional field #imports << 'com.google.common.base.Optional' << 'static com.google.common.base.Optional.fromNullable' if hasOpt end |
.lsCondition(parser, f, javaPackage, suffix, imports, one, another) ⇒ Object
DataMetaSame condition generated for the given field. This applies only for a single instance. All aggregation specifics are hhandled elsewhere (see genDataMetaSame)
466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 |
# File 'lib/dataMetaDom/pojo.rb', line 466 def lsCondition(parser, f, javaPackage, suffix, imports, one, another) dt = f.dataType g = "#{DataMetaDom.getterName(f)}()" if false # Ruby prints the warning that the var is unused, unable to figure out that it is used in the ERB file # and adding insult to injury, the developers didn't think of squelching the false warnings p g end typeRec = parser.records[dt.type] enumType = parser.enums[dt.type] case when typeRec ftNs, ftClassBase = DataMetaDom.splitNameSpace(typeRec.name) # the name of the DataMetaSame implementor of the Field's type, assuming it is available during compile time ftLsClassBase = "#{ftClassBase}#{suffix}" # import the class if it belogns to a different package imports << "#{DataMetaDom.combineNsBase(ftNs, ftLsClassBase)}" unless javaPackage == ftNs %Q<#{ftLsClassBase}.I.isSame(#{one}, #{another})> when (f.isRequired && PRIMITIVABLE_TYPES.member?(dt.type)) || (enumType && enumType.kind_of?(DataMetaDom::Enum)) %Q<(#{one} == #{another})> when enumType && enumType.kind_of?(DataMetaDom::Mappings) %Q<MAP_EQ.isSame(#{one}, #{another})> else # leverage the equals method, that works for the BitMaps too %Q<EQ.isSame(#{one}, #{another})> end end |
.primValMethod(dt) ⇒ Object
Methods to fetch primitives values:
110 111 112 113 114 115 116 117 118 119 |
# File 'lib/dataMetaDom/pojo.rb', line 110 def primValMethod(dt) case dt.type when INT dt.length < 5 ? 'intValue' : 'longValue' when FLOAT dt.length < 5 ? 'floatValue' : 'doubleValue' else raise ArgumentError, %<Can't determine primitive value method for the data type: #{dt}> end end |
.unaggrJavaType(dt, javaPackage) ⇒ Object
Unaggregated Java type
450 451 452 453 |
# File 'lib/dataMetaDom/pojo.rb', line 450 def unaggrJavaType(dt, javaPackage) typeRenderer = JAVA_TYPES[dt.type] typeRenderer ? typeRenderer.call(dt) : DataMetaDom.condenseType(dt.type, javaPackage) end |
.wrapOpt(isReq, t) ⇒ Object
Wraps type into com.google.common.base.Optional<>
if it is required <tt>
def wrapOpt(isReq, t); isReq ? t : "Optional<#{t}>" end
</tt>
After a bit of thinking, decided not to employ the Optional idiom by Guava then of the JDK 8 for the following reasons:
-
the pros don’t look a clear winner vs the cons
-
if an optional field is made non-optional, lots of refactoring would be needed that is hard to automate
-
Java developers are used to deal with nulls, not so with Optionals
-
it requires dragging another dependency - Guava with all the generated files.
-
wrapping objects into Optional would create a lot of extra objects in the heap potentially with long lifespan
-
134 |
# File 'lib/dataMetaDom/pojo.rb', line 134 def wrapOpt(isReq, t); t end |
Instance Method Details
#enumJavaDoc(docs) ⇒ Object
Java Enum class-level JavaDoc text with the Wiki reference.
242 243 244 245 246 247 248 249 250 |
# File 'lib/dataMetaDom/pojo.rb', line 242 def enumJavaDoc(docs) return <<ENUM_JAVADOC /** #{PojoLexer.javaDocs(docs)} * This enum is generated by * #{WIKI_REF_HTML}. */ ENUM_JAVADOC end |
#escapeJava(what) ⇒ Object
Used to escape the given string according to the Java syntax, now deprecated, use Ruby Object.inspect or the getJavaVal method.
288 289 290 291 292 293 294 295 |
# File 'lib/dataMetaDom/pojo.rb', line 288 def escapeJava(what) result = '' what.each_char { |c| replacement = JAVA_ESCAPE_HASH[c.to_sym] result << ( replacement ? replacement : c ) } result end |
#genBitSet(out, bitSet, javaPackage, baseName) ⇒ Object
Generates Java source code for the DataMeta DOM BitSet, DataMeta DOM keyword “bitset
”.
804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 |
# File 'lib/dataMetaDom/pojo.rb', line 804 def genBitSet(out, bitSet, javaPackage, baseName) keys = bitSet.keys toType = getJavaType(bitSet.toT) importable = JAVA_IMPORTS[bitSet.toT.type] importTxt = importable ? "import #{importable};" : '' maxBit = bitSet.keys.max raise "Mapping too big, size = #{maxBit}, max size #{MAX_MAPPING_SIZE}" if maxBit > MAX_MAPPING_SIZE out.puts <<BIT_SET_HEADER package #{javaPackage}; import org.ebay.datameta.dom.BitSetImpl; import org.ebay.datameta.util.jdk.SemanticVersion; #{importTxt} #{PojoLexer.classJavaDoc bitSet.docs}public final class #{baseName} extends BitSetImpl<#{toType}>{ public static final int MAX_BIT = #{maxBit}; public static final int COUNT = MAX_BIT + 1; // we do not expect huge arrays here, the sizes should be very limited and likely continuous. private static final #{toType}[] mapping = new #{toType}[COUNT]; static { BIT_SET_HEADER keys.sort.each { |k| out.puts %Q<#{INDENT*2}mapping[#{k}] = #{getJavaVal(bitSet.toT, bitSet[k])};> } out.puts <<BIT_SET_FOOTER } public #{baseName}() { } public #{baseName}(long[] image) { super(image); } public final int getCount() { return COUNT; } public final #{toType}[] getMap() { return mapping;} BIT_SET_FOOTER if bitSet.ver out.puts %Q< public static final SemanticVersion VERSION = SemanticVersion.parse("#{bitSet.ver.full}");> end out.puts %< #{INDENT}public final SemanticVersion getVersion() { return VERSION; } }> end |
#genEnumWorded(out, enum, javaPackage, baseName) ⇒ Object
Generates Java source code for the worded enum, DataMeta DOM keyword “enum
”.
645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 |
# File 'lib/dataMetaDom/pojo.rb', line 645 def genEnumWorded(out, enum, javaPackage, baseName) values = enum.keys.map{|k| enum[k]} # sort by ordinals to preserve the order out.puts <<ENUM_CLASS_HEADER package #{javaPackage}; import javax.annotation.Nullable; import java.util.HashMap; import java.util.Map; import org.ebay.datameta.dom.DataMetaEntity; import org.ebay.datameta.util.jdk.SemanticVersion; import static java.util.Collections.unmodifiableMap; #{enumJavaDoc(enum.docs)}public enum #{baseName} implements DataMetaEntity { #{values.join(', ')}; /** * Staple Java lazy init idiom. * See <a href="http://en.wikipedia.org/wiki/Initialization-on-demand_holder_idiom">this article</a>. */ private static class LazyInit { final static Map<String, #{baseName}> NAME_TO_ENUM; final static #{baseName}[] ORD_TO_ENUM = new #{baseName}[values().length]; static { final Map<String, #{baseName}> map = new HashMap<>(values().length * 3 / 2 + 1); for (int ix = 0; ix < values().length; ix++) { final #{baseName} val = values()[ix]; ORD_TO_ENUM[ix] = val; map.put(val.name(), val); } NAME_TO_ENUM = unmodifiableMap(map); } } /** * Retrieve a value for the given textual form. * Lenient replacement for {@link Enum#valueOf(Class, java.lang.String)} that returns null * instead of throwing an exception. */ @Nullable public static #{baseName} forName(final String textual) { return LazyInit.NAME_TO_ENUM.get(textual); } /** * Fast instance retrieval for the given ordinal, works super fast because it uses an array * indexing, not a map. */ @Nullable public static #{baseName} forOrd(final int ordinal) { return LazyInit.ORD_TO_ENUM[ordinal]; } public static interface Visitor<IN, OUT> { ENUM_CLASS_HEADER values.each { |v| out.puts " OUT visit#{v}(IN input);" } out.puts <<VISITOR_SWITCH_HEAD } /** Use this switch with your {@link Visitor} implementation, * There should be no other switches of this kind in your program. * If the enum changes, all implementations will break and will need to be fixed. * This will ensure that no unhandled cases will be left in the program. */ public static <IN, OUT> OUT visit(final #{baseName} value, final Visitor<IN, OUT> visitor, final IN input) { switch(value) { VISITOR_SWITCH_HEAD values.each { |v| out.puts " case #{v}:\n return visitor.visit#{v}(input);" } out.puts <<VISITOR_SWITCH_TAIL default: throw new IllegalArgumentException("Unsupported enum value: " + value); } } VISITOR_SWITCH_TAIL if enum.ver out.puts %Q< public static final SemanticVersion VERSION = SemanticVersion.parse("#{enum.ver.full}");> end out.puts %< #{INDENT}public final SemanticVersion getVersion() { return VERSION; } }> end |
#genMapping(out, mapping, javaPackage, baseName) ⇒ Object
Generates Java source code for the DataMeta DOM Mapping, DataMeta DOM keyword “mapping
”.
734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 |
# File 'lib/dataMetaDom/pojo.rb', line 734 def genMapping(out, mapping, javaPackage, baseName) keys = mapping.keys raise "Mapping too big, size = #{keys.length}, max size #{MAX_MAPPING_SIZE}" if keys.length > MAX_MAPPING_SIZE fromType = getJavaType(mapping.fromT) toType = getJavaType(mapping.toT) imports = {} importable = JAVA_IMPORTS[mapping.fromT.type]; imports[importable.to_sym] = 1 if importable importable = JAVA_IMPORTS[mapping.toT.type]; imports[importable.to_sym] = 1 if importable importText = imports.keys.to_a.map{|i| "import #{i};"}.join("\n") mapGeneric = "#{fromType}, #{toType}" out.puts <<MAPPING_CLASS_HEADER package #{javaPackage}; import org.ebay.datameta.dom.Mapping; #{importText} import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.Map; import java.util.Set; import org.ebay.datameta.util.jdk.SemanticVersion; #{PojoLexer.classJavaDoc mapping.docs}public final class #{baseName} implements Mapping<#{mapGeneric}>{ private final static Map<#{mapGeneric}> mapping; protected final static int count = #{keys.length}; static { final Map<#{mapGeneric}> m = new HashMap<#{mapGeneric}>(count * 3 / 2 + 1); MAPPING_CLASS_HEADER keys.sort.each { |k| out.puts %Q<#{INDENT*2}m.put(#{getJavaVal(mapping.fromT, k)}, #{getJavaVal(mapping.toT, mapping[k])});> } out.puts <<MAPPING_CLASS_FOOTER mapping = Collections.unmodifiableMap(m); } public static int size() { return mapping.size(); } public static boolean containsKey(#{fromType} key) { return mapping.containsKey(key); } public static #{toType} get(#{fromType} key) { return mapping.get(key); } public static Set<#{fromType}> keySet() { return mapping.keySet(); } public static Collection<#{toType}> values() { return mapping.values(); } private static void assertKey(#{fromType} key) { if(!mapping.containsKey(key)) throw new IllegalArgumentException("The key " + key + " does not belong to this mapping"); } private #{fromType} key; public #{baseName}(){} public #{baseName}(#{fromType} key){ assertKey(key); this.key = key;} public void setKey(#{fromType} key) {assertKey(key); this.key = key; } public #{fromType} getKey() { return key; } public #{toType} getValue() { return mapping.get(key); } @Override public String toString() { return getClass().getSimpleName() + '{' + key + "=>" + mapping.get(key) + '}'; } MAPPING_CLASS_FOOTER if mapping.ver out.puts %Q< public static final SemanticVersion VERSION = SemanticVersion.parse("#{mapping.ver.full}");> end out.puts %< #{INDENT}public final SemanticVersion getVersion() { return VERSION; } }> end |
#genMigrations(mo1, mo2, outRoot) ⇒ Object
Generates migration guides from the given model to the given model
903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 |
# File 'lib/dataMetaDom/pojo.rb', line 903 def genMigrations(mo1, mo2, outRoot) v1 = mo1.records.values.first.ver.full v2 = mo2.records.values.first.ver.full destDir = outRoot javaPackage = '' # set the scope for the var vars = OpenStruct.new # for template's local variables. ERB does not make them visible to the binding if false # Ruby prints the warning that the var is unused, unable to figure out that it is used in the ERB file # and adding insult to injury, the developers didn't think of squelching the false warnings p vars # it's interesting that there is no warning about the unused destDir and javaPackage. Duh! end # sort the models by versions out, 2nd to be the latest: raise ArgumentError, "Versions on the model are the same: #{v1}, nothing to migrate" if v1 == v2 if v1 > v2 model2 = mo1 model1 = mo2 ver1 = v2 ver2 = v1 else model2 = mo2 model1 = mo1 ver1 = v1 ver2 = v2 end puts "Migrating from ver #{ver1} to #{ver2}" ctxs = [] droppedRecs = [] addedRecs = [] (model1.enums.values + model1.records.values).each { |srcE| trgRecName = flipVer(srcE.name, ver1.toVarName, ver2.toVarName) trgE = model2.records[trgRecName] || model2.enums[trgRecName] droppedRecs << srcE.name unless trgE } (model2.enums.values + model2.records.values).each { |trgE| srcRecName = flipVer(trgE.name, ver2.toVarName, ver1.toVarName) srcE = model1.records[srcRecName] || model1.enums[srcRecName] unless srcE addedRecs << trgE.name next end javaPackage, baseName, packagePath = assertNamespace(trgE.name) javaClassName = migrClass(baseName, ver1, ver2) destDir = File.join(outRoot, packagePath) migrCtx = MigrCtx.new trgE.name ctxs << migrCtx FileUtils.mkdir_p destDir javaDestFile = File.join(destDir, "#{javaClassName}.java") case when trgE.kind_of?(DataMetaDom::Record) if File.file?(javaDestFile) migrCtx.isSkipped = true $stderr.puts %<Migration target "#{javaDestFile} present, therefore skipped"> else IO::write(javaDestFile, ERB.new(IO.read(File.join(File.dirname(__FILE__), '../../tmpl/java/migrationEntityEnums.erb')), $SAFE, '%<>').result(binding), mode: 'wb') end when trgE.kind_of?(DataMetaDom::Mappings) $stderr.puts "WARN: Migration guides for the mapping #{trgE.name} are not generated; migration is not implemented for mappings" when trgE.kind_of?(DataMetaDom::Enum) # handled by the POJO migrator above, i.e. the case when trgE.kind_of?(DataMetaDom::Record) when trgE.kind_of?(DataMetaDom::BitSet) $stderr.puts "WARN: Migration guides for the bitset #{trgE.name} are not generated; migration is not implemented for bitsets" else raise "Unsupported Entity: #{trgE.inspect}" end } noAutos = ctxs.reject{|c| c.canAuto} skipped = ctxs.select{|c| c.isSkipped} unless skipped.empty? $stderr.puts %<Skipped: #{skipped.size}> end unless noAutos.empty? $stderr.puts %<#{noAutos.size} class#{noAutos.size > 1 ? 'es' : ''} out of #{ctxs.size} can not be migrated automatically: #{noAutos.map{|c| c.rec}.sort.join("\n")} Please edit the Migrate_ code for #{noAutos.size > 1 ? 'these' : 'this one'} manually. > end unless droppedRecs.empty? $stderr.puts %<#{droppedRecs.size} class#{droppedRecs.size > 1 ? 'es were' : ' was'} dropped from your model: #{droppedRecs.sort.join("\n")} -- you may want to review if #{droppedRecs.size > 1 ? 'these were' : 'this one was'} properly handled. > end unless addedRecs.empty? $stderr.puts %<#{addedRecs.size} class#{addedRecs.size > 1 ? 'es were' : ' was'} added to your model: #{addedRecs.sort.join("\n")} -- no migration guides were generated for #{addedRecs.size > 1 ? 'these' : 'this one'}. > end end |
#setAggrPrims(trgFld) ⇒ Object
The field must be an aggregate. The method puts together an argument for the proper collection setter
1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 |
# File 'lib/dataMetaDom/pojo.rb', line 1038 def setAggrPrims(trgFld) #new LinkedList<>(src.getInts().stream().map(Integer::longValue).collect(Collectors.toList())) case trgFld.aggr when Field::SET %|src.#{DataMetaDom.getterName(trgFld)}().stream().map(e -> e.#{primValMethod(trgFld.dataType)}()).collect(toSet())| when Field::LIST %|new ArrayList<>(src.#{DataMetaDom.getterName(trgFld)}().stream().map(e -> e.#{primValMethod(trgFld.dataType)}()).collect(toList()))| when Field::DEQUE %|new LinkedList<>(src.#{DataMetaDom.getterName(trgFld)}().stream().map(e -> e.#{primValMethod(trgFld.dataType)}()).collect(toList()))| else raise ArgumentError, %<Unsupported aggregation type on the field: #{trgFld}> end end |