001/* 002 * VM-Operator 003 * Copyright (C) 2023 Michael N. Lipp 004 * 005 * This program is free software: you can redistribute it and/or modify 006 * it under the terms of the GNU Affero General Public License as 007 * published by the Free Software Foundation, either version 3 of the 008 * License, or (at your option) any later version. 009 * 010 * This program is distributed in the hope that it will be useful, 011 * but WITHOUT ANY WARRANTY; without even the implied warranty of 012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 013 * GNU Affero General Public License for more details. 014 * 015 * You should have received a copy of the GNU Affero General Public License 016 * along with this program. If not, see <https://www.gnu.org/licenses/>. 017 */ 018 019package org.jdrupes.vmoperator.common; 020 021import java.math.BigDecimal; 022import java.math.BigInteger; 023import java.util.HashMap; 024import java.util.List; 025import java.util.Map; 026import java.util.regex.Pattern; 027 028/** 029 * Provides methods for parsing "official" memory sizes.. 030 */ 031@SuppressWarnings("PMD.UseUtilityClass") 032public class Convertions { 033 034 @SuppressWarnings({ "PMD.UseConcurrentHashMap", 035 "PMD.FieldNamingConventions" }) 036 private static final Map<String, BigInteger> unitMap = new HashMap<>(); 037 @SuppressWarnings({ "PMD.FieldNamingConventions" }) 038 private static final List<Map.Entry<String, BigInteger>> unitMappings; 039 @SuppressWarnings({ "PMD.FieldNamingConventions" }) 040 private static final Pattern memorySize 041 = Pattern.compile("^\\s*(\\d+(\\.\\d+)?)\\s*([A-Za-z]*)\\s*"); 042 043 static { 044 // SI units and common abbreviations 045 BigInteger factor = BigInteger.ONE; 046 unitMap.put("", factor); 047 BigInteger scale = BigInteger.valueOf(1000); 048 for (var unit : List.of("B", "kB", "MB", "GB", "TB", "PB", "EB")) { 049 unitMap.put(unit, factor); 050 factor = factor.multiply(scale); 051 } 052 // Binary units 053 factor = BigInteger.valueOf(1024); 054 scale = BigInteger.valueOf(1024); 055 for (var unit : List.of("KiB", "MiB", "GiB", "TiB", "PiB", "EiB")) { 056 unitMap.put(unit, factor); 057 factor = factor.multiply(scale); 058 } 059 unitMappings = unitMap.entrySet().stream() 060 .sorted((a, b) -> -1 * a.getValue().compareTo(b.getValue())) 061 .toList(); 062 } 063 064 /** 065 * Parses a memory size specification. 066 * 067 * @param amount the amount 068 * @return the big integer 069 */ 070 public static BigInteger parseMemory(Object amount) { 071 if (amount == null) { 072 return (BigInteger) amount; 073 } 074 if (amount instanceof BigInteger number) { 075 return number; 076 } 077 if (amount instanceof Number number) { 078 return BigInteger.valueOf(number.longValue()); 079 } 080 var matcher = memorySize.matcher(amount.toString()); 081 if (!matcher.matches()) { 082 throw new NumberFormatException(amount.toString()); 083 } 084 var unit = BigInteger.ONE; 085 if (matcher.group(3) != null) { 086 unit = unitMap.get(matcher.group(3)); 087 if (unit == null) { 088 throw new NumberFormatException("Illegal unit \"" 089 + matcher.group(3) + "\" in \"" + amount.toString() + "\""); 090 } 091 } 092 var number = matcher.group(1); 093 return new BigDecimal(number).multiply(new BigDecimal(unit)) 094 .toBigInteger(); 095 } 096 097 /** 098 * Format memory size for humans. 099 * 100 * @param size the size 101 * @return the string 102 */ 103 public static String formatMemory(BigInteger size) { 104 for (var mapping : unitMappings) { 105 if (size.compareTo(mapping.getValue()) >= 0 106 && size.mod(mapping.getValue()).equals(BigInteger.ZERO)) { 107 return (size.divide(mapping.getValue()).toString() 108 + " " + mapping.getKey()).trim(); 109 } 110 } 111 return size.toString(); 112 } 113}