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

Instance Method Summary collapse

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, options={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, options)
      genDataMetaSames(@parser, @target, style)
    rescue Exception => e
       puts "ERROR #{e.message}; #{@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