Skip to content

Commit 9f22896

Browse files
committed
Introduce Count
1 parent 7622bb8 commit 9f22896

File tree

9 files changed

+986
-0
lines changed

9 files changed

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