Skip to content

Commit 970d874

Browse files
authored
Add type and converters for MemorySize (#1230)
The type allows human friendly memory size specifications like `32k` or `64M`, including support for smallrye-config and Jackson.
1 parent 56f32ed commit 970d874

File tree

8 files changed

+668
-0
lines changed

8 files changed

+668
-0
lines changed

gradle/projects.main.properties

+1
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ aggregated-license-report=aggregated-license-report
3737
polaris-immutables=tools/immutables
3838
polaris-container-spec-helper=tools/container-spec-helper
3939
polaris-version=tools/version
40+
polaris-misc-types=tools/misc-types
4041

4142
polaris-config-docs-annotations=tools/config-docs/annotations
4243
polaris-config-docs-generator=tools/config-docs/generator

tools/misc-types/build.gradle.kts

+44
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
plugins {
21+
alias(libs.plugins.jandex)
22+
id("polaris-client")
23+
}
24+
25+
description =
26+
"Misc types used in configurations and converters for microprofile-config & Jackson, exposes no runtime dependencies"
27+
28+
dependencies {
29+
compileOnly(libs.smallrye.config.core)
30+
compileOnly(platform(libs.quarkus.bom))
31+
compileOnly("io.quarkus:quarkus-core")
32+
33+
compileOnly(platform(libs.jackson.bom))
34+
compileOnly("com.fasterxml.jackson.core:jackson-databind")
35+
36+
testImplementation(libs.smallrye.config.core)
37+
38+
testImplementation(platform(libs.jackson.bom))
39+
testImplementation("com.fasterxml.jackson.core:jackson-databind")
40+
testRuntimeOnly("com.fasterxml.jackson.datatype:jackson-datatype-jdk8")
41+
42+
testCompileOnly(project(":polaris-immutables"))
43+
testAnnotationProcessor(project(":polaris-immutables", configuration = "processor"))
44+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,233 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
package org.apache.polaris.misc.types.memorysize;
20+
21+
import static com.fasterxml.jackson.annotation.JsonFormat.*;
22+
import static java.lang.String.format;
23+
import static java.util.Locale.ROOT;
24+
25+
import com.fasterxml.jackson.annotation.JsonFormat;
26+
import com.fasterxml.jackson.databind.ObjectMapper;
27+
import jakarta.annotation.Nonnull;
28+
import java.math.BigInteger;
29+
import java.util.HashMap;
30+
import java.util.Map;
31+
import java.util.regex.Pattern;
32+
import org.eclipse.microprofile.config.spi.Converter;
33+
34+
/**
35+
* Type representing a memory size in bytes, using 1024 as the multiplier for kilo, mega, etc.
36+
*
37+
* <p>String representations, for both {@link #valueOf(String) parsing} and {@link #toString()
38+
* generating}, support memory size suffixes like {@code K} for "kilo", {@code M} for "mega".
39+
*
40+
* <p>(De)serialization support for Eclipse Microprofile Config / smallrye-config provided via a
41+
* {@link Converter} implementation, let smallrye-config discover converters automatically (default
42+
* in Quarkus).
43+
*
44+
* <p>(De)serialization support for Jackson provided via a Jackson module, provided via the Java
45+
* service loader mechanism. Use {@link ObjectMapper#findAndRegisterModules()} for manually created
46+
* object mappers.
47+
*
48+
* <p>Jackson serialization supports both {@link Shape#STRING string} (default) and {@link
49+
* Shape#NUMBER integer} representations via {@link JsonFormat @JsonFormat}{@code (shape =
50+
* JsonFormat.}{@link Shape Shape}{@code .NUMBER)}. Number/int serialization always represents the
51+
* number of bytes.
52+
*
53+
* <p>Note that, although unlikely in practice, memory sizes may exceed {@link Long#MAX_VALUE} and
54+
* calls to {@link #asLong()} the result in an {@link ArithmeticException}.
55+
*/
56+
public abstract class MemorySize {
57+
private static final Pattern MEMORY_SIZE_PATTERN =
58+
Pattern.compile("^(\\d+)([BbKkMmGgTtPpEeZzYy]?)$");
59+
private static final BigInteger KILO_BYTES = BigInteger.valueOf(1024);
60+
private static final Map<String, BigInteger> MEMORY_SIZE_MULTIPLIERS;
61+
private static final char[] SUFFIXES = new char[] {'B', 'K', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y'};
62+
63+
static {
64+
MEMORY_SIZE_MULTIPLIERS = new HashMap<>();
65+
MEMORY_SIZE_MULTIPLIERS.put("K", KILO_BYTES);
66+
MEMORY_SIZE_MULTIPLIERS.put("M", KILO_BYTES.pow(2));
67+
MEMORY_SIZE_MULTIPLIERS.put("G", KILO_BYTES.pow(3));
68+
MEMORY_SIZE_MULTIPLIERS.put("T", KILO_BYTES.pow(4));
69+
MEMORY_SIZE_MULTIPLIERS.put("P", KILO_BYTES.pow(5));
70+
MEMORY_SIZE_MULTIPLIERS.put("E", KILO_BYTES.pow(6));
71+
MEMORY_SIZE_MULTIPLIERS.put("Z", KILO_BYTES.pow(7));
72+
MEMORY_SIZE_MULTIPLIERS.put("Y", KILO_BYTES.pow(8));
73+
}
74+
75+
static final class MemorySizeLong extends MemorySize {
76+
private final long bytes;
77+
78+
MemorySizeLong(long bytes) {
79+
this.bytes = bytes;
80+
}
81+
82+
@Override
83+
public long asLong() {
84+
return bytes;
85+
}
86+
87+
@Nonnull
88+
@Override
89+
public BigInteger asBigInteger() {
90+
return BigInteger.valueOf(bytes);
91+
}
92+
93+
@Override
94+
public boolean equals(Object o) {
95+
if (!(o instanceof MemorySize)) {
96+
return false;
97+
}
98+
99+
if (o instanceof MemorySizeLong) {
100+
var l = (MemorySizeLong) o;
101+
return bytes == l.bytes;
102+
}
103+
104+
var that = (MemorySize) o;
105+
return asBigInteger().equals(that.asBigInteger());
106+
}
107+
108+
@Override
109+
public int hashCode() {
110+
return Long.hashCode(bytes);
111+
}
112+
113+
@Override
114+
public String toString() {
115+
var mask = 1024 - 1;
116+
var s = 0;
117+
var v = bytes;
118+
119+
while (v > 0 && (v & mask) == 0L) {
120+
v >>= 10;
121+
s++;
122+
}
123+
124+
return Long.toString(v) + SUFFIXES[s];
125+
}
126+
}
127+
128+
static final class MemorySizeBig extends MemorySize {
129+
private final BigInteger bytes;
130+
131+
MemorySizeBig(@Nonnull BigInteger bytes) {
132+
this.bytes = bytes;
133+
}
134+
135+
@Override
136+
public long asLong() {
137+
return bytes.longValueExact();
138+
}
139+
140+
@Nonnull
141+
@Override
142+
public BigInteger asBigInteger() {
143+
return bytes;
144+
}
145+
146+
@Override
147+
public boolean equals(Object o) {
148+
if (!(o instanceof MemorySize)) {
149+
return false;
150+
}
151+
152+
MemorySize that = (MemorySize) o;
153+
return bytes.equals(that.asBigInteger());
154+
}
155+
156+
@Override
157+
public int hashCode() {
158+
return bytes.hashCode();
159+
}
160+
161+
@Override
162+
public String toString() {
163+
var s = 0;
164+
var v = bytes;
165+
166+
while (v.signum() > 0 && v.remainder(KILO_BYTES).signum() == 0) {
167+
v = v.divide(KILO_BYTES);
168+
s++;
169+
}
170+
171+
return v.toString() + SUFFIXES[s];
172+
}
173+
}
174+
175+
public static MemorySize ofBytes(long bytes) {
176+
return new MemorySizeLong(bytes);
177+
}
178+
179+
public static MemorySize ofKilo(int kb) {
180+
return new MemorySizeLong(1024L * kb);
181+
}
182+
183+
public static MemorySize ofMega(int mb) {
184+
return new MemorySizeLong(1024L * 1024L * mb);
185+
}
186+
187+
public static MemorySize ofGiga(int gb) {
188+
return new MemorySizeLong(1024L * 1024L * 1024L * gb);
189+
}
190+
191+
/**
192+
* Convert data size configuration value respecting the following format (shown in regular
193+
* expression) "[0-9]+[BbKkMmGgTtPpEeZzYy]?" If the value contain no suffix, the size is treated
194+
* as bytes.
195+
*
196+
* @param value - value to convert.
197+
* @return {@link MemorySize} - a memory size represented by the given value
198+
*/
199+
public static MemorySize valueOf(String value) {
200+
value = value.trim();
201+
if (value.isEmpty()) {
202+
return null;
203+
}
204+
var matcher = MEMORY_SIZE_PATTERN.matcher(value);
205+
if (matcher.find()) {
206+
var number = new BigInteger(matcher.group(1));
207+
var scale = matcher.group(2).toUpperCase(ROOT);
208+
var multiplier = MEMORY_SIZE_MULTIPLIERS.get(scale);
209+
if (multiplier != null) {
210+
number = number.multiply(multiplier);
211+
}
212+
try {
213+
return new MemorySizeLong(number.longValueExact());
214+
} catch (ArithmeticException e) {
215+
return new MemorySizeBig(number);
216+
}
217+
}
218+
219+
throw new IllegalArgumentException(
220+
format(
221+
"value %s not in correct format (regular expression): [0-9]+[BbKkMmGgTtPpEeZzYy]?",
222+
value));
223+
}
224+
225+
@Nonnull
226+
public abstract BigInteger asBigInteger();
227+
228+
/**
229+
* Memory size as a {@code long} value. May throw an {@link ArithmeticException} if the value is
230+
* bigger than {@link Long#MAX_VALUE}.
231+
*/
232+
public abstract long asLong();
233+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
package org.apache.polaris.misc.types.memorysize;
20+
21+
import org.eclipse.microprofile.config.spi.Converter;
22+
23+
public class MemorySizeConfigConverter implements Converter<MemorySize> {
24+
25+
@Override
26+
public MemorySize convert(String value) {
27+
return MemorySize.valueOf(value);
28+
}
29+
}

0 commit comments

Comments
 (0)