Skip to content

Commit b805a0a

Browse files
committed
Introduce Count
1 parent b7fb250 commit b805a0a

File tree

9 files changed

+985
-0
lines changed

9 files changed

+985
-0
lines changed
Lines changed: 203 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,203 @@
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 com.fasterxml.jackson.annotation.JsonCreator;
17+
import com.fasterxml.jackson.annotation.JsonValue;
18+
19+
import java.util.regex.Matcher;
20+
import java.util.regex.Pattern;
21+
22+
import static io.airlift.units.Preconditions.checkArgument;
23+
import static java.lang.Long.parseLong;
24+
import static java.lang.Math.multiplyExact;
25+
import static java.lang.String.format;
26+
import static java.util.Objects.requireNonNull;
27+
28+
public final class Count
29+
implements Comparable<Count>
30+
{
31+
private static final Pattern PATTERN = Pattern.compile("^\\s*(\\d+)\\s*([a-zA-Z]?)\\s*$");
32+
33+
// We iterate over the MAGNITUDES constant in convertToMostSuccinctRounded()
34+
// instead of Magnitude.values() as the latter results in non-trivial amount of memory
35+
// allocation when that method is called in a tight loop. The reason is that the values()
36+
// call allocates a new array at each call.
37+
private static final Magnitude[] MAGNITUDES = Magnitude.values();
38+
39+
/**
40+
* @return count with no bigger value than 1000 in succinct magnitude, fractional part is rounded
41+
*/
42+
public static Count succinctRounded(long count)
43+
{
44+
return succinctRounded(count, Magnitude.SINGLE);
45+
}
46+
47+
/**
48+
* @return count with no bigger value than 1000 in succinct magnitude, fractional part is rounded
49+
*/
50+
public static Count succinctRounded(long count, Magnitude magnitude)
51+
{
52+
return new Count(count, magnitude).convertToMostSuccinctRounded();
53+
}
54+
55+
private final long value;
56+
private final Magnitude magnitude;
57+
58+
public Count(long count, Magnitude magnitude)
59+
{
60+
checkArgument(count >= 0, "count is negative");
61+
requireNonNull(magnitude, "magnitude is null");
62+
63+
this.value = count;
64+
this.magnitude = magnitude;
65+
}
66+
67+
public long getValue()
68+
{
69+
return value;
70+
}
71+
72+
public Magnitude getMagnitude()
73+
{
74+
return magnitude;
75+
}
76+
77+
public long getValue(Magnitude magnitude)
78+
{
79+
requireNonNull(magnitude, "magnitude is null");
80+
81+
if (value == 0L) {
82+
return 0L;
83+
}
84+
85+
long scale = this.magnitude.getFactor() / magnitude.getFactor();
86+
if (scale * magnitude.getFactor() != this.magnitude.getFactor()) {
87+
throw new IllegalArgumentException(format("Unable to represent %s in %s, conversion would cause a precision loss", this, magnitude));
88+
}
89+
try {
90+
return multiplyExact(value, scale);
91+
}
92+
catch (ArithmeticException e) {
93+
throw new IllegalArgumentException(format("Unable to represent %s in %s due the Long value overflow", this, magnitude));
94+
}
95+
}
96+
97+
public Count convertTo(Magnitude magnitude)
98+
{
99+
return new Count(getValue(magnitude), magnitude);
100+
}
101+
102+
/**
103+
* @return converted count with no bigger value than 1000 in succinct magnitude, fractional part is rounded
104+
*/
105+
public Count convertToMostSuccinctRounded()
106+
{
107+
for (Magnitude magnitude : MAGNITUDES) {
108+
double converted = (double) value * this.magnitude.getFactor() / magnitude.getFactor();
109+
if (converted < 1000) {
110+
return new Count(Math.round(converted), magnitude);
111+
}
112+
}
113+
throw new IllegalStateException();
114+
}
115+
116+
@JsonValue
117+
@Override
118+
public String toString()
119+
{
120+
return value + magnitude.getMagnitudeString();
121+
}
122+
123+
@JsonCreator
124+
public static Count valueOf(String count)
125+
throws IllegalArgumentException
126+
{
127+
requireNonNull(count, "count is null");
128+
checkArgument(!count.isEmpty(), "count is empty");
129+
130+
Matcher matcher = PATTERN.matcher(count);
131+
if (!matcher.matches()) {
132+
throw new IllegalArgumentException("Not a valid count string: " + count);
133+
}
134+
135+
long value = parseLong(matcher.group(1));
136+
String magnitutdeString = matcher.group(2);
137+
138+
for (Magnitude magnitude : Magnitude.values()) {
139+
if (magnitude.getMagnitudeString().equals(magnitutdeString)) {
140+
return new Count(value, magnitude);
141+
}
142+
}
143+
144+
throw new IllegalArgumentException("Unknown magnitude: " + magnitutdeString);
145+
}
146+
147+
@Override
148+
public int compareTo(Count o)
149+
{
150+
return Long.compare(getValue(Magnitude.SINGLE), o.getValue(Magnitude.SINGLE));
151+
}
152+
153+
@Override
154+
public boolean equals(Object o)
155+
{
156+
if (this == o) {
157+
return true;
158+
}
159+
if (o == null || getClass() != o.getClass()) {
160+
return false;
161+
}
162+
163+
Count count = (Count) o;
164+
165+
return compareTo(count) == 0;
166+
}
167+
168+
@Override
169+
public int hashCode()
170+
{
171+
return Long.hashCode(getValue(Magnitude.SINGLE));
172+
}
173+
174+
public enum Magnitude
175+
{
176+
// must be in increasing magnitude order
177+
SINGLE(1L, ""),
178+
THOUSAND(1000L, "K"),
179+
MILLION(1000_000L, "M"),
180+
BILLION(1000_000_000L, "B"),
181+
TRILION(1000_000_000_000L, "T"),
182+
QUADRILLION(1000_000_000_000_000L, "P");
183+
184+
private final long factor;
185+
private final String magnitudeString;
186+
187+
Magnitude(long factor, String magnitudeString)
188+
{
189+
this.factor = factor;
190+
this.magnitudeString = requireNonNull(magnitudeString, "magnitudeString is null");
191+
}
192+
193+
long getFactor()
194+
{
195+
return factor;
196+
}
197+
198+
public String getMagnitudeString()
199+
{
200+
return magnitudeString;
201+
}
202+
}
203+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
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 javax.validation.Constraint;
17+
import javax.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.METHOD;
25+
import static java.lang.annotation.RetentionPolicy.RUNTIME;
26+
27+
@Target({METHOD, ANNOTATION_TYPE})
28+
@Retention(RUNTIME)
29+
@Documented
30+
@Constraint(validatedBy = MaxCountValidator.class)
31+
public @interface MaxCount
32+
{
33+
String value();
34+
35+
String message() default "{io.airlift.units.MaxCount.message}";
36+
37+
Class<?>[] groups() default {};
38+
39+
Class<? extends Payload>[] payload() default {};
40+
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
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 javax.validation.ConstraintValidator;
17+
import javax.validation.ConstraintValidatorContext;
18+
19+
public class MaxCountValidator
20+
implements ConstraintValidator<MaxCount, Count>
21+
{
22+
private Count max;
23+
24+
@Override
25+
public void initialize(MaxCount count)
26+
{
27+
this.max = Count.valueOf(count.value());
28+
}
29+
30+
@Override
31+
public boolean isValid(Count count, ConstraintValidatorContext context)
32+
{
33+
return (count == null) || (count.compareTo(max) <= 0);
34+
}
35+
36+
@Override
37+
public String toString()
38+
{
39+
return "max:" + max;
40+
}
41+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
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 javax.validation.Constraint;
17+
import javax.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.METHOD;
25+
import static java.lang.annotation.RetentionPolicy.RUNTIME;
26+
27+
@Target({METHOD, ANNOTATION_TYPE})
28+
@Retention(RUNTIME)
29+
@Documented
30+
@Constraint(validatedBy = MinCountValidator.class)
31+
public @interface MinCount
32+
{
33+
String value();
34+
35+
String message() default "{io.airlift.units.MinCount.message}";
36+
37+
Class<?>[] groups() default {};
38+
39+
Class<? extends Payload>[] payload() default {};
40+
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
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 javax.validation.ConstraintValidator;
17+
import javax.validation.ConstraintValidatorContext;
18+
19+
public class MinCountValidator
20+
implements ConstraintValidator<MinCount, Count>
21+
{
22+
private Count min;
23+
24+
@Override
25+
public void initialize(MinCount dataSize)
26+
{
27+
this.min = Count.valueOf(dataSize.value());
28+
}
29+
30+
@Override
31+
public boolean isValid(Count count, ConstraintValidatorContext context)
32+
{
33+
return (count == null) || (count.compareTo(min) >= 0);
34+
}
35+
36+
@Override
37+
public String toString()
38+
{
39+
return "min:" + min;
40+
}
41+
}

0 commit comments

Comments
 (0)