Module: DataMetaDom::ScalaLexer

Includes:
DataMetaDom
Defined in:
lib/dataMetaDom/scala.rb

Overview

Definition for generating Scala artifacts such as case classes and everything related that depends on Scala distro only witout any other dependencies.

For command line details either check the new method’s source or the README.rdoc file, the usage section.

Constant Summary collapse

SCALA_SUBPACKAGE =

Scala Data Meta export subpackage, to distinguish between other platforms. Can not make it just “.scala” because if someone imports all the data model classes with underscore, they may pick up the “scala” subpackage too.

In which case, they will have trouble importing anything from the Scala core by “scala.*”, that violates the principle of “Least Astonishment”, they may dink around till they find out that they will have to use the root package to access the Scala core’s “scala”, not the exported DataMeta “scala”.

'scadm'
TEXTUAL_TYPER =

Renderer for the String type.

lambda{|t| 'String'}
SCALA_IMPORTS =

Maps DataMeta DOM datatypes to the matching Scala classes, for those that need to be imported. The Scala source generator will import these if they are used in the class.

{
        DATETIME => 'java.time.ZonedDateTime',
        NUMERIC => 'scala.math.BigDecimal'
}
AGGR_CLASSES =

DataMeta DOM aggregated field type spec mapped to matching Scala Case class:

{
        Field::SET => 'scala.collection.Seq', # for Case classes, if the identity is different that full set of fields, Set makes no sense
        # which is a majority of the cases. Wait for full implementation and switch to scala.collection.mutable.Set
        Field::LIST => 'scala.collection.immutable.List',
        Field::DEQUE => 'scala.collection.Seq',
}
SCALA_TYPES =

A map from DataMeta DOM standard types to the lambdas that render correspondent Scala types per Scala 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; 'Int'
                when len <=8; '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| 'Array[Byte]'},
        URL => lambda{|t| URL_CLASS},
        NUMERIC => lambda{|t| 'BigDecimal'}
}
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, fullTypeName, getParenDimInfo, getterName, help, helpAndVerFirstArg, helpMySqlDdl, #helpOracleDdl, helpPojoGen, helpScalaGen, #migrClass, nsAdjustment, #qualName, setterName, splitNameSpace, uniPath, validNs?

Class Method Details

.aggrScalaType(f, scalaPackage) ⇒ Object

aggregated Scala type



125
126
127
128
129
# File 'lib/dataMetaDom/scala.rb', line 125

def self.aggrScalaType(f, scalaPackage)
    rawType = self.unaggrScalaType(f.dataType, scalaPackage)
    aggr = f.aggr? ? DataMetaDom.splitNameSpace(AGGR_CLASSES[f.aggr])[1] : nil
    ScalaLexer.aggrType(aggr, f.trgType, rawType, scalaPackage)
end

.aggrType(aggr, trg, rawType, scalaPackage) ⇒ Object

Figures out type adjusted for aggregates and maps.



91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
# File 'lib/dataMetaDom/scala.rb', line 91

def aggrType(aggr, trg, rawType, scalaPackage)
    if aggr
        k = rawType.to_sym
        subType = rawType # PRIMS_TO_WRAP.has_key?(k) ? PRIMS_TO_WRAP[k] :
        "#{aggr}[#{subType}]"
    elsif trg
        k = rawType.to_sym
        srcType = rawType
        typeRenderer = SCALA_TYPES[trg.type]
        rawTrg = typeRenderer ? typeRenderer.call(trg) : self.condenseType(self.scalaNs(trg.type), scalaPackage)
        k = rawTrg.to_sym
        trgType = rawTrg
        "Map[#{srcType}, #{trgType}]"
    else
        rawType
    end
end

.condenseType(fullType, ref_namespace) ⇒ Object



111
112
113
114
115
116
# File 'lib/dataMetaDom/scala.rb', line 111

def self.condenseType(fullType, ref_namespace)
    ns, base = DataMetaDom.splitNameSpace(fullType)
    ns = self.scalaNs(ns)
    # noinspection RubyNestedTernaryOperatorsInspection
    DataMetaDom.validNs?(ns, base) ? ( ns == ref_namespace ? base : fullType) : fullType
end

.genCaseClasses(model, outRoot) ⇒ Object

Generates scala sources for the model, the POJOs.

  • Parameters

    • parser - instance of Model

    • outRoot - output directory

Raises:

  • (ArgumentError)


241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
# File 'lib/dataMetaDom/scala.rb', line 241

def genCaseClasses(model, outRoot)
    firstRec = model.records.values.first
    raise ArgumentError, "No records defined in the model #{model.sources.masterPath}" unless firstRec

    scalaPackage, base, packagePath = assertNamespace(firstRec.name)
    scalaPackage = self.scalaNs(scalaPackage)
    destDir = File.join(outRoot, packagePath, SCALA_SUBPACKAGE) # keep this in sync with scalaNs
    FileUtils.mkdir_p destDir
    out = File.open(File.join(destDir, 'Model.scala'), 'wb')
    begin
        out.puts %<package #{scalaPackage}

import java.time.ZonedDateTime
import scala.math.BigDecimal
import scala.collection.immutable.Set
import scala.collection.immutable.List
import scala.collection.Seq

/**
 * This content is generated by DataMeta, do not edit manually!
 */
>

        (model.enums.values + model.records.values).each {|e|
            case
                when e.kind_of?(DataMetaDom::Record)
                    self.genEntity model, out, e, scalaPackage
                when e.kind_of?(DataMetaDom::Mappings)
                    raise ArgumentError, "For name #{e.name}: Mappings can not be generated to a case class"
                when e.kind_of?(DataMetaDom::Enum)
                    self.genEnumWorded out, e
                when e.kind_of?(DataMetaDom::BitSet)
                    raise ArgumentError, "For name #{e.name}: BitsSets can not be generated to a case class"
                else
                    raise "Unsupported Entity: #{e.inspect}"
            end
        }
    ensure
        out.close
    end
end

.genEntity(model, out, record, scalaPackage) ⇒ Object

Generates Scala source code, the Scala 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

  • scalaPackage - Scala package to export to

  • baseName - the name of the class to generate.



184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
# File 'lib/dataMetaDom/scala.rb', line 184

def self.genEntity(model, out, record, scalaPackage)
    baseName = record.baseName
    fields = record.fields
    out.puts <<ENTITY_CLASS_HEADER

#{record.docs.empty? ? '' : ScalaLexer.classScalaDoc(record.docs)}case class #{baseName} (
  #{fields.keys.map { |k|
        f = fields[k]
        typeDef = self.aggrScalaType(f, scalaPackage)
        "  `#{f.name}`: #{typeDef}#{model.enums.keys.member?(f.dataType.type) ? '.Value' : ''}"
    }.join(",\n  ")
  }
)
ENTITY_CLASS_HEADER
end

.genEnumWorded(out, enum) ⇒ Object

Generates Scala source code for the worded enum, DataMeta DOM keyword “enum”.



203
204
205
206
207
208
209
210
211
212
213
# File 'lib/dataMetaDom/scala.rb', line 203

def self.genEnumWorded(out, enum)
    values = enum.keys.map{|k| enum[k]} # sort by ordinals to preserve the order
    _, base, _ = assertNamespace(enum.name)
    out.puts %<

#{enum.docs.empty? ? '' : enumScalaDoc(enum.docs)}object #{base} extends Enumeration {
  type #{base} = Value
  val #{values.map{|v| "`#{v}`"}.join(', ')} = Value
}
>
end

.scalaNs(ns) ⇒ Object

Distinguish JVM classes by the platform, unless it’s Java



216
217
218
# File 'lib/dataMetaDom/scala.rb', line 216

def self.scalaNs(ns)
    "#{ns}.#{SCALA_SUBPACKAGE}"
end

.unaggrScalaType(dt, scalaPackage) ⇒ Object

Unaggregated Scala type



119
120
121
122
# File 'lib/dataMetaDom/scala.rb', line 119

def self.unaggrScalaType(dt, scalaPackage)
    typeRenderer = SCALA_TYPES[dt.type]
    typeRenderer ? typeRenderer.call(dt) : self.condenseType(self.scalaNs(dt.type), scalaPackage)
end

Instance Method Details

#assertNamespace(name) ⇒ Object

Extracts 3 pieces of information from the given full name:

  • The namespace if any, i.e. Scala package, empty string if none

  • The base name for the type, without the namespace

  • Scala 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.



228
229
230
231
232
233
234
# File 'lib/dataMetaDom/scala.rb', line 228

def assertNamespace(name)
    ns, base = DataMetaDom.splitNameSpace(name)
    scalaPackage = DataMetaDom.validNs?(ns, base) ? ns : ''
    packagePath = scalaPackage.empty? ? '' : scalaPackage.gsub('.', File::SEPARATOR)

    [scalaPackage, base, packagePath]
end

#classScalaDoc(docs) ⇒ Object

Scala Class ScalaDoc text with the Wiki reference.



146
147
148
149
150
151
152
# File 'lib/dataMetaDom/scala.rb', line 146

def classScalaDoc(docs)
    return  <<CLASS_SCALADOC
/**
#{ScalaLexer.scalaDocs(docs)}
 */
CLASS_SCALADOC
end

#enumScalaDoc(docs) ⇒ Object

Scala Enum class-level ScalaDoc text with the Wiki reference.



157
158
159
160
161
162
163
# File 'lib/dataMetaDom/scala.rb', line 157

def enumScalaDoc(docs)
    return <<ENUM_SCALADOC
/**
#{ScalaLexer.scalaDocs(docs)}
 */
ENUM_SCALADOC
end

#getScalaType(dmDomType) ⇒ Object

For the given DataMeta DOM data type and the isRequired flag, builds and returns the matching Scala data type declaration. For standard types, uses the SCALA_TYPES map



169
170
171
172
# File 'lib/dataMetaDom/scala.rb', line 169

def getScalaType(dmDomType)
    typeRenderer = SCALA_TYPES[dmDomType.type]
    typeRenderer ? typeRenderer.call(dmDomType) : dmDomType.type
end

#scalaDocs(docs) ⇒ Object

Given the property docs of Documentable, return the SCALA_DOC_TARGET if it is present, PLAIN_DOC_TARGET otherwise. Returns empty string if the argument is nil.



134
135
136
137
138
139
140
141
142
# File 'lib/dataMetaDom/scala.rb', line 134

def scalaDocs(docs)
    return '' unless docs
    case
        when docs[PLAIN_DOC_TARGET]
            docs[PLAIN_DOC_TARGET].text
        else
            ''
    end
end