| /* |
| * 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 |
| } |