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

za.co.absa.cobrix.cobol.parser.ast.Statement.scala Maven / Gradle / Ivy

There is a newer version: 1.1.2
Show newest version
/*
 * Copyright 2018 Barclays Africa Group Limited
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package za.co.absa.cobrix.cobol.parser.ast

import scodec.Codec
import scodec.bits.BitVector
import za.co.absa.cobrix.cobol.parser.common.BinaryUtils._
import za.co.absa.cobrix.cobol.parser.ast.datatype.{AlphaNumeric, CobolType, Decimal, Integer}
import za.co.absa.cobrix.cobol.parser.common.Constants
import za.co.absa.cobrix.cobol.parser.encoding.EBCDIC

/** An abstraction of the leaves in the Cobol copybook
  *
  * @param level       A level for the statement
  * @param name        An identifier
  * @param redefines   A name of a field which is redefined by this one
  * @param occurs      The number of elements in an fixed size array / minimum items in variable-sized array
  * @param to          The maximum number of items in a variable size array
  * @param dependingOn A field which specifies size of the array in a record
  * @param parent      A parent node
  */
case class Statement(
                      level: Int,
                      name: String,
                      dataType: CobolType,
                      redefines: Option[String] = None,
                      isRedefined: Boolean = false,
                      occurs: Option[Int] = None,
                      to: Option[Int] = None,
                      dependingOn: Option[String] = None,
                      isDependee: Boolean = false,
                      binaryProperties: BinaryProperties = BinaryProperties(0, 0, 0)
                    )
                    (val parent: Option[Group] = None)
  extends CBTree {

  /** Returns a string representation of the field */
  override def toString: String = {
    s"${" " * 2 * level}$camelCased ${camelCase(redefines.getOrElse(""))} $dataType"
  }

  /** Returns the original field with updated binary properties */
  def withUpdatedBinaryProperties(newBinaryProperties: BinaryProperties): Statement = {
    copy(binaryProperties = newBinaryProperties)(parent)
  }

  /** Returns the original field with updated `isRedefined` flag */
  def withUpdatedIsRedefined(newIsRedefined: Boolean): Statement = {
    copy(isRedefined = newIsRedefined)(parent)
  }

  /** Returns the original field with updated `isDependee` flag */
  def withUpdatedIsDependee(newIsDependee: Boolean): Statement = {
    copy(isDependee = newIsDependee)(parent)
  }

  /** Returns the binary size in bits for the field */
  def getBinarySizeBits: Int = {
    val size = dataType match {
      case a: AlphaNumeric =>
        // Each character is represented by a byte
        val codec = a.enc.getOrElse(EBCDIC()).codec(None, a.length, None)
        getBitCount(codec, None, a.length) //count of entire word
      case d: Decimal =>
        val codec = d.enc.getOrElse(EBCDIC()).codec(d.compact, d.scale, d.signPosition)
        // Support explicit decimal point (aka REAL DECIMAL, PIC 999.99.)
        val precision = if (d.compact.isEmpty && d.explicitDecimal) d.precision + 1 else d.precision
        getBitCount(codec, d.compact, precision)
      case i: Integer =>
        val codec = i.enc.getOrElse(EBCDIC()).codec(i.compact, i.precision, i.signPosition)
        // Hack around byte-alignment
        getBitCount(codec, i.compact, i.precision)
    }
    // round size up to next byte
    ((size + 7)/8)*8
  }

  /** Returns the string representation of a field biven a binary data.
    * Returns None if the contents of the field is empty.
    *
    * @param bitOffset An offset of the field inside the binary data
    * @param record    A record in a binary format represented as a vector of bits
    */
  def decodeValue2(bitOffset: Long, record: BitVector): Option[String] = {
    val bitCount = binaryProperties.dataSize
    val idx = bitOffset
    val bytes = record.slice(idx, idx + bitCount).toByteArray

    val value = dataType match {
      case AlphaNumeric(length, wordAlligned, enc) =>
        val encoding = enc.getOrElse(EBCDIC())
        Some(decodeString(encoding, bytes, length))
      case Decimal(scale, precision, explicitDecimal, signPosition, wordAlligned, compact, enc) =>
        val encoding = enc.getOrElse(EBCDIC())
        decodeCobolNumber(encoding, bytes, compact, precision, scale, explicitDecimal, signPosition.nonEmpty)
      case Integer(precision, signPosition, wordAlligned, compact, enc) =>
        val encoding = enc.getOrElse(EBCDIC())
        decodeCobolNumber(encoding, bytes, compact, precision, 0, explicitDecimal = false, signPosition.nonEmpty)
      case _ => throw new IllegalStateException("Unknown AST object")
    }
    value
  }

  /** Returns a value of a field biven a binary data.
    * The return data type depends on the data type of the field
    *
    * @param itOffset An offset of the field inside the binary data
    * @param record   A record in a binary format represented as a vector of bits
    */
  def decodeTypeValue(itOffset: Long, record: BitVector): Any = {
    val str = decodeValue2(itOffset, record)
    str match {
      case None => null
      case Some(strValue) =>
        val value = dataType match {
          case _: AlphaNumeric =>
            strValue
          case _: Decimal =>
            BigDecimal(strValue)
          case dt: Integer =>
            // Here the explicit converters to boxed types are used.
            // This is because Scala tries to generalize output and will
            // produce java.lang.Long for both str.get.toLong and str.get.toInt
            if (dt.precision > Constants.maxIntegerPrecision) {
              val longValue: java.lang.Long = strValue.toLong
              longValue
            }
            else {
              val intValue: java.lang.Integer = strValue.toInt
              intValue
            }
          case _ => throw new IllegalStateException("Unknown AST object")
        }
        value
    }
  }


  /** Returns the number of bits an integral value occupies given an encoding and a binary representation format.
    *
    * @param codec     A type of encoding (EBCDIC / ASCII)
    * @param comp      A type of binary number representation format
    * @param precision A precision that is the number of digits in a number
    *
    */
  private def getBitCount(codec: Codec[_ <: AnyVal], comp: Option[Int], precision: Int): Int = {
    comp match {
      case Some(x) =>
        x match {
          case a if a == 3 =>
            (precision + 1) * codec.sizeBound.lowerBound.toInt //bcd
          case _ => codec.sizeBound.lowerBound.toInt // bin/float/floatL
        }
      case None => precision * codec.sizeBound.lowerBound.toInt
    }
  }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy