All Downloads are FREE. Search and download functionalities are using the official Maven repository.

ch.ninecode.model.Element.scala Maven / Gradle / Ivy

There is a newer version: 2.12-3.0.1-5.1.1
Show newest version
package ch.ninecode.model

import java.util.regex.Pattern

import com.esotericsoftware.kryo.Kryo
import com.esotericsoftware.kryo.Serializer
import com.esotericsoftware.kryo.io.Input
import com.esotericsoftware.kryo.io.Output
import org.apache.spark.sql.Row
import org.apache.spark.sql.types.SQLUserDefinedType

import ch.ninecode.cim.CIMContext
import ch.ninecode.cim.CIMParseable
import ch.ninecode.cim.CIMParser
import ch.ninecode.cim.CIMParser.namespace
import ch.ninecode.cim.CIMSerializer
// NOTE: ElementUDT is actually in this artifact
import org.apache.spark.sql.types.ElementUDT

/**
 * Lowest level class in the CIM hierarchy.
 *
 * All CIM model objects inherit from this class, either directly or indirectly.
 *
 * Provides overridable functionality to:
 *
 - access the typed superclass for each object (implemented as null in this trait)
 - access the unique ID for each object
 - access the class name and hierarchy class list
 - act as a Row object for use in SQL DataFrames ([[length]], [[get]] and [[copy]])
 - export the object as XML (implemented as "" in this trait)
 *
 * @group Added
 * @groupname Added Classes added by CIMReader
 * @groupdesc Low level classes needed to parse the hierarchical CIM classes and generate nested RDD of.
 */
@SQLUserDefinedType(udt = classOf[ElementUDT])
trait Element
extends
    Row
with
    Serializable
with
    Cloneable
with
    Product
{
    /**
     * Get the superclass object.
     *
     * @return The typed superclass (in this case null).
     * @group Hierarchy
     * @groupname Hierarchy Class Hierarchy Related
     * @groupdesc Hierarchy Members related to the nested hierarchy of CIM classes.
     */
    def sup: Element = null

    /**
     * Return the unique ID for the object, the mRID for IdentifiedObject derived classes.
     *
     * @return The object ID.
     * @group Hierarchy
     * @groupname Hierarchy Class Hierarchy Related
     * @groupdesc Hierarchy Members related to the nested hierarchy of CIM classes.
     */
    def id: String = if (null == sup) "0" else sup.id

    /**
     * Valid fields bitmap.
     *
     * One (1) in a bit position means that field was found in parsing, zero means it has an indeterminate value.
     * Field order is specified by the fields array.
     *
     * For classes constructed manually, we initially fill this in with the worst case scenario.
     * ToDo: this won't work for classes with more than 128 fields (so far none).
     */
    var bitfields: Array[Int] = Array(-1, -1, -1, -1)

    /**
     * Is a field present predicate.
     *
     * Determines if the field at the given position was encountered while parsing.
     *
     * @param position the field position in the fields array
     * @return true if the field was parsed, false otherwise.
     */
    def mask (position: Int): Boolean = 0 != (bitfields(position / 32) & (1 << (position % 32)))

    /**
     * Flag for rdf:about elements.
     *
     * @return true if this is an rdf:about element, false otherwise.
     */
    def about: Boolean = if (null == sup) false else sup.about

    /**
     * This class name.
     *
     * @return the class name without the package prefixes
     */
    def baseclass: String =
    {
        if (null == sup)
            "Element"
        else
        {
            val s = getClass.getName
            s.substring (s.lastIndexOf (".") + 1)
        }
    }

    /**
     * This class and the hierarchical list of classes this class derives from.
     *
     * @return the list of classes without the package prefixes.
     */
    def classes: Seq[String] =
    {
        if (null == sup) Seq (baseclass) else Seq (baseclass) ++ sup.classes
    }

    //
    // Row overrides
    //

    /**
     * The number of fields in the object definition.
     *
     * @return The number of defined fields for use in Row manipulations.
     * @group Row
     * @groupname Row SQL Row Implementation
     * @groupdesc Row Members related to implementing the SQL Row interface
     */
    override def length: Int = productArity

    /**
     * Get the value of the field at index i.
     *
     * @param i The index of the field desired.
     * @return The value stored in the field, or null if none.
     * @group Row
     * @groupname Row SQL Row Implementation
     * @groupdesc Row Members related to implementing the SQL Row interface
     */
    override def get (i: Int): Object =
    {
        if (i < productArity)
            productElement (i).asInstanceOf[AnyRef]
        else
            throw new IllegalArgumentException (s"invalid property index $i")
    }

    /**
     * Emit one XML element.
     *
     * @example <cim:IdentifiedObject.name>WGS 84</cim:IdentifiedObject.name>
     *
     * @param field The name of the field.
     * @param value The value of the field.
     * @param clz The class name (e.g. ACLineSegment) of this element
     * @param s The builder to write into.
     */
    def emit_element (field: String, value: Any)(implicit clz: String, s: StringBuilder): Unit =
    {
        if (null != value)
        {
            val _ = s.append (s"		<$namespace:$clz.$field>${value.toString}\n")
        }
    }

    /**
     * Emit one XML attribute.
     *
     * @example <cim:Location.CoordinateSystem rdf:resource="#wgs84"/>
     *
     * @param field The name of the field.
     * @param value The value of the field.
     * @param clz The class name (e.g. ACLineSegment) of this element
     * @param s The builder to write into.
     */
    def emit_attribute (field: String, value: Any)(implicit clz: String, s: StringBuilder): Unit =
    {
        if (null != value)
        {
            val v = value.toString
            val prefix = if (!v.startsWith ("http://")) "#" else "" // relative
            val q = "\""
            val _ = s.append (s"		<$namespace:$clz.$field rdf:resource=$q$prefix$v$q/>\n")
        }
    }

    /**
     * Return a string containing the fields of this object suitable for inclusion in an XML object.
     *
     * @return A string with the fields coded in XML
     * @group Serialization
     * @groupname Serialization Export Implementation
     * @groupdesc Serialization Members related to export of CIM classes
     */
    def export_fields: String = ""

    /**
     * Return a string containing this object as XML.
     *
     * @return The XML corresponding to this object.
     * @group Serialization
     * @groupname Serialization Export Implementation
     * @groupdesc Serialization Members related to export of CIM classes
     */
    def export: String = ""
}

/**
 * Top level element.
 *
 * Not all elements really have an mRID (classes in package Common like PositionPoint and PostalAddress)
 * But Spark needs identifiers for joins, so, for now all elements have an mRID.
 *
 * @param Element Reference to the superclass object.
 * @param mRID Master resource identifier issued by a model authority. By convention, this is used as the RDF id in the CIM XML.
 * @group Added
 * @groupname Added Classes added by CIMReader
 * @groupdesc Low level classes needed to parse the hierarchical CIM classes and generate nested RDD of.
 */
case class BasicElement
(
    Element: Element = null,
    mRID: String = null
)
extends
    Element
{
    /**
     * Zero arg constructor.
     */
    def this () = { this (null, null) }

    override def sup: Element = Element

    /**
     * Return the unique ID for the object, the mRID for IdentifiedObject derived classes.
     *
     * @return The object ID.
     * @group Hierarchy
     * @groupname Hierarchy Class Hierarchy Related
     * @groupdesc Hierarchy Members related to the nested hierarchy of CIM classes.
     */
    override def id: String = mRID

    /**
     * Set true for elements with rdf:about instead of rdf:ID.
     */
    var _about: Boolean = false

    /**
     * Flag for rdf:about elements.
     *
     * @return true if this is an rdf:about element, false otherwise.
     */
    override def about: Boolean = _about

    //
    // Row overrides
    //

    /**
     * Return a copy of this object as a Row.
     *
     * Creates a clone of this object for use in Row manipulations.
     *
     * @return The copy of the object.
     * @group Row
     * @groupname Row SQL Row Implementation
     * @groupdesc Row Members related to implementing the SQL Row interface
     */
    override def copy (): Row = { clone ().asInstanceOf[Row] }
}

object BasicElement
    extends CIMParser // ToDo: why isn't this CIMParseable[BasicElement]
{
    // ToDo: this could probably be handled by an OR case in one regular expression:

    /**
     * Parse an element.
     * Simply extracts the rdf:ID.
     */
    val ID: FielderFunction = parse_element ((Pattern.compile("""rdf:ID=("|')([\s\S]*?)\1>?"""), 2))
    /**
     * Parse an element.
     * Simply extracts the rdf:about.
     */
    val about: FielderFunction = parse_element ((Pattern.compile("""rdf:about=("|')([\s\S]*?)\1>?"""), 2))

    override val fields: Array[String] = Array[String] ("mRID")

    override def parse (context: CIMContext): BasicElement =
    {
        implicit val ctx: CIMContext = context
        val id = ID.apply ().orNull
        val ab = about.apply ()
        val mRID = ab match
        {
            case Some (mrid) =>
                if ('#' == mrid.charAt (0)) mrid.substring (1) else mrid // remove '#'
            case None =>
                id // id may be null anyway
        }
        val basic = BasicElement (null, mRID)
        basic._about = ab.isDefined
        basic
    }

    def serializer: Serializer[BasicElement] = BasicElementSerializer
}

object BasicElementSerializer extends CIMSerializer[BasicElement]
{
    def write (kryo: Kryo, output: Output, obj: BasicElement): Unit =
    {
        output.writeString (obj.id)
        output.writeBoolean (obj.about)
    }

    def read (kryo: Kryo, input: Input, cls: Class[BasicElement]): BasicElement =
    {
        val mrid = input.readString
        val about = input.readBoolean
        val basic = BasicElement (null, mrid)
        basic._about = about
        basic
    }
}

/**
 * Unknown element.
 *
 * Default parsed element, when no other more specific class applies.
 *
 * @param Element Reference to the superclass object.
 * @param guts Internal contents of the XML element with the unrecognized name.
 * @param line The line number on which the unknown XML element starts, in Spark this is relative to the split being processed.
 * @param start The starting character position of the unknown XML element, in Spark this is relative to the split being processed.
 * @param end The ending character position of the unknown XML element, in Spark this is relative to the split being processed.
 * @group Added
 * @groupname Added Classes added by CIMReader
 * @groupdesc Low level classes needed to parse the hierarchical CIM classes and generate nested RDD of.
 */
case class Unknown(
    Element: Element = null,
    guts: String,
    line: Int,
    start: Long,
    end: Long)
extends
    Element
{
    /**
     * Zero arg constructor.
     */
    def this() = { this (null, null, 0, 0L, 0L) }
    override def sup: Element = Element
    override def copy (): Row = { clone ().asInstanceOf[Unknown] }
    override def get (i: Int): Object =
    {
        if (i < productArity)
            productElement (i).asInstanceOf[AnyRef]
        else
            throw new IllegalArgumentException (s"invalid property index $i")
    }
    override def length: Int = productArity
}

object Unknown
extends
    CIMParseable[Unknown]
{
    /**
     * The current element name.
     * Used for error messages.
     */
    var name: String = ""

    def parse (context: CIMContext): Unknown =
    {
        if (CIMContext.DEBUG && (context.errors.size < CIMContext.MAXERRORS))
        {
            val _ = context.errors += s"""Unknown element "$name" at line ${context.line_number()}"""
        }
        Unknown (
            BasicElement.parse(context),
            context.subxml,
            context.line_number(),
            context.start,
            context.end)
    }

    def serializer: Serializer[Unknown] = UnknownSerializer
}

object UnknownSerializer extends CIMSerializer[Unknown]
{
    def write (kryo: Kryo, output: Output, obj: Unknown): Unit =
    {
        BasicElementSerializer.write (kryo, output, obj.Element.asInstanceOf[BasicElement])
        output.writeString (obj.guts)
        output.writeInt (obj.line)
        output.writeLong (obj.start)
        output.writeLong (obj.end)
    }

    def read (kryo: Kryo, input: Input, cls: Class[Unknown]): Unknown =
    {
        Unknown (
            BasicElementSerializer.read (kryo, input, classOf[BasicElement]),
            input.readString,
            input.readInt,
            input.readLong,
            input.readLong)
    }
}





© 2015 - 2024 Weber Informatics LLC | Privacy Policy