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

indigoplugin.DataType.scala Maven / Gradle / Ivy

The newest version!
package indigoplugin

import scala.util.matching.Regex

sealed trait DataType {

  def nullable: Boolean
  def makeOptional: DataType

  def isString: Boolean =
    this match {
      case _: DataType.StringData => true
      case _                      => false
    }

  def isDouble: Boolean =
    this match {
      case _: DataType.DoubleData => true
      case _                      => false
    }

  def isInt: Boolean =
    this match {
      case _: DataType.IntData => true
      case _                   => false
    }

  def isBoolean: Boolean =
    this match {
      case _: DataType.BooleanData => true
      case _                       => false
    }

  def isNull: Boolean =
    this match {
      case DataType.NullData => true
      case _                 => false
    }

  def toStringData: DataType.StringData =
    this match {
      case s: DataType.StringData if s.nullable              => DataType.StringData(s"""Some("${s.value}")""", true)
      case s: DataType.StringData                            => s
      case DataType.BooleanData(value, nullable) if nullable => DataType.StringData(s"Some(${value.toString})", true)
      case DataType.BooleanData(value, _)                    => DataType.StringData(value.toString, false)
      case DataType.DoubleData(value, nullable) if nullable  => DataType.StringData(s"Some(${value.toString})", true)
      case DataType.DoubleData(value, _)                     => DataType.StringData(value.toString, false)
      case DataType.IntData(value, nullable) if nullable     => DataType.StringData(s"Some(${value.toString})", true)
      case DataType.IntData(value, _)                        => DataType.StringData(value.toString, false)
      case DataType.NullData                                 => DataType.StringData("None", true)
    }

  def asString: String =
    this match {
      case s: DataType.StringData if s.nullable              => s"""Some("${s.value}")"""
      case s: DataType.StringData                            => s""""${s.value}""""
      case DataType.BooleanData(value, nullable) if nullable => s"Some(${value.toString})"
      case DataType.BooleanData(value, _)                    => value.toString
      case DataType.DoubleData(value, nullable) if nullable  => s"Some(${value.toString})"
      case DataType.DoubleData(value, _)                     => value.toString
      case DataType.IntData(value, nullable) if nullable     => s"Some(${value.toString})"
      case DataType.IntData(value, _)                        => value.toString
      case DataType.NullData                                 => "None"
    }

  def giveTypeName: String =
    this match {
      case d: DataType.StringData if d.nullable  => "Option[String]"
      case _: DataType.StringData                => "String"
      case d: DataType.BooleanData if d.nullable => "Option[Boolean]"
      case _: DataType.BooleanData               => "Boolean"
      case d: DataType.DoubleData if d.nullable  => "Option[Double]"
      case _: DataType.DoubleData                => "Double"
      case d: DataType.IntData if d.nullable     => "Option[Int]"
      case _: DataType.IntData                   => "Int"
      case DataType.NullData                     => "Option[Any]"
    }

}
object DataType {

  // Most to least specific: Boolean, Int, Double, String
  final case class BooleanData(value: Boolean, nullable: Boolean) extends DataType {
    def makeOptional: BooleanData = this.copy(nullable = true)
  }
  object BooleanData {
    def apply(value: Boolean): BooleanData = BooleanData(value, false)
  }

  final case class IntData(value: Int, nullable: Boolean) extends DataType {
    def toDoubleData: DoubleData = DoubleData(value.toDouble, nullable)
    def makeOptional: IntData    = this.copy(nullable = true)
  }
  object IntData {
    def apply(value: Int): IntData = IntData(value, false)
  }

  final case class DoubleData(value: Double, nullable: Boolean) extends DataType {
    def makeOptional: DoubleData = this.copy(nullable = true)
  }
  object DoubleData {
    def apply(value: Double): DoubleData = DoubleData(value, false)
  }

  final case class StringData(value: String, nullable: Boolean) extends DataType {
    def makeOptional: StringData = this.copy(nullable = true)
  }
  object StringData {
    def apply(value: String): StringData = StringData(value, false)
  }

  case object NullData extends DataType {
    val nullable: Boolean      = true
    def makeOptional: DataType = this
  }

  private val isBoolean: Regex = """^(true|false)$""".r
  private val isInt: Regex     = """^(\-?[0-9]+)$""".r
  private val isDouble: Regex  = """^(\-?[0-9]+?)\.([0-9]+)$""".r
  private val isNull: Regex    = """^$""".r

  def decideType: String => DataType = {
    case isBoolean(v)     => BooleanData(v.toBoolean, false)
    case isInt(v)         => IntData(v.toInt, false)
    case isDouble(v1, v2) => DoubleData(s"$v1.$v2".toDouble, false)
    case isNull(_)        => NullData
    case v                => StringData(v, false)
  }

  def sameType(a: DataType, b: DataType): Boolean =
    (a, b) match {
      case (_: DataType.StringData, _: DataType.StringData)   => true
      case (DataType.NullData, _: DataType.StringData)        => true
      case (_: DataType.StringData, DataType.NullData)        => true
      case (_: DataType.BooleanData, _: DataType.BooleanData) => true
      case (DataType.NullData, _: DataType.BooleanData)       => true
      case (_: DataType.BooleanData, DataType.NullData)       => true
      case (_: DataType.DoubleData, _: DataType.DoubleData)   => true
      case (DataType.NullData, _: DataType.DoubleData)        => true
      case (_: DataType.DoubleData, DataType.NullData)        => true
      case (_: DataType.IntData, _: DataType.IntData)         => true
      case (DataType.NullData, _: DataType.IntData)           => true
      case (_: DataType.IntData, DataType.NullData)           => true
      case _                                                  => false
    }

  def allSameType(l: List[DataType]): Boolean =
    l match {
      case Nil    => true
      case h :: t => t.forall(d => sameType(h, d))
    }

  def allNumericTypes(l: List[DataType]): Boolean =
    l.forall(d => d.isDouble || d.isInt || d.isNull)

  def hasOptionalValues(l: List[DataType]): Boolean =
    l.contains(DataType.NullData)

  def convertToBestType(l: List[DataType]): List[DataType] =
    // Cases we can manage:
    // - They're all the same! Maybe optional...
    // - Doubles and Ints, convert Ints to Doubles
    // - Fallback is that everything is a string.
    if (allSameType(l)) {
      // All the same! Great!
      l
    } else if (allNumericTypes(l)) {
      l.map {
        case v @ DataType.DoubleData(_, _) => v
        case v @ DataType.IntData(_, _)    => v.toDoubleData
        case DataType.NullData             => DataType.NullData
        case s => throw new Exception(s"Unexpected non-numeric type '$s'") // Shouldn't get here.
      }
    } else {
      // Nothing else to do, but make everything a string that isn't null.
      l.map {
        case DataType.NullData => DataType.NullData
        case d                 => d.toStringData
      }
    }

  def matchHeaderRowLength(rows: Array[Array[DataType]]): Array[Array[DataType]] =
    rows.toList match {
      case Nil =>
        rows

      case headers :: data =>
        val l = headers.length
        val res =
          headers :: data.map { r =>
            val diff = l - r.length
            if (diff > 0) {
              r ++ List.fill(diff)(DataType.NullData)
            } else {
              r
            }
          }

        res.toArray
    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy