Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions gradle/projects.main.properties
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ aggregated-license-report=aggregated-license-report
polaris-immutables=tools/immutables
polaris-container-spec-helper=tools/container-spec-helper
polaris-version=tools/version
polaris-misc-types=tools/misc-types

polaris-config-docs-annotations=tools/config-docs/annotations
polaris-config-docs-generator=tools/config-docs/generator
Expand Down
44 changes: 44 additions & 0 deletions tools/misc-types/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/*
* 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.
*/

plugins {
alias(libs.plugins.jandex)
id("polaris-client")
}

description =
"Misc types used in configurations and converters for microprofile-config & Jackson, exposes no runtime dependencies"

dependencies {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this really require its own build file?

And if we're going to add one with its own dependencies, can't we just pull in a dependency for this? For example, there is org.apache.commons.io.FileUtils.ONE_MIB

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A dependency for a constant feels a bit too much IMO

compileOnly(libs.smallrye.config.core)
compileOnly(platform(libs.quarkus.bom))
compileOnly("io.quarkus:quarkus-core")

compileOnly(platform(libs.jackson.bom))
compileOnly("com.fasterxml.jackson.core:jackson-databind")

testImplementation(libs.smallrye.config.core)

testImplementation(platform(libs.jackson.bom))
testImplementation("com.fasterxml.jackson.core:jackson-databind")
testRuntimeOnly("com.fasterxml.jackson.datatype:jackson-datatype-jdk8")

testCompileOnly(project(":polaris-immutables"))
testAnnotationProcessor(project(":polaris-immutables", configuration = "processor"))
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,233 @@
/*
* 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.polaris.misc.types.memorysize;

import static com.fasterxml.jackson.annotation.JsonFormat.*;
import static java.lang.String.format;
import static java.util.Locale.ROOT;

import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.annotation.Nonnull;
import java.math.BigInteger;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Pattern;
import org.eclipse.microprofile.config.spi.Converter;

/**
* Type representing a memory size in bytes, using 1024 as the multiplier for kilo, mega, etc.
*
* <p>String representations, for both {@link #valueOf(String) parsing} and {@link #toString()
* generating}, support memory size suffixes like {@code K} for "kilo", {@code M} for "mega".
*
* <p>(De)serialization support for Eclipse Microprofile Config / smallrye-config provided via a
* {@link Converter} implementation, let smallrye-config discover converters automatically (default
* in Quarkus).
*
* <p>(De)serialization support for Jackson provided via a Jackson module, provided via the Java
* service loader mechanism. Use {@link ObjectMapper#findAndRegisterModules()} for manually created
* object mappers.
*
* <p>Jackson serialization supports both {@link Shape#STRING string} (default) and {@link
* Shape#NUMBER integer} representations via {@link JsonFormat @JsonFormat}{@code (shape =
* JsonFormat.}{@link Shape Shape}{@code .NUMBER)}. Number/int serialization always represents the
* number of bytes.
*
* <p>Note that, although unlikely in practice, memory sizes may exceed {@link Long#MAX_VALUE} and
* calls to {@link #asLong()} the result in an {@link ArithmeticException}.
*/
public abstract class MemorySize {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn't seem specific to memory

private static final Pattern MEMORY_SIZE_PATTERN =
Pattern.compile("^(\\d+)([BbKkMmGgTtPpEeZzYy]?)$");
private static final BigInteger KILO_BYTES = BigInteger.valueOf(1024);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"kilobyte" is one word

private static final Map<String, BigInteger> MEMORY_SIZE_MULTIPLIERS;
private static final char[] SUFFIXES = new char[] {'B', 'K', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y'};

static {
MEMORY_SIZE_MULTIPLIERS = new HashMap<>();
MEMORY_SIZE_MULTIPLIERS.put("K", KILO_BYTES);
MEMORY_SIZE_MULTIPLIERS.put("M", KILO_BYTES.pow(2));
MEMORY_SIZE_MULTIPLIERS.put("G", KILO_BYTES.pow(3));
MEMORY_SIZE_MULTIPLIERS.put("T", KILO_BYTES.pow(4));
MEMORY_SIZE_MULTIPLIERS.put("P", KILO_BYTES.pow(5));
MEMORY_SIZE_MULTIPLIERS.put("E", KILO_BYTES.pow(6));
MEMORY_SIZE_MULTIPLIERS.put("Z", KILO_BYTES.pow(7));
MEMORY_SIZE_MULTIPLIERS.put("Y", KILO_BYTES.pow(8));
Comment on lines +69 to +72
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we expect Polaris to run on systems with exabytes of memory?

}

static final class MemorySizeLong extends MemorySize {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this optimization worth the trouble? I suppose there won't be so many MemorySize instances in runtime... probably just a few for config entries 🤔

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are cases in #1189 that read the value in hot paths, that's why I opted to separate the types.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn't seem as usable as just using a long or BigInteger. You can't do arithmetic with it, sort it, etc.

private final long bytes;

MemorySizeLong(long bytes) {
this.bytes = bytes;
}

@Override
public long asLong() {
return bytes;
}

@Nonnull
@Override
public BigInteger asBigInteger() {
return BigInteger.valueOf(bytes);
}

@Override
public boolean equals(Object o) {
if (!(o instanceof MemorySize)) {
return false;
}

if (o instanceof MemorySizeLong) {
var l = (MemorySizeLong) o;
return bytes == l.bytes;
}

var that = (MemorySize) o;
return asBigInteger().equals(that.asBigInteger());
}

@Override
public int hashCode() {
return Long.hashCode(bytes);
}

@Override
public String toString() {
var mask = 1024 - 1;
var s = 0;
var v = bytes;

while (v > 0 && (v & mask) == 0L) {
v >>= 10;
s++;
}

return Long.toString(v) + SUFFIXES[s];
}
}

static final class MemorySizeBig extends MemorySize {
private final BigInteger bytes;

MemorySizeBig(@Nonnull BigInteger bytes) {
this.bytes = bytes;
}

@Override
public long asLong() {
return bytes.longValueExact();
}

@Nonnull
@Override
public BigInteger asBigInteger() {
return bytes;
}

@Override
public boolean equals(Object o) {
if (!(o instanceof MemorySize)) {
return false;
}

MemorySize that = (MemorySize) o;
return bytes.equals(that.asBigInteger());
}

@Override
public int hashCode() {
return bytes.hashCode();
}

@Override
public String toString() {
var s = 0;
var v = bytes;

while (v.signum() > 0 && v.remainder(KILO_BYTES).signum() == 0) {
v = v.divide(KILO_BYTES);
s++;
}

return v.toString() + SUFFIXES[s];
}
}

public static MemorySize ofBytes(long bytes) {
return new MemorySizeLong(bytes);
}

public static MemorySize ofKilo(int kb) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not ofKiloBytes

Copy link
Contributor

@dimas-b dimas-b Mar 21, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1, but I'd use simple case: ofKilobytes , cf. https://en.wikipedia.org/wiki/Kilobyte

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, technically these are kibibytes

return new MemorySizeLong(1024L * kb);
}

public static MemorySize ofMega(int mb) {
return new MemorySizeLong(1024L * 1024L * mb);
}

public static MemorySize ofGiga(int gb) {
return new MemorySizeLong(1024L * 1024L * 1024L * gb);
}

/**
* Convert data size configuration value respecting the following format (shown in regular
* expression) "[0-9]+[BbKkMmGgTtPpEeZzYy]?" If the value contain no suffix, the size is treated
* as bytes.
*
* @param value - value to convert.
* @return {@link MemorySize} - a memory size represented by the given value
*/
public static MemorySize valueOf(String value) {
value = value.trim();
if (value.isEmpty()) {
return null;
}
var matcher = MEMORY_SIZE_PATTERN.matcher(value);
if (matcher.find()) {
var number = new BigInteger(matcher.group(1));
var scale = matcher.group(2).toUpperCase(ROOT);
var multiplier = MEMORY_SIZE_MULTIPLIERS.get(scale);
if (multiplier != null) {
number = number.multiply(multiplier);
}
try {
return new MemorySizeLong(number.longValueExact());
} catch (ArithmeticException e) {
return new MemorySizeBig(number);
}
}

throw new IllegalArgumentException(
format(
"value %s not in correct format (regular expression): [0-9]+[BbKkMmGgTtPpEeZzYy]?",
value));
}

@Nonnull
public abstract BigInteger asBigInteger();

/**
* Memory size as a {@code long} value. May throw an {@link ArithmeticException} if the value is
* bigger than {@link Long#MAX_VALUE}.
*/
public abstract long asLong();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/*
* 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.polaris.misc.types.memorysize;

import org.eclipse.microprofile.config.spi.Converter;

public class MemorySizeConfigConverter implements Converter<MemorySize> {

@Override
public MemorySize convert(String value) {
return MemorySize.valueOf(value);
}
}
Loading