├── .github └── workflows │ └── ci.yml ├── .gitignore ├── .mvn ├── extensions.xml ├── jvm.config ├── maven.config ├── modernizer │ ├── violations-production-code-only.xml │ └── violations.xml ├── rrf │ ├── groupId-central.maven.org.txt │ ├── groupId-confluent.txt │ ├── groupId-flyway-repo.txt │ ├── groupId-google-maven-central-copy.txt │ ├── groupId-jitpack.io.txt │ ├── groupId-maven-central.txt │ ├── groupId-ossrh.txt │ └── groupId-repo.gradle.org.txt └── wrapper │ └── maven-wrapper.properties ├── LICENSE ├── README.md ├── mvnw ├── pom.xml └── src ├── main ├── java │ ├── io │ │ └── airlift │ │ │ └── units │ │ │ ├── DataSize.java │ │ │ ├── Duration.java │ │ │ ├── MaxDataSize.java │ │ │ ├── MaxDataSizeValidator.java │ │ │ ├── MaxDuration.java │ │ │ ├── MaxDurationValidator.java │ │ │ ├── MaxThreadCount.java │ │ │ ├── MaxThreadCountValidator.java │ │ │ ├── MinDataSize.java │ │ │ ├── MinDataSizeValidator.java │ │ │ ├── MinDuration.java │ │ │ ├── MinDurationValidator.java │ │ │ ├── MinThreadCount.java │ │ │ ├── MinThreadCountValidator.java │ │ │ ├── Preconditions.java │ │ │ └── ThreadCount.java │ └── module-info.java └── resources │ └── ValidationMessages.properties └── test └── java └── io └── airlift └── units ├── ConstraintValidatorAssert.java ├── MockMaxDataSize.java ├── MockMaxDuration.java ├── MockMaxThreadCount.java ├── MockMinDataSize.java ├── MockMinDuration.java ├── MockMinThreadCount.java ├── TestDataSize.java ├── TestDataSizeValidator.java ├── TestDuration.java ├── TestDurationValidator.java ├── TestThreadCountValidator.java └── TestThreadsCount.java /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: ci 2 | 3 | on: 4 | - push 5 | - pull_request 6 | 7 | jobs: 8 | build: 9 | runs-on: ubuntu-latest 10 | strategy: 11 | fail-fast: false 12 | matrix: 13 | java-version: 14 | - 17 15 | steps: 16 | - uses: actions/checkout@v1 17 | - uses: actions/setup-java@v1 18 | with: 19 | java-version: ${{ matrix.java-version }} 20 | - name: Maven Install 21 | run: ./mvnw install -B -V -DskipTests -Dair.check.skip-all 22 | - name: Maven Tests 23 | run: ./mvnw install -B -P ci 24 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | *.ipr 3 | *.iws 4 | target/ 5 | /var 6 | pom.xml.versionsBackup 7 | test-output/ 8 | /atlassian-ide-plugin.xml 9 | .idea 10 | .*.swp 11 | .*.swo 12 | .project 13 | .classpath 14 | .settings 15 | .pmdruleset.xml 16 | .pmd 17 | -------------------------------------------------------------------------------- /.mvn/extensions.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | io.takari.maven 5 | takari-smart-builder 6 | 0.6.6 7 | 8 | 9 | -------------------------------------------------------------------------------- /.mvn/jvm.config: -------------------------------------------------------------------------------- 1 | -Xmx8192m 2 | --add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED 3 | --add-exports=jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED 4 | --add-exports=jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED 5 | --add-exports=jdk.compiler/com.sun.tools.javac.model=ALL-UNNAMED 6 | --add-exports=jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED 7 | --add-exports=jdk.compiler/com.sun.tools.javac.processing=ALL-UNNAMED 8 | --add-exports=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED 9 | --add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED 10 | --add-opens=jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED 11 | --add-opens=jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED 12 | -XX:+ExitOnOutOfMemoryError 13 | -------------------------------------------------------------------------------- /.mvn/maven.config: -------------------------------------------------------------------------------- 1 | --strict-checksums 2 | -Daether.syncContext.named.factory=file-lock 3 | -Daether.syncContext.named.nameMapper=file-hgav 4 | -Daether.syncContext.named.retry=5 5 | -Daether.dependencyCollector.impl=bf 6 | -Daether.remoteRepositoryFilter.prefixes=true 7 | -Daether.remoteRepositoryFilter.prefixes.basedir=${session.rootDirectory}/.mvn/rrf/ 8 | -Daether.remoteRepositoryFilter.groupId=true 9 | -Daether.remoteRepositoryFilter.groupId.basedir=${session.rootDirectory}/.mvn/rrf/ 10 | -b 11 | smart 12 | -Dair.main.basedir=${session.rootDirectory} 13 | -------------------------------------------------------------------------------- /.mvn/modernizer/violations-production-code-only.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | java/util/concurrent/ThreadPoolExecutor."<init>":(IIJLjava/util/concurrent/TimeUnit;Ljava/util/concurrent/BlockingQueue;)V 5 | 1.1 6 | Use constructor that takes ThreadFactory and name the threads 7 | 8 | 9 | 10 | java/util/concurrent/ThreadPoolExecutor."<init>":(IIJLjava/util/concurrent/TimeUnit;Ljava/util/concurrent/BlockingQueue;Ljava/util/concurrent/RejectedExecutionHandler;)V 11 | 1.1 12 | Use constructor that takes ThreadFactory and name the threads 13 | 14 | 15 | 16 | java/util/concurrent/ScheduledThreadPoolExecutor."<init>":(I)V 17 | 1.1 18 | Use constructor that takes ThreadFactory and name the threads 19 | 20 | 21 | 22 | java/util/concurrent/ScheduledThreadPoolExecutor."<init>":(ILjava/util/concurrent/RejectedExecutionHandler;)V 23 | 1.1 24 | Use constructor that takes ThreadFactory and name the threads 25 | 26 | 27 | 28 | java/util/concurrent/Executors.newFixedThreadPool:(I)Ljava/util/concurrent/ExecutorService; 29 | 1.1 30 | Use factory method that takes ThreadFactory and name the threads 31 | 32 | 33 | 34 | java/util/concurrent/Executors.newWorkStealingPool:()Ljava/util/concurrent/ExecutorService; 35 | 1.1 36 | Use factory method that takes ThreadFactory and name the threads 37 | 38 | 39 | 40 | java/util/concurrent/Executors.newWorkStealingPool:(I)Ljava/util/concurrent/ExecutorService; 41 | 1.1 42 | Use factory method that takes ThreadFactory and name the threads 43 | 44 | 45 | 46 | java/util/concurrent/Executors.newSingleThreadExecutor:()Ljava/util/concurrent/ExecutorService; 47 | 1.1 48 | Use factory method that takes ThreadFactory and name the threads 49 | 50 | 51 | 52 | java/util/concurrent/Executors.newCachedThreadPool:()Ljava/util/concurrent/ExecutorService; 53 | 1.1 54 | Use factory method that takes ThreadFactory and name the threads 55 | 56 | 57 | 58 | java/util/concurrent/Executors.newSingleThreadScheduledExecutor:()Ljava/util/concurrent/ScheduledExecutorService; 59 | 1.1 60 | Use factory method that takes ThreadFactory and name the threads 61 | 62 | 63 | 64 | java/util/concurrent/Executors.newScheduledThreadPool:(I)Ljava/util/concurrent/ScheduledExecutorService; 65 | 1.1 66 | Use factory method that takes ThreadFactory and name the threads 67 | 68 | 69 | -------------------------------------------------------------------------------- /.mvn/modernizer/violations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | java/lang/Class.newInstance:()Ljava/lang/Object; 5 | 1.1 6 | Prefer Class.getConstructor().newInstance() 7 | 8 | 9 | 10 | java/lang/String."<init>":([B)V 11 | 1.1 12 | Prefer new String(byte[], Charset) 13 | 14 | 15 | 16 | java/lang/String.getBytes:()[B 17 | 1.1 18 | Prefer String.getBytes(Charset) 19 | 20 | 21 | 22 | java/lang/String.toString:()Ljava/lang/String; 23 | 1.1 24 | Call to toString() is redundant 25 | 26 | 27 | 28 | 29 | java/io/File.toString:()Ljava/lang/String; 30 | 1.1 31 | Prefer File.getPath() 32 | 33 | 34 | 35 | java/lang/Thread$Builder.factory:()Ljava/util/concurrent/ThreadFactory; 36 | 1.1 37 | Use io.airlift.concurrent.Threads's thread factories, as the set thread context class loader 38 | 39 | 40 | java/lang/Thread$Builder$OfPlatform.factory:()Ljava/util/concurrent/ThreadFactory; 41 | 1.1 42 | Use io.airlift.concurrent.Threads's thread factories, as the set thread context class loader 43 | 44 | 45 | java/lang/Thread$Builder$OfVirtual.factory:()Ljava/util/concurrent/ThreadFactory; 46 | 1.1 47 | Use io.airlift.concurrent.Threads's thread factories, as the set thread context class loader 48 | 49 | 50 | 51 | com/google/common/primitives/Ints.checkedCast:(J)I 52 | 1.8 53 | Prefer Math.toIntExact(long) 54 | 55 | 56 | 57 | com/google/common/collect/ImmutableMap$Builder.build:()Lcom/google/common/collect/ImmutableMap; 58 | 1.8 59 | Use buildOrThrow() instead, as it makes it clear that it will throw on duplicated values 60 | 61 | 62 | com/google/common/collect/ImmutableTable$Builder.build:()Lcom/google/common/collect/ImmutableTable; 63 | 1.8 64 | Use buildOrThrow() instead, as it makes it clear that it will throw on duplicated values 65 | 66 | 67 | 68 | com/google/common/collect/ImmutableBiMap$Builder."<init>":()V 69 | 1.8 70 | Use builder() static factory method instead 71 | 72 | 73 | com/google/common/collect/ImmutableList$Builder."<init>":()V 74 | 1.8 75 | Use builder() static factory method instead 76 | 77 | 78 | com/google/common/collect/ImmutableMap$Builder."<init>":()V 79 | 1.8 80 | Use builder() static factory method instead 81 | 82 | 83 | com/google/common/collect/ImmutableMultimap$Builder."<init>":()V 84 | 1.8 85 | Use builder() static factory method instead 86 | 87 | 88 | com/google/common/collect/ImmutableMultiset$Builder."<init>":()V 89 | 1.8 90 | Use builder() static factory method instead 91 | 92 | 93 | com/google/common/collect/ImmutableSet$Builder."<init>":()V 94 | 1.8 95 | Use builder() static factory method instead 96 | 97 | 98 | com/google/common/collect/ImmutableSortedMap$Builder."<init>":()V 99 | 1.8 100 | Use orderedBy() static factory method instead 101 | 102 | 103 | com/google/common/collect/ImmutableSortedSet$Builder."<init>":()V 104 | 1.8 105 | Use orderedBy() static factory method instead 106 | 107 | 108 | com/google/common/collect/ImmutableTable$Builder."<init>":()V 109 | 1.8 110 | Use builder() static factory method instead 111 | 112 | 113 | 114 | com/google/common/cache/CacheBuilder.build:()Lcom/google/common/cache/Cache; 115 | 1.8 116 | Guava Cache has concurrency issues around invalidation and ongoing loads. Use EvictableCacheBuilder or SafeCaches to build caches. 117 | See https://github.com/trinodb/trino/issues/10512 for more information and see https://github.com/trinodb/trino/issues/10512#issuecomment-1016221168 118 | for why Caffeine does not solve the problem. 119 | 120 | 121 | 122 | com/google/common/cache/CacheBuilder.build:(Lcom/google/common/cache/CacheLoader;)Lcom/google/common/cache/LoadingCache; 123 | 1.8 124 | Guava LoadingCache has concurrency issues around invalidation and ongoing loads. Use EvictableCacheBuilder or SafeCaches to build caches. 125 | See https://github.com/trinodb/trino/issues/10512 for more information and see https://github.com/trinodb/trino/issues/10512#issuecomment-1016221168 126 | for why Caffeine does not solve the problem. 127 | 128 | 129 | 130 | org/testng/Assert.assertEquals:(Ljava/lang/Iterable;Ljava/lang/Iterable;)V 131 | 1.8 132 | Use AssertJ or QueryAssertions due to TestNG #543 133 | 134 | 135 | 136 | org/testng/Assert.assertEquals:(Ljava/lang/Iterable;Ljava/lang/Iterable;Ljava/lang/String;)V 137 | 1.8 138 | Use AssertJ or QueryAssertions due to TestNG #543 139 | 140 | 141 | 142 | org/testng/Assert.assertThrows:(Lorg/testng/Assert$ThrowingRunnable;)V 143 | 1.8 144 | Use AssertJ's assertThatThrownBy, see https://github.com/trinodb/trino/issues/5320 for rationale 145 | 146 | 147 | 148 | org/testng/Assert.assertThrows:(Ljava/lang/Class;Lorg/testng/Assert$ThrowingRunnable;)V 149 | 1.8 150 | Use AssertJ's assertThatThrownBy, see https://github.com/trinodb/trino/issues/5320 for rationale 151 | 152 | 153 | 154 | com/amazonaws/services/glue/model/Table.getTableType:()Ljava/lang/String; 155 | 1.1 156 | Table type is nullable in Glue model, which is too easy to forget about. Prefer GlueToTrinoConverter.getTableType 157 | 158 | 159 | 160 | com/amazonaws/services/glue/model/Column.getParameters:()Ljava/util/Map; 161 | 1.1 162 | Column parameters map is nullable in Glue model, which is too easy to forget about. Prefer GlueToTrinoConverter.getColumnParameters 163 | 164 | 165 | 166 | com/amazonaws/services/glue/model/Table.getParameters:()Ljava/util/Map; 167 | 1.1 168 | Table parameters map is nullable in Glue model, which is too easy to forget about. Prefer GlueToTrinoConverter.getTableParameters 169 | 170 | 171 | 172 | com/amazonaws/services/glue/model/Partition.getParameters:()Ljava/util/Map; 173 | 1.1 174 | Partition parameters map is nullable in Glue model, which is too easy to forget about. Prefer GlueToTrinoConverter.getPartitionParameters 175 | 176 | 177 | 178 | com/amazonaws/services/glue/model/SerDeInfo.getParameters:()Ljava/util/Map; 179 | 1.1 180 | SerDeInfo parameters map is nullable in Glue model, which is too easy to forget about. Prefer GlueToTrinoConverter.getSerDeInfoParameters 181 | 182 | 183 | 184 | org/apache/hadoop/fs/FileSystem.close:()V 185 | 1.1 186 | Hadoop FileSystem instances are shared and should not be closed 187 | 188 | 189 | 190 | java/util/TimeZone.getTimeZone:(Ljava/lang/String;)Ljava/util/TimeZone; 191 | 1.8 192 | Avoid TimeZone.getTimeZone as it returns GMT for a zone not supported by the JVM. Use TimeZone.getTimeZone(ZoneId.of(..)) instead, or TimeZone.getTimeZone(..., false). 193 | 194 | 195 | 196 | org/joda/time/DateTimeZone.toTimeZone:()Ljava/util/TimeZone; 197 | 1.8 198 | Avoid DateTimeZone.toTimeZone as it returns GMT for a zone not supported by the JVM. Use TimeZone.getTimeZone(ZoneId.of(dtz.getId())) instead. 199 | 200 | 201 | 202 | com/esri/core/geometry/ogc/OGCGeometry.equals:(Lcom/esri/core/geometry/ogc/OGCGeometry;)Z 203 | 1.6 204 | Prefer OGCGeometry.Equals(OGCGeometry) 205 | 206 | 207 | 208 | com/esri/core/geometry/ogc/OGCGeometry.equals:(Ljava/lang/Object;)Z 209 | 1.6 210 | Prefer OGCGeometry.Equals(OGCGeometry) 211 | 212 | 213 | 214 | io/airlift/units/DataSize."<init>":(DLio/airlift/units/DataSize$Unit;)V 215 | 1.8 216 | Use io.airlift.units.DataSize.of(long, DataSize.Unit) 217 | 218 | 219 | 220 | io/airlift/units/DataSize.succinctDataSize:(DLio/airlift/units/DataSize$Unit;)Lio/airlift/units/DataSize; 221 | 1.8 222 | Use io.airlift.units.DataSize.of(long, DataSize.Unit).succinct() -- Note that succinct conversion only affects toString() results 223 | 224 | 225 | 226 | io/airlift/units/DataSize.getValue:()D 227 | 1.8 228 | Use io.airlift.units.DataSize.toBytes() and Unit.inBytes() for conversion 229 | 230 | 231 | 232 | io/airlift/units/DataSize.getValue:(Lio/airlift/units/DataSize$Unit;)D 233 | 1.8 234 | Use io.airlift.units.DataSize.toBytes() and Unit.inBytes() for conversion 235 | 236 | 237 | 238 | io/airlift/units/DataSize.roundTo:(Lio/airlift/units/DataSize$Unit;)J 239 | 1.8 240 | Method is deprecated for removal 241 | 242 | 243 | 244 | io/airlift/units/DataSize.convertTo:(Lio/airlift/units/DataSize$Unit;)Lio/airlift/units/DataSize; 245 | 1.8 246 | Use io.airlift.units.DataSize.to(DataSize.Unit) 247 | 248 | 249 | 250 | io/airlift/units/DataSize.convertToMostSuccinctDataSize:()Lio/airlift/units/DataSize; 251 | 1.8 252 | Use io.airlift.units.DataSize.succinct() 253 | 254 | 255 | 256 | io/airlift/testing/Closeables.closeQuietly:([Ljava/io/Closeable;)V 257 | 1.0 258 | Use Closeables.closeAll() or Closer. 259 | 260 | 261 | 262 | com/google/inject/util/Modules.combine:(Ljava/lang/Iterable;)Lcom/google/inject/Module; 263 | 1.8 264 | Use io.airlift.configuration.ConfigurationAwareModule.combine 265 | 266 | 267 | 268 | com/google/inject/util/Modules.combine:([Lcom/google/inject/Module;)Lcom/google/inject/Module; 269 | 1.8 270 | Use io.airlift.configuration.ConfigurationAwareModule.combine 271 | 272 | 273 | 274 | io/jsonwebtoken/Jwts.builder:()Lio/jsonwebtoken/JwtBuilder; 275 | 1.8 276 | Use io.trino.server.security.jwt.JwtsUtil or equivalent 277 | 278 | 279 | 280 | io/jsonwebtoken/Jwts.parserBuilder:()Lio/jsonwebtoken/JwtParserBuilder; 281 | 1.8 282 | Use io.trino.server.security.jwt.JwtsUtil or equivalent 283 | 284 | 285 | 286 | org/openjdk/jol/info/ClassLayout.instanceSize:()J 287 | 1.8 288 | Use io.airlift.slice.SizeOf.instanceSize 289 | 290 | 291 | 292 | org/testng/annotations/BeforeTest 293 | 1.8 294 | Prefer org.testng.annotations.BeforeClass 295 | 296 | 297 | 298 | org/testng/annotations/AfterTest 299 | 1.8 300 | Prefer org.testng.annotations.AfterClass 301 | 302 | 303 | 304 | com/fasterxml/jackson/core/JsonFactory."<init>":()V 305 | 1.8 306 | Use io.trino.plugin.base.util.JsonUtils.jsonFactory() 307 | 308 | 309 | 310 | com/fasterxml/jackson/core/JsonFactoryBuilder."<init>":()V 311 | 1.8 312 | Use io.trino.plugin.base.util.JsonUtils.jsonFactoryBuilder() instead 313 | 314 | 315 | -------------------------------------------------------------------------------- /.mvn/rrf/groupId-central.maven.org.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/airlift/units/2099ad602a106369f3b182be077ab69ed8df87b6/.mvn/rrf/groupId-central.maven.org.txt -------------------------------------------------------------------------------- /.mvn/rrf/groupId-confluent.txt: -------------------------------------------------------------------------------- 1 | io.confluent 2 | org.apache.kafka 3 | 4 | -------------------------------------------------------------------------------- /.mvn/rrf/groupId-flyway-repo.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/airlift/units/2099ad602a106369f3b182be077ab69ed8df87b6/.mvn/rrf/groupId-flyway-repo.txt -------------------------------------------------------------------------------- /.mvn/rrf/groupId-google-maven-central-copy.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/airlift/units/2099ad602a106369f3b182be077ab69ed8df87b6/.mvn/rrf/groupId-google-maven-central-copy.txt -------------------------------------------------------------------------------- /.mvn/rrf/groupId-jitpack.io.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/airlift/units/2099ad602a106369f3b182be077ab69ed8df87b6/.mvn/rrf/groupId-jitpack.io.txt -------------------------------------------------------------------------------- /.mvn/rrf/groupId-maven-central.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/airlift/units/2099ad602a106369f3b182be077ab69ed8df87b6/.mvn/rrf/groupId-maven-central.txt -------------------------------------------------------------------------------- /.mvn/rrf/groupId-ossrh.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/airlift/units/2099ad602a106369f3b182be077ab69ed8df87b6/.mvn/rrf/groupId-ossrh.txt -------------------------------------------------------------------------------- /.mvn/rrf/groupId-repo.gradle.org.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/airlift/units/2099ad602a106369f3b182be077ab69ed8df87b6/.mvn/rrf/groupId-repo.gradle.org.txt -------------------------------------------------------------------------------- /.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | # Licensed to the Apache Software Foundation (ASF) under one 2 | # or more contributor license agreements. See the NOTICE file 3 | # distributed with this work for additional information 4 | # regarding copyright ownership. The ASF licenses this file 5 | # to you under the Apache License, Version 2.0 (the 6 | # "License"); you may not use this file except in compliance 7 | # with the License. You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, 12 | # software distributed under the License is distributed on an 13 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 14 | # KIND, either express or implied. See the License for the 15 | # specific language governing permissions and limitations 16 | # under the License. 17 | wrapperVersion=3.3.2 18 | distributionType=only-script 19 | distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.9/apache-maven-3.9.9-bin.zip 20 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Units 2 | [![Maven Central](https://img.shields.io/maven-central/v/io.airlift/units.svg?label=Maven%20Central)](https://search.maven.org/#search%7Cga%7C1%7Cg%3A%22io.airlift%22%20AND%20a%3A%22units%22) 3 | 4 | Units is a Java library for immutable, human-friendly units. 5 | -------------------------------------------------------------------------------- /mvnw: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # ---------------------------------------------------------------------------- 3 | # Licensed to the Apache Software Foundation (ASF) under one 4 | # or more contributor license agreements. See the NOTICE file 5 | # distributed with this work for additional information 6 | # regarding copyright ownership. The ASF licenses this file 7 | # to you under the Apache License, Version 2.0 (the 8 | # "License"); you may not use this file except in compliance 9 | # with the License. You may obtain a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, 14 | # software distributed under the License is distributed on an 15 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 16 | # KIND, either express or implied. See the License for the 17 | # specific language governing permissions and limitations 18 | # under the License. 19 | # ---------------------------------------------------------------------------- 20 | 21 | # ---------------------------------------------------------------------------- 22 | # Apache Maven Wrapper startup batch script, version 3.3.2 23 | # 24 | # Optional ENV vars 25 | # ----------------- 26 | # JAVA_HOME - location of a JDK home dir, required when download maven via java source 27 | # MVNW_REPOURL - repo url base for downloading maven distribution 28 | # MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven 29 | # MVNW_VERBOSE - true: enable verbose log; debug: trace the mvnw script; others: silence the output 30 | # ---------------------------------------------------------------------------- 31 | 32 | set -euf 33 | [ "${MVNW_VERBOSE-}" != debug ] || set -x 34 | 35 | # OS specific support. 36 | native_path() { printf %s\\n "$1"; } 37 | case "$(uname)" in 38 | CYGWIN* | MINGW*) 39 | [ -z "${JAVA_HOME-}" ] || JAVA_HOME="$(cygpath --unix "$JAVA_HOME")" 40 | native_path() { cygpath --path --windows "$1"; } 41 | ;; 42 | esac 43 | 44 | # set JAVACMD and JAVACCMD 45 | set_java_home() { 46 | # For Cygwin and MinGW, ensure paths are in Unix format before anything is touched 47 | if [ -n "${JAVA_HOME-}" ]; then 48 | if [ -x "$JAVA_HOME/jre/sh/java" ]; then 49 | # IBM's JDK on AIX uses strange locations for the executables 50 | JAVACMD="$JAVA_HOME/jre/sh/java" 51 | JAVACCMD="$JAVA_HOME/jre/sh/javac" 52 | else 53 | JAVACMD="$JAVA_HOME/bin/java" 54 | JAVACCMD="$JAVA_HOME/bin/javac" 55 | 56 | if [ ! -x "$JAVACMD" ] || [ ! -x "$JAVACCMD" ]; then 57 | echo "The JAVA_HOME environment variable is not defined correctly, so mvnw cannot run." >&2 58 | echo "JAVA_HOME is set to \"$JAVA_HOME\", but \"\$JAVA_HOME/bin/java\" or \"\$JAVA_HOME/bin/javac\" does not exist." >&2 59 | return 1 60 | fi 61 | fi 62 | else 63 | JAVACMD="$( 64 | 'set' +e 65 | 'unset' -f command 2>/dev/null 66 | 'command' -v java 67 | )" || : 68 | JAVACCMD="$( 69 | 'set' +e 70 | 'unset' -f command 2>/dev/null 71 | 'command' -v javac 72 | )" || : 73 | 74 | if [ ! -x "${JAVACMD-}" ] || [ ! -x "${JAVACCMD-}" ]; then 75 | echo "The java/javac command does not exist in PATH nor is JAVA_HOME set, so mvnw cannot run." >&2 76 | return 1 77 | fi 78 | fi 79 | } 80 | 81 | # hash string like Java String::hashCode 82 | hash_string() { 83 | str="${1:-}" h=0 84 | while [ -n "$str" ]; do 85 | char="${str%"${str#?}"}" 86 | h=$(((h * 31 + $(LC_CTYPE=C printf %d "'$char")) % 4294967296)) 87 | str="${str#?}" 88 | done 89 | printf %x\\n $h 90 | } 91 | 92 | verbose() { :; } 93 | [ "${MVNW_VERBOSE-}" != true ] || verbose() { printf %s\\n "${1-}"; } 94 | 95 | die() { 96 | printf %s\\n "$1" >&2 97 | exit 1 98 | } 99 | 100 | trim() { 101 | # MWRAPPER-139: 102 | # Trims trailing and leading whitespace, carriage returns, tabs, and linefeeds. 103 | # Needed for removing poorly interpreted newline sequences when running in more 104 | # exotic environments such as mingw bash on Windows. 105 | printf "%s" "${1}" | tr -d '[:space:]' 106 | } 107 | 108 | # parse distributionUrl and optional distributionSha256Sum, requires .mvn/wrapper/maven-wrapper.properties 109 | while IFS="=" read -r key value; do 110 | case "${key-}" in 111 | distributionUrl) distributionUrl=$(trim "${value-}") ;; 112 | distributionSha256Sum) distributionSha256Sum=$(trim "${value-}") ;; 113 | esac 114 | done <"${0%/*}/.mvn/wrapper/maven-wrapper.properties" 115 | [ -n "${distributionUrl-}" ] || die "cannot read distributionUrl property in ${0%/*}/.mvn/wrapper/maven-wrapper.properties" 116 | 117 | case "${distributionUrl##*/}" in 118 | maven-mvnd-*bin.*) 119 | MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ 120 | case "${PROCESSOR_ARCHITECTURE-}${PROCESSOR_ARCHITEW6432-}:$(uname -a)" in 121 | *AMD64:CYGWIN* | *AMD64:MINGW*) distributionPlatform=windows-amd64 ;; 122 | :Darwin*x86_64) distributionPlatform=darwin-amd64 ;; 123 | :Darwin*arm64) distributionPlatform=darwin-aarch64 ;; 124 | :Linux*x86_64*) distributionPlatform=linux-amd64 ;; 125 | *) 126 | echo "Cannot detect native platform for mvnd on $(uname)-$(uname -m), use pure java version" >&2 127 | distributionPlatform=linux-amd64 128 | ;; 129 | esac 130 | distributionUrl="${distributionUrl%-bin.*}-$distributionPlatform.zip" 131 | ;; 132 | maven-mvnd-*) MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ ;; 133 | *) MVN_CMD="mvn${0##*/mvnw}" _MVNW_REPO_PATTERN=/org/apache/maven/ ;; 134 | esac 135 | 136 | # apply MVNW_REPOURL and calculate MAVEN_HOME 137 | # maven home pattern: ~/.m2/wrapper/dists/{apache-maven-,maven-mvnd--}/ 138 | [ -z "${MVNW_REPOURL-}" ] || distributionUrl="$MVNW_REPOURL$_MVNW_REPO_PATTERN${distributionUrl#*"$_MVNW_REPO_PATTERN"}" 139 | distributionUrlName="${distributionUrl##*/}" 140 | distributionUrlNameMain="${distributionUrlName%.*}" 141 | distributionUrlNameMain="${distributionUrlNameMain%-bin}" 142 | MAVEN_USER_HOME="${MAVEN_USER_HOME:-${HOME}/.m2}" 143 | MAVEN_HOME="${MAVEN_USER_HOME}/wrapper/dists/${distributionUrlNameMain-}/$(hash_string "$distributionUrl")" 144 | 145 | exec_maven() { 146 | unset MVNW_VERBOSE MVNW_USERNAME MVNW_PASSWORD MVNW_REPOURL || : 147 | exec "$MAVEN_HOME/bin/$MVN_CMD" "$@" || die "cannot exec $MAVEN_HOME/bin/$MVN_CMD" 148 | } 149 | 150 | if [ -d "$MAVEN_HOME" ]; then 151 | verbose "found existing MAVEN_HOME at $MAVEN_HOME" 152 | exec_maven "$@" 153 | fi 154 | 155 | case "${distributionUrl-}" in 156 | *?-bin.zip | *?maven-mvnd-?*-?*.zip) ;; 157 | *) die "distributionUrl is not valid, must match *-bin.zip or maven-mvnd-*.zip, but found '${distributionUrl-}'" ;; 158 | esac 159 | 160 | # prepare tmp dir 161 | if TMP_DOWNLOAD_DIR="$(mktemp -d)" && [ -d "$TMP_DOWNLOAD_DIR" ]; then 162 | clean() { rm -rf -- "$TMP_DOWNLOAD_DIR"; } 163 | trap clean HUP INT TERM EXIT 164 | else 165 | die "cannot create temp dir" 166 | fi 167 | 168 | mkdir -p -- "${MAVEN_HOME%/*}" 169 | 170 | # Download and Install Apache Maven 171 | verbose "Couldn't find MAVEN_HOME, downloading and installing it ..." 172 | verbose "Downloading from: $distributionUrl" 173 | verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName" 174 | 175 | # select .zip or .tar.gz 176 | if ! command -v unzip >/dev/null; then 177 | distributionUrl="${distributionUrl%.zip}.tar.gz" 178 | distributionUrlName="${distributionUrl##*/}" 179 | fi 180 | 181 | # verbose opt 182 | __MVNW_QUIET_WGET=--quiet __MVNW_QUIET_CURL=--silent __MVNW_QUIET_UNZIP=-q __MVNW_QUIET_TAR='' 183 | [ "${MVNW_VERBOSE-}" != true ] || __MVNW_QUIET_WGET='' __MVNW_QUIET_CURL='' __MVNW_QUIET_UNZIP='' __MVNW_QUIET_TAR=v 184 | 185 | # normalize http auth 186 | case "${MVNW_PASSWORD:+has-password}" in 187 | '') MVNW_USERNAME='' MVNW_PASSWORD='' ;; 188 | has-password) [ -n "${MVNW_USERNAME-}" ] || MVNW_USERNAME='' MVNW_PASSWORD='' ;; 189 | esac 190 | 191 | if [ -z "${MVNW_USERNAME-}" ] && command -v wget >/dev/null; then 192 | verbose "Found wget ... using wget" 193 | wget ${__MVNW_QUIET_WGET:+"$__MVNW_QUIET_WGET"} "$distributionUrl" -O "$TMP_DOWNLOAD_DIR/$distributionUrlName" || die "wget: Failed to fetch $distributionUrl" 194 | elif [ -z "${MVNW_USERNAME-}" ] && command -v curl >/dev/null; then 195 | verbose "Found curl ... using curl" 196 | curl ${__MVNW_QUIET_CURL:+"$__MVNW_QUIET_CURL"} -f -L -o "$TMP_DOWNLOAD_DIR/$distributionUrlName" "$distributionUrl" || die "curl: Failed to fetch $distributionUrl" 197 | elif set_java_home; then 198 | verbose "Falling back to use Java to download" 199 | javaSource="$TMP_DOWNLOAD_DIR/Downloader.java" 200 | targetZip="$TMP_DOWNLOAD_DIR/$distributionUrlName" 201 | cat >"$javaSource" <<-END 202 | public class Downloader extends java.net.Authenticator 203 | { 204 | protected java.net.PasswordAuthentication getPasswordAuthentication() 205 | { 206 | return new java.net.PasswordAuthentication( System.getenv( "MVNW_USERNAME" ), System.getenv( "MVNW_PASSWORD" ).toCharArray() ); 207 | } 208 | public static void main( String[] args ) throws Exception 209 | { 210 | setDefault( new Downloader() ); 211 | java.nio.file.Files.copy( java.net.URI.create( args[0] ).toURL().openStream(), java.nio.file.Paths.get( args[1] ).toAbsolutePath().normalize() ); 212 | } 213 | } 214 | END 215 | # For Cygwin/MinGW, switch paths to Windows format before running javac and java 216 | verbose " - Compiling Downloader.java ..." 217 | "$(native_path "$JAVACCMD")" "$(native_path "$javaSource")" || die "Failed to compile Downloader.java" 218 | verbose " - Running Downloader.java ..." 219 | "$(native_path "$JAVACMD")" -cp "$(native_path "$TMP_DOWNLOAD_DIR")" Downloader "$distributionUrl" "$(native_path "$targetZip")" 220 | fi 221 | 222 | # If specified, validate the SHA-256 sum of the Maven distribution zip file 223 | if [ -n "${distributionSha256Sum-}" ]; then 224 | distributionSha256Result=false 225 | if [ "$MVN_CMD" = mvnd.sh ]; then 226 | echo "Checksum validation is not supported for maven-mvnd." >&2 227 | echo "Please disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2 228 | exit 1 229 | elif command -v sha256sum >/dev/null; then 230 | if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | sha256sum -c >/dev/null 2>&1; then 231 | distributionSha256Result=true 232 | fi 233 | elif command -v shasum >/dev/null; then 234 | if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | shasum -a 256 -c >/dev/null 2>&1; then 235 | distributionSha256Result=true 236 | fi 237 | else 238 | echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available." >&2 239 | echo "Please install either command, or disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2 240 | exit 1 241 | fi 242 | if [ $distributionSha256Result = false ]; then 243 | echo "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised." >&2 244 | echo "If you updated your Maven version, you need to update the specified distributionSha256Sum property." >&2 245 | exit 1 246 | fi 247 | fi 248 | 249 | # unzip and move 250 | if command -v unzip >/dev/null; then 251 | unzip ${__MVNW_QUIET_UNZIP:+"$__MVNW_QUIET_UNZIP"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -d "$TMP_DOWNLOAD_DIR" || die "failed to unzip" 252 | else 253 | tar xzf${__MVNW_QUIET_TAR:+"$__MVNW_QUIET_TAR"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -C "$TMP_DOWNLOAD_DIR" || die "failed to untar" 254 | fi 255 | printf %s\\n "$distributionUrl" >"$TMP_DOWNLOAD_DIR/$distributionUrlNameMain/mvnw.url" 256 | mv -- "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" "$MAVEN_HOME" || [ -d "$MAVEN_HOME" ] || die "fail to move MAVEN_HOME" 257 | 258 | clean || : 259 | exec_maven "$@" 260 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | 5 | 6 | io.airlift 7 | airbase 8 | 182 9 | 10 | units 11 | 1.11-SNAPSHOT 12 | jar 13 | 14 | units 15 | Library for immutable human-friendly units 16 | https://github.com/airlift/units 17 | 18 | 2016 19 | 20 | 21 | Airlift 22 | 23 | 24 | 25 | 26 | Apache License 2.0 27 | http://www.apache.org/licenses/LICENSE-2.0 28 | repo 29 | 30 | 31 | 32 | 33 | true 34 | true 35 | -missing 36 | 17 37 | 234 38 | 39 | 40 | 41 | 42 | 43 | com.fasterxml.jackson.core 44 | jackson-annotations 45 | true 46 | 47 | 48 | jakarta.validation 49 | jakarta.validation-api 50 | true 51 | 52 | 53 | 54 | com.google.guava 55 | guava 56 | test 57 | 58 | 59 | 60 | io.airlift 61 | json 62 | ${dep.airlift.version} 63 | test 64 | 65 | 66 | 67 | io.airlift 68 | testing 69 | ${dep.airlift.version} 70 | test 71 | 72 | 73 | 74 | org.assertj 75 | assertj-core 76 | test 77 | 78 | 79 | 80 | org.hibernate.validator 81 | hibernate-validator 82 | test 83 | 84 | 85 | 86 | 87 | org.testng 88 | testng 89 | test 90 | 91 | 92 | 93 | -------------------------------------------------------------------------------- /src/main/java/io/airlift/units/DataSize.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2010 Proofpoint, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.airlift.units; 17 | 18 | import com.fasterxml.jackson.annotation.JsonCreator; 19 | import com.fasterxml.jackson.annotation.JsonValue; 20 | 21 | import java.util.Locale; 22 | import java.util.regex.Matcher; 23 | import java.util.regex.Pattern; 24 | 25 | import static io.airlift.units.Preconditions.checkArgument; 26 | import static java.lang.Math.floor; 27 | import static java.lang.Math.multiplyExact; 28 | import static java.lang.String.format; 29 | import static java.util.Objects.requireNonNull; 30 | 31 | public final class DataSize 32 | implements Comparable 33 | { 34 | private static final Pattern DECIMAL_WITH_UNIT_PATTERN = Pattern.compile("^\\s*(\\d+(?:\\.\\d+)?)\\s*([a-zA-Z]+)\\s*$"); 35 | 36 | // We iterate over the DATASIZE_UNITS constant in convertToMostSuccinctDataSize() 37 | // instead of Unit.values() as the latter results in non-trivial amount of memory 38 | // allocation when that method is called in a tight loop. The reason is that the values() 39 | // call allocates a new array at each call. 40 | private static final Unit[] DATASIZE_UNITS = Unit.values(); 41 | 42 | /** 43 | * Creates a {@link DataSize} instance with the provided quantity of the provided {@link Unit}. This 44 | * value is immediately converted to bytes which might overflow. 45 | * 46 | * @param size The quantity of the supplied unit 47 | * @param unit The unit to use as the default unit for the constructed instance and to convert the size to bytes 48 | * @throws IllegalArgumentException If the provided size would overflow a long value when converting to bytes 49 | */ 50 | public static DataSize of(long size, Unit unit) 51 | throws IllegalArgumentException 52 | { 53 | requireNonNull(unit, "unit is null"); 54 | checkArgument(size >= 0, "size is negative: %s", size); 55 | if (unit == Unit.BYTE) { 56 | return new DataSize(size, unit); 57 | } 58 | try { 59 | return new DataSize(multiplyExact(size, unit.inBytes()), unit); 60 | } 61 | catch (ArithmeticException e) { 62 | throw new IllegalArgumentException(format("size is too large to be represented in bytes: %s%s", size, unit.getUnitString())); 63 | } 64 | } 65 | 66 | public static DataSize ofBytes(long bytes) 67 | { 68 | return new DataSize(bytes, Unit.BYTE); 69 | } 70 | 71 | /** 72 | * Prefer {@link DataSize#ofBytes(long)} when conversion to the most 'succinct' unit is not necessary or desirable 73 | */ 74 | public static DataSize succinctBytes(long bytes) 75 | { 76 | return ofBytes(bytes).succinct(); 77 | } 78 | 79 | /** 80 | * Prefer {@link DataSize#of(long, Unit)} when conversion to the most 'succinct' unit is not necessary or desirable. 81 | * Otherwise, use {@link DataSize#succinctBytes(long)} since it will not incur rounding and loss of precision. 82 | * 83 | * @deprecated use {@link DataSize#succinctBytes(long)} instead, double values are imprecise 84 | */ 85 | @Deprecated 86 | public static DataSize succinctDataSize(double size, Unit unit) 87 | { 88 | return new DataSize(roundDoubleSizeInUnitToLongBytes(size, unit), unit).succinct(); 89 | } 90 | 91 | private final long bytes; 92 | private final Unit unit; 93 | 94 | /** 95 | * Private constructor to avoid confusing usage sites with having to pass a number of bytes 96 | * alongside non-bytes unit 97 | * 98 | * @param bytes The number of bytes, regardless of unit 99 | * @param unit The preferred display unit of this value 100 | */ 101 | private DataSize(long bytes, Unit unit) 102 | { 103 | this.unit = requireNonNull(unit, "unit is null"); 104 | checkArgument(bytes >= 0, "bytes is negative"); 105 | this.bytes = bytes; 106 | } 107 | 108 | /** 109 | * @deprecated Use {@link DataSize#of(long, Unit)} instead. The imprecise nature of using doubles for DataSize is deprecated for removal 110 | */ 111 | @Deprecated 112 | public DataSize(double size, Unit unit) 113 | { 114 | this.unit = requireNonNull(unit, "unit is null"); 115 | this.bytes = roundDoubleSizeInUnitToLongBytes(size, unit); 116 | } 117 | 118 | public long toBytes() 119 | { 120 | return bytes; 121 | } 122 | 123 | /** 124 | * @deprecated Use {@link DataSize#toBytes()} instead to avoid floating point precision semantics 125 | */ 126 | @Deprecated 127 | public double getValue() 128 | { 129 | return getValue(this.unit); 130 | } 131 | 132 | public Unit getUnit() 133 | { 134 | return unit; 135 | } 136 | 137 | /** 138 | * @deprecated Use {@link DataSize#toBytes()} instead to avoid floating point precision semantics 139 | */ 140 | @Deprecated 141 | public double getValue(Unit unit) 142 | { 143 | requireNonNull(unit, "unit is null"); 144 | if (unit == Unit.BYTE) { 145 | return (double) bytes; 146 | } 147 | return bytes * (1.0d / unit.inBytes()); 148 | } 149 | 150 | /** 151 | * @deprecated Use {@link DataSize#toBytes()} instead. This method uses floating point semantics to compute the 152 | * rounded value which can yield to unexpected loss of precision beyond the intended rounding 153 | */ 154 | @Deprecated 155 | public long roundTo(Unit unit) 156 | { 157 | requireNonNull(unit, "unit is null"); 158 | if (unit == Unit.BYTE) { 159 | return bytes; 160 | } 161 | double rounded = floor(getValue(unit) + 0.5d); 162 | checkArgument(rounded <= Long.MAX_VALUE, 163 | "size is too large to be represented in requested unit as a long: %s%s", rounded, unit.getUnitString()); 164 | return (long) rounded; 165 | } 166 | 167 | private Unit succinctUnit() 168 | { 169 | Unit unitToUse = Unit.BYTE; 170 | for (Unit unitToTest : DATASIZE_UNITS) { 171 | if (unitToTest.bytes <= bytes) { 172 | unitToUse = unitToTest; 173 | } 174 | else { 175 | break; 176 | } 177 | } 178 | return unitToUse; 179 | } 180 | 181 | /** 182 | * @deprecated Use {@link DataSize#to(Unit)} instead. No conversion occurs when calling this method, only the unit 183 | * used for the default string representation is changed. This has no effect on the unit used during JSON serialization 184 | */ 185 | @Deprecated 186 | public DataSize convertTo(Unit unit) 187 | { 188 | return to(unit); 189 | } 190 | 191 | public DataSize to(Unit unit) 192 | { 193 | return new DataSize(bytes, unit); 194 | } 195 | 196 | public DataSize succinct() 197 | { 198 | return to(succinctUnit()); 199 | } 200 | 201 | /** 202 | * @deprecated Use {@link DataSize#succinct()} instead. No conversion occurs when calling this method, only the unit 203 | * used for the default string representation is changed. This has no effect on the unit used during JSON serialization 204 | */ 205 | @Deprecated 206 | public DataSize convertToMostSuccinctDataSize() 207 | { 208 | return succinct(); 209 | } 210 | 211 | @JsonValue 212 | public String toBytesValueString() 213 | { 214 | return bytes + Unit.BYTE.getUnitString(); 215 | } 216 | 217 | @Override 218 | public String toString() 219 | { 220 | // Fast-path for exact byte values 221 | if (this.unit == Unit.BYTE) { 222 | return toBytesValueString(); 223 | } 224 | double unitValue = getValue(); 225 | //noinspection FloatingPointEquality 226 | if (floor(unitValue) == unitValue) { 227 | return ((long) unitValue) + unit.getUnitString(); 228 | } 229 | return format(Locale.ENGLISH, "%.2f%s", unitValue, unit.getUnitString()); 230 | } 231 | 232 | @JsonCreator 233 | public static DataSize valueOf(String size) 234 | throws IllegalArgumentException 235 | { 236 | requireNonNull(size, "size is null"); 237 | checkArgument(!size.isEmpty(), "size is empty"); 238 | 239 | // Attempt fast path parsing of JSON values without regex validation 240 | int stringLength = size.length(); 241 | if (stringLength > 1 && stringLength <= 20 && size.charAt(0) != '+' && size.charAt(stringLength - 1) == 'B') { 242 | // must have at least 1 numeric char, less than Long.MAX_VALUE numeric chars, not start with a sign indicator, and be in unit BYTES 243 | try { 244 | return DataSize.ofBytes(Long.parseLong(size, 0, stringLength - 1, 10)); 245 | } 246 | catch (Exception ignored) { 247 | // Ignored, slow path will either handle or produce the appropriate error from here 248 | } 249 | } 250 | 251 | Matcher longOrDouble = DECIMAL_WITH_UNIT_PATTERN.matcher(size); 252 | if (!longOrDouble.matches()) { 253 | throw new IllegalArgumentException("size is not a valid data size string: " + size); 254 | } 255 | Unit unit = Unit.fromUnitString(longOrDouble.group(2)); 256 | String number = longOrDouble.group(1); 257 | if (number.indexOf('.') == -1) { 258 | // Strings without decimals can avoid precision loss by parsing as long 259 | return DataSize.of(Long.parseLong(number), unit); 260 | } 261 | return new DataSize(roundDoubleSizeInUnitToLongBytes(Double.parseDouble(number), unit), unit); 262 | } 263 | 264 | private static long roundDoubleSizeInUnitToLongBytes(double size, Unit unit) 265 | { 266 | checkArgument(!Double.isInfinite(size), "size is infinite"); 267 | checkArgument(!Double.isNaN(size), "size is not a number"); 268 | checkArgument(size >= 0, "size is negative: %s", size); 269 | requireNonNull(unit, "unit is null"); 270 | double rounded = floor((size / (1.0d / unit.inBytes())) + 0.5d); 271 | checkArgument(rounded <= Long.MAX_VALUE, 272 | "size is too large to be represented in requested unit as a long: %s%s", size, unit.getUnitString()); 273 | return (long) rounded; 274 | } 275 | 276 | @Override 277 | public int compareTo(DataSize o) 278 | { 279 | return Long.compare(bytes, o.bytes); 280 | } 281 | 282 | @Override 283 | public boolean equals(Object o) 284 | { 285 | if (this == o) { 286 | return true; 287 | } 288 | if (o == null || getClass() != o.getClass()) { 289 | return false; 290 | } 291 | return bytes == ((DataSize) o).bytes; 292 | } 293 | 294 | @Override 295 | public int hashCode() 296 | { 297 | return Long.hashCode(bytes); 298 | } 299 | 300 | public enum Unit 301 | { 302 | //This order is important, it should be in increasing magnitude. 303 | BYTE(1L, "B"), 304 | KILOBYTE(1L << 10, "kB"), 305 | MEGABYTE(1L << 20, "MB"), 306 | GIGABYTE(1L << 30, "GB"), 307 | TERABYTE(1L << 40, "TB"), 308 | PETABYTE(1L << 50, "PB"); 309 | 310 | private final long bytes; 311 | private final String unitString; 312 | 313 | Unit(long bytes, String unitString) 314 | { 315 | this.bytes = bytes; 316 | this.unitString = unitString; 317 | } 318 | 319 | public long inBytes() 320 | { 321 | return bytes; 322 | } 323 | 324 | public String getUnitString() 325 | { 326 | return unitString; 327 | } 328 | 329 | private static Unit fromUnitString(String unitString) 330 | { 331 | for (Unit unit : DATASIZE_UNITS) { 332 | if (unit.unitString.equals(unitString)) { 333 | return unit; 334 | } 335 | } 336 | throw new IllegalArgumentException("Unknown unit: " + unitString); 337 | } 338 | } 339 | } 340 | -------------------------------------------------------------------------------- /src/main/java/io/airlift/units/Duration.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2010 Proofpoint, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.airlift.units; 17 | 18 | import com.fasterxml.jackson.annotation.JsonCreator; 19 | import com.fasterxml.jackson.annotation.JsonValue; 20 | 21 | import java.util.Locale; 22 | import java.util.concurrent.TimeUnit; 23 | import java.util.regex.Matcher; 24 | import java.util.regex.Pattern; 25 | 26 | import static io.airlift.units.Preconditions.checkArgument; 27 | import static java.lang.Math.floor; 28 | import static java.lang.String.format; 29 | import static java.util.Objects.requireNonNull; 30 | import static java.util.concurrent.TimeUnit.DAYS; 31 | import static java.util.concurrent.TimeUnit.HOURS; 32 | import static java.util.concurrent.TimeUnit.MICROSECONDS; 33 | import static java.util.concurrent.TimeUnit.MILLISECONDS; 34 | import static java.util.concurrent.TimeUnit.MINUTES; 35 | import static java.util.concurrent.TimeUnit.NANOSECONDS; 36 | import static java.util.concurrent.TimeUnit.SECONDS; 37 | 38 | public final class Duration 39 | implements Comparable 40 | { 41 | private static final Pattern PATTERN = Pattern.compile("^\\s*(\\d+(?:\\.\\d+)?)\\s*([a-zA-Z]+)\\s*$"); 42 | 43 | // We iterate over the TIME_UNITS constant in convertToMostSuccinctTimeUnit() 44 | // instead of TimeUnit.values() as the latter results in non-trivial amount of memory 45 | // allocation when that method is called in a tight loop. The reason is that the values() 46 | // call allocates a new array at each call. 47 | private static final TimeUnit[] TIME_UNITS = TimeUnit.values(); 48 | 49 | public static final Duration ZERO = new Duration(0, SECONDS); 50 | 51 | public static Duration nanosSince(long start) 52 | { 53 | return succinctNanos(System.nanoTime() - start); 54 | } 55 | 56 | public static Duration succinctNanos(long nanos) 57 | { 58 | return succinctDuration(nanos, NANOSECONDS); 59 | } 60 | 61 | public static Duration succinctDuration(double value, TimeUnit unit) 62 | { 63 | if (value == 0) { 64 | return ZERO; 65 | } 66 | return new Duration(value, unit).convertToMostSuccinctTimeUnit(); 67 | } 68 | 69 | private final double value; 70 | private final TimeUnit unit; 71 | 72 | public Duration(double value, TimeUnit unit) 73 | { 74 | if (Double.isInfinite(value)) { 75 | throw new IllegalArgumentException("value is infinite: " + value); 76 | } 77 | checkArgument(!Double.isNaN(value), "value is not a number"); 78 | if (value < 0) { 79 | throw new IllegalArgumentException("value is negative: " + value); 80 | } 81 | requireNonNull(unit, "unit is null"); 82 | 83 | this.value = value; 84 | this.unit = unit; 85 | } 86 | 87 | public long toMillis() 88 | { 89 | return roundTo(MILLISECONDS); 90 | } 91 | 92 | public double getValue() 93 | { 94 | return value; 95 | } 96 | 97 | public TimeUnit getUnit() 98 | { 99 | return unit; 100 | } 101 | 102 | public double getValue(TimeUnit timeUnit) 103 | { 104 | requireNonNull(timeUnit, "timeUnit is null"); 105 | return value * (millisPerTimeUnit(this.unit) * 1.0 / millisPerTimeUnit(timeUnit)); 106 | } 107 | 108 | public long roundTo(TimeUnit timeUnit) 109 | { 110 | requireNonNull(timeUnit, "timeUnit is null"); 111 | double rounded = Math.floor(getValue(timeUnit) + 0.5d); 112 | if (rounded > Long.MAX_VALUE) { 113 | throw new IllegalArgumentException(format("value %s %s is too large to be represented in requested unit %s as a long", value, unit, timeUnit)); 114 | } 115 | return (long) rounded; 116 | } 117 | 118 | public Duration convertTo(TimeUnit timeUnit) 119 | { 120 | requireNonNull(timeUnit, "timeUnit is null"); 121 | return new Duration(getValue(timeUnit), timeUnit); 122 | } 123 | 124 | public Duration convertToMostSuccinctTimeUnit() 125 | { 126 | TimeUnit unitToUse = NANOSECONDS; 127 | for (TimeUnit unitToTest : TIME_UNITS) { 128 | // since time units are powers of ten, we can get rounding errors here, so fuzzy match 129 | if (getValue(unitToTest) > 0.9999) { 130 | unitToUse = unitToTest; 131 | } 132 | else { 133 | break; 134 | } 135 | } 136 | return convertTo(unitToUse); 137 | } 138 | 139 | public java.time.Duration toJavaTime() 140 | { 141 | long seconds; 142 | long nanoAdjustment; 143 | long secondsPerUnit = SECONDS.convert(1, unit); 144 | long nanosPerUnit = NANOSECONDS.convert(1, unit); 145 | if (secondsPerUnit > 1) { 146 | seconds = (long) floor(value * secondsPerUnit); 147 | nanoAdjustment = (long) floor((value - (double) seconds / secondsPerUnit) * nanosPerUnit); 148 | } 149 | else { 150 | long unitsPerSecond = unit.convert(1, SECONDS); 151 | seconds = (long) floor(value / unitsPerSecond); 152 | nanoAdjustment = (long) floor((value - (double) seconds * unitsPerSecond) * nanosPerUnit); 153 | } 154 | 155 | if (seconds == Long.MAX_VALUE) { 156 | nanoAdjustment = 0; 157 | } 158 | return java.time.Duration.ofSeconds(seconds, nanoAdjustment); 159 | } 160 | 161 | @JsonValue 162 | @Override 163 | public String toString() 164 | { 165 | return toString(unit); 166 | } 167 | 168 | public String toString(TimeUnit timeUnit) 169 | { 170 | requireNonNull(timeUnit, "timeUnit is null"); 171 | double magnitude = getValue(timeUnit); 172 | String timeUnitAbbreviation = timeUnitToString(timeUnit); 173 | return format(Locale.ENGLISH, "%.2f%s", magnitude, timeUnitAbbreviation); 174 | } 175 | 176 | @JsonCreator 177 | public static Duration valueOf(String duration) 178 | throws IllegalArgumentException 179 | { 180 | requireNonNull(duration, "duration is null"); 181 | checkArgument(!duration.isEmpty(), "duration is empty"); 182 | 183 | Matcher matcher = PATTERN.matcher(duration); 184 | if (!matcher.matches()) { 185 | throw new IllegalArgumentException("duration is not a valid data duration string: " + duration); 186 | } 187 | 188 | double value = Double.parseDouble(matcher.group(1)); 189 | String unitString = matcher.group(2); 190 | 191 | TimeUnit timeUnit = valueOfTimeUnit(unitString); 192 | return new Duration(value, timeUnit); 193 | } 194 | 195 | @Override 196 | public int compareTo(Duration o) 197 | { 198 | return Double.compare(getValue(MILLISECONDS), o.getValue(MILLISECONDS)); 199 | } 200 | 201 | public boolean isZero() 202 | { 203 | return equals(ZERO); 204 | } 205 | 206 | @Override 207 | public boolean equals(Object o) 208 | { 209 | if (this == o) { 210 | return true; 211 | } 212 | if (o == null || getClass() != o.getClass()) { 213 | return false; 214 | } 215 | 216 | Duration duration = (Duration) o; 217 | 218 | return compareTo(duration) == 0; 219 | } 220 | 221 | @Override 222 | public int hashCode() 223 | { 224 | double value = getValue(MILLISECONDS); 225 | return Double.hashCode(value); 226 | } 227 | 228 | public static TimeUnit valueOfTimeUnit(String timeUnitString) 229 | { 230 | requireNonNull(timeUnitString, "timeUnitString is null"); 231 | switch (timeUnitString) { 232 | case "ns": 233 | return NANOSECONDS; 234 | case "us": 235 | return MICROSECONDS; 236 | case "ms": 237 | return MILLISECONDS; 238 | case "s": 239 | return SECONDS; 240 | case "m": 241 | return MINUTES; 242 | case "h": 243 | return HOURS; 244 | case "d": 245 | return DAYS; 246 | default: 247 | throw new IllegalArgumentException("Unknown time unit: " + timeUnitString); 248 | } 249 | } 250 | 251 | public static String timeUnitToString(TimeUnit timeUnit) 252 | { 253 | requireNonNull(timeUnit, "timeUnit is null"); 254 | switch (timeUnit) { 255 | case NANOSECONDS: 256 | return "ns"; 257 | case MICROSECONDS: 258 | return "us"; 259 | case MILLISECONDS: 260 | return "ms"; 261 | case SECONDS: 262 | return "s"; 263 | case MINUTES: 264 | return "m"; 265 | case HOURS: 266 | return "h"; 267 | case DAYS: 268 | return "d"; 269 | default: 270 | throw new IllegalArgumentException("Unsupported time unit " + timeUnit); 271 | } 272 | } 273 | 274 | private static double millisPerTimeUnit(TimeUnit timeUnit) 275 | { 276 | switch (timeUnit) { 277 | case NANOSECONDS: 278 | return 1.0 / 1000000.0; 279 | case MICROSECONDS: 280 | return 1.0 / 1000.0; 281 | case MILLISECONDS: 282 | return 1; 283 | case SECONDS: 284 | return 1000; 285 | case MINUTES: 286 | return 1000 * 60; 287 | case HOURS: 288 | return 1000 * 60 * 60; 289 | case DAYS: 290 | return 1000 * 60 * 60 * 24; 291 | default: 292 | throw new IllegalArgumentException("Unsupported time unit " + timeUnit); 293 | } 294 | } 295 | } 296 | -------------------------------------------------------------------------------- /src/main/java/io/airlift/units/MaxDataSize.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed under the Apache License, Version 2.0 (the "License"); 3 | * you may not use this file except in compliance with the License. 4 | * You may obtain a copy of the License at 5 | * 6 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * 8 | * Unless required by applicable law or agreed to in writing, software 9 | * distributed under the License is distributed on an "AS IS" BASIS, 10 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | * See the License for the specific language governing permissions and 12 | * limitations under the License. 13 | */ 14 | package io.airlift.units; 15 | 16 | import jakarta.validation.Constraint; 17 | import jakarta.validation.Payload; 18 | 19 | import java.lang.annotation.Documented; 20 | import java.lang.annotation.Retention; 21 | import java.lang.annotation.Target; 22 | 23 | import static java.lang.annotation.ElementType.ANNOTATION_TYPE; 24 | import static java.lang.annotation.ElementType.CONSTRUCTOR; 25 | import static java.lang.annotation.ElementType.FIELD; 26 | import static java.lang.annotation.ElementType.METHOD; 27 | import static java.lang.annotation.ElementType.PARAMETER; 28 | import static java.lang.annotation.ElementType.TYPE_USE; 29 | import static java.lang.annotation.RetentionPolicy.RUNTIME; 30 | 31 | @Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE}) 32 | @Retention(RUNTIME) 33 | @Documented 34 | @Constraint(validatedBy = MaxDataSizeValidator.class) 35 | public @interface MaxDataSize 36 | { 37 | String value(); 38 | 39 | String message() default "{io.airlift.units.MaxDataSize.message}"; 40 | 41 | Class[] groups() default {}; 42 | 43 | Class[] payload() default {}; 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/io/airlift/units/MaxDataSizeValidator.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed under the Apache License, Version 2.0 (the "License"); 3 | * you may not use this file except in compliance with the License. 4 | * You may obtain a copy of the License at 5 | * 6 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * 8 | * Unless required by applicable law or agreed to in writing, software 9 | * distributed under the License is distributed on an "AS IS" BASIS, 10 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | * See the License for the specific language governing permissions and 12 | * limitations under the License. 13 | */ 14 | package io.airlift.units; 15 | 16 | import jakarta.validation.ConstraintValidator; 17 | import jakarta.validation.ConstraintValidatorContext; 18 | 19 | public class MaxDataSizeValidator 20 | implements ConstraintValidator 21 | { 22 | private DataSize max; 23 | 24 | @Override 25 | public void initialize(MaxDataSize dataSize) 26 | { 27 | this.max = DataSize.valueOf(dataSize.value()); 28 | } 29 | 30 | @Override 31 | public boolean isValid(DataSize dataSize, ConstraintValidatorContext context) 32 | { 33 | return (dataSize == null) || (dataSize.compareTo(max) <= 0); 34 | } 35 | 36 | @Override 37 | public String toString() 38 | { 39 | return "max:" + max; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/main/java/io/airlift/units/MaxDuration.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2010 Proofpoint, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.airlift.units; 17 | 18 | import jakarta.validation.Constraint; 19 | import jakarta.validation.Payload; 20 | 21 | import java.lang.annotation.Documented; 22 | import java.lang.annotation.Retention; 23 | import java.lang.annotation.Target; 24 | 25 | import static java.lang.annotation.ElementType.ANNOTATION_TYPE; 26 | import static java.lang.annotation.ElementType.CONSTRUCTOR; 27 | import static java.lang.annotation.ElementType.FIELD; 28 | import static java.lang.annotation.ElementType.METHOD; 29 | import static java.lang.annotation.ElementType.PARAMETER; 30 | import static java.lang.annotation.ElementType.TYPE_USE; 31 | import static java.lang.annotation.RetentionPolicy.RUNTIME; 32 | 33 | @Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE}) 34 | @Retention(RUNTIME) 35 | @Documented 36 | @Constraint(validatedBy = MaxDurationValidator.class) 37 | public @interface MaxDuration 38 | { 39 | String value(); 40 | 41 | String message() default "{io.airlift.units.MaxDuration.message}"; 42 | 43 | Class[] groups() default {}; 44 | 45 | Class[] payload() default {}; 46 | } 47 | -------------------------------------------------------------------------------- /src/main/java/io/airlift/units/MaxDurationValidator.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2010 Proofpoint, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.airlift.units; 17 | 18 | import jakarta.validation.ConstraintValidator; 19 | import jakarta.validation.ConstraintValidatorContext; 20 | 21 | public class MaxDurationValidator 22 | implements ConstraintValidator 23 | { 24 | private Duration max; 25 | 26 | @Override 27 | public void initialize(MaxDuration duration) 28 | { 29 | this.max = Duration.valueOf(duration.value()); 30 | } 31 | 32 | @Override 33 | public boolean isValid(Duration duration, ConstraintValidatorContext context) 34 | { 35 | return duration == null || duration.compareTo(max) <= 0; 36 | } 37 | 38 | @Override 39 | public String toString() 40 | { 41 | return "max:" + max; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/io/airlift/units/MaxThreadCount.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed under the Apache License, Version 2.0 (the "License"); 3 | * you may not use this file except in compliance with the License. 4 | * You may obtain a copy of the License at 5 | * 6 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * 8 | * Unless required by applicable law or agreed to in writing, software 9 | * distributed under the License is distributed on an "AS IS" BASIS, 10 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | * See the License for the specific language governing permissions and 12 | * limitations under the License. 13 | */ 14 | package io.airlift.units; 15 | 16 | import jakarta.validation.Constraint; 17 | import jakarta.validation.Payload; 18 | 19 | import java.lang.annotation.Documented; 20 | import java.lang.annotation.Retention; 21 | import java.lang.annotation.Target; 22 | 23 | import static java.lang.annotation.ElementType.ANNOTATION_TYPE; 24 | import static java.lang.annotation.ElementType.CONSTRUCTOR; 25 | import static java.lang.annotation.ElementType.FIELD; 26 | import static java.lang.annotation.ElementType.METHOD; 27 | import static java.lang.annotation.ElementType.PARAMETER; 28 | import static java.lang.annotation.ElementType.TYPE_USE; 29 | import static java.lang.annotation.RetentionPolicy.RUNTIME; 30 | 31 | @Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE}) 32 | @Retention(RUNTIME) 33 | @Documented 34 | @Constraint(validatedBy = MaxThreadCountValidator.class) 35 | public @interface MaxThreadCount 36 | { 37 | String value(); 38 | 39 | String message() default "{io.airlift.units.MaxThreadCount.message}"; 40 | 41 | Class[] groups() default {}; 42 | 43 | Class[] payload() default {}; 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/io/airlift/units/MaxThreadCountValidator.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed under the Apache License, Version 2.0 (the "License"); 3 | * you may not use this file except in compliance with the License. 4 | * You may obtain a copy of the License at 5 | * 6 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * 8 | * Unless required by applicable law or agreed to in writing, software 9 | * distributed under the License is distributed on an "AS IS" BASIS, 10 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | * See the License for the specific language governing permissions and 12 | * limitations under the License. 13 | */ 14 | package io.airlift.units; 15 | 16 | import jakarta.validation.ConstraintValidator; 17 | import jakarta.validation.ConstraintValidatorContext; 18 | 19 | public class MaxThreadCountValidator 20 | implements ConstraintValidator 21 | { 22 | private ThreadCount max; 23 | 24 | @Override 25 | public void initialize(MaxThreadCount annotation) 26 | { 27 | this.max = ThreadCount.valueOf(annotation.value()); 28 | } 29 | 30 | @Override 31 | public boolean isValid(ThreadCount threadCount, ConstraintValidatorContext constraintValidatorContext) 32 | { 33 | return threadCount == null || threadCount.compareTo(max) <= 0; 34 | } 35 | 36 | @Override 37 | public String toString() 38 | { 39 | return "max: " + max; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/main/java/io/airlift/units/MinDataSize.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed under the Apache License, Version 2.0 (the "License"); 3 | * you may not use this file except in compliance with the License. 4 | * You may obtain a copy of the License at 5 | * 6 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * 8 | * Unless required by applicable law or agreed to in writing, software 9 | * distributed under the License is distributed on an "AS IS" BASIS, 10 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | * See the License for the specific language governing permissions and 12 | * limitations under the License. 13 | */ 14 | package io.airlift.units; 15 | 16 | import jakarta.validation.Constraint; 17 | import jakarta.validation.Payload; 18 | 19 | import java.lang.annotation.Documented; 20 | import java.lang.annotation.Retention; 21 | import java.lang.annotation.Target; 22 | 23 | import static java.lang.annotation.ElementType.ANNOTATION_TYPE; 24 | import static java.lang.annotation.ElementType.CONSTRUCTOR; 25 | import static java.lang.annotation.ElementType.FIELD; 26 | import static java.lang.annotation.ElementType.METHOD; 27 | import static java.lang.annotation.ElementType.PARAMETER; 28 | import static java.lang.annotation.ElementType.TYPE_USE; 29 | import static java.lang.annotation.RetentionPolicy.RUNTIME; 30 | 31 | @Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE}) 32 | @Retention(RUNTIME) 33 | @Documented 34 | @Constraint(validatedBy = MinDataSizeValidator.class) 35 | public @interface MinDataSize 36 | { 37 | String value(); 38 | 39 | String message() default "{io.airlift.units.MinDataSize.message}"; 40 | 41 | Class[] groups() default {}; 42 | 43 | Class[] payload() default {}; 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/io/airlift/units/MinDataSizeValidator.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed under the Apache License, Version 2.0 (the "License"); 3 | * you may not use this file except in compliance with the License. 4 | * You may obtain a copy of the License at 5 | * 6 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * 8 | * Unless required by applicable law or agreed to in writing, software 9 | * distributed under the License is distributed on an "AS IS" BASIS, 10 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | * See the License for the specific language governing permissions and 12 | * limitations under the License. 13 | */ 14 | package io.airlift.units; 15 | 16 | import jakarta.validation.ConstraintValidator; 17 | import jakarta.validation.ConstraintValidatorContext; 18 | 19 | public class MinDataSizeValidator 20 | implements ConstraintValidator 21 | { 22 | private DataSize min; 23 | 24 | @Override 25 | public void initialize(MinDataSize dataSize) 26 | { 27 | this.min = DataSize.valueOf(dataSize.value()); 28 | } 29 | 30 | @Override 31 | public boolean isValid(DataSize dataSize, ConstraintValidatorContext context) 32 | { 33 | return (dataSize == null) || (dataSize.compareTo(min) >= 0); 34 | } 35 | 36 | @Override 37 | public String toString() 38 | { 39 | return "min:" + min; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/main/java/io/airlift/units/MinDuration.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2010 Proofpoint, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.airlift.units; 17 | 18 | import jakarta.validation.Constraint; 19 | import jakarta.validation.Payload; 20 | 21 | import java.lang.annotation.Documented; 22 | import java.lang.annotation.Retention; 23 | import java.lang.annotation.Target; 24 | 25 | import static java.lang.annotation.ElementType.ANNOTATION_TYPE; 26 | import static java.lang.annotation.ElementType.CONSTRUCTOR; 27 | import static java.lang.annotation.ElementType.FIELD; 28 | import static java.lang.annotation.ElementType.METHOD; 29 | import static java.lang.annotation.ElementType.PARAMETER; 30 | import static java.lang.annotation.ElementType.TYPE_USE; 31 | import static java.lang.annotation.RetentionPolicy.RUNTIME; 32 | 33 | @Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE}) 34 | @Retention(RUNTIME) 35 | @Documented 36 | @Constraint(validatedBy = MinDurationValidator.class) 37 | public @interface MinDuration 38 | { 39 | String value(); 40 | 41 | String message() default "{io.airlift.units.MinDuration.message}"; 42 | 43 | Class[] groups() default {}; 44 | 45 | Class[] payload() default {}; 46 | } 47 | -------------------------------------------------------------------------------- /src/main/java/io/airlift/units/MinDurationValidator.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2010 Proofpoint, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.airlift.units; 17 | 18 | import jakarta.validation.ConstraintValidator; 19 | import jakarta.validation.ConstraintValidatorContext; 20 | 21 | public class MinDurationValidator 22 | implements ConstraintValidator 23 | { 24 | private Duration min; 25 | 26 | @Override 27 | public void initialize(MinDuration duration) 28 | { 29 | this.min = Duration.valueOf(duration.value()); 30 | } 31 | 32 | @Override 33 | public boolean isValid(Duration duration, ConstraintValidatorContext context) 34 | { 35 | return duration == null || duration.compareTo(min) >= 0; 36 | } 37 | 38 | @Override 39 | public String toString() 40 | { 41 | return "min:" + min; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/io/airlift/units/MinThreadCount.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed under the Apache License, Version 2.0 (the "License"); 3 | * you may not use this file except in compliance with the License. 4 | * You may obtain a copy of the License at 5 | * 6 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * 8 | * Unless required by applicable law or agreed to in writing, software 9 | * distributed under the License is distributed on an "AS IS" BASIS, 10 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | * See the License for the specific language governing permissions and 12 | * limitations under the License. 13 | */ 14 | package io.airlift.units; 15 | 16 | import jakarta.validation.Constraint; 17 | import jakarta.validation.Payload; 18 | 19 | import java.lang.annotation.Documented; 20 | import java.lang.annotation.Retention; 21 | import java.lang.annotation.Target; 22 | 23 | import static java.lang.annotation.ElementType.ANNOTATION_TYPE; 24 | import static java.lang.annotation.ElementType.CONSTRUCTOR; 25 | import static java.lang.annotation.ElementType.FIELD; 26 | import static java.lang.annotation.ElementType.METHOD; 27 | import static java.lang.annotation.ElementType.PARAMETER; 28 | import static java.lang.annotation.ElementType.TYPE_USE; 29 | import static java.lang.annotation.RetentionPolicy.RUNTIME; 30 | 31 | @Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE}) 32 | @Retention(RUNTIME) 33 | @Documented 34 | @Constraint(validatedBy = MinThreadCountValidator.class) 35 | public @interface MinThreadCount 36 | { 37 | String value(); 38 | 39 | String message() default "{io.airlift.units.MinThreadCount.message}"; 40 | 41 | Class[] groups() default {}; 42 | 43 | Class[] payload() default {}; 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/io/airlift/units/MinThreadCountValidator.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed under the Apache License, Version 2.0 (the "License"); 3 | * you may not use this file except in compliance with the License. 4 | * You may obtain a copy of the License at 5 | * 6 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * 8 | * Unless required by applicable law or agreed to in writing, software 9 | * distributed under the License is distributed on an "AS IS" BASIS, 10 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | * See the License for the specific language governing permissions and 12 | * limitations under the License. 13 | */ 14 | package io.airlift.units; 15 | 16 | import jakarta.validation.ConstraintValidator; 17 | import jakarta.validation.ConstraintValidatorContext; 18 | 19 | public class MinThreadCountValidator 20 | implements ConstraintValidator 21 | { 22 | private ThreadCount min; 23 | 24 | @Override 25 | public void initialize(MinThreadCount annotation) 26 | { 27 | this.min = ThreadCount.valueOf(annotation.value()); 28 | } 29 | 30 | @Override 31 | public boolean isValid(ThreadCount threadCount, ConstraintValidatorContext constraintValidatorContext) 32 | { 33 | return threadCount == null || threadCount.compareTo(min) >= 0; 34 | } 35 | 36 | @Override 37 | public String toString() 38 | { 39 | return "min: " + min; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/main/java/io/airlift/units/Preconditions.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2007 The Guava Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.airlift.units; 17 | 18 | // forked from com.google.common.base.Preconditions 19 | final class Preconditions 20 | { 21 | private Preconditions() {} 22 | 23 | static void checkArgument(boolean expression, String errorMessage) 24 | { 25 | if (!expression) { 26 | throw new IllegalArgumentException(errorMessage); 27 | } 28 | } 29 | 30 | static void checkArgument(boolean expression, String errorMessageTemplate, Object... errorMessageArgs) 31 | { 32 | if (!expression) { 33 | throw new IllegalArgumentException(format(errorMessageTemplate, errorMessageArgs)); 34 | } 35 | } 36 | 37 | static void checkState(boolean expression, String errorMessage) 38 | { 39 | if (!expression) { 40 | throw new IllegalStateException(errorMessage); 41 | } 42 | } 43 | 44 | static void checkState(boolean expression, String errorMessageTemplate, Object... errorMessageArgs) 45 | { 46 | if (!expression) { 47 | throw new IllegalStateException(format(errorMessageTemplate, errorMessageArgs)); 48 | } 49 | } 50 | 51 | private static String format(String template, Object... args) 52 | { 53 | template = String.valueOf(template); // null -> "null" 54 | 55 | // start substituting the arguments into the '%s' placeholders 56 | StringBuilder builder = new StringBuilder(template.length() + 16 * args.length); 57 | int templateStart = 0; 58 | int i = 0; 59 | while (i < args.length) { 60 | int placeholderStart = template.indexOf("%s", templateStart); 61 | if (placeholderStart == -1) { 62 | break; 63 | } 64 | builder.append(template.substring(templateStart, placeholderStart)); 65 | builder.append(args[i++]); 66 | templateStart = placeholderStart + 2; 67 | } 68 | builder.append(template.substring(templateStart)); 69 | 70 | // if we run out of placeholders, append the extra args in square braces 71 | if (i < args.length) { 72 | builder.append(" ["); 73 | builder.append(args[i++]); 74 | while (i < args.length) { 75 | builder.append(", "); 76 | builder.append(args[i++]); 77 | } 78 | builder.append(']'); 79 | } 80 | 81 | return builder.toString(); 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/main/java/io/airlift/units/ThreadCount.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed under the Apache License, Version 2.0 (the "License"); 3 | * you may not use this file except in compliance with the License. 4 | * You may obtain a copy of the License at 5 | * 6 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * 8 | * Unless required by applicable law or agreed to in writing, software 9 | * distributed under the License is distributed on an "AS IS" BASIS, 10 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | * See the License for the specific language governing permissions and 12 | * limitations under the License. 13 | */ 14 | package io.airlift.units; 15 | 16 | import java.io.IOException; 17 | import java.nio.file.Path; 18 | import java.nio.file.Paths; 19 | import java.util.OptionalInt; 20 | import java.util.function.Supplier; 21 | import java.util.stream.Stream; 22 | 23 | import static io.airlift.units.Preconditions.checkArgument; 24 | import static java.lang.Math.min; 25 | import static java.lang.Math.round; 26 | import static java.lang.Math.toIntExact; 27 | import static java.lang.String.format; 28 | import static java.nio.file.Files.exists; 29 | import static java.nio.file.Files.lines; 30 | 31 | public class ThreadCount 32 | implements Comparable 33 | { 34 | private static final String PER_CORE_SUFFIX = "C"; 35 | private static final Supplier AVAILABLE_PROCESSORS = MachineInfo::getAvailablePhysicalProcessorCount; 36 | private final int threadCount; 37 | 38 | ThreadCount(int threadCount) 39 | { 40 | checkArgument(threadCount >= 0, "Thread count cannot be negative"); 41 | this.threadCount = threadCount; 42 | } 43 | 44 | public int getThreadCount() 45 | { 46 | return threadCount; 47 | } 48 | 49 | public static ThreadCount exactValueOf(int value) 50 | { 51 | return new ThreadCount(value); 52 | } 53 | 54 | public static ThreadCount valueOf(String value) 55 | { 56 | if (value.endsWith(PER_CORE_SUFFIX)) { 57 | float parsedMultiplier = parseFloat(value.substring(0, value.lastIndexOf(PER_CORE_SUFFIX)).trim()); 58 | checkArgument(parsedMultiplier > 0, "Thread multiplier cannot be negative"); 59 | float threadCount = parsedMultiplier * AVAILABLE_PROCESSORS.get(); 60 | checkArgument(threadCount <= Integer.MAX_VALUE, "Thread count is greater than 2^32 - 1"); 61 | return new ThreadCount(round(threadCount)); 62 | } 63 | 64 | return new ThreadCount(parseInteger(value)); 65 | } 66 | 67 | public static ThreadCount boundedValueOf(String value, String minValue, String maxValue) 68 | { 69 | ThreadCount parsed = ThreadCount.valueOf(value); 70 | ThreadCount min = ThreadCount.valueOf(minValue); 71 | ThreadCount max = ThreadCount.valueOf(maxValue); 72 | 73 | if (parsed.compareTo(min) < 0) { 74 | return min; 75 | } 76 | 77 | if (parsed.compareTo(max) > 0) { 78 | return max; 79 | } 80 | return parsed; 81 | } 82 | 83 | private static float parseFloat(String value) 84 | { 85 | try { 86 | return Float.parseFloat(value); 87 | } 88 | catch (NumberFormatException e) { 89 | throw new IllegalArgumentException(format("Cannot parse value '%s' as float", value), e); 90 | } 91 | } 92 | 93 | private static int parseInteger(String value) 94 | { 95 | try { 96 | long parsed = Long.parseLong(value); 97 | checkArgument(parsed <= Integer.MAX_VALUE, "Thread count is greater than 2^32 - 1"); 98 | return toIntExact(parsed); 99 | } 100 | catch (NumberFormatException e) { 101 | throw new IllegalArgumentException(format("Cannot parse value '%s' as integer", value), e); 102 | } 103 | } 104 | 105 | @Override 106 | public int compareTo(ThreadCount o) 107 | { 108 | return Integer.compare(threadCount, o.threadCount); 109 | } 110 | 111 | @Override 112 | public boolean equals(Object o) 113 | { 114 | if (this == o) { 115 | return true; 116 | } 117 | if (o == null || getClass() != o.getClass()) { 118 | return false; 119 | } 120 | 121 | ThreadCount that = (ThreadCount) o; 122 | return threadCount == that.threadCount; 123 | } 124 | 125 | @Override 126 | public int hashCode() 127 | { 128 | return threadCount; 129 | } 130 | 131 | @Override 132 | public String toString() 133 | { 134 | return (threadCount == 1) ? "1 thread" : (threadCount + " threads"); 135 | } 136 | 137 | static final class MachineInfo 138 | { 139 | private static final Path CPU_INFO_PATH = Paths.get("/proc/cpuinfo"); 140 | 141 | // cache physical processor count, so that it's not queried multiple times during tests 142 | private static volatile int physicalProcessorCount = -1; 143 | 144 | private MachineInfo() {} 145 | 146 | public static int getAvailablePhysicalProcessorCount() 147 | { 148 | if (physicalProcessorCount != -1) { 149 | return physicalProcessorCount; 150 | } 151 | 152 | String osArch = System.getProperty("os.arch"); 153 | // logical core count (including container cpu quota if there is any) 154 | int availableProcessorCount = Runtime.getRuntime().availableProcessors(); 155 | int totalPhysicalProcessorCount = availableProcessorCount; 156 | if ("amd64".equals(osArch) || "x86_64".equals(osArch)) { 157 | OptionalInt procInfo = tryReadFromProcCpuinfo(); 158 | if (procInfo.isPresent()) { 159 | totalPhysicalProcessorCount = procInfo.getAsInt(); 160 | } 161 | } 162 | 163 | // cap available processor count to container cpu quota (if there is any). 164 | physicalProcessorCount = min(totalPhysicalProcessorCount, availableProcessorCount); 165 | return physicalProcessorCount; 166 | } 167 | 168 | private static OptionalInt tryReadFromProcCpuinfo() 169 | { 170 | if (!exists(CPU_INFO_PATH)) { 171 | return OptionalInt.empty(); 172 | } 173 | 174 | try (Stream lines = lines(CPU_INFO_PATH)) { 175 | return OptionalInt.of(toIntExact(lines.filter(line -> 176 | line.matches("^processor\\s+: \\d")).count())); 177 | } 178 | catch (IOException e) { 179 | return OptionalInt.empty(); 180 | } 181 | } 182 | } 183 | } 184 | -------------------------------------------------------------------------------- /src/main/java/module-info.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed under the Apache License, Version 2.0 (the "License"); 3 | * you may not use this file except in compliance with the License. 4 | * You may obtain a copy of the License at 5 | * 6 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * 8 | * Unless required by applicable law or agreed to in writing, software 9 | * distributed under the License is distributed on an "AS IS" BASIS, 10 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | * See the License for the specific language governing permissions and 12 | * limitations under the License. 13 | */ 14 | module org.airlift.units { 15 | requires com.fasterxml.jackson.annotation; 16 | requires jakarta.validation; 17 | 18 | exports io.airlift.units; 19 | } 20 | -------------------------------------------------------------------------------- /src/main/resources/ValidationMessages.properties: -------------------------------------------------------------------------------- 1 | io.airlift.units.MinDuration.message=must be greater than or equal to {value} 2 | io.airlift.units.MaxDuration.message=must be less than or equal to {value} 3 | io.airlift.units.MinDataSize.message=must be greater than or equal to {value} 4 | io.airlift.units.MaxDataSize.message=must be less than or equal to {value} 5 | io.airlift.units.MinThreadCount.message=must be greater than or equal to {value} 6 | io.airlift.units.MaxThreadCount.message=must be less than or equal to {value} 7 | -------------------------------------------------------------------------------- /src/test/java/io/airlift/units/ConstraintValidatorAssert.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed under the Apache License, Version 2.0 (the "License"); 3 | * you may not use this file except in compliance with the License. 4 | * You may obtain a copy of the License at 5 | * 6 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * 8 | * Unless required by applicable law or agreed to in writing, software 9 | * distributed under the License is distributed on an "AS IS" BASIS, 10 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | * See the License for the specific language governing permissions and 12 | * limitations under the License. 13 | */ 14 | package io.airlift.units; 15 | 16 | import jakarta.validation.ClockProvider; 17 | import jakarta.validation.ConstraintValidator; 18 | import jakarta.validation.ConstraintValidatorContext; 19 | import org.assertj.core.api.AbstractAssert; 20 | 21 | import java.lang.annotation.Annotation; 22 | 23 | final class ConstraintValidatorAssert 24 | extends AbstractAssert, ConstraintValidator> 25 | { 26 | public ConstraintValidatorAssert(ConstraintValidator actual) 27 | { 28 | super(actual, ConstraintValidatorAssert.class); 29 | } 30 | 31 | public static ConstraintValidatorAssert assertThat(ConstraintValidator actual) 32 | { 33 | return new ConstraintValidatorAssert<>(actual); 34 | } 35 | 36 | public void isValidFor(T value) 37 | { 38 | isNotNull(); 39 | if (!actual.isValid(value, new MockContext())) { 40 | failWithMessage("Expected <%s> to be valid for <%s>", actual, value); 41 | } 42 | } 43 | 44 | public void isInvalidFor(T value) 45 | { 46 | isNotNull(); 47 | if (actual.isValid(value, new MockContext())) { 48 | failWithMessage("Expected <%s> to be invalid for <%s>", actual, value); 49 | } 50 | } 51 | 52 | private static class MockContext 53 | implements ConstraintValidatorContext 54 | { 55 | @Override 56 | public void disableDefaultConstraintViolation() 57 | { 58 | throw new UnsupportedOperationException(); 59 | } 60 | 61 | @Override 62 | public String getDefaultConstraintMessageTemplate() 63 | { 64 | throw new UnsupportedOperationException(); 65 | } 66 | 67 | @Override 68 | public ClockProvider getClockProvider() 69 | { 70 | throw new UnsupportedOperationException(); 71 | } 72 | 73 | @Override 74 | public ConstraintViolationBuilder buildConstraintViolationWithTemplate(String s) 75 | { 76 | throw new UnsupportedOperationException(); 77 | } 78 | 79 | @Override 80 | public T unwrap(Class type) 81 | { 82 | throw new UnsupportedOperationException(); 83 | } 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/test/java/io/airlift/units/MockMaxDataSize.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed under the Apache License, Version 2.0 (the "License"); 3 | * you may not use this file except in compliance with the License. 4 | * You may obtain a copy of the License at 5 | * 6 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * 8 | * Unless required by applicable law or agreed to in writing, software 9 | * distributed under the License is distributed on an "AS IS" BASIS, 10 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | * See the License for the specific language governing permissions and 12 | * limitations under the License. 13 | */ 14 | package io.airlift.units; 15 | 16 | import jakarta.validation.Payload; 17 | 18 | import java.lang.annotation.Annotation; 19 | 20 | import static java.util.Objects.requireNonNull; 21 | 22 | @SuppressWarnings("ClassExplicitlyAnnotation") 23 | class MockMaxDataSize 24 | implements MaxDataSize 25 | { 26 | private final DataSize dataSize; 27 | 28 | public MockMaxDataSize(DataSize dataSize) 29 | { 30 | this.dataSize = requireNonNull(dataSize, "dataSize is null"); 31 | } 32 | 33 | @Override 34 | public String value() 35 | { 36 | return dataSize.toString(); 37 | } 38 | 39 | @Override 40 | public String message() 41 | { 42 | throw new UnsupportedOperationException(); 43 | } 44 | 45 | @Override 46 | public Class[] groups() 47 | { 48 | throw new UnsupportedOperationException(); 49 | } 50 | 51 | @Override 52 | public Class[] payload() 53 | { 54 | throw new UnsupportedOperationException(); 55 | } 56 | 57 | @Override 58 | public Class annotationType() 59 | { 60 | return MaxDuration.class; 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/test/java/io/airlift/units/MockMaxDuration.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2010 Proofpoint, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.airlift.units; 17 | 18 | import jakarta.validation.Payload; 19 | 20 | import java.lang.annotation.Annotation; 21 | 22 | @SuppressWarnings("ClassExplicitlyAnnotation") 23 | class MockMaxDuration 24 | implements MaxDuration 25 | { 26 | private final Duration duration; 27 | 28 | public MockMaxDuration(Duration duration) 29 | { 30 | this.duration = duration; 31 | } 32 | 33 | @Override 34 | public String value() 35 | { 36 | return duration.toString(); 37 | } 38 | 39 | @Override 40 | public String message() 41 | { 42 | throw new UnsupportedOperationException(); 43 | } 44 | 45 | @Override 46 | public Class[] groups() 47 | { 48 | throw new UnsupportedOperationException(); 49 | } 50 | 51 | @Override 52 | public Class[] payload() 53 | { 54 | throw new UnsupportedOperationException(); 55 | } 56 | 57 | @Override 58 | public Class annotationType() 59 | { 60 | return MaxDuration.class; 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/test/java/io/airlift/units/MockMaxThreadCount.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed under the Apache License, Version 2.0 (the "License"); 3 | * you may not use this file except in compliance with the License. 4 | * You may obtain a copy of the License at 5 | * 6 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * 8 | * Unless required by applicable law or agreed to in writing, software 9 | * distributed under the License is distributed on an "AS IS" BASIS, 10 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | * See the License for the specific language governing permissions and 12 | * limitations under the License. 13 | */ 14 | package io.airlift.units; 15 | 16 | import jakarta.validation.Payload; 17 | 18 | import java.lang.annotation.Annotation; 19 | 20 | import static java.util.Objects.requireNonNull; 21 | 22 | @SuppressWarnings("ClassExplicitlyAnnotation") 23 | class MockMaxThreadCount 24 | implements MaxThreadCount 25 | { 26 | private final ThreadCount threadCount; 27 | 28 | public MockMaxThreadCount(ThreadCount threadCount) 29 | { 30 | this.threadCount = requireNonNull(threadCount, "threadCount is null"); 31 | } 32 | 33 | @Override 34 | public String value() 35 | { 36 | return String.valueOf(threadCount.getThreadCount()); 37 | } 38 | 39 | @Override 40 | public String message() 41 | { 42 | throw new UnsupportedOperationException(); 43 | } 44 | 45 | @Override 46 | public Class[] groups() 47 | { 48 | throw new UnsupportedOperationException(); 49 | } 50 | 51 | @Override 52 | public Class[] payload() 53 | { 54 | throw new UnsupportedOperationException(); 55 | } 56 | 57 | @Override 58 | public Class annotationType() 59 | { 60 | return MaxThreadCount.class; 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/test/java/io/airlift/units/MockMinDataSize.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed under the Apache License, Version 2.0 (the "License"); 3 | * you may not use this file except in compliance with the License. 4 | * You may obtain a copy of the License at 5 | * 6 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * 8 | * Unless required by applicable law or agreed to in writing, software 9 | * distributed under the License is distributed on an "AS IS" BASIS, 10 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | * See the License for the specific language governing permissions and 12 | * limitations under the License. 13 | */ 14 | package io.airlift.units; 15 | 16 | import jakarta.validation.Payload; 17 | 18 | import java.lang.annotation.Annotation; 19 | 20 | import static java.util.Objects.requireNonNull; 21 | 22 | @SuppressWarnings("ClassExplicitlyAnnotation") 23 | class MockMinDataSize 24 | implements MinDataSize 25 | { 26 | private final DataSize dataSize; 27 | 28 | public MockMinDataSize(DataSize dataSize) 29 | { 30 | this.dataSize = requireNonNull(dataSize, "dataSize is null"); 31 | } 32 | 33 | @Override 34 | public String value() 35 | { 36 | return dataSize.toString(); 37 | } 38 | 39 | @Override 40 | public String message() 41 | { 42 | throw new UnsupportedOperationException(); 43 | } 44 | 45 | @Override 46 | public Class[] groups() 47 | { 48 | throw new UnsupportedOperationException(); 49 | } 50 | 51 | @Override 52 | public Class[] payload() 53 | { 54 | throw new UnsupportedOperationException(); 55 | } 56 | 57 | @Override 58 | public Class annotationType() 59 | { 60 | return MinDataSize.class; 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/test/java/io/airlift/units/MockMinDuration.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2010 Proofpoint, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.airlift.units; 17 | 18 | import jakarta.validation.Payload; 19 | 20 | import java.lang.annotation.Annotation; 21 | 22 | @SuppressWarnings("ClassExplicitlyAnnotation") 23 | class MockMinDuration 24 | implements MinDuration 25 | { 26 | private final Duration duration; 27 | 28 | public MockMinDuration(Duration duration) 29 | { 30 | this.duration = duration; 31 | } 32 | 33 | @Override 34 | public String value() 35 | { 36 | return duration.toString(); 37 | } 38 | 39 | @Override 40 | public String message() 41 | { 42 | throw new UnsupportedOperationException(); 43 | } 44 | 45 | @Override 46 | public Class[] groups() 47 | { 48 | throw new UnsupportedOperationException(); 49 | } 50 | 51 | @Override 52 | public Class[] payload() 53 | { 54 | throw new UnsupportedOperationException(); 55 | } 56 | 57 | @Override 58 | public Class annotationType() 59 | { 60 | return MinDuration.class; 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/test/java/io/airlift/units/MockMinThreadCount.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed under the Apache License, Version 2.0 (the "License"); 3 | * you may not use this file except in compliance with the License. 4 | * You may obtain a copy of the License at 5 | * 6 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * 8 | * Unless required by applicable law or agreed to in writing, software 9 | * distributed under the License is distributed on an "AS IS" BASIS, 10 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | * See the License for the specific language governing permissions and 12 | * limitations under the License. 13 | */ 14 | package io.airlift.units; 15 | 16 | import jakarta.validation.Payload; 17 | 18 | import java.lang.annotation.Annotation; 19 | 20 | import static java.util.Objects.requireNonNull; 21 | 22 | @SuppressWarnings("ClassExplicitlyAnnotation") 23 | class MockMinThreadCount 24 | implements MinThreadCount 25 | { 26 | private final ThreadCount threadCount; 27 | 28 | public MockMinThreadCount(ThreadCount threadCount) 29 | { 30 | this.threadCount = requireNonNull(threadCount, "threadCount is null"); 31 | } 32 | 33 | @Override 34 | public String value() 35 | { 36 | return String.valueOf(threadCount.getThreadCount()); 37 | } 38 | 39 | @Override 40 | public String message() 41 | { 42 | throw new UnsupportedOperationException(); 43 | } 44 | 45 | @Override 46 | public Class[] groups() 47 | { 48 | throw new UnsupportedOperationException(); 49 | } 50 | 51 | @Override 52 | public Class[] payload() 53 | { 54 | throw new UnsupportedOperationException(); 55 | } 56 | 57 | @Override 58 | public Class annotationType() 59 | { 60 | return MinThreadCount.class; 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/test/java/io/airlift/units/TestDataSize.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2010 Proofpoint, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.airlift.units; 17 | 18 | import com.google.common.collect.ImmutableList; 19 | import io.airlift.json.JsonCodec; 20 | import org.testng.annotations.DataProvider; 21 | import org.testng.annotations.Test; 22 | 23 | import java.util.Locale; 24 | 25 | import static io.airlift.testing.EquivalenceTester.comparisonTester; 26 | import static io.airlift.units.DataSize.Unit.BYTE; 27 | import static io.airlift.units.DataSize.Unit.GIGABYTE; 28 | import static io.airlift.units.DataSize.Unit.KILOBYTE; 29 | import static io.airlift.units.DataSize.Unit.MEGABYTE; 30 | import static io.airlift.units.DataSize.Unit.PETABYTE; 31 | import static io.airlift.units.DataSize.Unit.TERABYTE; 32 | import static io.airlift.units.DataSize.succinctBytes; 33 | import static io.airlift.units.DataSize.succinctDataSize; 34 | import static org.assertj.core.api.Assertions.assertThat; 35 | import static org.assertj.core.api.Assertions.within; 36 | import static org.testng.Assert.assertEquals; 37 | 38 | public class TestDataSize 39 | { 40 | @Test 41 | public void testSuccinctFactories() 42 | { 43 | assertEquals(succinctBytes(123), DataSize.ofBytes(123)); 44 | assertEquals(succinctBytes(123).getUnit(), BYTE); 45 | 46 | DataSize fiveMiBi = DataSize.ofBytes(5 * 1024 * 1024); 47 | assertEquals(succinctBytes(fiveMiBi.toBytes()), fiveMiBi); 48 | assertEquals(succinctBytes(fiveMiBi.toBytes()).getUnit(), MEGABYTE); 49 | } 50 | 51 | @Test 52 | public void testDeprecatedSuccinctFactories() 53 | { 54 | assertEquals(succinctDataSize(123, BYTE), DataSize.ofBytes(123)); 55 | assertEquals(succinctDataSize(123, BYTE).getUnit(), BYTE); 56 | 57 | assertEquals(succinctDataSize((long) (5.5 * 1024), BYTE), new DataSize(5.5, KILOBYTE)); 58 | assertEquals(succinctDataSize((long) (5.5 * 1024), BYTE).getUnit(), KILOBYTE); 59 | 60 | assertEquals(succinctDataSize(5 * 1024, KILOBYTE), DataSize.of(5, MEGABYTE)); 61 | assertEquals(succinctDataSize(5 * 1024, KILOBYTE).getUnit(), MEGABYTE); 62 | } 63 | 64 | @Test 65 | public void testToBytesValueString() 66 | { 67 | DataSize oneByte = DataSize.ofBytes(1); 68 | assertEquals(oneByte.toString(), oneByte.toBytesValueString(), "exact values match toString()"); 69 | assertEquals(oneByte.toString(), "1B"); 70 | 71 | for (DataSize.Unit unit : DataSize.Unit.values()) { 72 | DataSize oneInUnit = DataSize.of(1, unit); 73 | assertEquals(oneInUnit.toBytesValueString(), unit.inBytes() + "B"); 74 | assertEquals(DataSize.valueOf(oneInUnit.toBytesValueString()), oneInUnit); 75 | } 76 | 77 | assertEquals(DataSize.of(1, KILOBYTE).toBytesValueString(), "1024B"); 78 | assertEquals(DataSize.of(2, MEGABYTE).toBytesValueString(), "2097152B"); 79 | assertEquals(DataSize.of(3, GIGABYTE).toBytesValueString(), "3221225472B"); 80 | assertEquals(DataSize.of(4, TERABYTE).toBytesValueString(), "4398046511104B"); 81 | assertEquals(DataSize.of(5, PETABYTE).toBytesValueString(), "5629499534213120B"); 82 | } 83 | 84 | @Test(dataProvider = "conversions") 85 | public void testConversions(DataSize.Unit unit, DataSize.Unit toUnit, double factor) 86 | { 87 | DataSize size = DataSize.of(1, unit).to(toUnit); 88 | assertEquals(size.getUnit(), toUnit); 89 | assertEquals(size.getValue(), factor); 90 | 91 | assertEquals(size.getValue(toUnit), factor); 92 | } 93 | 94 | @Test(dataProvider = "conversions") 95 | public void testConvertToMostSuccinctDataSize(DataSize.Unit unit, DataSize.Unit toUnit, double factor) 96 | { 97 | DataSize size = new DataSize(factor, toUnit); 98 | DataSize actual = size.succinct(); 99 | assertThat(actual).isEqualTo(DataSize.of(1, unit)); 100 | assertThat(actual.getValue(unit)).isCloseTo(1.0, within(0.001)); 101 | assertThat(actual.getUnit()).isEqualTo(unit); 102 | } 103 | 104 | @Test 105 | public void testValueEquivalence() 106 | { 107 | comparisonTester() 108 | .addLesserGroup(group(0)) 109 | .addGreaterGroup(group(1)) 110 | .addGreaterGroup(group(123352)) 111 | .addGreaterGroup(group(Long.MAX_VALUE)) 112 | .check(); 113 | } 114 | 115 | private static Iterable group(long bytes) 116 | { 117 | return ImmutableList.of( 118 | DataSize.ofBytes(bytes), 119 | DataSize.ofBytes(bytes).convertTo(KILOBYTE), 120 | DataSize.ofBytes(bytes).convertTo(MEGABYTE), 121 | DataSize.ofBytes(bytes).convertTo(GIGABYTE), 122 | DataSize.ofBytes(bytes).convertTo(TERABYTE), 123 | DataSize.ofBytes(bytes).convertTo(PETABYTE)); 124 | } 125 | 126 | @Test 127 | public void testDeprecatedDoubleValueEquivalence() 128 | { 129 | comparisonTester() 130 | .addLesserGroup(deprecatedDoubleValueGroup(0)) 131 | .addGreaterGroup(deprecatedDoubleValueGroup(1)) 132 | .addGreaterGroup(deprecatedDoubleValueGroup(123352)) 133 | .addGreaterGroup(deprecatedDoubleValueGroup(Long.MAX_VALUE)) 134 | .check(); 135 | } 136 | 137 | private static Iterable deprecatedDoubleValueGroup(double bytes) 138 | { 139 | return ImmutableList.of( 140 | new DataSize(bytes, BYTE), 141 | new DataSize(bytes / 1024, KILOBYTE), 142 | new DataSize(bytes / 1024 / 1024, MEGABYTE), 143 | new DataSize(bytes / 1024 / 1024 / 1024, GIGABYTE), 144 | new DataSize(bytes / 1024 / 1024 / 1024 / 1024, TERABYTE), 145 | new DataSize(bytes / 1024 / 1024 / 1024 / 1024 / 1024, PETABYTE)); 146 | } 147 | 148 | @Test(dataProvider = "printedValues") 149 | public void testToString(String expectedString, double value, DataSize.Unit unit) 150 | { 151 | assertEquals(new DataSize(value, unit).toString(), expectedString); 152 | } 153 | 154 | @Test(dataProvider = "printedValues") 155 | public void testNonEnglishLocale(String expectedString, double value, DataSize.Unit unit) 156 | { 157 | synchronized (Locale.class) { 158 | Locale previous = Locale.getDefault(); 159 | Locale.setDefault(Locale.GERMAN); 160 | try { 161 | assertEquals(new DataSize(value, unit).toString(), expectedString); 162 | } 163 | finally { 164 | Locale.setDefault(previous); 165 | } 166 | } 167 | } 168 | 169 | @Test(dataProvider = "parseableValues") 170 | public void testValueOf(String string, double expectedValue, DataSize.Unit expectedUnit) 171 | { 172 | DataSize size = DataSize.valueOf(string); 173 | 174 | assertEquals(size.getUnit(), expectedUnit); 175 | assertThat(size.getValue()).isCloseTo(expectedValue, within(0.001)); 176 | } 177 | 178 | @Test 179 | public void testValueOfDecimalAndLongHandling() 180 | { 181 | // If parsed as double, this results in only 2^53 not (2^53) + 1 182 | DataSize tooLargeAsDouble = DataSize.ofBytes((1L << 53) + 1); 183 | assertEquals(DataSize.valueOf(Long.toString(tooLargeAsDouble.toBytes()) + "B"), tooLargeAsDouble, "should parse as long and not double"); 184 | assertEquals(DataSize.valueOf(Long.toString(tooLargeAsDouble.toBytes()) + ".0B"), DataSize.ofBytes(1L << 53), "should parse as double"); 185 | } 186 | 187 | @Test(expectedExceptions = NullPointerException.class, expectedExceptionsMessageRegExp = "size is null") 188 | public void testValueOfRejectsNull() 189 | { 190 | DataSize.valueOf(null); 191 | } 192 | 193 | @Test(expectedExceptions = IllegalArgumentException.class, expectedExceptionsMessageRegExp = "size is empty") 194 | public void testValueOfRejectsEmptyString() 195 | { 196 | DataSize.valueOf(""); 197 | } 198 | 199 | @Test(expectedExceptions = IllegalArgumentException.class, expectedExceptionsMessageRegExp = "Unknown unit: kg") 200 | public void testValueOfRejectsInvalidUnit() 201 | { 202 | DataSize.valueOf("1.234 kg"); 203 | } 204 | 205 | @Test(expectedExceptions = IllegalArgumentException.class, expectedExceptionsMessageRegExp = "size is not a valid.*") 206 | public void testValueOfRejectsInvalidNumber() 207 | { 208 | DataSize.valueOf("1.2x4 B"); 209 | } 210 | 211 | @SuppressWarnings("ResultOfObjectAllocationIgnored") 212 | @Test(expectedExceptions = IllegalArgumentException.class, expectedExceptionsMessageRegExp = "size is negative: -1.0") 213 | public void testDeprecatedConstructorRejectsNegativeSize() 214 | { 215 | new DataSize(-1.0, BYTE); 216 | } 217 | 218 | @SuppressWarnings("ResultOfObjectAllocationIgnored") 219 | @Test(expectedExceptions = IllegalArgumentException.class, expectedExceptionsMessageRegExp = "size is infinite") 220 | public void testDeprecatedConstructorRejectsInfiniteSize() 221 | { 222 | new DataSize(Double.POSITIVE_INFINITY, BYTE); 223 | } 224 | 225 | @SuppressWarnings("ResultOfObjectAllocationIgnored") 226 | @Test(expectedExceptions = IllegalArgumentException.class, expectedExceptionsMessageRegExp = "size is infinite") 227 | public void testDeprecatedConstructorRejectsInfiniteSize2() 228 | { 229 | new DataSize(Double.NEGATIVE_INFINITY, BYTE); 230 | } 231 | 232 | @SuppressWarnings("ResultOfObjectAllocationIgnored") 233 | @Test(expectedExceptions = IllegalArgumentException.class, expectedExceptionsMessageRegExp = "size is not a number") 234 | public void testDeprecatedConstructorRejectsNaN() 235 | { 236 | new DataSize(Double.NaN, BYTE); 237 | } 238 | 239 | @SuppressWarnings("ResultOfObjectAllocationIgnored") 240 | @Test(expectedExceptions = NullPointerException.class, expectedExceptionsMessageRegExp = "unit is null") 241 | public void testConstructorRejectsNullUnit() 242 | { 243 | new DataSize(1, null); 244 | } 245 | 246 | @Test(expectedExceptions = IllegalArgumentException.class, expectedExceptionsMessageRegExp = "size is negative: -11") 247 | public void testOfRejectsNegativeSize() 248 | { 249 | DataSize.of(-11, MEGABYTE); 250 | } 251 | 252 | @Test(expectedExceptions = IllegalArgumentException.class, expectedExceptionsMessageRegExp = "size is too large to be represented in bytes: 9223372036854775807MB") 253 | public void testOfDetectsOverflow() 254 | { 255 | DataSize.of(Long.MAX_VALUE, MEGABYTE); 256 | } 257 | 258 | @Test(expectedExceptions = IllegalArgumentException.class, expectedExceptionsMessageRegExp = "bytes is negative") 259 | public void testOfBytesRejectsNegativeSize() 260 | { 261 | DataSize.ofBytes(-1); 262 | } 263 | 264 | @Test(expectedExceptions = NullPointerException.class, expectedExceptionsMessageRegExp = "unit is null") 265 | public void testOfRejectsNullUnit() 266 | { 267 | DataSize.of(1, null); 268 | } 269 | 270 | @Test 271 | public void testToBytes() 272 | { 273 | assertEquals(DataSize.of(0, BYTE).toBytes(), 0); 274 | assertEquals(DataSize.of(0, MEGABYTE).toBytes(), 0); 275 | assertEquals(DataSize.of(1, BYTE).toBytes(), 1); 276 | assertEquals(DataSize.of(1, KILOBYTE).toBytes(), 1024); 277 | assertEquals(DataSize.of(42, MEGABYTE).toBytes(), 42L * 1024 * 1024); 278 | assertEquals(DataSize.of(1, TERABYTE).toBytes(), 1024L * 1024 * 1024 * 1024); 279 | assertEquals(DataSize.of(1, PETABYTE).toBytes(), 1024L * 1024 * 1024 * 1024 * 1024); 280 | assertEquals(DataSize.of(1024, PETABYTE).toBytes(), 1024L * 1024 * 1024 * 1024 * 1024 * 1024); 281 | assertEquals(DataSize.of(8191, PETABYTE).toBytes(), 8191L * 1024 * 1024 * 1024 * 1024 * 1024); 282 | assertEquals(DataSize.of(Long.MAX_VALUE, BYTE).toBytes(), Long.MAX_VALUE); 283 | } 284 | 285 | @Test 286 | public void testDeprecatedToBytes() 287 | { 288 | assertEquals(new DataSize(37.0 / 1024, KILOBYTE).toBytes(), 37); 289 | assertEquals(new DataSize(Long.MAX_VALUE / 1024.0, KILOBYTE).toBytes(), Long.MAX_VALUE); 290 | } 291 | 292 | @Test 293 | public void testRoundTo() 294 | { 295 | assertEquals(DataSize.ofBytes(0).roundTo(BYTE), 0); 296 | assertEquals(new DataSize(0.5, BYTE).roundTo(BYTE), 1); 297 | assertEquals(new DataSize((42 * 1024) + 511, BYTE).roundTo(KILOBYTE), 42); 298 | assertEquals(new DataSize((42 * 1024) + 512, BYTE).roundTo(KILOBYTE), 43); 299 | assertEquals(new DataSize(513, TERABYTE).roundTo(PETABYTE), 1); 300 | } 301 | 302 | @Test(expectedExceptions = IllegalArgumentException.class, expectedExceptionsMessageRegExp = "size is too large .*") 303 | public void testSizeTooLarge() 304 | { 305 | new DataSize(Long.MAX_VALUE + 1024.0001, BYTE); 306 | } 307 | 308 | @Test(expectedExceptions = IllegalArgumentException.class, expectedExceptionsMessageRegExp = "size is too large .*") 309 | public void testSizeTooLargeInStaticOfMethod() 310 | { 311 | DataSize.of(9000, PETABYTE); 312 | } 313 | 314 | @Test(expectedExceptions = IllegalArgumentException.class, expectedExceptionsMessageRegExp = "size is too large .*") 315 | public void testRoundToTooLarge3() 316 | { 317 | new DataSize(9000 * 1024, PETABYTE).roundTo(KILOBYTE); 318 | } 319 | 320 | @Test 321 | public void testJsonRoundTrip() 322 | { 323 | assertJsonRoundTrip(DataSize.ofBytes(1L)); 324 | assertJsonRoundTrip(new DataSize(1.234, KILOBYTE)); 325 | assertJsonRoundTrip(new DataSize(1.234, MEGABYTE)); 326 | assertJsonRoundTrip(new DataSize(1.234, GIGABYTE)); 327 | assertJsonRoundTrip(new DataSize(1.234, TERABYTE)); 328 | assertJsonRoundTrip(new DataSize(1.234, PETABYTE)); 329 | 330 | assertJsonRoundTrip(DataSize.ofBytes(Long.MAX_VALUE)); 331 | 332 | // Arbitrary assortment of values other than 1, primes and multiples of 2 and 10 333 | long[] sizes = new long[] {1, 2, 3, 5, 7, 8, 11, 16, 100}; 334 | 335 | for (DataSize.Unit unit : DataSize.Unit.values()) { 336 | for (long size : sizes) { 337 | assertJsonRoundTrip(DataSize.ofBytes(size).convertTo(unit)); 338 | assertJsonRoundTrip(DataSize.of(size, unit)); 339 | } 340 | } 341 | } 342 | 343 | private static void assertJsonRoundTrip(DataSize dataSize) 344 | { 345 | JsonCodec dataSizeCodec = JsonCodec.jsonCodec(DataSize.class); 346 | String json = dataSizeCodec.toJson(dataSize); 347 | DataSize dataSizeCopy = dataSizeCodec.fromJson(json); 348 | 349 | assertThat(dataSizeCopy.getValue(BYTE)) 350 | .isCloseTo(dataSize.getValue(BYTE), within(0.001)); 351 | 352 | assertEquals(dataSizeCopy.getUnit(), BYTE, "JSON serialization should always be in bytes"); 353 | assertEquals(dataSize.toBytes(), dataSizeCopy.toBytes(), "byte value equivalence"); 354 | assertEquals(dataSize, dataSizeCopy, "equals method equivalence"); 355 | } 356 | 357 | @DataProvider(name = "parseableValues", parallel = true) 358 | private Object[][] parseableValues() 359 | { 360 | return new Object[][] { 361 | // spaces 362 | new Object[] {"1234 B", 1234, BYTE}, 363 | new Object[] {"1234 kB", 1234, KILOBYTE}, 364 | new Object[] {"1234 MB", 1234, MEGABYTE}, 365 | new Object[] {"1234 GB", 1234, GIGABYTE}, 366 | new Object[] {"1234 TB", 1234, TERABYTE}, 367 | new Object[] {"1234 PB", 1234, PETABYTE}, 368 | new Object[] {"1234.567 kB", 1234.567, KILOBYTE}, 369 | new Object[] {"1234.567 MB", 1234.567, MEGABYTE}, 370 | new Object[] {"1234.567 GB", 1234.567, GIGABYTE}, 371 | new Object[] {"1234.567 TB", 1234.567, TERABYTE}, 372 | new Object[] {"1234.567 PB", 1234.567, PETABYTE}, 373 | // no spaces 374 | new Object[] {"1234B", 1234, BYTE}, 375 | new Object[] {"1234kB", 1234, KILOBYTE}, 376 | new Object[] {"1234MB", 1234, MEGABYTE}, 377 | new Object[] {"1234GB", 1234, GIGABYTE}, 378 | new Object[] {"1234TB", 1234, TERABYTE}, 379 | new Object[] {"1234PB", 1234, PETABYTE}, 380 | new Object[] {"1234.567kB", 1234.567, KILOBYTE}, 381 | new Object[] {"1234.567MB", 1234.567, MEGABYTE}, 382 | new Object[] {"1234.567GB", 1234.567, GIGABYTE}, 383 | new Object[] {"1234.567TB", 1234.567, TERABYTE}, 384 | new Object[] {"1234.567PB", 1234.567, PETABYTE} 385 | }; 386 | } 387 | 388 | @DataProvider(name = "printedValues", parallel = true) 389 | private Object[][] printedValues() 390 | { 391 | return new Object[][] { 392 | new Object[] {"1234B", 1234, BYTE}, 393 | new Object[] {"1234kB", 1234, KILOBYTE}, 394 | new Object[] {"1234MB", 1234, MEGABYTE}, 395 | new Object[] {"1234GB", 1234, GIGABYTE}, 396 | new Object[] {"1234TB", 1234, TERABYTE}, 397 | new Object[] {"1234PB", 1234, PETABYTE}, 398 | new Object[] {"1234.57kB", 1234.567, KILOBYTE}, 399 | new Object[] {"1234.57MB", 1234.567, MEGABYTE}, 400 | new Object[] {"1234.57GB", 1234.567, GIGABYTE}, 401 | new Object[] {"1234.57TB", 1234.567, TERABYTE}, 402 | new Object[] {"1234.57PB", 1234.567, PETABYTE} 403 | }; 404 | } 405 | 406 | @DataProvider(name = "conversions", parallel = true) 407 | private Object[][] conversions() 408 | { 409 | return new Object[][] { 410 | new Object[] {BYTE, BYTE, 1}, 411 | new Object[] {BYTE, KILOBYTE, 1.0 / 1024}, 412 | new Object[] {BYTE, MEGABYTE, 1.0 / 1024 / 1024}, 413 | new Object[] {BYTE, GIGABYTE, 1.0 / 1024 / 1024 / 1024}, 414 | new Object[] {BYTE, TERABYTE, 1.0 / 1024 / 1024 / 1024 / 1024}, 415 | new Object[] {BYTE, PETABYTE, 1.0 / 1024 / 1024 / 1024 / 1024 / 1024}, 416 | 417 | new Object[] {KILOBYTE, BYTE, 1024}, 418 | new Object[] {KILOBYTE, KILOBYTE, 1}, 419 | new Object[] {KILOBYTE, MEGABYTE, 1.0 / 1024}, 420 | new Object[] {KILOBYTE, GIGABYTE, 1.0 / 1024 / 1024}, 421 | new Object[] {KILOBYTE, TERABYTE, 1.0 / 1024 / 1024 / 1024}, 422 | new Object[] {KILOBYTE, PETABYTE, 1.0 / 1024 / 1024 / 1024 / 1024}, 423 | 424 | new Object[] {MEGABYTE, BYTE, 1024 * 1024}, 425 | new Object[] {MEGABYTE, KILOBYTE, 1024}, 426 | new Object[] {MEGABYTE, MEGABYTE, 1}, 427 | new Object[] {MEGABYTE, GIGABYTE, 1.0 / 1024}, 428 | new Object[] {MEGABYTE, TERABYTE, 1.0 / 1024 / 1024}, 429 | new Object[] {MEGABYTE, PETABYTE, 1.0 / 1024 / 1024 / 1024}, 430 | 431 | new Object[] {GIGABYTE, BYTE, 1024 * 1024 * 1024}, 432 | new Object[] {GIGABYTE, KILOBYTE, 1024 * 1024}, 433 | new Object[] {GIGABYTE, MEGABYTE, 1024}, 434 | new Object[] {GIGABYTE, GIGABYTE, 1}, 435 | new Object[] {GIGABYTE, TERABYTE, 1.0 / 1024}, 436 | new Object[] {GIGABYTE, PETABYTE, 1.0 / 1024 / 1024}, 437 | 438 | new Object[] {TERABYTE, BYTE, 1024L * 1024 * 1024 * 1024}, 439 | new Object[] {TERABYTE, KILOBYTE, 1024 * 1024 * 1024}, 440 | new Object[] {TERABYTE, MEGABYTE, 1024 * 1024}, 441 | new Object[] {TERABYTE, GIGABYTE, 1024}, 442 | new Object[] {TERABYTE, TERABYTE, 1}, 443 | new Object[] {TERABYTE, PETABYTE, 1.0 / 1024}, 444 | 445 | new Object[] {PETABYTE, BYTE, 1024L * 1024 * 1024 * 1024 * 1024}, 446 | new Object[] {PETABYTE, KILOBYTE, 1024L * 1024 * 1024 * 1024}, 447 | new Object[] {PETABYTE, MEGABYTE, 1024 * 1024 * 1024}, 448 | new Object[] {PETABYTE, GIGABYTE, 1024 * 1024}, 449 | new Object[] {PETABYTE, TERABYTE, 1024}, 450 | new Object[] {PETABYTE, PETABYTE, 1}, 451 | }; 452 | } 453 | } 454 | -------------------------------------------------------------------------------- /src/test/java/io/airlift/units/TestDataSizeValidator.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed under the Apache License, Version 2.0 (the "License"); 3 | * you may not use this file except in compliance with the License. 4 | * You may obtain a copy of the License at 5 | * 6 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * 8 | * Unless required by applicable law or agreed to in writing, software 9 | * distributed under the License is distributed on an "AS IS" BASIS, 10 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | * See the License for the specific language governing permissions and 12 | * limitations under the License. 13 | */ 14 | package io.airlift.units; 15 | 16 | import jakarta.validation.ValidationException; 17 | import org.testng.annotations.Test; 18 | 19 | import java.util.Optional; 20 | 21 | import static io.airlift.testing.ValidationAssertions.assertFailsValidation; 22 | import static io.airlift.testing.ValidationAssertions.assertValidates; 23 | import static io.airlift.units.ConstraintValidatorAssert.assertThat; 24 | import static io.airlift.units.DataSize.Unit.GIGABYTE; 25 | import static io.airlift.units.DataSize.Unit.KILOBYTE; 26 | import static io.airlift.units.DataSize.Unit.MEGABYTE; 27 | import static org.assertj.core.api.Assertions.assertThatThrownBy; 28 | 29 | public class TestDataSizeValidator 30 | { 31 | @Test 32 | public void testMaxDataSizeValidator() 33 | { 34 | MaxDataSizeValidator maxValidator = new MaxDataSizeValidator(); 35 | maxValidator.initialize(new MockMaxDataSize(new DataSize(8, MEGABYTE))); 36 | 37 | assertThat(maxValidator).isValidFor(new DataSize(0, KILOBYTE)); 38 | assertThat(maxValidator).isValidFor(new DataSize(5, KILOBYTE)); 39 | assertThat(maxValidator).isValidFor(new DataSize(5005, KILOBYTE)); 40 | assertThat(maxValidator).isValidFor(new DataSize(5, MEGABYTE)); 41 | assertThat(maxValidator).isValidFor(new DataSize(8, MEGABYTE)); 42 | assertThat(maxValidator).isValidFor(new DataSize(8192, KILOBYTE)); 43 | assertThat(maxValidator).isInvalidFor(new DataSize(9, MEGABYTE)); 44 | assertThat(maxValidator).isInvalidFor(new DataSize(1, GIGABYTE)); 45 | } 46 | 47 | @Test 48 | public void testMinDataSizeValidator() 49 | { 50 | MinDataSizeValidator minValidator = new MinDataSizeValidator(); 51 | minValidator.initialize(new MockMinDataSize(new DataSize(4, MEGABYTE))); 52 | 53 | assertThat(minValidator).isValidFor(new DataSize(4, MEGABYTE)); 54 | assertThat(minValidator).isValidFor(new DataSize(4096, KILOBYTE)); 55 | assertThat(minValidator).isValidFor(new DataSize(5, MEGABYTE)); 56 | assertThat(minValidator).isInvalidFor(new DataSize(0, GIGABYTE)); 57 | assertThat(minValidator).isInvalidFor(new DataSize(1, MEGABYTE)); 58 | } 59 | 60 | @Test 61 | public void testAllowsNullMinAnnotation() 62 | { 63 | assertValidates(new NullMinAnnotation()); 64 | } 65 | 66 | @Test 67 | public void testAllowsNullMaxAnnotation() 68 | { 69 | assertValidates(new NullMaxAnnotation()); 70 | } 71 | 72 | @Test 73 | public void testDetectsBrokenMinAnnotation() 74 | { 75 | assertThatThrownBy(() -> assertValidates(new BrokenMinAnnotation())) 76 | .isInstanceOf(ValidationException.class) 77 | .hasMessage("HV000032: Unable to initialize io.airlift.units.MinDataSizeValidator.") 78 | .hasRootCauseInstanceOf(IllegalArgumentException.class) 79 | .hasRootCauseMessage("size is not a valid data size string: broken"); 80 | 81 | assertThatThrownBy(() -> assertValidates(new MinAnnotationOnOptional())) 82 | .isInstanceOf(ValidationException.class) 83 | .hasMessageContaining("No validator could be found for constraint 'io.airlift.units.MinDataSize' validating type 'java.util.Optional'. Check configuration for 'constrainedByMin'"); 84 | 85 | assertThatThrownBy(() -> assertValidates(new BrokenOptionalMinAnnotation())) 86 | .isInstanceOf(ValidationException.class) 87 | .hasMessage("HV000032: Unable to initialize io.airlift.units.MinDataSizeValidator.") 88 | .hasRootCauseInstanceOf(IllegalArgumentException.class) 89 | .hasRootCauseMessage("size is not a valid data size string: broken"); 90 | } 91 | 92 | @Test 93 | public void testDetectsBrokenMaxAnnotation() 94 | { 95 | assertThatThrownBy(() -> assertValidates(new BrokenMaxAnnotation())) 96 | .isInstanceOf(ValidationException.class) 97 | .hasMessage("HV000032: Unable to initialize io.airlift.units.MaxDataSizeValidator.") 98 | .hasRootCauseInstanceOf(IllegalArgumentException.class) 99 | .hasRootCauseMessage("size is not a valid data size string: broken"); 100 | 101 | assertThatThrownBy(() -> assertValidates(new MaxAnnotationOnOptional())) 102 | .isInstanceOf(ValidationException.class) 103 | .hasMessageContaining("No validator could be found for constraint 'io.airlift.units.MaxDataSize' validating type 'java.util.Optional'. Check configuration for 'constrainedByMin'"); 104 | 105 | assertThatThrownBy(() -> assertValidates(new BrokenOptionalMaxAnnotation())) 106 | .isInstanceOf(ValidationException.class) 107 | .hasMessage("HV000032: Unable to initialize io.airlift.units.MaxDataSizeValidator.") 108 | .hasRootCauseInstanceOf(IllegalArgumentException.class) 109 | .hasRootCauseMessage("size is not a valid data size string: broken"); 110 | } 111 | 112 | @Test 113 | public void testPassesValidation() 114 | { 115 | assertValidates(new ConstrainedDataSize(new DataSize(7, MEGABYTE))); 116 | assertValidates(new ConstrainedOptionalDataSize(Optional.of(new DataSize(7, MEGABYTE)))); 117 | assertValidates(new ConstrainedOptionalDataSize(Optional.empty())); 118 | assertValidates(new ConstrainedOptionalDataSize(null)); 119 | } 120 | 121 | @Test 122 | public void testFailsMaxDataSizeConstraint() 123 | { 124 | assertFailsValidation(new ConstrainedDataSize(new DataSize(11, MEGABYTE)), "constrainedByMinAndMax", "must be less than or equal to 10000kB", MaxDataSize.class); 125 | assertFailsValidation(new ConstrainedDataSize(new DataSize(11, MEGABYTE)), "constrainedByMax", "must be less than or equal to 10MB", MaxDataSize.class); 126 | 127 | assertFailsValidation(new ConstrainedOptionalDataSize(Optional.of(new DataSize(11, MEGABYTE))), "constrainedByMinAndMax", "must be less than or equal to 10000kB", MaxDataSize.class); 128 | assertFailsValidation(new ConstrainedOptionalDataSize(Optional.of(new DataSize(11, MEGABYTE))), "constrainedByMax", "must be less than or equal to 10MB", MaxDataSize.class); 129 | } 130 | 131 | @Test 132 | public void testFailsMinDataSizeConstraint() 133 | { 134 | assertFailsValidation(new ConstrainedDataSize(new DataSize(1, MEGABYTE)), "constrainedByMin", "must be greater than or equal to 5MB", MinDataSize.class); 135 | assertFailsValidation(new ConstrainedDataSize(new DataSize(1, MEGABYTE)), "constrainedByMinAndMax", "must be greater than or equal to 5000kB", MinDataSize.class); 136 | 137 | assertFailsValidation(new ConstrainedOptionalDataSize(Optional.of(new DataSize(1, MEGABYTE))), "constrainedByMin", "must be greater than or equal to 5MB", MinDataSize.class); 138 | assertFailsValidation(new ConstrainedOptionalDataSize(Optional.of(new DataSize(1, MEGABYTE))), "constrainedByMinAndMax", "must be greater than or equal to 5000kB", MinDataSize.class); 139 | } 140 | 141 | @SuppressWarnings("UnusedDeclaration") 142 | public static class ConstrainedDataSize 143 | { 144 | private final DataSize dataSize; 145 | 146 | public ConstrainedDataSize(DataSize dataSize) 147 | { 148 | this.dataSize = dataSize; 149 | } 150 | 151 | @MinDataSize("5MB") 152 | public DataSize getConstrainedByMin() 153 | { 154 | return dataSize; 155 | } 156 | 157 | @MaxDataSize("10MB") 158 | public DataSize getConstrainedByMax() 159 | { 160 | return dataSize; 161 | } 162 | 163 | @MinDataSize("5000kB") 164 | @MaxDataSize("10000kB") 165 | public DataSize getConstrainedByMinAndMax() 166 | { 167 | return dataSize; 168 | } 169 | } 170 | 171 | @SuppressWarnings("UnusedDeclaration") 172 | public static class ConstrainedOptionalDataSize 173 | { 174 | private final Optional dataSize; 175 | 176 | public ConstrainedOptionalDataSize(Optional dataSize) 177 | { 178 | this.dataSize = dataSize; 179 | } 180 | 181 | public Optional<@MinDataSize("5MB") DataSize> getConstrainedByMin() 182 | { 183 | return dataSize; 184 | } 185 | 186 | public Optional<@MaxDataSize("10MB") DataSize> getConstrainedByMax() 187 | { 188 | return dataSize; 189 | } 190 | 191 | public Optional<@MinDataSize("5000kB") @MaxDataSize("10000kB") DataSize> getConstrainedByMinAndMax() 192 | { 193 | return dataSize; 194 | } 195 | } 196 | 197 | public static class NullMinAnnotation 198 | { 199 | @SuppressWarnings("UnusedDeclaration") 200 | @MinDataSize("1MB") 201 | public DataSize getConstrainedByMin() 202 | { 203 | return null; 204 | } 205 | } 206 | 207 | public static class NullMaxAnnotation 208 | { 209 | @SuppressWarnings("UnusedDeclaration") 210 | @MaxDataSize("1MB") 211 | public DataSize getConstrainedByMin() 212 | { 213 | return null; 214 | } 215 | } 216 | 217 | public static class BrokenMinAnnotation 218 | { 219 | @SuppressWarnings("UnusedDeclaration") 220 | @MinDataSize("broken") 221 | public DataSize getConstrainedByMin() 222 | { 223 | return new DataSize(32, KILOBYTE); 224 | } 225 | } 226 | 227 | public static class BrokenMaxAnnotation 228 | { 229 | @SuppressWarnings("UnusedDeclaration") 230 | @MaxDataSize("broken") 231 | public DataSize getConstrainedByMin() 232 | { 233 | return new DataSize(32, KILOBYTE); 234 | } 235 | } 236 | 237 | public static class MinAnnotationOnOptional 238 | { 239 | @SuppressWarnings("UnusedDeclaration") 240 | @MinDataSize("1MB") 241 | public Optional getConstrainedByMin() 242 | { 243 | return Optional.empty(); 244 | } 245 | } 246 | 247 | public static class MaxAnnotationOnOptional 248 | { 249 | @SuppressWarnings("UnusedDeclaration") 250 | @MaxDataSize("1MB") 251 | public Optional getConstrainedByMin() 252 | { 253 | return Optional.empty(); 254 | } 255 | } 256 | 257 | public static class BrokenOptionalMinAnnotation 258 | { 259 | @SuppressWarnings("UnusedDeclaration") 260 | public Optional<@MinDataSize("broken") DataSize> getConstrainedByMin() 261 | { 262 | return Optional.empty(); 263 | } 264 | } 265 | 266 | public static class BrokenOptionalMaxAnnotation 267 | { 268 | @SuppressWarnings("UnusedDeclaration") 269 | public Optional<@MaxDataSize("broken") DataSize> getConstrainedByMax() 270 | { 271 | return Optional.empty(); 272 | } 273 | } 274 | } 275 | -------------------------------------------------------------------------------- /src/test/java/io/airlift/units/TestDuration.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2010 Proofpoint, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.airlift.units; 17 | 18 | import io.airlift.json.JsonCodec; 19 | import org.testng.annotations.DataProvider; 20 | import org.testng.annotations.Test; 21 | 22 | import java.util.ArrayList; 23 | import java.util.List; 24 | import java.util.Locale; 25 | import java.util.concurrent.TimeUnit; 26 | 27 | import static io.airlift.testing.EquivalenceTester.comparisonTester; 28 | import static io.airlift.units.Duration.succinctDuration; 29 | import static io.airlift.units.Duration.succinctNanos; 30 | import static java.util.concurrent.TimeUnit.DAYS; 31 | import static java.util.concurrent.TimeUnit.HOURS; 32 | import static java.util.concurrent.TimeUnit.MICROSECONDS; 33 | import static java.util.concurrent.TimeUnit.MILLISECONDS; 34 | import static java.util.concurrent.TimeUnit.MINUTES; 35 | import static java.util.concurrent.TimeUnit.NANOSECONDS; 36 | import static java.util.concurrent.TimeUnit.SECONDS; 37 | import static org.assertj.core.api.Assertions.assertThat; 38 | import static org.assertj.core.api.Assertions.offset; 39 | import static org.assertj.core.data.Percentage.withPercentage; 40 | import static org.testng.Assert.assertEquals; 41 | import static org.testng.Assert.assertFalse; 42 | import static org.testng.Assert.assertNotEquals; 43 | import static org.testng.Assert.assertTrue; 44 | import static org.testng.Assert.fail; 45 | 46 | public class TestDuration 47 | { 48 | @Test 49 | public void testSuccinctFactories() 50 | { 51 | assertEquals(succinctNanos(123), new Duration(123, NANOSECONDS)); 52 | assertEquals(succinctNanos(123456), new Duration(123.456, MICROSECONDS)); 53 | assertEquals(succinctNanos(SECONDS.toNanos(300)), new Duration(5, MINUTES)); 54 | 55 | assertEquals(succinctDuration(123, NANOSECONDS), new Duration(123, NANOSECONDS)); 56 | assertEquals(succinctDuration(123456, NANOSECONDS), new Duration(123.456, MICROSECONDS)); 57 | assertEquals(succinctDuration(300, SECONDS), new Duration(5, MINUTES)); 58 | } 59 | 60 | @Test 61 | public void testGetValue() 62 | { 63 | double millis = 12346789.0d; 64 | Duration duration = new Duration(millis, MILLISECONDS); 65 | assertThat(duration.getValue(MILLISECONDS)).isEqualTo(millis); 66 | assertThat(duration.getValue(SECONDS)).isCloseTo(millis / 1000, offset(0.001)); 67 | assertThat(duration.getValue(MINUTES)).isCloseTo(millis / 1000 / 60, offset(0.001)); 68 | assertThat(duration.getValue(HOURS)).isCloseTo(millis / 1000 / 60 / 60, offset(0.001)); 69 | assertThat(duration.getValue(DAYS)).isCloseTo(millis / 1000 / 60 / 60 / 24, offset(0.001)); 70 | 71 | double days = 3.0; 72 | duration = new Duration(days, DAYS); 73 | assertThat(duration.getValue(DAYS)).isEqualTo(days); 74 | assertThat(duration.getValue(HOURS)).isCloseTo(days * 24, offset(0.001)); 75 | assertThat(duration.getValue(MINUTES)).isCloseTo(days * 24 * 60, offset(0.001)); 76 | assertThat(duration.getValue(SECONDS)).isCloseTo(days * 24 * 60 * 60, offset(0.001)); 77 | assertThat(duration.getValue(MILLISECONDS)).isCloseTo(days * 24 * 60 * 60 * 1000, offset(0.001)); 78 | } 79 | 80 | @Test(dataProvider = "conversions") 81 | public void testConversions(TimeUnit unit, TimeUnit toTimeUnit, double factor) 82 | { 83 | Duration duration = new Duration(1, unit).convertTo(toTimeUnit); 84 | assertThat(duration.getUnit()).isEqualTo(toTimeUnit); 85 | assertThat(duration.getValue()).isCloseTo(factor, withPercentage(0.1)); 86 | assertThat(duration.getValue(toTimeUnit)).isCloseTo(factor, withPercentage(0.1)); 87 | } 88 | 89 | @Test(dataProvider = "conversions") 90 | public void testConvertToMostSuccinctDuration(TimeUnit unit, TimeUnit toTimeUnit, double factor) 91 | { 92 | Duration duration = new Duration(factor, toTimeUnit); 93 | Duration actual = duration.convertToMostSuccinctTimeUnit(); 94 | assertThat(actual.getValue(toTimeUnit)).isCloseTo(factor, withPercentage(0.1)); 95 | assertThat(actual.getValue(unit)).isCloseTo(1.0, offset(0.001)); 96 | assertThat(actual.getUnit()).isEqualTo(unit); 97 | } 98 | 99 | @Test 100 | public void testEquivalence() 101 | { 102 | comparisonTester() 103 | .addLesserGroup(generateTimeBucket(0)) 104 | .addGreaterGroup(generateTimeBucket(1)) 105 | .addGreaterGroup(generateTimeBucket(123352)) 106 | .addGreaterGroup(generateTimeBucket(Long.MAX_VALUE)) 107 | .check(); 108 | } 109 | 110 | @Test 111 | public void testToJavaTime() 112 | { 113 | assertThat(new Duration(0, DAYS).toJavaTime()) 114 | .isEqualTo(java.time.Duration.ZERO); 115 | 116 | assertThat(new Duration(1, NANOSECONDS).toJavaTime()) 117 | .isEqualTo(java.time.Duration.ofNanos(1)); 118 | assertThat(new Duration(1, MICROSECONDS).toJavaTime()) 119 | .isEqualTo(java.time.Duration.ofNanos(1000)); 120 | assertThat(new Duration(1, MILLISECONDS).toJavaTime()) 121 | .isEqualTo(java.time.Duration.ofMillis(1)); 122 | assertThat(new Duration(1, SECONDS).toJavaTime()) 123 | .isEqualTo(java.time.Duration.ofSeconds(1)); 124 | assertThat(new Duration(1, MINUTES).toJavaTime()) 125 | .isEqualTo(java.time.Duration.ofMinutes(1)); 126 | assertThat(new Duration(1, HOURS).toJavaTime()) 127 | .isEqualTo(java.time.Duration.ofHours(1)); 128 | assertThat(new Duration(1, DAYS).toJavaTime()) 129 | .isEqualTo(java.time.Duration.ofDays(1)); 130 | 131 | assertThat(new Duration(42, NANOSECONDS).toJavaTime()) 132 | .isEqualTo(java.time.Duration.ofNanos(42)); 133 | assertThat(new Duration(42, MICROSECONDS).toJavaTime()) 134 | .isEqualTo(java.time.Duration.ofNanos(42000)); 135 | assertThat(new Duration(42, MILLISECONDS).toJavaTime()) 136 | .isEqualTo(java.time.Duration.ofMillis(42)); 137 | assertThat(new Duration(42, SECONDS).toJavaTime()) 138 | .isEqualTo(java.time.Duration.ofSeconds(42)); 139 | assertThat(new Duration(42, MINUTES).toJavaTime()) 140 | .isEqualTo(java.time.Duration.ofMinutes(42)); 141 | assertThat(new Duration(42, HOURS).toJavaTime()) 142 | .isEqualTo(java.time.Duration.ofHours(42)); 143 | assertThat(new Duration(42, DAYS).toJavaTime()) 144 | .isEqualTo(java.time.Duration.ofDays(42)); 145 | 146 | assertThat(new Duration(123456789, MILLISECONDS).toJavaTime()) 147 | .isEqualTo(java.time.Duration.ofMillis(123456789)); 148 | assertThat(new Duration(123456789, SECONDS).toJavaTime()) 149 | .isEqualTo(java.time.Duration.ofSeconds(123456789)); 150 | assertThat(new Duration(123456789, DAYS).toJavaTime()) 151 | .isEqualTo(java.time.Duration.ofDays(123456789)); 152 | 153 | assertThat(new Duration(1234567890123456789d, MILLISECONDS).toJavaTime()) 154 | .isBetween( 155 | java.time.Duration.ofMillis(1234567890123456700L), 156 | java.time.Duration.ofMillis(1234567890123456800L)); 157 | assertThat(new Duration(1234567890123456789d, SECONDS).toJavaTime()) 158 | .isBetween( 159 | java.time.Duration.ofSeconds(1234567890123456700L), 160 | java.time.Duration.ofSeconds(1234567890123456800L)); 161 | assertThat(new Duration(1234567890123456789d, DAYS).toJavaTime()) 162 | .isEqualTo(java.time.Duration.ofSeconds(Long.MAX_VALUE)); 163 | 164 | assertThat(new Duration(0.01, MILLISECONDS).toJavaTime()) 165 | .isEqualTo(java.time.Duration.ofNanos(10_000)); 166 | assertThat(new Duration(0.01, SECONDS).toJavaTime()) 167 | .isEqualTo(java.time.Duration.ofMillis(10)); 168 | assertThat(new Duration(0.01, DAYS).toJavaTime()) 169 | .isEqualTo(java.time.Duration.ofSeconds(86400 / 100)); 170 | 171 | assertThat(new Duration(0.25, MILLISECONDS).toJavaTime()) 172 | .isEqualTo(java.time.Duration.ofNanos(250_000)); 173 | assertThat(new Duration(0.25, SECONDS).toJavaTime()) 174 | .isEqualTo(java.time.Duration.ofNanos(250_000_000)); 175 | assertThat(new Duration(0.25, DAYS).toJavaTime()) 176 | .isEqualTo(java.time.Duration.ofSeconds(86400 / 4)); 177 | 178 | assertThat(new Duration(0.5, MILLISECONDS).toJavaTime()) 179 | .isEqualTo(java.time.Duration.ofNanos(500_000)); 180 | assertThat(new Duration(0.5, SECONDS).toJavaTime()) 181 | .isEqualTo(java.time.Duration.ofNanos(500_000_000)); 182 | assertThat(new Duration(0.5, DAYS).toJavaTime()) 183 | .isEqualTo(java.time.Duration.ofSeconds(86400 / 2)); 184 | } 185 | 186 | private static List generateTimeBucket(double seconds) 187 | { 188 | List bucket = new ArrayList<>(); 189 | bucket.add(new Duration(seconds * 1000 * 1000 * 1000, NANOSECONDS)); 190 | bucket.add(new Duration(seconds * 1000 * 1000, MICROSECONDS)); 191 | bucket.add(new Duration(seconds * 1000, MILLISECONDS)); 192 | bucket.add(new Duration(seconds, SECONDS)); 193 | bucket.add(new Duration(seconds / 60, MINUTES)); 194 | bucket.add(new Duration(seconds / 60 / 60, HOURS)); 195 | // skip days for larger values as this results in rounding errors 196 | if (seconds <= 1.0) { 197 | bucket.add(new Duration(seconds / 60 / 60 / 24, DAYS)); 198 | } 199 | return bucket; 200 | } 201 | 202 | @Test(dataProvider = "printedValues") 203 | public void testToString(String expectedString, double value, TimeUnit unit) 204 | { 205 | assertEquals(new Duration(value, unit).toString(), expectedString); 206 | } 207 | 208 | @Test(dataProvider = "printedValues") 209 | public void testNonEnglishLocale(String expectedString, double value, TimeUnit unit) 210 | throws Exception 211 | { 212 | synchronized (Locale.class) { 213 | Locale previous = Locale.getDefault(); 214 | Locale.setDefault(Locale.GERMAN); 215 | try { 216 | assertEquals(new Duration(value, unit).toString(), expectedString); 217 | } 218 | finally { 219 | Locale.setDefault(previous); 220 | } 221 | } 222 | } 223 | 224 | @Test(dataProvider = "parseableValues") 225 | public void testValueOf(String string, double expectedValue, TimeUnit expectedUnit) 226 | { 227 | Duration duration = Duration.valueOf(string); 228 | 229 | assertEquals(duration.getUnit(), expectedUnit); 230 | assertEquals(duration.getValue(), expectedValue); 231 | } 232 | 233 | @Test(expectedExceptions = NullPointerException.class, expectedExceptionsMessageRegExp = "duration is null") 234 | public void testValueOfRejectsNull() 235 | { 236 | Duration.valueOf(null); 237 | } 238 | 239 | @Test(expectedExceptions = IllegalArgumentException.class, expectedExceptionsMessageRegExp = "duration is empty") 240 | public void testValueOfRejectsEmptyString() 241 | { 242 | Duration.valueOf(""); 243 | } 244 | 245 | @Test(expectedExceptions = IllegalArgumentException.class, expectedExceptionsMessageRegExp = "Unknown time unit: kg") 246 | public void testValueOfRejectsInvalidUnit() 247 | { 248 | Duration.valueOf("1.234 kg"); 249 | } 250 | 251 | @Test(expectedExceptions = IllegalArgumentException.class, expectedExceptionsMessageRegExp = "duration is not a valid.*") 252 | public void testValueOfRejectsInvalidNumber() 253 | { 254 | Duration.valueOf("1.2x4 s"); 255 | } 256 | 257 | @SuppressWarnings("ResultOfObjectAllocationIgnored") 258 | @Test(expectedExceptions = IllegalArgumentException.class, expectedExceptionsMessageRegExp = "value is negative: -1.0") 259 | public void testConstructorRejectsNegativeValue() 260 | { 261 | new Duration(-1, SECONDS); 262 | } 263 | 264 | @SuppressWarnings("ResultOfObjectAllocationIgnored") 265 | @Test(expectedExceptions = IllegalArgumentException.class, expectedExceptionsMessageRegExp = "value is infinite: Infinity") 266 | public void testConstructorRejectsInfiniteValue() 267 | { 268 | new Duration(Double.POSITIVE_INFINITY, SECONDS); 269 | } 270 | 271 | @SuppressWarnings("ResultOfObjectAllocationIgnored") 272 | @Test(expectedExceptions = IllegalArgumentException.class, expectedExceptionsMessageRegExp = "value is infinite: -Infinity") 273 | public void testConstructorRejectsNegativeInfiniteValue() 274 | { 275 | new Duration(Double.NEGATIVE_INFINITY, SECONDS); 276 | } 277 | 278 | @SuppressWarnings("ResultOfObjectAllocationIgnored") 279 | @Test(expectedExceptions = IllegalArgumentException.class, expectedExceptionsMessageRegExp = "value is not a number") 280 | public void testConstructorRejectsNaN() 281 | { 282 | new Duration(Double.NaN, SECONDS); 283 | } 284 | 285 | @SuppressWarnings("ResultOfObjectAllocationIgnored") 286 | @Test(expectedExceptions = NullPointerException.class, expectedExceptionsMessageRegExp = "unit is null") 287 | public void testConstructorRejectsNullUnit() 288 | { 289 | new Duration(1, null); 290 | } 291 | 292 | @Test 293 | public void testEquals() 294 | { 295 | assertEquals(new Duration(12359.0d, MILLISECONDS), new Duration(12359.0d, MILLISECONDS)); 296 | assertNotEquals(new Duration(4444.0d, MILLISECONDS), new Duration(12359.0d, MILLISECONDS)); 297 | } 298 | 299 | @Test 300 | public void testIsZero() 301 | { 302 | assertTrue(Duration.ZERO.isZero()); 303 | assertTrue(new Duration(0, HOURS).isZero()); 304 | assertFalse(new Duration(12359.0d, MILLISECONDS).isZero()); 305 | 306 | // small values 307 | assertFalse(new Duration(1e-20, MILLISECONDS).isZero()); 308 | assertFalse(new Duration(1e-20, NANOSECONDS).isZero()); 309 | 310 | // very small values 311 | assertFalse(new Duration(Double.MIN_VALUE, MILLISECONDS).isZero()); 312 | assertTrue(new Duration(Double.MIN_VALUE, NANOSECONDS).isZero()); // TODO incorrect, due to equals converting to MILLIS 313 | } 314 | 315 | @Test 316 | public void testHashCode() 317 | { 318 | assertEquals(new Duration(12359.0d, MILLISECONDS).hashCode(), new Duration(12359.0d, MILLISECONDS).hashCode()); 319 | assertNotEquals(new Duration(4444.0d, MILLISECONDS).hashCode(), new Duration(12359.0d, MILLISECONDS).hashCode()); 320 | } 321 | 322 | @Test 323 | public void testNanoConversions() 324 | { 325 | double nanos = 1.0d; 326 | Duration duration = new Duration(nanos, NANOSECONDS); 327 | assertThat(duration.getValue()).isEqualTo(nanos); 328 | assertThat(duration.getValue(NANOSECONDS)).isEqualTo(nanos); 329 | assertThat(duration.getValue(MILLISECONDS)).isEqualTo(nanos / 1000000); 330 | assertThat(duration.getValue(SECONDS)).isEqualTo(nanos / 1000000 / 1000); 331 | assertThat(duration.getValue(MINUTES)).isCloseTo(nanos / 1000000 / 1000 / 60, offset(1.0E10)); 332 | assertThat(duration.getValue(HOURS)).isCloseTo(nanos / 1000000 / 1000 / 60 / 60, offset(1.0E10)); 333 | assertThat(duration.getValue(DAYS)).isCloseTo(nanos / 1000000 / 1000 / 60 / 60 / 24, offset(1.0E10)); 334 | } 335 | 336 | @SuppressWarnings("ResultOfMethodCallIgnored") 337 | @Test 338 | public void invalidParameters() 339 | { 340 | failDurationConstruction(Double.NEGATIVE_INFINITY, MILLISECONDS); 341 | failDurationConstruction(Double.POSITIVE_INFINITY, MILLISECONDS); 342 | failDurationConstruction(Double.NaN, MILLISECONDS); 343 | failDurationConstruction(42, null); 344 | failDurationConstruction(-42, MILLISECONDS); 345 | 346 | Duration duration = new Duration(42, MILLISECONDS); 347 | try { 348 | duration.convertTo(null); 349 | fail("Expected NullPointerException"); 350 | } 351 | catch (NullPointerException e) { 352 | // ok 353 | } 354 | try { 355 | duration.toString(null); 356 | fail("Expected NullPointerException"); 357 | } 358 | catch (NullPointerException e) { 359 | // ok 360 | } 361 | } 362 | 363 | @Test 364 | public void testJsonRoundTrip() 365 | throws Exception 366 | { 367 | assertJsonRoundTrip(new Duration(1.234, MILLISECONDS)); 368 | assertJsonRoundTrip(new Duration(1.234, SECONDS)); 369 | assertJsonRoundTrip(new Duration(1.234, MINUTES)); 370 | assertJsonRoundTrip(new Duration(1.234, HOURS)); 371 | assertJsonRoundTrip(new Duration(1.234, DAYS)); 372 | } 373 | 374 | private static void assertJsonRoundTrip(Duration duration) 375 | { 376 | JsonCodec durationCodec = JsonCodec.jsonCodec(Duration.class); 377 | String json = durationCodec.toJson(duration); 378 | Duration durationCopy = durationCodec.fromJson(json); 379 | 380 | assertThat(durationCopy.getValue(MILLISECONDS)) 381 | .isCloseTo(duration.getValue(MILLISECONDS), withPercentage(1)); 382 | } 383 | 384 | @SuppressWarnings("ResultOfObjectAllocationIgnored") 385 | private static void failDurationConstruction(double value, TimeUnit timeUnit) 386 | { 387 | try { 388 | new Duration(value, timeUnit); 389 | fail("Expected NullPointerException or IllegalArgumentException"); 390 | } 391 | catch (NullPointerException | IllegalArgumentException e) { 392 | // ok 393 | } 394 | } 395 | 396 | @DataProvider(name = "parseableValues", parallel = true) 397 | private Object[][] parseableValues() 398 | { 399 | return new Object[][] { 400 | // spaces 401 | new Object[] {"1234 ns", 1234, NANOSECONDS}, 402 | new Object[] {"1234 ms", 1234, MILLISECONDS}, 403 | new Object[] {"1234 s", 1234, SECONDS}, 404 | new Object[] {"1234 m", 1234, MINUTES}, 405 | new Object[] {"1234 h", 1234, HOURS}, 406 | new Object[] {"1234 d", 1234, DAYS}, 407 | new Object[] {"1234.567 ns", 1234.567, NANOSECONDS}, 408 | new Object[] {"1234.567 ms", 1234.567, MILLISECONDS}, 409 | new Object[] {"1234.567 s", 1234.567, SECONDS}, 410 | new Object[] {"1234.567 m", 1234.567, MINUTES}, 411 | new Object[] {"1234.567 h", 1234.567, HOURS}, 412 | new Object[] {"1234.567 d", 1234.567, DAYS}, 413 | // no spaces 414 | new Object[] {"1234ns", 1234, NANOSECONDS}, 415 | new Object[] {"1234ms", 1234, MILLISECONDS}, 416 | new Object[] {"1234s", 1234, SECONDS}, 417 | new Object[] {"1234m", 1234, MINUTES}, 418 | new Object[] {"1234h", 1234, HOURS}, 419 | new Object[] {"1234d", 1234, DAYS}, 420 | new Object[] {"1234.567ns", 1234.567, NANOSECONDS}, 421 | new Object[] {"1234.567ms", 1234.567, MILLISECONDS}, 422 | new Object[] {"1234.567s", 1234.567, SECONDS}, 423 | new Object[] {"1234.567m", 1234.567, MINUTES}, 424 | new Object[] {"1234.567h", 1234.567, HOURS}, 425 | new Object[] {"1234.567d", 1234.567, DAYS} 426 | }; 427 | } 428 | 429 | @DataProvider(name = "printedValues", parallel = true) 430 | private Object[][] printedValues() 431 | { 432 | return new Object[][] { 433 | new Object[] {"1234.00ns", 1234, NANOSECONDS}, 434 | new Object[] {"1234.00us", 1234, MICROSECONDS}, 435 | new Object[] {"1234.00ms", 1234, MILLISECONDS}, 436 | new Object[] {"1234.00s", 1234, SECONDS}, 437 | new Object[] {"1234.00m", 1234, MINUTES}, 438 | new Object[] {"1234.00h", 1234, HOURS}, 439 | new Object[] {"1234.00d", 1234, DAYS}, 440 | new Object[] {"1234.57ns", 1234.567, NANOSECONDS}, 441 | new Object[] {"1234.57us", 1234.567, MICROSECONDS}, 442 | new Object[] {"1234.57ms", 1234.567, MILLISECONDS}, 443 | new Object[] {"1234.57s", 1234.567, SECONDS}, 444 | new Object[] {"1234.57m", 1234.567, MINUTES}, 445 | new Object[] {"1234.57h", 1234.567, HOURS}, 446 | new Object[] {"1234.57d", 1234.567, DAYS} 447 | }; 448 | } 449 | 450 | @DataProvider(name = "conversions", parallel = true) 451 | private Object[][] conversions() 452 | { 453 | return new Object[][] { 454 | new Object[] {NANOSECONDS, NANOSECONDS, 1.0}, 455 | new Object[] {NANOSECONDS, MILLISECONDS, 1.0 / 1000_000}, 456 | new Object[] {NANOSECONDS, SECONDS, 1.0 / 1000_000 / 1000}, 457 | new Object[] {NANOSECONDS, MINUTES, 1.0 / 1000_000 / 1000 / 60}, 458 | new Object[] {NANOSECONDS, HOURS, 1.0 / 1000_000 / 1000 / 60 / 60}, 459 | new Object[] {NANOSECONDS, DAYS, 1.0 / 1000_000 / 1000 / 60 / 60 / 24}, 460 | 461 | new Object[] {MILLISECONDS, NANOSECONDS, 1000000.0}, 462 | new Object[] {MILLISECONDS, MILLISECONDS, 1.0}, 463 | new Object[] {MILLISECONDS, SECONDS, 1.0 / 1000}, 464 | new Object[] {MILLISECONDS, MINUTES, 1.0 / 1000 / 60}, 465 | new Object[] {MILLISECONDS, HOURS, 1.0 / 1000 / 60 / 60}, 466 | new Object[] {MILLISECONDS, DAYS, 1.0 / 1000 / 60 / 60 / 24}, 467 | 468 | new Object[] {SECONDS, NANOSECONDS, 1000000.0 * 1000}, 469 | new Object[] {SECONDS, MILLISECONDS, 1000}, 470 | new Object[] {SECONDS, SECONDS, 1}, 471 | new Object[] {SECONDS, MINUTES, 1.0 / 60}, 472 | new Object[] {SECONDS, HOURS, 1.0 / 60 / 60}, 473 | new Object[] {SECONDS, DAYS, 1.0 / 60 / 60 / 24}, 474 | 475 | new Object[] {MINUTES, NANOSECONDS, 1000000.0 * 1000 * 60}, 476 | new Object[] {MINUTES, MILLISECONDS, 1000 * 60}, 477 | new Object[] {MINUTES, SECONDS, 60}, 478 | new Object[] {MINUTES, MINUTES, 1}, 479 | new Object[] {MINUTES, HOURS, 1.0 / 60}, 480 | new Object[] {MINUTES, DAYS, 1.0 / 60 / 24}, 481 | 482 | new Object[] {HOURS, NANOSECONDS, 1000000.0 * 1000 * 60 * 60}, 483 | new Object[] {HOURS, MILLISECONDS, 1000 * 60 * 60}, 484 | new Object[] {HOURS, SECONDS, 60 * 60}, 485 | new Object[] {HOURS, MINUTES, 60}, 486 | new Object[] {HOURS, HOURS, 1}, 487 | new Object[] {HOURS, DAYS, 1.0 / 24}, 488 | 489 | new Object[] {DAYS, NANOSECONDS, 1000000.0 * 1000 * 60 * 60 * 24}, 490 | new Object[] {DAYS, MILLISECONDS, 1000 * 60 * 60 * 24}, 491 | new Object[] {DAYS, SECONDS, 60 * 60 * 24}, 492 | new Object[] {DAYS, MINUTES, 60 * 24}, 493 | new Object[] {DAYS, HOURS, 24}, 494 | new Object[] {DAYS, DAYS, 1}, 495 | }; 496 | } 497 | } 498 | -------------------------------------------------------------------------------- /src/test/java/io/airlift/units/TestDurationValidator.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2010 Proofpoint, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.airlift.units; 17 | 18 | import jakarta.validation.ValidationException; 19 | import org.testng.annotations.Test; 20 | 21 | import java.util.Optional; 22 | import java.util.concurrent.TimeUnit; 23 | 24 | import static io.airlift.testing.ValidationAssertions.assertFailsValidation; 25 | import static io.airlift.testing.ValidationAssertions.assertValidates; 26 | import static io.airlift.units.ConstraintValidatorAssert.assertThat; 27 | import static org.assertj.core.api.Assertions.assertThatThrownBy; 28 | 29 | public class TestDurationValidator 30 | { 31 | @Test 32 | public void testMaxDurationValidator() 33 | { 34 | MaxDurationValidator maxValidator = new MaxDurationValidator(); 35 | maxValidator.initialize(new MockMaxDuration(new Duration(5, TimeUnit.SECONDS))); 36 | 37 | assertThat(maxValidator).isValidFor(new Duration(0, TimeUnit.SECONDS)); 38 | assertThat(maxValidator).isValidFor(new Duration(5, TimeUnit.SECONDS)); 39 | assertThat(maxValidator).isInvalidFor(new Duration(6, TimeUnit.SECONDS)); 40 | } 41 | 42 | @Test 43 | public void testMinDurationValidator() 44 | { 45 | MinDurationValidator minValidator = new MinDurationValidator(); 46 | minValidator.initialize(new MockMinDuration(new Duration(5, TimeUnit.SECONDS))); 47 | 48 | assertThat(minValidator).isValidFor(new Duration(5, TimeUnit.SECONDS)); 49 | assertThat(minValidator).isValidFor(new Duration(6, TimeUnit.SECONDS)); 50 | assertThat(minValidator).isInvalidFor(new Duration(0, TimeUnit.SECONDS)); 51 | } 52 | 53 | @Test 54 | public void testAllowsNullMinAnnotation() 55 | { 56 | assertValidates(new NullMinAnnotation()); 57 | } 58 | 59 | @Test 60 | public void testAllowsNullMaxAnnotation() 61 | { 62 | assertValidates(new NullMaxAnnotation()); 63 | } 64 | 65 | @Test 66 | public void testDetectsBrokenMinAnnotation() 67 | { 68 | assertThatThrownBy(() -> assertValidates(new BrokenMinAnnotation())) 69 | .isInstanceOf(ValidationException.class) 70 | .hasMessage("HV000032: Unable to initialize io.airlift.units.MinDurationValidator.") 71 | .hasRootCauseInstanceOf(IllegalArgumentException.class) 72 | .hasRootCauseMessage("duration is not a valid data duration string: broken"); 73 | 74 | assertThatThrownBy(() -> assertValidates(new MinAnnotationOnOptional())) 75 | .isInstanceOf(ValidationException.class) 76 | .hasMessageContaining("No validator could be found for constraint 'io.airlift.units.MinDuration' validating type 'java.util.Optional'. Check configuration for 'constrainedByMin'"); 77 | 78 | assertThatThrownBy(() -> assertValidates(new BrokenOptionalMinAnnotation())) 79 | .isInstanceOf(ValidationException.class) 80 | .hasMessage("HV000032: Unable to initialize io.airlift.units.MinDurationValidator.") 81 | .hasRootCauseInstanceOf(IllegalArgumentException.class) 82 | .hasRootCauseMessage("duration is not a valid data duration string: broken"); 83 | } 84 | 85 | @Test 86 | public void testDetectsBrokenMaxAnnotation() 87 | { 88 | assertThatThrownBy(() -> assertValidates(new BrokenMaxAnnotation())) 89 | .isInstanceOf(ValidationException.class) 90 | .hasMessage("HV000032: Unable to initialize io.airlift.units.MinDurationValidator.") 91 | .hasRootCauseInstanceOf(IllegalArgumentException.class) 92 | .hasRootCauseMessage("duration is not a valid data duration string: broken"); 93 | 94 | assertThatThrownBy(() -> assertValidates(new MaxAnnotationOnOptional())) 95 | .isInstanceOf(ValidationException.class) 96 | .hasMessageContaining("No validator could be found for constraint 'io.airlift.units.MaxDuration' validating type 'java.util.Optional'. Check configuration for 'constrainedByMin'"); 97 | 98 | assertThatThrownBy(() -> assertValidates(new BrokenOptionalMaxAnnotation())) 99 | .isInstanceOf(ValidationException.class) 100 | .hasMessage("HV000032: Unable to initialize io.airlift.units.MaxDurationValidator.") 101 | .hasRootCauseInstanceOf(IllegalArgumentException.class) 102 | .hasRootCauseMessage("duration is not a valid data duration string: broken"); 103 | } 104 | 105 | @Test 106 | public void testPassesValidation() 107 | { 108 | assertValidates(new ConstrainedDuration(new Duration(7, TimeUnit.SECONDS))); 109 | assertValidates(new ConstrainedOptionalDuration(Optional.of(new Duration(7, TimeUnit.SECONDS)))); 110 | assertValidates(new ConstrainedOptionalDuration(Optional.empty())); 111 | assertValidates(new ConstrainedOptionalDuration(null)); 112 | } 113 | 114 | @Test 115 | public void testFailsMaxDurationConstraint() 116 | { 117 | assertFailsValidation(new ConstrainedDuration(new Duration(11, TimeUnit.SECONDS)), "constrainedByMax", "must be less than or equal to 10s", MaxDuration.class); 118 | assertFailsValidation(new ConstrainedDuration(new Duration(11, TimeUnit.SECONDS)), "constrainedByMinAndMax", "must be less than or equal to 10000ms", MaxDuration.class); 119 | 120 | assertFailsValidation(new ConstrainedOptionalDuration(Optional.of(new Duration(11, TimeUnit.SECONDS))), "constrainedByMax", "must be less than or equal to 10s", MaxDuration.class); 121 | assertFailsValidation(new ConstrainedOptionalDuration(Optional.of(new Duration(11, TimeUnit.SECONDS))), "constrainedByMinAndMax", "must be less than or equal to 10000ms", MaxDuration.class); 122 | } 123 | 124 | @Test 125 | public void testFailsMinDurationConstraint() 126 | { 127 | assertFailsValidation(new ConstrainedDuration(new Duration(1, TimeUnit.SECONDS)), "constrainedByMin", "must be greater than or equal to 5s", MinDuration.class); 128 | assertFailsValidation(new ConstrainedDuration(new Duration(1, TimeUnit.SECONDS)), "constrainedByMinAndMax", "must be greater than or equal to 5000ms", MinDuration.class); 129 | 130 | assertFailsValidation(new ConstrainedOptionalDuration(Optional.of(new Duration(1, TimeUnit.SECONDS))), "constrainedByMin", "must be greater than or equal to 5s", MinDuration.class); 131 | assertFailsValidation(new ConstrainedOptionalDuration(Optional.of(new Duration(1, TimeUnit.SECONDS))), "constrainedByMinAndMax", "must be greater than or equal to 5000ms", MinDuration.class); 132 | } 133 | 134 | @SuppressWarnings("UnusedDeclaration") 135 | public static class ConstrainedDuration 136 | { 137 | private final Duration duration; 138 | 139 | public ConstrainedDuration(Duration duration) 140 | { 141 | this.duration = duration; 142 | } 143 | 144 | @MinDuration("5s") 145 | public Duration getConstrainedByMin() 146 | { 147 | return duration; 148 | } 149 | 150 | @MaxDuration("10s") 151 | public Duration getConstrainedByMax() 152 | { 153 | return duration; 154 | } 155 | 156 | @MinDuration("5000ms") 157 | @MaxDuration("10000ms") 158 | public Duration getConstrainedByMinAndMax() 159 | { 160 | return duration; 161 | } 162 | } 163 | 164 | @SuppressWarnings("UnusedDeclaration") 165 | public static class ConstrainedOptionalDuration 166 | { 167 | private final Optional duration; 168 | 169 | public ConstrainedOptionalDuration(Optional duration) 170 | { 171 | this.duration = duration; 172 | } 173 | 174 | public Optional<@MinDuration("5s") Duration> getConstrainedByMin() 175 | { 176 | return duration; 177 | } 178 | 179 | public Optional<@MaxDuration("10s") Duration> getConstrainedByMax() 180 | { 181 | return duration; 182 | } 183 | 184 | public Optional<@MinDuration("5000ms") @MaxDuration("10000ms") Duration> getConstrainedByMinAndMax() 185 | { 186 | return duration; 187 | } 188 | } 189 | 190 | public static class NullMinAnnotation 191 | { 192 | @SuppressWarnings("UnusedDeclaration") 193 | @MinDuration("1s") 194 | public Duration getConstrainedByMin() 195 | { 196 | return null; 197 | } 198 | } 199 | 200 | public static class NullMaxAnnotation 201 | { 202 | @SuppressWarnings("UnusedDeclaration") 203 | @MaxDuration("1s") 204 | public Duration getConstrainedByMin() 205 | { 206 | return null; 207 | } 208 | } 209 | 210 | public static class BrokenMinAnnotation 211 | { 212 | @SuppressWarnings("UnusedDeclaration") 213 | @MinDuration("broken") 214 | public Duration getConstrainedByMin() 215 | { 216 | return new Duration(10, TimeUnit.SECONDS); 217 | } 218 | } 219 | 220 | public static class BrokenMaxAnnotation 221 | { 222 | @SuppressWarnings("UnusedDeclaration") 223 | @MinDuration("broken") 224 | public Duration getConstrainedByMin() 225 | { 226 | return new Duration(10, TimeUnit.SECONDS); 227 | } 228 | } 229 | 230 | public static class MinAnnotationOnOptional 231 | { 232 | @SuppressWarnings("UnusedDeclaration") 233 | @MinDuration("1s") 234 | public Optional getConstrainedByMin() 235 | { 236 | return Optional.empty(); 237 | } 238 | } 239 | 240 | public static class MaxAnnotationOnOptional 241 | { 242 | @SuppressWarnings("UnusedDeclaration") 243 | @MaxDuration("1s") 244 | public Optional getConstrainedByMin() 245 | { 246 | return Optional.empty(); 247 | } 248 | } 249 | 250 | public static class BrokenOptionalMinAnnotation 251 | { 252 | @SuppressWarnings("UnusedDeclaration") 253 | public Optional<@MinDuration("broken") Duration> getConstrainedByMin() 254 | { 255 | return Optional.empty(); 256 | } 257 | } 258 | 259 | public static class BrokenOptionalMaxAnnotation 260 | { 261 | @SuppressWarnings("UnusedDeclaration") 262 | public Optional<@MaxDuration("broken") Duration> getConstrainedByMax() 263 | { 264 | return Optional.empty(); 265 | } 266 | } 267 | } 268 | -------------------------------------------------------------------------------- /src/test/java/io/airlift/units/TestThreadCountValidator.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed under the Apache License, Version 2.0 (the "License"); 3 | * you may not use this file except in compliance with the License. 4 | * You may obtain a copy of the License at 5 | * 6 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * 8 | * Unless required by applicable law or agreed to in writing, software 9 | * distributed under the License is distributed on an "AS IS" BASIS, 10 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | * See the License for the specific language governing permissions and 12 | * limitations under the License. 13 | */ 14 | package io.airlift.units; 15 | 16 | import jakarta.validation.ValidationException; 17 | import org.testng.annotations.Test; 18 | 19 | import java.util.Optional; 20 | 21 | import static io.airlift.testing.ValidationAssertions.assertFailsValidation; 22 | import static io.airlift.testing.ValidationAssertions.assertValidates; 23 | import static io.airlift.units.ConstraintValidatorAssert.assertThat; 24 | import static org.assertj.core.api.Assertions.assertThatThrownBy; 25 | 26 | public class TestThreadCountValidator 27 | { 28 | @Test 29 | public void testMaxThreadCountValidator() 30 | { 31 | MaxThreadCountValidator maxValidator = new MaxThreadCountValidator(); 32 | maxValidator.initialize(new MockMaxThreadCount(new ThreadCount(8))); 33 | 34 | assertThat(maxValidator).isValidFor(new ThreadCount(0)); 35 | assertThat(maxValidator).isValidFor(new ThreadCount(5)); 36 | assertThat(maxValidator).isValidFor(new ThreadCount(8)); 37 | } 38 | 39 | @Test 40 | public void testMinThreadCountValidator() 41 | { 42 | MinThreadCountValidator minValidator = new MinThreadCountValidator(); 43 | minValidator.initialize(new MockMinThreadCount(new ThreadCount(4))); 44 | 45 | assertThat(minValidator).isValidFor(new ThreadCount(4)); 46 | assertThat(minValidator).isValidFor(new ThreadCount(5)); 47 | } 48 | 49 | @Test 50 | public void testAllowsNullMinAnnotation() 51 | { 52 | assertValidates(new TestThreadCountValidator.NullMinAnnotation()); 53 | } 54 | 55 | @Test 56 | public void testAllowsNullMaxAnnotation() 57 | { 58 | assertValidates(new TestThreadCountValidator.NullMaxAnnotation()); 59 | } 60 | 61 | @Test 62 | public void testDetectsBrokenMinAnnotation() 63 | { 64 | assertThatThrownBy(() -> assertValidates(new TestThreadCountValidator.BrokenMinAnnotation())) 65 | .isInstanceOf(ValidationException.class) 66 | .hasMessage("HV000032: Unable to initialize io.airlift.units.MinThreadCountValidator.") 67 | .hasRootCauseInstanceOf(NumberFormatException.class) 68 | .hasRootCauseMessage("For input string: \"broken\""); 69 | 70 | assertThatThrownBy(() -> assertValidates(new TestThreadCountValidator.MinAnnotationOnOptional())) 71 | .isInstanceOf(ValidationException.class) 72 | .hasMessageContaining("No validator could be found for constraint 'io.airlift.units.MinThreadCount' validating type 'java.util.Optional'. Check configuration for 'constrainedByMin'"); 73 | 74 | assertThatThrownBy(() -> assertValidates(new TestThreadCountValidator.BrokenOptionalMinAnnotation())) 75 | .isInstanceOf(ValidationException.class) 76 | .hasMessage("HV000032: Unable to initialize io.airlift.units.MinThreadCountValidator.") 77 | .hasRootCauseInstanceOf(NumberFormatException.class) 78 | .hasRootCauseMessage("For input string: \"broken\""); 79 | } 80 | 81 | @Test 82 | public void testDetectsBrokenMaxAnnotation() 83 | { 84 | assertThatThrownBy(() -> assertValidates(new TestThreadCountValidator.BrokenMaxAnnotation())) 85 | .isInstanceOf(ValidationException.class) 86 | .hasMessage("HV000032: Unable to initialize io.airlift.units.MaxThreadCountValidator.") 87 | .hasRootCauseInstanceOf(NumberFormatException.class) 88 | .hasRootCauseMessage("For input string: \"broken\""); 89 | 90 | assertThatThrownBy(() -> assertValidates(new TestThreadCountValidator.MaxAnnotationOnOptional())) 91 | .isInstanceOf(ValidationException.class) 92 | .hasMessageContaining("No validator could be found for constraint 'io.airlift.units.MaxThreadCount' validating type 'java.util.Optional'. Check configuration for 'constrainedByMin'"); 93 | 94 | assertThatThrownBy(() -> assertValidates(new TestThreadCountValidator.BrokenOptionalMaxAnnotation())) 95 | .isInstanceOf(ValidationException.class) 96 | .hasMessage("HV000032: Unable to initialize io.airlift.units.MaxThreadCountValidator.") 97 | .hasRootCauseInstanceOf(NumberFormatException.class) 98 | .hasRootCauseMessage("For input string: \"broken\""); 99 | } 100 | 101 | @Test 102 | public void testPassesValidation() 103 | { 104 | assertValidates(new TestThreadCountValidator.ConstrainedThreadCount(new ThreadCount(7))); 105 | assertValidates(new TestThreadCountValidator.ConstrainedOptionalThreadCount(Optional.of(new ThreadCount(7)))); 106 | assertValidates(new TestThreadCountValidator.ConstrainedOptionalThreadCount(Optional.empty())); 107 | assertValidates(new TestThreadCountValidator.ConstrainedOptionalThreadCount(null)); 108 | } 109 | 110 | @Test 111 | public void testFailsMaxThreadCountConstraint() 112 | { 113 | assertFailsValidation(new TestThreadCountValidator.ConstrainedThreadCount(new ThreadCount(11)), "constrainedByMinAndMax", "must be less than or equal to 10", MaxThreadCount.class); 114 | assertFailsValidation(new TestThreadCountValidator.ConstrainedThreadCount(new ThreadCount(11)), "constrainedByMax", "must be less than or equal to 10", MaxThreadCount.class); 115 | 116 | assertFailsValidation(new TestThreadCountValidator.ConstrainedOptionalThreadCount(Optional.of(new ThreadCount(11))), "constrainedByMinAndMax", "must be less than or equal to 10", MaxThreadCount.class); 117 | assertFailsValidation(new TestThreadCountValidator.ConstrainedOptionalThreadCount(Optional.of(new ThreadCount(11))), "constrainedByMax", "must be less than or equal to 10", MaxThreadCount.class); 118 | } 119 | 120 | @Test 121 | public void testFailsMinThreadCountConstraint() 122 | { 123 | assertFailsValidation(new TestThreadCountValidator.ConstrainedThreadCount(new ThreadCount(1)), "constrainedByMin", "must be greater than or equal to 5", MinThreadCount.class); 124 | assertFailsValidation(new TestThreadCountValidator.ConstrainedThreadCount(new ThreadCount(1)), "constrainedByMinAndMax", "must be greater than or equal to 5", MinThreadCount.class); 125 | 126 | assertFailsValidation(new TestThreadCountValidator.ConstrainedOptionalThreadCount(Optional.of(new ThreadCount(1))), "constrainedByMin", "must be greater than or equal to 5", MinThreadCount.class); 127 | assertFailsValidation(new TestThreadCountValidator.ConstrainedOptionalThreadCount(Optional.of(new ThreadCount(1))), "constrainedByMinAndMax", "must be greater than or equal to 5", MinThreadCount.class); 128 | } 129 | 130 | @SuppressWarnings("UnusedDeclaration") 131 | public static class ConstrainedThreadCount 132 | { 133 | private final ThreadCount threadCount; 134 | 135 | public ConstrainedThreadCount(ThreadCount threadCount) 136 | { 137 | this.threadCount = threadCount; 138 | } 139 | 140 | @MinThreadCount("5") 141 | public ThreadCount getConstrainedByMin() 142 | { 143 | return threadCount; 144 | } 145 | 146 | @MaxThreadCount("10") 147 | public ThreadCount getConstrainedByMax() 148 | { 149 | return threadCount; 150 | } 151 | 152 | @MinThreadCount("5") 153 | @MaxThreadCount("10") 154 | public ThreadCount getConstrainedByMinAndMax() 155 | { 156 | return threadCount; 157 | } 158 | } 159 | 160 | @SuppressWarnings("UnusedDeclaration") 161 | public static class ConstrainedOptionalThreadCount 162 | { 163 | private final Optional threadCount; 164 | 165 | public ConstrainedOptionalThreadCount(Optional threadCount) 166 | { 167 | this.threadCount = threadCount; 168 | } 169 | 170 | public Optional<@MinThreadCount("5") ThreadCount> getConstrainedByMin() 171 | { 172 | return threadCount; 173 | } 174 | 175 | public Optional<@MaxThreadCount("10") ThreadCount> getConstrainedByMax() 176 | { 177 | return threadCount; 178 | } 179 | 180 | public Optional<@MinThreadCount("5") @MaxThreadCount("10") ThreadCount> getConstrainedByMinAndMax() 181 | { 182 | return threadCount; 183 | } 184 | } 185 | 186 | public static class NullMinAnnotation 187 | { 188 | @SuppressWarnings("UnusedDeclaration") 189 | @MinThreadCount("16") 190 | public ThreadCount getConstrainedByMin() 191 | { 192 | return null; 193 | } 194 | } 195 | 196 | public static class NullMaxAnnotation 197 | { 198 | @SuppressWarnings("UnusedDeclaration") 199 | @MaxThreadCount("16") 200 | public ThreadCount getConstrainedByMin() 201 | { 202 | return null; 203 | } 204 | } 205 | 206 | public static class BrokenMinAnnotation 207 | { 208 | @SuppressWarnings("UnusedDeclaration") 209 | @MinThreadCount("broken") 210 | public ThreadCount getConstrainedByMin() 211 | { 212 | return new ThreadCount(32); 213 | } 214 | } 215 | 216 | public static class BrokenMaxAnnotation 217 | { 218 | @SuppressWarnings("UnusedDeclaration") 219 | @MaxThreadCount("broken") 220 | public ThreadCount getConstrainedByMin() 221 | { 222 | return new ThreadCount(32); 223 | } 224 | } 225 | 226 | public static class MinAnnotationOnOptional 227 | { 228 | @SuppressWarnings("UnusedDeclaration") 229 | @MinThreadCount("16") 230 | public Optional getConstrainedByMin() 231 | { 232 | return Optional.empty(); 233 | } 234 | } 235 | 236 | public static class MaxAnnotationOnOptional 237 | { 238 | @SuppressWarnings("UnusedDeclaration") 239 | @MaxThreadCount("16") 240 | public Optional getConstrainedByMin() 241 | { 242 | return Optional.empty(); 243 | } 244 | } 245 | 246 | public static class BrokenOptionalMinAnnotation 247 | { 248 | @SuppressWarnings("UnusedDeclaration") 249 | public Optional<@MinThreadCount("broken") ThreadCount> getConstrainedByMin() 250 | { 251 | return Optional.empty(); 252 | } 253 | } 254 | 255 | public static class BrokenOptionalMaxAnnotation 256 | { 257 | @SuppressWarnings("UnusedDeclaration") 258 | public Optional<@MaxThreadCount("broken") ThreadCount> getConstrainedByMax() 259 | { 260 | return Optional.empty(); 261 | } 262 | } 263 | } 264 | -------------------------------------------------------------------------------- /src/test/java/io/airlift/units/TestThreadsCount.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed under the Apache License, Version 2.0 (the "License"); 3 | * you may not use this file except in compliance with the License. 4 | * You may obtain a copy of the License at 5 | * 6 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * 8 | * Unless required by applicable law or agreed to in writing, software 9 | * distributed under the License is distributed on an "AS IS" BASIS, 10 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | * See the License for the specific language governing permissions and 12 | * limitations under the License. 13 | */ 14 | package io.airlift.units; 15 | 16 | import org.testng.annotations.Test; 17 | 18 | import static io.airlift.units.ThreadCount.MachineInfo.getAvailablePhysicalProcessorCount; 19 | import static java.lang.Math.round; 20 | import static org.assertj.core.api.Assertions.assertThat; 21 | import static org.assertj.core.api.Assertions.assertThatThrownBy; 22 | 23 | public class TestThreadsCount 24 | { 25 | public static final int AVAILABLE_PROCESSORS = getAvailablePhysicalProcessorCount(); 26 | 27 | @Test 28 | public void ensureRequiredNumberOfProcessors() 29 | { 30 | // For tests to work properly we need at least two threads 31 | assertThat(AVAILABLE_PROCESSORS).isGreaterThan(1); 32 | } 33 | 34 | @Test 35 | public void testParsingIntegerValues() 36 | { 37 | assertThreadsCount("1", 1); 38 | assertThreadsCount("2", 2); 39 | assertThreadsCount("67", 67); 40 | assertThreadsCount("0", 0); 41 | assertThreadsCount(Integer.valueOf(Integer.MAX_VALUE).toString(), Integer.MAX_VALUE); 42 | assertInvalidValue("-1", "Thread count cannot be negative"); 43 | assertInvalidValue("67.0", "Cannot parse value '67.0' as integer"); 44 | assertInvalidValue(Long.valueOf(((long) Integer.MAX_VALUE) + 1).toString(), "Thread count is greater than 2^32 - 1"); 45 | } 46 | 47 | @Test 48 | public void testParsingMultiplierPerCore() 49 | { 50 | assertThreadsCount("1C", AVAILABLE_PROCESSORS); 51 | assertThreadsCount("0.5 C", AVAILABLE_PROCESSORS / 2); 52 | assertThreadsCount("0.2 C", round(AVAILABLE_PROCESSORS / 5.0f)); 53 | assertThreadsCount("1.5C", round(AVAILABLE_PROCESSORS * 1.5f)); 54 | assertThreadsCount("2 C", AVAILABLE_PROCESSORS * 2); 55 | assertThreadsCount("0.0001 C", 0); 56 | assertInvalidValue("-0.0001 C", "Thread multiplier cannot be negative"); 57 | assertInvalidValue(-1, "Thread count cannot be negative"); 58 | assertInvalidValue("-1C", "Thread multiplier cannot be negative"); 59 | assertInvalidValue("-1SC", "Cannot parse value '-1S' as float"); 60 | assertInvalidValue("2147483647C", "Thread count is greater than 2^32 - 1"); 61 | assertInvalidValue("3147483648C", "Thread count is greater than 2^32 - 1"); 62 | } 63 | 64 | @Test 65 | public void testParsingBoundedValue() 66 | { 67 | assertBoundedThreadsCount("3", "1", "1", 1); 68 | assertBoundedThreadsCount("256C", "4", "16", 16); 69 | assertBoundedThreadsCount("3", "4", "16", 4); 70 | } 71 | 72 | private void assertThreadsCount(String value, int expected) 73 | { 74 | ThreadCount threadCount = ThreadCount.valueOf(value); 75 | assertThat(threadCount).isEqualTo(ThreadCount.exactValueOf(expected)); 76 | assertThat(threadCount.getThreadCount()).isEqualTo(expected); 77 | } 78 | 79 | private void assertBoundedThreadsCount(String value, String min, String max, int expected) 80 | { 81 | ThreadCount threadCount = ThreadCount.boundedValueOf(value, min, max); 82 | assertThat(threadCount).isEqualTo(new ThreadCount(expected)); 83 | } 84 | 85 | private void assertInvalidValue(String value, String expectedMessage) 86 | { 87 | assertThatThrownBy(() -> ThreadCount.valueOf(value)) 88 | .isInstanceOf(IllegalArgumentException.class) 89 | .hasMessage(expectedMessage); 90 | } 91 | 92 | private void assertInvalidValue(int value, String expectedMessage) 93 | { 94 | assertThatThrownBy(() -> new ThreadCount(value)) 95 | .isInstanceOf(IllegalArgumentException.class) 96 | .hasMessage(expectedMessage); 97 | } 98 | } 99 | --------------------------------------------------------------------------------