Module: DataMetaJacksonSer
- Includes:
- DataMetaDom, DataMetaDom::PojoLexer
- Defined in:
- lib/dataMetaJacksonSer.rb,
lib/dataMetaJacksonSer/util.rb,
lib/dataMetaJacksonSer/ver_reads.rb
Overview
JSON Serialization artifacts generation.
For command line details either check the new method’s source or the README.rdoc file, the usage section.
Defined Under Namespace
Modules: VerReadsJson Classes: RendCtx, RwHolder
Constant Summary collapse
- VERSION =
Current version
'2.0.2'
- TEXT_RW_METHODS =
JSON Reader and Writer for textual Java types such as String.
RwHolder.new( lambda{|ctx| ctx.fld.aggr ? ctx.rw.call("JU.read#{aggrBaseName(aggrJavaFull(ctx.fld.aggr))}String(in)") : ctx.rw.call('JU.readText(in)') }, lambda{|ctx| ctx.fld.aggr ? ctx.rw.call("JU.write#{aggrBaseName(aggrJavaFull(ctx.fld.aggr))}String(\"#{ctx.fld.name}\", out, value.#{ctx.valGetter}#{ctx.|})") : "out.writeStringField(\"#{ctx.fld.name}\", value.#{ctx.valGetter}#{ctx.|})" } )
- INTEGRAL_RW_METHODS =
JSON Reader and Writer for integral Java types such as Integer or Long.
RwHolder.new( lambda{ |ctx| mapsNotSupported(ctx.fld) if ctx.fld.trgType # map case when ctx.fType.length <= 4; ctx.fld.aggr ? ctx.rw.call("JU.read#{aggrBaseName(aggrJavaFull(ctx.fld.aggr))}Integer(in)") : ctx.rw.call("in.getIntValue#{ctx.|}") when ctx.fType.length <= 8; ; ctx.fld.aggr ? ctx.rw.call("JU.read#{aggrBaseName(aggrJavaFull(ctx.fld.aggr))}Long(in)") : ctx.rw.call("in.getLongValue#{ctx.|}") else; raise "Invalid integer field #{ctx.fld}" end }, lambda{ |ctx| case when ctx.fType.length <= 4; ctx.fld.aggr ? "JU.write#{aggrBaseName(aggrJavaFull(ctx.fld.aggr)) }Integer(\"#{ctx.fld.name}\", out, value.#{ctx.valGetter}#{ctx.|})" : "out.writeNumberField(\"#{ctx.fld.name}\", value.#{ctx.valGetter}#{ctx.|})" when ctx.fType.length <= 8; ctx.fld.aggr ? "JU.write#{aggrBaseName(aggrJavaFull(ctx.fld.aggr)) }Long(\"#{ctx.fld.name}\", out, value.#{ctx.valGetter}#{ctx.|})" : "out.writeNumberField(\"#{ctx.fld.name}\", value.#{ctx.valGetter}#{ctx.|})" else; raise "Invalid integer field #{ctx.fld}" end })
- FLOAT_RW_METHODS =
JSON Reader and Writer for floating point Java types such as Float or Double.
RwHolder.new( lambda{|ctx| mapsNotSupported(ctx.fld) if ctx.fld.trgType # map case when ctx.fType.length <= 4; ctx.fld.aggr ? ctx.rw.call("JU.read#{aggrBaseName(aggrJavaFull(ctx.fld.aggr))}Float(in)") : ctx.rw.call('in.getFloatValue()') when ctx.fType.length <= 8; ctx.fld.aggr ? ctx.rw.call("JU.read#{aggrBaseName(aggrJavaFull(ctx.fld.aggr))}Double(in)") : ctx.rw.call('in.getDoubleValue()') else; raise "Invalid float field #{ctx.fld}" end }, lambda{|ctx| case when ctx.fType.length <= 4; ctx.fld.aggr ? "JU.write#{aggrBaseName(aggrJavaFull(ctx.fld.aggr))}Float(\"#{ctx.fld.name}\", out, value.#{ctx.valGetter}#{ctx.|})" : "out.writeNumberField(\"#{ctx.fld.name}\", value.#{ctx.valGetter}#{ctx.|})" when ctx.fType.length <= 8; ctx.fld.aggr ? "JU.write#{aggrBaseName(aggrJavaFull(ctx.fld.aggr))}Double(\"#{ctx.fld.name}\", out, value.#{ctx.valGetter}#{ctx.|})" : "out.writeNumberField(\"#{ctx.fld.name}\", value.#{ctx.valGetter}#{ctx.|})" else; raise "Invalid float field #{ctx.fld}" end })
- DTTM_RW_METHODS =
JSON Reader and Writer for the temporal type, the DateTime
RwHolder.new( lambda { |ctx| ctx.fld.aggr ? ctx.rw.call("JU.read#{aggrBaseName(aggrJavaFull(ctx.fld.aggr))}ZonedDateTime(in)") : ctx.rw.call('JU.readDttm(in)') }, lambda { |ctx| ctx.fld.aggr ? %<JU.write#{aggrBaseName(aggrJavaFull(ctx.fld.aggr))}ZonedDateTime("#{ ctx.fld.name}", out, value.#{ctx.valGetter}#{ctx.|})> : "JU.writeDttmFld(\"#{ctx.fld.name}\", out, value.#{ctx.valGetter}#{ctx.|})" } )
- BOOL_RW_METHODS =
JSON Reader and Writer for boolean Java type.
RwHolder.new( lambda { |ctx| aggrNotSupported(ctx.fld, 'Booleans') if ctx.fld.aggr ctx.rw.call('in.getBooleanValue()') }, lambda { |ctx| aggrNotSupported(ctx.fld, 'Booleans') if ctx.fld.aggr "out.writeBooleanField(\"#{ctx.fld.name}\", value.#{ctx.valGetter}#{ctx.|})" } )
- RAW_RW_METHODS =
JSON Reader and Writer the raw data type, the byte array.
RwHolder.new( lambda { |ctx| aggrNotSupported(ctx.fld, 'Raw Data') if ctx.fld.aggr ctx.rw.call('JU.readByteArrayPrim(in)') }, lambda { |ctx| aggrNotSupported(ctx.fld, 'Raw Data') if ctx.fld.aggr "JU.writeByteArrayFld(\"#{ctx.fld.name}\", out, value.#{ctx.valGetter}#{ctx.|})" } )
- NUMERIC_RW_METHODS =
JSON Reader and Writer the variable size Decimal data type.
RwHolder.new(lambda{|ctx| ctx.fld.aggr ? ctx.rw.call("JU.read#{aggrBaseName(aggrJavaFull(ctx.fld.aggr))}BigDecimal(in)") : ctx.rw.call('JU.readBigDecimal(in)')}, lambda{|ctx| "out.writeNumberField(\"#{ctx.fld.name}\", value.#{ctx.valGetter}#{ctx.|})"})
- ENUM_RW_METHODS =
JSON Reader and Writer the Java Enums.
RwHolder.new( lambda{|ctx| ctx.fld.aggr ? "JU.read#{aggrBaseName(aggrJavaFull(ctx.fld.aggr))}Enum(in, #{ctx.fType.type}.class)" : "Enum.valueOf(#{DataMetaDom.condenseType(ctx.fType.type, ctx.pckg)}.class, JU.readText(in))" }, lambda { |ctx| ctx.fld.aggr ? %<JU.write#{aggrBaseName(aggrJavaFull(ctx.fld.aggr))}Enum("#{ctx.fld.name}", out, value.#{ctx.valGetter}#{ctx.|})> : "out.writeStringField(\"#{ctx.fld.name}\", value.#{ctx.valGetter}#{ctx.|}.name())" } )
- BITSET_RW_METHODS =
JSON Reader and Writer the BitSet.
RwHolder.new( lambda { |ctx| aggrNotSupported(ctx.fld, 'BitSets') if ctx.fld.aggr "new #{DataMetaDom.condenseType(ctx.fld.dataType, ctx.pckg)}(readLongArray(in))" }, lambda { |ctx| aggrNotSupported(ctx.fld, 'BitSets') if ctx.fld.aggr "writeBitSetFld(\"#{ctx.fld.name}\", out, value.#{ctx.valGetter}#{ctx.|})" } )
- URL_RW_METHODS =
JSON Reader and Writer the URL.
RwHolder.new( lambda { |ctx| aggrNotSupported(ctx.fld, 'URLs') if ctx.fld.aggr 'new java.net.URL(in.getText())' }, lambda { |ctx| aggrNotSupported(ctx.fld, 'URLs') if ctx.fld.aggr "out.writeStringField(\"#{ctx.fld.name}\", value.#{ctx.valGetter}#{ctx.|}.toExternalForm())" } )
- STD_RW_METHODS =
Read/write methods for the standard data types.
{ INT => INTEGRAL_RW_METHODS, STRING => TEXT_RW_METHODS, DATETIME => DTTM_RW_METHODS, BOOL => BOOL_RW_METHODS, CHAR => TEXT_RW_METHODS, FLOAT => FLOAT_RW_METHODS, RAW => RAW_RW_METHODS, NUMERIC => NUMERIC_RW_METHODS, URL => URL_RW_METHODS }
- RECORD_RW_METHODS =
DataMeta DOM object renderer
RwHolder.new( lambda { |ctx| if ctx.fld.aggr if ctx.fld.trgType # map mapsNotSupported(ctx.fld) else # list, set or deque "JU.read#{aggrBaseName(aggrJavaFull(ctx.fld.aggr))}(in, #{ jsonableClassName(DataMetaDom.condenseType(ctx.fType.type, ctx.pckg))}.getInstance())" end else # scalar "#{jsonableClassName(DataMetaDom.condenseType(ctx.fType.type, ctx.pckg))}.getInstance().read(in)" end }, lambda { |ctx| if ctx.fld.aggr && !ctx.fld.trgType if ctx.fld.trgType # map mapsNotSupported(ctx.fld) else # list, set or deque "JU.writeCollectionFld(\"#{ctx.fld.name}\", out, value.#{ctx.valGetter}#{ctx.|}, #{jsonableClassName(DataMetaDom.condenseType(ctx.fType.type, ctx.pckg))}.getInstance())" end else # scalar "#{jsonableClassName(DataMetaDom.condenseType(ctx.fType.type, ctx.pckg))}.getInstance().writeField(\"#{ctx.fld.name}\", out, value.#{ctx.valGetter}#{ctx.|})" end } )
- MAP_RW_METHODS =
Read/write methods for the DataMeta DOM Maps, accidentally all the same as for the standard data types.
STD_RW_METHODS
- SCALA_FMT =
Output format: Scala.
:scala
- JAVA_FMT =
Output format: Java.
:java
- VER_KEY =
The key for the Version field in the generated JSON. The “=” symbol is used to avoid any collision with fields/variables.
'*v*'
- DT_KEY =
The key for the DataType field in the generated JSON. The “=” symbol is used to avoid any collision with fields/variables.
'*dt*'
Class Method Summary collapse
-
.aggrBaseName(aggr) ⇒ Object
Transforms the given full Java name for the aggregate class into base name to interpolate into methods.
-
.aggrJavaFull(aggr) ⇒ Object
Transforms the given DataMeta DOM aggregate type to full pathed Java class name.
- .aggrNotSupported(fld, forWhat) ⇒ Object
-
.assertOutFmt(fmt) ⇒ Object
Verifies the validity of the output format, raises an error if it’s wrong.
-
.badFmt(fmt) ⇒ Object
Helper error raiser for an unsupported JSONable format.
-
.genJacksonable(model, ioOut, record, javaPackage, baseName, fmt = :scala) ⇒ Object
generates Scala JSONable via delegation.
-
.genJacksonables(model, outRoot, fmt = :scala) ⇒ Object
Generates all the writables for the given model.
-
.getRwRenderer(ctx) ⇒ Object
Build the Read/Write operation renderer for the given context:.
-
.helpDataMetaJacksonSerGen(file, errorText = nil) ⇒ Object
Shortcut to help for the Hadoop Writables generator.
-
.jsonableClassName(baseName) ⇒ Object
Builds a class name for a InOutable.
- .mapsNotSupported(fld) ⇒ Object
Instance Method Summary collapse
-
#tmpVar(name) ⇒ Object
Temporary/scratch var – avoiding collisions at all costs.
Class Method Details
.aggrBaseName(aggr) ⇒ Object
Transforms the given full Java name for the aggregate class into base name to interpolate into methods
213 214 215 |
# File 'lib/dataMetaJacksonSer.rb', line 213 def aggrBaseName(aggr) /^(\w+\.)+(\w+)$/.match(aggr)[2] end |
.aggrJavaFull(aggr) ⇒ Object
Transforms the given DataMeta DOM aggregate type to full pathed Java class name
208 209 210 |
# File 'lib/dataMetaJacksonSer.rb', line 208 def aggrJavaFull(aggr) PojoLexer::AGGR_CLASSES[aggr] || (raise ArgumentError, "No Aggregate classes for type #{aggr}" ) end |
.aggrNotSupported(fld, forWhat) ⇒ Object
200 201 202 |
# File 'lib/dataMetaJacksonSer/util.rb', line 200 def aggrNotSupported(fld, forWhat) raise ArgumentError, "Field #{fld.name}: aggregate types are not supported for #{forWhat} on JSON serialization layer" end |
.assertOutFmt(fmt) ⇒ Object
Verifies the validity of the output format, raises an error if it’s wrong.
182 183 184 185 186 187 188 189 |
# File 'lib/dataMetaJacksonSer/util.rb', line 182 def assertOutFmt(fmt) case fmt when JAVA_FMT, SCALA_FMT fmt else badFmt fmt end end |
.badFmt(fmt) ⇒ Object
Helper error raiser for an unsupported JSONable format
177 178 179 |
# File 'lib/dataMetaJacksonSer/util.rb', line 177 def badFmt(fmt) raise ArgumentError, %<Unsupported JSONable format "#{fmt}, use "java" or "scala""> end |
.genJacksonable(model, ioOut, record, javaPackage, baseName, fmt = :scala) ⇒ Object
generates Scala JSONable via delegation
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 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 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 |
# File 'lib/dataMetaJacksonSer.rb', line 248 def genJacksonable(model, ioOut, record, javaPackage, baseName, fmt = :scala) assertOutFmt fmt ctx = RendCtx.new.init(model, record, javaPackage, baseName) fields = record.fields ioName = jsonableClassName(baseName) # scan for imports needed hasOptional = fields.values.map{|f| # !model.records[f.dataType.type] && !f.isRequired }.reduce(:|) # true if there is at least one optional field which isn't a record #fields.values.each { |f| # ctx << DataMetaDom::PojoLexer::JAVA_IMPORTS[f.dataType.type] #} # field keys (names) in the order of reading/writing to the in/out record keysInOrder = fields.each_key.map{|k| k.to_s}.sort.map{|k| k.to_sym} reads = '' writes = '' indent = "#{' ' * 2}" # sorting provides predictable read/write order keysInOrder.each { |k| f = fields[k] ctx.fld = f rwRenderer = getRwRenderer(ctx) # unless ctx.refType.kind_of?(DataMetaDom::Record) reads << case fmt when SCALA_FMT %/ #{indent*5}case "#{f.name}" => #{indent*6}target.#{DataMetaDom.setterName(ctx.fld)}(#{rwRenderer.r.call(ctx)}) / when JAVA_FMT %/ #{indent*6}case "#{f.name}": #{indent*7}target.#{DataMetaDom.setterName(ctx.fld)}(#{rwRenderer.r.call(ctx)}); #{indent*7}break; / else badFmt fmt end # rendering of noReqFld - using the Verifiable interface instead #=begin writes << case fmt when SCALA_FMT "\n" + (indent*2) + (f.isRequired ? (PRIMITIVABLE_TYPES.member?(f.dataType.type) ? '' : ''): #%Q<if(value.#{DataMetaDom::PojoLexer::getterName(ctx.fld)}() == null) throw noReqFld("#{f.name}"); >) : "if(value.#{DataMetaDom.getterName(ctx.fld)} != null) ") + "#{rwRenderer.w.call(ctx)}" #=end when JAVA_FMT "\n" + (indent*2) + (f.isRequired ? (PRIMITIVABLE_TYPES.member?(f.dataType.type) ? '' : ''): "if(value.#{DataMetaDom.getterName(ctx.fld)}#{ctx.|} != null) ") + "#{rwRenderer.w.call(ctx)};" else badFmt fmt end } ioOut.puts case fmt when SCALA_FMT %^ package #{javaPackage} import org.ebay.datameta.ser.jackson.fasterxml.JacksonUtil._ import org.ebay.datameta.ser.jackson.fasterxml.Jsonable import com.fasterxml.jackson.core.{JsonFactory, JsonGenerator, JsonParser, JsonToken} import com.fasterxml.jackson.core.JsonToken.{END_ARRAY, END_OBJECT} #{DataMetaDom::PojoLexer.classJavaDoc({})}object #{ioName} extends Jsonable[#{baseName}] { override def write(out: JsonGenerator, value: #{baseName}) { value.verify() #{writes} } override def readInto(in: JsonParser, target: #{baseName}, ignoreUnknown: Boolean = true): #{baseName} = { while(in.nextToken() != END_OBJECT) { val fldName = in.getCurrentName if(fldName != null) { in.nextToken() fldName match { #{reads} case _ => if(!ignoreUnknown) throw new IllegalArgumentException(s"""Unhandled field "$fldName" """) } } } target } override def read(in: JsonParser, ignoreUnknown: Boolean = true): #{baseName} = { readInto(in, new #{baseName}(), ignoreUnknown) } } ^ when JAVA_FMT %^ package #{javaPackage}; import static org.ebay.datameta.ser.jackson.fasterxml.JacksonUtil.*; import org.ebay.datameta.ser.jackson.fasterxml.JacksonUtil; import org.ebay.datameta.ser.jackson.fasterxml.Jsonable; import com.fasterxml.jackson.core.JsonFactory; import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.core.JsonToken; import java.io.IOException; import static com.fasterxml.jackson.core.JsonToken.END_ARRAY; import static com.fasterxml.jackson.core.JsonToken.END_OBJECT; /** * Json Serializer/Deserializer for the type {@link #{baseName}}. * This class is completely immutable, state-free and therefore thread-safe, implemented and used as a singleton. */ #{DataMetaDom::PojoLexer.classJavaDoc({})}public class #{ioName} extends Jsonable<#{baseName}> { /** An instance of the {@link JacksonUtil} - can use it anywhere because {@link JacksonUtil} is a singleton, * immutable and state-free. */ public final static JacksonUtil JU = JacksonUtil.getInstance(); public static #{ioName} getInstance() { return INSTANCE; } private final static #{ioName} INSTANCE = new #{ioName}(); /** * Constructor is private - use {@link #getInstance()} to get an instance. */ private #{ioName}() {} public void write(final JsonGenerator out, final #{baseName} value) throws IOException { value.verify(); #{writes} } public #{baseName} readInto(final JsonParser in, final #{baseName} target, final boolean ignoreUnknown) throws IOException { JsonToken t = null; while ( (t = in.nextToken()) != END_OBJECT) { if(t == null) throw new IllegalArgumentException("NULL token at " + in.getParsingContext()); final String fldName = in.getCurrentName(); if(fldName != null) { in.nextToken(); switch(fldName){ case "#{VER_KEY}": break; // skip the version field case "#{DT_KEY}": break; // skip the data type field #{reads} default: if(!ignoreUnknown) throw new IllegalArgumentException("Unhandled field \\"" + fldName + '\\"'); } } } return target; } public #{baseName} read(final JsonParser in, boolean ignoreUnknown) throws IOException { return readInto(in, new #{baseName}(), ignoreUnknown); } } ^ else badFmt fmt end end |
.genJacksonables(model, outRoot, fmt = :scala) ⇒ Object
Generates all the writables for the given model. Parameters:
-
model
- the model to generate Writables from. -
outRoot
- destination directory name.
423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 |
# File 'lib/dataMetaJacksonSer.rb', line 423 def genJacksonables(model, outRoot, fmt = :scala) assertOutFmt fmt model.records.values.each { |e| javaPackage, base, packagePath = DataMetaDom::PojoLexer::assertNamespace(e.name) destDir = File.join(outRoot, packagePath) FileUtils.mkdir_p destDir ioOut = File.open(File.join(destDir, "#{jsonableClassName(base)}.#{fmt}"), 'wb') begin case when e.kind_of?(DataMetaDom::Record) genJacksonable model, ioOut, e, javaPackage, base, fmt else raise "Unsupported Entity: #{e.inspect}" end ensure ioOut.close end } end |
.getRwRenderer(ctx) ⇒ Object
Build the Read/Write operation renderer for the given context:
222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 |
# File 'lib/dataMetaJacksonSer.rb', line 222 def getRwRenderer(ctx) dt = ctx.fld.dataType ctx.refType = nil # reset to avoid misrendering primitives rwRenderer = STD_RW_METHODS[dt.type] return rwRenderer if rwRenderer refKey = dt.type ctx.refType = ctx.model.enums[refKey] || ctx.model.records[refKey] case when ctx.refType.kind_of?(DataMetaDom::Record) RECORD_RW_METHODS when ctx.refType.kind_of?(DataMetaDom::Enum) ENUM_RW_METHODS when ctx.refType.kind_of?(DataMetaDom::BitSet) BITSET_RW_METHODS when ctx.refType.kind_of?(DataMetaDom::Mapping) MAP_RW_METHODS[ctx.fType.type] || (raise ArgumentError, "No renderer found for the key type #{ ctx.fType.type}, record #{ctx.rec}, field #{ctx.fld}") else raise "No renderer defined for field #{ctx.fld}" end end |
.helpDataMetaJacksonSerGen(file, errorText = nil) ⇒ Object
Shortcut to help for the Hadoop Writables generator.
444 445 446 |
# File 'lib/dataMetaJacksonSer.rb', line 444 def helpDataMetaJacksonSerGen(file, errorText=nil) DataMetaDom::help(file, 'DataMeta Serialization to/from Jackson', '<DataMeta DOM source> <Target Directory> [java | scala]', errorText) end |
.jsonableClassName(baseName) ⇒ Object
Builds a class name for a InOutable.
194 |
# File 'lib/dataMetaJacksonSer/util.rb', line 194 def jsonableClassName(baseName); "#{baseName}_JSONable" end |
.mapsNotSupported(fld) ⇒ Object
196 197 198 |
# File 'lib/dataMetaJacksonSer/util.rb', line 196 def mapsNotSupported(fld) raise ArgumentError, "Field #{fld.name}: maps are not currently supported on JSON serialization layer" end |
Instance Method Details
#tmpVar(name) ⇒ Object
Temporary/scratch var – avoiding collisions at all costs
245 |
# File 'lib/dataMetaJacksonSer.rb', line 245 def tmpVar(name); "#{'_'*3}#{name}#{'_'*3}" end |