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", "PMD.VariableNamingConventions" }) 036 private static final Map<String, BigInteger> unitMap = new HashMap<>(); 037 @SuppressWarnings({ "PMD.FieldNamingConventions", 038 "PMD.VariableNamingConventions" }) 039 private static final List<Map.Entry<String, BigInteger>> unitMappings; 040 @SuppressWarnings({ "PMD.FieldNamingConventions", 041 "PMD.VariableNamingConventions" }) 042 private static final Pattern memorySize 043 = Pattern.compile("^\\s*(\\d+(\\.\\d+)?)\\s*([A-Za-z]*)\\s*"); 044 045 static { 046 // SI units and common abbreviations 047 BigInteger factor = BigInteger.ONE; 048 unitMap.put("", factor); 049 BigInteger scale = BigInteger.valueOf(1000); 050 for (var unit : List.of("B", "kB", "MB", "GB", "TB", "PB", "EB")) { 051 unitMap.put(unit, factor); 052 factor = factor.multiply(scale); 053 } 054 // Binary units 055 factor = BigInteger.valueOf(1024); 056 scale = BigInteger.valueOf(1024); 057 for (var unit : List.of("KiB", "MiB", "GiB", "TiB", "PiB", "EiB")) { 058 unitMap.put(unit, factor); 059 factor = factor.multiply(scale); 060 } 061 unitMappings = unitMap.entrySet().stream() 062 .sorted((a, b) -> -1 * a.getValue().compareTo(b.getValue())) 063 .toList(); 064 } 065 066 /** 067 * Parses a memory size specification. 068 * 069 * @param amount the amount 070 * @return the big integer 071 */ 072 @SuppressWarnings("PMD.DataflowAnomalyAnalysis") 073 public static BigInteger parseMemory(Object amount) { 074 if (amount == null) { 075 return (BigInteger) amount; 076 } 077 if (amount instanceof BigInteger number) { 078 return number; 079 } 080 if (amount instanceof Number number) { 081 return BigInteger.valueOf(number.longValue()); 082 } 083 var matcher = memorySize.matcher(amount.toString()); 084 if (!matcher.matches()) { 085 throw new NumberFormatException(amount.toString()); 086 } 087 var unit = BigInteger.ONE; 088 if (matcher.group(3) != null) { 089 unit = unitMap.get(matcher.group(3)); 090 if (unit == null) { 091 throw new NumberFormatException("Illegal unit \"" 092 + matcher.group(3) + "\" in \"" + amount.toString() + "\""); 093 } 094 } 095 var number = matcher.group(1); 096 return new BigDecimal(number).multiply(new BigDecimal(unit)) 097 .toBigInteger(); 098 } 099 100 /** 101 * Format memory size for humans. 102 * 103 * @param size the size 104 * @return the string 105 */ 106 public static String formatMemory(BigInteger size) { 107 for (var mapping : unitMappings) { 108 if (size.compareTo(mapping.getValue()) >= 0 109 && size.mod(mapping.getValue()).equals(BigInteger.ZERO)) { 110 return (size.divide(mapping.getValue()).toString() 111 + " " + mapping.getKey()).trim(); 112 } 113 } 114 return size.toString(); 115 } 116}