RobotCore 1.0
WPILib port to raspberry Pi
Loading...
Searching...
No Matches
UnitBuilder.java
1// Copyright (c) FIRST and other WPILib contributors.
2// Open Source Software; you can modify and/or share it under the terms of
3// the WPILib BSD license file in the root directory of this project.
4
5package org.wpilib.units;
6
7import java.lang.reflect.Constructor;
8import java.lang.reflect.InvocationTargetException;
9import java.util.Objects;
10
21public final class UnitBuilder<U extends Unit> {
22 private final U m_base;
23 private UnaryFunction m_fromBase = UnaryFunction.IDENTITY;
24 private UnaryFunction m_toBase = UnaryFunction.IDENTITY;
25 private String m_name;
26 private String m_symbol;
27
34 public UnitBuilder(U base) {
35 this.m_base = Objects.requireNonNull(base, "Base unit cannot be null");
36 }
37
45 public UnitBuilder<U> offset(double offset) {
46 m_toBase = derivedValue -> derivedValue + offset;
47 m_fromBase = baseValue -> baseValue - offset;
48 return this;
49 }
50
64 // NOTE: This method lives here instead of in MappingBuilder because inner classes can't
65 // define static methods prior to Java 16.
66 private static double mapValue(
67 double value, double inMin, double inMax, double outMin, double outMax) {
68 return (value - inMin) * (outMax - outMin) / (inMax - inMin) + outMin;
69 }
70
72 public final class MappingBuilder {
73 private final double m_minInput;
74 private final double m_maxInput;
75
76 private MappingBuilder(double minInput, double maxInput) {
77 this.m_minInput = minInput;
78 this.m_maxInput = maxInput;
79 }
80
88 public UnitBuilder<U> toOutputRange(double minOutput, double maxOutput) {
89 UnitBuilder.this.m_fromBase = x -> mapValue(x, m_minInput, m_maxInput, minOutput, maxOutput);
90 UnitBuilder.this.m_toBase = y -> mapValue(y, minOutput, maxOutput, m_minInput, m_maxInput);
91 return UnitBuilder.this;
92 }
93 }
94
104 public MappingBuilder mappingInputRange(double minBase, double maxBase) {
105 return new MappingBuilder(minBase, maxBase);
106 }
107
116 this.m_fromBase = Objects.requireNonNull(fromBase, "fromBase function cannot be null");
117 return this;
118 }
119
128 this.m_toBase = Objects.requireNonNull(toBase, "toBase function cannot be null");
129 return this;
130 }
131
138 public UnitBuilder<U> named(String name) {
139 this.m_name = name;
140 return this;
141 }
142
149 public UnitBuilder<U> symbol(String symbol) {
150 this.m_symbol = symbol;
151 return this;
152 }
153
164 public UnitBuilder<U> splitInto(double fraction) {
165 if (fraction == 0) {
166 throw new IllegalArgumentException("Fraction must be nonzero");
167 }
168
169 return toBase(x -> x / fraction).fromBase(b -> b * fraction);
170 }
171
180 public UnitBuilder<U> aggregate(double aggregation) {
181 if (aggregation == 0) {
182 throw new IllegalArgumentException("Aggregation amount must be nonzero");
183 }
184
185 return toBase(x -> x * aggregation).fromBase(b -> b / aggregation);
186 }
187
193 @FunctionalInterface
194 public interface UnitConstructorFunction<U extends Unit> {
208 U baseUnit,
209 UnaryFunction toBaseUnits,
210 UnaryFunction fromBaseUnits,
211 String name,
212 String symbol);
213 }
214
223 @SuppressWarnings("unchecked")
224 public U make(UnitConstructorFunction<U> constructor) {
225 Objects.requireNonNull(m_fromBase, "fromBase function was not set");
226 Objects.requireNonNull(m_toBase, "toBase function was not set");
227 Objects.requireNonNull(m_name, "new unit name was not set");
228 Objects.requireNonNull(m_symbol, "new unit symbol was not set");
229
230 return constructor.create(
231 (U) m_base.getBaseUnit(),
232 m_toBase.pipeTo(m_base.getConverterToBase()),
233 m_base.getConverterFromBase().pipeTo(m_fromBase),
234 m_name,
235 m_symbol);
236 }
237
246 public U make() {
247 return make(
248 (baseUnit, toBaseUnits, fromBaseUnits, name, symbol) -> {
249 var baseClass = baseUnit.getClass();
250
251 try {
252 var ctor = getConstructor(baseUnit);
253
254 return ctor.newInstance(baseUnit, toBaseUnits, fromBaseUnits, name, symbol);
255 } catch (InstantiationException e) {
256 throw new RuntimeException("Could not instantiate class " + baseClass.getName(), e);
257 } catch (IllegalAccessException e) {
258 throw new RuntimeException("Could not access constructor", e);
259 } catch (InvocationTargetException e) {
260 throw new RuntimeException(
261 "Constructing " + baseClass.getName() + " raised an exception", e);
262 } catch (NoSuchMethodException e) {
263 throw new RuntimeException(
264 "No compatible constructor "
265 + baseClass.getSimpleName()
266 + "("
267 + baseClass.getSimpleName()
268 + ", UnaryFunction, UnaryFunction, String, String)",
269 e);
270 }
271 });
272 }
273
274 @SuppressWarnings({"unchecked", "PMD.AvoidAccessibilityAlteration"})
275 private static <U extends Unit> Constructor<? extends U> getConstructor(U baseUnit)
276 throws NoSuchMethodException {
277 var baseClass = baseUnit.getClass();
278
279 var ctor =
280 baseClass.getDeclaredConstructor(
281 baseClass, // baseUnit
282 UnaryFunction.class, // toBaseUnits
283 UnaryFunction.class, // fromBaseUnits
284 String.class, // name
285 String.class); // symbol
286
287 // need to flag the constructor as accessible so we can use private, package-private,
288 // and protected constructors
289 ctor.setAccessible(true);
290
291 return (Constructor<? extends U>) ctor;
292 }
293}
UnitBuilder< U > toOutputRange(double minOutput, double maxOutput)
UnitBuilder< U > splitInto(double fraction)
UnitBuilder< U > fromBase(UnaryFunction fromBase)
UnitBuilder< U > named(String name)
UnitBuilder< U > offset(double offset)
MappingBuilder mappingInputRange(double minBase, double maxBase)
UnitBuilder< U > aggregate(double aggregation)
UnitBuilder< U > toBase(UnaryFunction toBase)
UnitBuilder< U > symbol(String symbol)
U make(UnitConstructorFunction< U > constructor)
U create(U baseUnit, UnaryFunction toBaseUnits, UnaryFunction fromBaseUnits, String name, String symbol)