blob: 64d749c23336acb7ef49a02c44a00ac9ea5584c1 [file] [log] [blame]
/*
* Copyright (c) OSGi Alliance (2002, 2016). All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.osgi.util.measurement;
import java.util.HashMap;
import java.util.Map;
/**
* A unit system for measurements.
*
* This class contains definitions of the most common SI units.
* <p>
*
* <p>
* This class only support exponents for the base SI units in the range -64 to
* +63. Any operation which produces an exponent outside of this range will
* result in a {@code Unit} object with undefined exponents.
*
* @Immutable
* @author $Id$
*/
/*
* This local class maintains the information about units. It can calculate new
* units when two values are multiplied, divided, added or subtracted. <p> The
* unit works with the 7 basic SI types + rad + up to 2^6 custom types. For each
* type, the unit keeps a bit mask with the exponents of the basic types. Eg.
* m/s is m = 1, s = -1. Multiplying one unit with another means that the bit
* masks are added, dividing means that the bit masks are subtracted. <p> This
* class can handle any reasonable combination of SI units. However, it will
* always try to coerce results back into the basic set. E.g. when you do V*A
* you should get W and not m2.kg/s3 . Only when the existing types do not match
* does the unit fallback to the expanded form. <p> This class uses offset
* arithmetic. This means that the exponents are stored in an long. The special
* field is used for units that should not be arithmetically divided or
* multiplied, like longitude and lattitude. These special units can however, be
* divided and multiplied by the basic 7 constants of the SI, e.g. deg/s.
*/
public class Unit {
private final static long UNITY = createType(0, 0, 0, 0, 0, 0, 0, 0, 0);
private final static long ZERO = 0x40L;
private final static long MASK = 0x7fL;
private final static int m_SHIFT = 0;
private final static int s_SHIFT = 7;
private final static int kg_SHIFT = 14;
private final static int K_SHIFT = 21;
private final static int A_SHIFT = 28;
private final static int mol_SHIFT = 35;
private final static int cd_SHIFT = 42;
private final static int rad_SHIFT = 49;
private final static int x_SHIFT = 56;
private final static long x_MASK = MASK << x_SHIFT;
/** No Unit (Unity) */
public final static Unit unity = new Unit("", UNITY); // Unity
/* SI Base Units */
/** The length unit meter (m) */
public final static Unit m = new Unit("m", createType(0, 0, 0, 0, 0, 0, 0, 0, 1)); // Distance
// meter
/** The time unit second (s) */
public final static Unit s = new Unit("s", createType(0, 0, 0, 0, 0, 0, 0, 1, 0)); // Time
// Seconds
// s
/** The mass unit kilogram (kg) */
public final static Unit kg = new Unit("kg", createType(0, 0, 0, 0, 0, 0, 1, 0, 0)); // Mass
// kilogram
// kg
/** The temperature unit kelvin (K) */
public final static Unit K = new Unit("K", createType(0, 0, 0, 0, 0, 1, 0, 0, 0)); // Temperature
// kelvin
// K
/** The electric current unit ampere (A) */
public final static Unit A = new Unit("A", createType(0, 0, 0, 0, 1, 0, 0, 0, 0)); // Current
// ampere
// A
/** The amount of substance unit mole (mol) */
public final static Unit mol = new Unit("mol", createType(0, 0, 0, 1, 0, 0, 0, 0, 0)); // Substance
// mole
// mol
/** The luminous intensity unit candela (cd) */
public final static Unit cd = new Unit("cd", createType(0, 0, 1, 0, 0, 0, 0, 0, 0)); // Light
// candela
// cd
/* SI Derived Units */
/** The speed unit meter per second (m/s) */
public final static Unit m_s = new Unit("m/s", createType(0, 0, 0, 0, 0, 0, 0, -1, 1)); // Speed
// m/s
/** The acceleration unit meter per second squared (m/s<sup>2</sup>) */
public final static Unit m_s2 = new Unit("m/s2", createType(0, 0, 0, 0, 0, 0, 0, -2, 1)); // Acceleration
// m/s^2
/** The area unit square meter (m<sup>2</sup>) */
public final static Unit m2 = new Unit("m2", createType(0, 0, 0, 0, 0, 0, 0, 0, 2)); // Surface
// m^2
/** The volume unit cubic meter (m<sup>3</sup>) */
public final static Unit m3 = new Unit("m3", createType(0, 0, 0, 0, 0, 0, 0, 0, 3)); // Volume
// m^3
/**
* The frequency unit hertz (Hz).
* <p>
* hertz is expressed in SI units as 1/s
*/
public final static Unit Hz = new Unit("Hz", createType(0, 0, 0, 0, 0, 0, 0, -1, 0)); // Frequency
// 1/s
/**
* The force unit newton (N).
* <p>
* N is expressed in SI units as m&#183;kg/s<sup>2</sup>
*/
public final static Unit N = new Unit("N", createType(0, 0, 0, 0, 0, 0, 1, -2, 1)); // Force
// newton
// (m*kg)/s^2
/**
* The pressure unit pascal (Pa).
* <p>
* Pa is equal to N/m<sup>2</sup> or is expressed in SI units as
* kg/m&#183;s<sup>2</sup>
*/
public final static Unit Pa = new Unit("Pa", createType(0, 0, 0, 0, 0, 0, 1, -2, -1)); // Pressure
// pascal
// kg/(m*s^2)
/**
* The energy unit joule (J).
* <p>
* joule is equal to N&#183;m or is expressed in SI units as
* m<sup>2</sup>&#183;kg/s<sup>2</sup>
*/
public final static Unit J = new Unit("J", createType(0, 0, 0, 0, 0, 0, 1, -2, 2)); // Energy
// joule
// (m^2*kg)/s^2
/**
* The power unit watt (W).
* <p>
* watt is equal to J/s or is expressed in SI units as
* m<sup>2</sup>&#183;kg/s<sup>3</sup>
*/
public final static Unit W = new Unit("W", createType(0, 0, 0, 0, 0, 0, 1, -3, 2)); // Power
// watt
// (m^2*kg)/s^3
/**
* The electric charge unit coulomb (C).
* <p>
* coulomb is expressed in SI units as s&#183;A
*/
public final static Unit C = new Unit("C", createType(0, 0, 0, 0, 1, 0, 0, 1, 0)); // Charge
// coulumb
// s*A
/**
* The electric potential difference unit volt (V).
* <p>
* volt is equal to W/A or is expressed in SI units as
* m<sup>2</sup>&#183;kg/s<sup>3</sup>&#183;A
*/
public final static Unit V = new Unit("V", createType(0, 0, 0, 0, -1, 0, 1, -3, 2)); // El.
// Potent.
// volt
// (m^2*kg)/(s^3*A)
/**
* The capacitance unit farad (F).
* <p>
* farad is equal to C/V or is expressed in SI units as
* s<sup>4</sup>&#183;A<sup>2</sup>/m<sup>2</sup>&#183;kg
*/
public final static Unit F = new Unit("F", createType(0, 0, 0, 0, 2, 0, -1, 4, -2)); // Capacitance
// farad
// (s^4*A^2)/(m^2*kg)
/**
* The electric resistance unit ohm.
* <p>
* ohm is equal to V/A or is expressed in SI units as
* m<sup>2</sup>&#183;kg/s<sup>3</sup>&#183;A<sup>2</sup>
*/
public final static Unit Ohm = new Unit("Ohm", createType(0, 0, 0, 0, -2, 0, 1, -3, 2)); // Resistance
// ohm
// (m^2*kg)/(s^3*A^2)
/**
* The electric conductance unit siemens (S).
* <p>
* siemens is equal to A/V or is expressed in SI units as
* s<sup>3</sup>&#183;A<sup>2</sup>/m<sup>2</sup>&#183;kg
*/
public final static Unit S = new Unit("S", createType(0, 0, 0, 0, 2, 0, -1, 3, -2)); // Conductance
// siemens
// (s^3*A^2)/(m^2*kg)
/**
* The magnetic flux unit weber (Wb).
* <p>
* weber is equal to V&#183;s or is expressed in SI units as
* m<sup>2</sup>&#183;kg/s<sup>2</sup>&#183;A
*/
public final static Unit Wb = new Unit("Wb", createType(0, 0, 0, 0, -1, 0, 1, -2, 2)); // Magn.
// Flux
// weber
// (m^2*kg)/(s^2*A)
/**
* The magnetic flux density unit tesla (T).
* <p>
* tesla is equal to Wb/m<sup>2</sup> or is expressed in SI units as
* kg/s<sup>2</sup>&#183;A
*/
public final static Unit T = new Unit("T", createType(0, 0, 0, 0, -1, 0, 1, -2, 0)); // Magn.
// Flux
// Dens.
// tesla
// kg/(s^2*A)
/**
* The illuminance unit lux (lx).
* <p>
* lux is expressed in SI units as cd/m<sup>2</sup>
*/
public final static Unit lx = new Unit("lx", createType(0, 0, 1, 0, 0, 0, 0, 0, -2)); // Illuminace
// lux
// cd/m^2
/**
* The absorbed dose unit gray (Gy).
* <p>
* Gy is equal to J/kg or is expressed in SI units as
* m<sup>2</sup>/s<sup>2</sup>
*/
public final static Unit Gy = new Unit("Gy", createType(0, 0, 0, 0, 0, 0, 0, -2, 2)); // Absorbed
// dose
// gray
// m^2/s^2
/**
* The catalytic activity unit katal (kat).
* <p>
* katal is expressed in SI units as mol/s
*/
public final static Unit kat = new Unit("kat", createType(0, 0, 0, 1, 0, 0, 0, -1, 0)); // Catalytic
// Act.
// katal
// mol/s
/** The angle unit radians (rad) */
public final static Unit rad = new Unit("rad", createType(0, 1, 0, 0, 0, 0, 0, 0, 0)); // Angle
// radians
// rad
/**
* An array containing all units defined. The first seven items must be m,
* s, kg, K, A, mol, cd, rad in this order!
*/
private final static Unit[] allUnits = new Unit[] {m, s, kg, K, A, mol, cd, rad, m_s, m_s2, m2, m3, Hz, N, Pa, J, W, C, V, F, Ohm, S, Wb, T, lx, Gy, kat, unity};
/* @GuardedBy("Unit.class") */
private static Map<Unit, Unit> base;
private final String name;
private final long type;
/**
* Creates a new {@code Unit} instance.
*
* @param name the name of the {@code Unit}
* @param type the type of the {@code Unit}
*/
private Unit(String name, long type) {
if (name == null) {
name = computeName(type);
}
this.name = name;
this.type = type;
// System.out.println( name + " " + Long.toHexString( type ) );
}
/**
* Create a type field from the base SI unit exponent values.
*
*/
private static long createType(int _x, int _rad, int _cd, int _mol, int _A, int _K, int _kg, int _s, int _m) {
return (((ZERO + _m) & MASK) << m_SHIFT) | (((ZERO + _s) & MASK) << s_SHIFT) | (((ZERO + _kg) & MASK) << kg_SHIFT) | (((ZERO + _K) & MASK) << K_SHIFT) | (((ZERO + _A) & MASK) << A_SHIFT)
| (((ZERO + _mol) & MASK) << mol_SHIFT) | (((ZERO + _cd) & MASK) << cd_SHIFT) | (((ZERO + _rad) & MASK) << rad_SHIFT) | (((long) _x) << x_SHIFT);
}
/**
* Checks whether this {@code Unit} object is equal to the specified
* {@code Unit} object. The {@code Unit} objects are considered equal if
* their exponents are equal.
*
* @param obj the {@code Unit} object that should be checked for equality
*
* @return true if the specified {@code Unit} object is equal to this
* {@code Unit} object.
*/
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (!(obj instanceof Unit)) {
return false;
}
return ((Unit) obj).type == type;
}
/**
* Returns the hash code for this object.
*
* @return This object's hash code.
*/
@Override
public int hashCode() {
return 31 * 17 + (int) (type ^ (type >>> 32));
}
/**
* Returns a new {@code Unit} that is the multiplication of this
* {@code Unit} and the {@code Unit} specified
*
* @param that the {@code Unit} that will be multiplied with this
* {@code Unit}
*
* @return a new {@code Unit} that is the multiplication of this
* {@code Unit} and the {@code Unit} specified
*
* @throws RuntimeException if both {@code Unit} s are special
*
* @see Unit#isSpecial()
*/
Unit mul(Unit that) {
if (this.isSpecial() && that.isSpecial()) {
throw new ArithmeticException("Cannot multiply " + this + " with " + that);
}
return find(this.type - UNITY + that.type);
}
/**
* Returns a new {@code Unit} that is the division of this {@code Unit} and
* the {@code Unit} specified
*
* @param that the {@code Unit} that this {@code Unit} will be divided with
* @return a new {@code Unit} that is the division of this {@code Unit} and
* the {@code Unit} specified
*
* @throws RuntimeException if both {@code Unit} s are special
*
* @see Unit#isSpecial()
*/
Unit div(Unit that) {
if (this.isSpecial() && that.isSpecial()) {
if (this.type == that.type) {
return Unit.unity;
}
throw new ArithmeticException("Cannot divide " + this + " by " + that);
}
return find(this.type - that.type + UNITY);
}
/**
* Returns a new {@code Unit} that is the addition of this {@code Unit} and
* the {@code Unit} specified.
*
* @param that the {@code Unit} that should be added to this {@code Unit}
*
* @return a new {@code Unit} that is the addition of this {@code Unit} and
* the {@code Unit} specified.
*
* @throws RuntimeException if the two {@code Unit} s are not the same
*/
Unit add(Unit that) {
if (!this.equals(that)) {
throw new ArithmeticException("Cannot add " + this + " to " + that);
}
return this;
}
/**
* Returns a new {@code Unit} that is the subtraction between this
* {@code Unit} and the {@code Unit} specified.
*
* @param that the {@code Unit} that will be subtracted from this
* {@code Unit}
* @return a new {@code Unit} that is the subtraction between this
* {@code Unit} and the {@code Unit} specified.
*
* @throws RuntimeException if the {@code Unit} specified is not the same as
* this {@code Unit}
*/
Unit sub(Unit that) {
if (!this.equals(that)) {
throw new ArithmeticException("Cannot subtract " + that + " from " + this);
}
return this;
}
/**
* Finds a {@code Unit} based on a type. If the {@code Unit} is not found,
* it will be created and added to the list of all units under a null name.
*
* @param type the type of the {@code Unit} to find
*
* @return the {@code Unit}
*/
static synchronized Unit find(long type) {
if (base == null) {
int size = allUnits.length;
base = new HashMap<>(size << 1);
for (int i = 0; i < size; i++) {
base.put(allUnits[i], allUnits[i]);
}
}
Unit unit = new Unit(null, type);
Unit out = base.get(unit);
if (out == null) {
base.put(unit, unit);
out = unit;
}
return out;
}
/**
* Returns a {@code String} object representing the {@code Unit}
*
* @return A {@code String} object representing the {@code Unit}
*/
@Override
public String toString() {
return name;
}
private static String computeName(long type) {
int _m = (int) (((type >> m_SHIFT) & MASK) - ZERO);
int _s = (int) (((type >> s_SHIFT) & MASK) - ZERO);
int _kg = (int) (((type >> kg_SHIFT) & MASK) - ZERO);
int _K = (int) (((type >> K_SHIFT) & MASK) - ZERO);
int _A = (int) (((type >> A_SHIFT) & MASK) - ZERO);
int _mol = (int) (((type >> mol_SHIFT) & MASK) - ZERO);
int _cd = (int) (((type >> cd_SHIFT) & MASK) - ZERO);
int _rad = (int) (((type >> rad_SHIFT) & MASK) - ZERO);
StringBuilder numerator = new StringBuilder();
StringBuilder denominator = new StringBuilder();
addSIname(_m, "m", numerator, denominator);
addSIname(_s, "s", numerator, denominator);
addSIname(_kg, "kg", numerator, denominator);
addSIname(_K, "K", numerator, denominator);
addSIname(_A, "A", numerator, denominator);
addSIname(_mol, "mol", numerator, denominator);
addSIname(_cd, "cd", numerator, denominator);
addSIname(_rad, "rad", numerator, denominator);
if (denominator.length() > 0) {
if (numerator.length() == 0) {
numerator.append("1");
}
numerator.append("/");
numerator.append(denominator.toString());
}
return numerator.toString();
}
private static void addSIname(int si, String name, StringBuilder numerator,
StringBuilder denominator) {
if (si != 0) {
StringBuilder sb = (si > 0) ? numerator : denominator;
if (sb.length() > 0) {
sb.append("*");
}
sb.append(name);
int power = Math.abs(si);
if (power > 1) {
sb.append("^");
sb.append(power);
}
}
}
/**
* Checks whether the unit has a special type, i.e. not a SI unit.
*
* @return true if the type is special, otherwise false.
*/
private boolean isSpecial() {
return (type & x_MASK) != 0;
}
}