blob: 578ac0ba18f1d6fbf1297593b4ce7a616485e0f7 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 org.apache.openwhisk.core.entity
import java.nio.charset.StandardCharsets
import com.typesafe.config.ConfigValue
import pureconfig._
import pureconfig.generic.auto._
import spray.json._
import ByteSize.formatError
object SizeUnits extends Enumeration {
sealed abstract class Unit() {
def toBytes(n: Long): Long
def toKBytes(n: Long): Long
def toMBytes(n: Long): Long
def toGBytes(n: Long): Long
}
case object BYTE extends Unit {
def toBytes(n: Long): Long = n
def toKBytes(n: Long): Long = n / 1024
def toMBytes(n: Long): Long = n / 1024 / 1024
def toGBytes(n: Long): Long = n / 1024 / 1024 / 1024
}
case object KB extends Unit {
def toBytes(n: Long): Long = n * 1024
def toKBytes(n: Long): Long = n
def toMBytes(n: Long): Long = n / 1024
def toGBytes(n: Long): Long = n / 1024 / 1024
}
case object MB extends Unit {
def toBytes(n: Long): Long = n * 1024 * 1024
def toKBytes(n: Long): Long = n * 1024
def toMBytes(n: Long): Long = n
def toGBytes(n: Long): Long = n / 1024
}
case object GB extends Unit {
def toBytes(n: Long): Long = n * 1024 * 1024 * 1024
def toKBytes(n: Long): Long = n * 1024 * 1024
def toMBytes(n: Long): Long = n * 1024
def toGBytes(n: Long): Long = n
}
}
case class ByteSize(size: Long, unit: SizeUnits.Unit) extends Ordered[ByteSize] {
require(size >= 0, "a negative size of an object is not allowed.")
def toBytes = unit.toBytes(size)
def toKB = unit.toKBytes(size)
def toMB = unit.toMBytes(size)
def +(other: ByteSize): ByteSize = {
val commonUnit = SizeUnits.BYTE
val commonSize = other.toBytes + toBytes
ByteSize(commonSize, commonUnit)
}
def -(other: ByteSize): ByteSize = {
val commonUnit = SizeUnits.BYTE
val commonSize = toBytes - other.toBytes
ByteSize(commonSize, commonUnit)
}
def *(other: Int): ByteSize = {
ByteSize(toBytes * other, SizeUnits.BYTE)
}
def /(other: ByteSize): Double = {
// Without throwing the exception the result would be `Infinity` here
if (other.toBytes == 0) {
throw new ArithmeticException
} else {
(1.0 * toBytes) / (1.0 * other.toBytes)
}
}
def /(other: Int): ByteSize = {
ByteSize(toBytes / other, SizeUnits.BYTE)
}
def compare(other: ByteSize) = toBytes compare other.toBytes
override def equals(that: Any): Boolean = that match {
case t: ByteSize => compareTo(t) == 0
case _ => false
}
override def toString = {
unit match {
case SizeUnits.BYTE => s"$size B"
case SizeUnits.KB => s"$size KB"
case SizeUnits.MB => s"$size MB"
case SizeUnits.GB => s"$size GB"
}
}
}
object ByteSize {
private val regex = """(?i)\s?(\d+)\s?(GB|MB|KB|B|G|M|K)\s?""".r.pattern
protected[entity] val formatError = """Size Unit not supported. Only "B", "K[B]", "M[B]" and "G[B]" are supported."""
def fromString(sizeString: String): ByteSize = {
val matcher = regex.matcher(sizeString)
if (matcher.matches()) {
val size = matcher.group(1).toLong
val unit = matcher.group(2).charAt(0).toUpper match {
case 'B' => SizeUnits.BYTE
case 'K' => SizeUnits.KB
case 'M' => SizeUnits.MB
case 'G' => SizeUnits.GB
}
ByteSize(size, unit)
} else {
throw new IllegalArgumentException(formatError)
}
}
}
object size {
implicit class SizeInt(n: Int) extends SizeConversion {
def sizeIn(unit: SizeUnits.Unit): ByteSize = ByteSize(n, unit)
}
implicit class SizeLong(n: Long) extends SizeConversion {
def sizeIn(unit: SizeUnits.Unit): ByteSize = ByteSize(n, unit)
}
implicit class SizeString(n: String) extends SizeConversion {
def sizeIn(unit: SizeUnits.Unit): ByteSize = ByteSize(n.getBytes(StandardCharsets.UTF_8).length, unit)
}
implicit class SizeOptionString(n: Option[String]) extends SizeConversion {
def sizeIn(unit: SizeUnits.Unit): ByteSize =
n map { s =>
s.sizeIn(unit)
} getOrElse {
ByteSize(0, unit)
}
}
// Creation of an intermediary Config object is necessary here, since "getBytes" is only part of that interface.
implicit val pureconfigReader =
ConfigReader[ConfigValue].map(v => ByteSize(v.atKey("key").getBytes("key"), SizeUnits.BYTE))
protected[core] implicit val serdes = new RootJsonFormat[ByteSize] {
def write(b: ByteSize) = JsString(b.toString)
def read(value: JsValue): ByteSize = value match {
case JsString(s) => ByteSize.fromString(s)
case _ => deserializationError(formatError)
}
}
}
trait SizeConversion {
def B = sizeIn(SizeUnits.BYTE)
def KB = sizeIn(SizeUnits.KB)
def MB = sizeIn(SizeUnits.MB)
def GB: ByteSize = sizeIn(SizeUnits.GB)
def bytes = B
def kilobytes = KB
def megabytes = MB
def gigabytes: ByteSize = GB
def sizeInBytes = sizeIn(SizeUnits.BYTE)
def sizeIn(unit: SizeUnits.Unit): ByteSize
}