From db854ce6e5038900c4ca1ad79154a4cd13ee257e Mon Sep 17 00:00:00 2001 From: Adrien Hopkins Date: Fri, 25 Jan 2019 14:23:10 -0500 Subject: Added basic code for the units. --- src/unitConverter/unit/AbstractUnit.java | 112 +++++++++++++++++++++++++++++++ src/unitConverter/unit/BaseUnit.java | 69 +++++++++++++++++++ src/unitConverter/unit/LinearUnit.java | 86 ++++++++++++++++++++++++ src/unitConverter/unit/SI.java | 49 ++++++++++++++ src/unitConverter/unit/UnitSystem.java | 12 ++++ 5 files changed, 328 insertions(+) create mode 100644 src/unitConverter/unit/AbstractUnit.java create mode 100755 src/unitConverter/unit/BaseUnit.java create mode 100644 src/unitConverter/unit/LinearUnit.java create mode 100644 src/unitConverter/unit/SI.java diff --git a/src/unitConverter/unit/AbstractUnit.java b/src/unitConverter/unit/AbstractUnit.java new file mode 100644 index 0000000..62c07a2 --- /dev/null +++ b/src/unitConverter/unit/AbstractUnit.java @@ -0,0 +1,112 @@ +/** + * Copyright (C) 2019 Adrien Hopkins + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package unitConverter.unit; + +import java.util.Objects; + +import unitConverter.dimension.UnitDimension; + +/** + * The default abstract implementation of the {@code Unit} interface. + * + * @author Adrien Hopkins + * @since 2019-01-25 + */ +abstract class AbstractUnit implements Unit { + /** + * The dimension, or what the unit measures. + * + * @since 2018-12-22 + */ + private final UnitDimension dimension; + + /** + * The unit's base unit. Values converted by {@code convertFromBase} and {@code convertToBase} are expressed in this + * unit. + * + * @since 2018-12-22 + */ + private final BaseUnit base; + + /** + * The system that this unit is a part of. + * + * @since 2018-12-23 + */ + private final UnitSystem system; + + /** + * Creates the {@code AbstractUnit}. + * + * @param base + * unit's base + * @throws NullPointerException + * if name, symbol or base is null + * @since 2018-12-22 + */ + public AbstractUnit(final BaseUnit base) { + this.base = Objects.requireNonNull(base, "base must not be null."); + this.dimension = this.base.getDimension(); + this.system = this.base.getSystem(); + } + + /** + * Creates the {@code AbstractUnit} using a unique dimension. This constructor is for making base units and should + * only be used by {@code BaseUnit}. + * + * @param dimension + * dimension measured by unit + * @param system + * system that unit is a part of + * @throws AssertionError + * if this constructor is not run by {@code BaseUnit} or a subclass + * @throws NullPointerException + * if name, symbol or dimension is null + * @since 2018-12-23 + */ + AbstractUnit(final UnitDimension dimension, final UnitSystem system) { + // try to set this as a base unit + if (this instanceof BaseUnit) { + this.base = (BaseUnit) this; + } else + throw new AssertionError(); + + this.dimension = Objects.requireNonNull(dimension, "dimension must not be null."); + this.system = Objects.requireNonNull(system, "system must not be null."); + } + + @Override + public final BaseUnit getBase() { + return this.base; + } + + @Override + public final UnitDimension getDimension() { + return this.dimension; + } + + @Override + public final UnitSystem getSystem() { + return this.system; + } + + // TODO document and revise units' toString methods + @Override + public String toString() { + return String.format("%s-derived unit of dimension %s", this.getSystem(), this.getDimension()); + } +} diff --git a/src/unitConverter/unit/BaseUnit.java b/src/unitConverter/unit/BaseUnit.java new file mode 100755 index 0000000..339b7bf --- /dev/null +++ b/src/unitConverter/unit/BaseUnit.java @@ -0,0 +1,69 @@ +/** + * Copyright (C) 2018 Adrien Hopkins + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package unitConverter.unit; + +import unitConverter.dimension.UnitDimension; + +/** + * A unit that is the base for its dimension. It does not have to be for a base dimension, so units like the Newton and + * Joule are still base units. + * + * @author Adrien Hopkins + * @since 2018-12-23 + */ +public final class BaseUnit extends AbstractUnit { + /** + * Is this unit a full base (i.e. m, s, ... but not N, J, ...) + * + * @since 2019-01-15 + */ + private final boolean isFullBase; + + /** + * Creates the {@code BaseUnit}. + * + * @param dimension + * dimension measured by unit + * @param system + * system that unit is a part of + * @param name + * name of unit + * @param symbol + * symbol of unit + * @since 2018-12-23 + */ + BaseUnit(final UnitDimension dimension, final UnitSystem system) { + super(dimension, system); + this.isFullBase = dimension.isBase(); + } + + @Override + public double convertFromBase(final double value) { + return value; + } + + @Override + public double convertToBase(final double value) { + return value; + } + + @Override + public String toString() { + return String.format("%s base unit of%s dimension %s", this.getSystem(), this.isFullBase ? " base" : "", + this.getDimension()); + } +} diff --git a/src/unitConverter/unit/LinearUnit.java b/src/unitConverter/unit/LinearUnit.java new file mode 100644 index 0000000..229710c --- /dev/null +++ b/src/unitConverter/unit/LinearUnit.java @@ -0,0 +1,86 @@ +/** + * Copyright (C) 2019 Adrien Hopkins + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package unitConverter.unit; + +import unitConverter.dimension.UnitDimension; + +/** + * A unit that is equal to a certain number multiplied by its base. + * + * @author Adrien Hopkins + * @since 2019-01-25 + */ +public class LinearUnit extends AbstractUnit { + /** + * The value of one of this unit in this unit's base unit + * + * @since 2018-12-22 + */ + private final double conversionFactor; + + /** + * + * Creates the {@code LinearUnit}. + * + * @param base + * unit's base + * @param conversionFactor + * value of one of this unit in its base + * @since 2018-12-23 + */ + LinearUnit(final BaseUnit base, final double conversionFactor) { + super(base); + this.conversionFactor = conversionFactor; + } + + /** + * Creates the {@code LinearUnit} as a base unit. + * + * @param dimension + * dimension measured by unit + * @param system + * system unit is part of + * @since 2019-01-25 + */ + LinearUnit(final UnitDimension dimension, final UnitSystem system, final double conversionFactor) { + super(dimension, system); + this.conversionFactor = conversionFactor; + } + + @Override + public double convertFromBase(final double value) { + return value / this.getConversionFactor(); + } + + @Override + public double convertToBase(final double value) { + return value * this.getConversionFactor(); + } + + /** + * @return conversionFactor + * @since 2019-01-25 + */ + public final double getConversionFactor() { + return this.conversionFactor; + } + + @Override + public String toString() { + return super.toString() + String.format(" (equal to %s * base)", this.getConversionFactor()); + } +} diff --git a/src/unitConverter/unit/SI.java b/src/unitConverter/unit/SI.java new file mode 100644 index 0000000..4486bf9 --- /dev/null +++ b/src/unitConverter/unit/SI.java @@ -0,0 +1,49 @@ +/** + * Copyright (C) 2018 Adrien Hopkins + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package unitConverter.unit; + +import unitConverter.dimension.StandardDimensions; +import unitConverter.dimension.UnitDimension; + +/** + * The SI, which holds all SI units + * + * @author Adrien Hopkins + * @since 2018-12-23 + */ +public enum SI implements UnitSystem { + SI; + + // base units + public static final BaseUnit METRE = SI.getBaseUnit(StandardDimensions.LENGTH); + public static final BaseUnit KILOGRAM = SI.getBaseUnit(StandardDimensions.MASS); + public static final BaseUnit SECOND = SI.getBaseUnit(StandardDimensions.TIME); + public static final BaseUnit AMPERE = SI.getBaseUnit(StandardDimensions.ELECTRIC_CURRENT); + public static final BaseUnit KELVIN = SI.getBaseUnit(StandardDimensions.TEMPERATURE); + public static final BaseUnit MOLE = SI.getBaseUnit(StandardDimensions.QUANTITY); + public static final BaseUnit CANDELA = SI.getBaseUnit(StandardDimensions.LUMINOUS_INTENSITY); + + @Override + public BaseUnit getBaseUnit(final UnitDimension dimension) { + return new BaseUnit(dimension, this); + } + + @Override + public String getName() { + return "SI"; + } +} diff --git a/src/unitConverter/unit/UnitSystem.java b/src/unitConverter/unit/UnitSystem.java index 2e3a5d8..0a50062 100755 --- a/src/unitConverter/unit/UnitSystem.java +++ b/src/unitConverter/unit/UnitSystem.java @@ -16,6 +16,8 @@ */ package unitConverter.unit; +import unitConverter.dimension.UnitDimension; + /** * A system of units. Each unit should be aware of its system. * @@ -23,6 +25,16 @@ package unitConverter.unit; * @since 2018-12-23 */ public interface UnitSystem { + /** + * Gets a base unit for this system and the provided dimension. + * + * @param dimension + * dimension used by base unit + * @return base unit + * @since 2019-01-25 + */ + BaseUnit getBaseUnit(UnitDimension dimension); + /** * @return name of system * @since 2019-01-25 -- cgit v1.2.3 From e7d6b4ee2286dd9320550d95cb27020ee71bb9d1 Mon Sep 17 00:00:00 2001 From: Adrien Hopkins Date: Fri, 25 Jan 2019 16:47:35 -0500 Subject: Added multiplication, division and exponentitation to linear/base --- src/unitConverter/unit/BaseUnit.java | 76 ++++++++++++++++++++++++++++++++++ src/unitConverter/unit/LinearUnit.java | 72 +++++++++++++++++++++++++++++++- 2 files changed, 147 insertions(+), 1 deletion(-) diff --git a/src/unitConverter/unit/BaseUnit.java b/src/unitConverter/unit/BaseUnit.java index 339b7bf..46316bf 100755 --- a/src/unitConverter/unit/BaseUnit.java +++ b/src/unitConverter/unit/BaseUnit.java @@ -16,6 +16,8 @@ */ package unitConverter.unit; +import java.util.Objects; + import unitConverter.dimension.UnitDimension; /** @@ -61,6 +63,80 @@ public final class BaseUnit extends AbstractUnit { return value; } + /** + * Divides this unit by another unit. + * + * @param other + * unit to divide by + * @return quotient of two units + * @throws IllegalArgumentException + * if this unit's system is not other's system + * @throws NullPointerException + * if other is null + * @since 2018-12-22 + */ + public BaseUnit dividedBy(final BaseUnit other) { + Objects.requireNonNull(other, "other must not be null."); + if (!this.getSystem().equals(other.getSystem())) + throw new IllegalArgumentException("Incompatible base units for division."); + return new BaseUnit(this.getDimension().dividedBy(other.getDimension()), this.getSystem()); + } + + /** + * Divides this unit by a divisor + * + * @param divisor + * amount to divide by + * @return quotient + * @since 2018-12-23 + */ + public LinearUnit dividedBy(final double divisor) { + return new LinearUnit(this, 1 / divisor); + } + + /** + * Multiplies this unit by another unit. + * + * @param other + * unit to multiply by + * @return product of two units + * @throws IllegalArgumentException + * if this unit's system is not other's system + * @throws NullPointerException + * if other is null + * @since 2018-12-22 + */ + public BaseUnit times(final BaseUnit other) { + Objects.requireNonNull(other, "other must not be null."); + if (!this.getSystem().equals(other.getSystem())) + throw new IllegalArgumentException("Incompatible base units for multiplication."); + return new BaseUnit(this.getDimension().times(other.getDimension()), this.getSystem()); + } + + /** + * Multiplies this unit by a multiplier. + * + * @param multiplier + * amount to multiply by + * @return product + * @since 2018-12-23B + */ + public LinearUnit times(final double multiplier) { + return new LinearUnit(this, multiplier); + } + + /** + * Returns this unit, but to an exponent. + * + * @param exponent + * exponent + * @return result of exponentiation + * @since 2019-01-15 + */ + public BaseUnit toExponent(final int exponent) { + return this.toExponent(exponent); + } + @Override public String toString() { return String.format("%s base unit of%s dimension %s", this.getSystem(), this.isFullBase ? " base" : "", diff --git a/src/unitConverter/unit/LinearUnit.java b/src/unitConverter/unit/LinearUnit.java index 229710c..e2c9eb2 100644 --- a/src/unitConverter/unit/LinearUnit.java +++ b/src/unitConverter/unit/LinearUnit.java @@ -16,6 +16,8 @@ */ package unitConverter.unit; +import java.util.Objects; + import unitConverter.dimension.UnitDimension; /** @@ -24,7 +26,7 @@ import unitConverter.dimension.UnitDimension; * @author Adrien Hopkins * @since 2019-01-25 */ -public class LinearUnit extends AbstractUnit { +public final class LinearUnit extends AbstractUnit { /** * The value of one of this unit in this unit's base unit * @@ -71,6 +73,34 @@ public class LinearUnit extends AbstractUnit { return value * this.getConversionFactor(); } + /** + * Divides this unit by a scalar. + * + * @param divisor + * scalar to divide by + * @return quotient + * @since 2018-12-23 + */ + public LinearUnit dividedBy(final double divisor) { + return new LinearUnit(this.getBase(), this.getConversionFactor() / divisor); + } + + /** + * Divides this unit by another unit. + * + * @param other + * unit to divide by + * @return quotient of two units + * @throws NullPointerException + * if other is null + * @since 2018-12-22 + */ + public LinearUnit dividedBy(final LinearUnit other) { + Objects.requireNonNull(other, "other must not be null"); + final BaseUnit base = this.getBase().dividedBy(other.getBase()); + return new LinearUnit(base, this.getConversionFactor() / other.getConversionFactor()); + } + /** * @return conversionFactor * @since 2019-01-25 @@ -79,6 +109,46 @@ public class LinearUnit extends AbstractUnit { return this.conversionFactor; } + /** + * Multiplies this unit by a scalar. + * + * @param multiplier + * scalar to multiply by + * @return product + * @since 2018-12-23 + */ + public LinearUnit times(final double multiplier) { + return new LinearUnit(this.getBase(), this.getConversionFactor() * multiplier); + } + + /** + * Multiplies this unit by another unit. + * + * @param other + * unit to multiply by= + * @return product of two units + * @throws NullPointerException + * if other is null + * @since 2018-12-22 + */ + public LinearUnit times(final LinearUnit other) { + Objects.requireNonNull(other, "other must not be null"); + final BaseUnit base = this.getBase().times(other.getBase()); + return new LinearUnit(base, this.getConversionFactor() * other.getConversionFactor()); + } + + /** + * Returns this unit but to an exponent. + * + * @param exponent + * exponent to exponientate unit to + * @return exponientated unit + * @since 2019-01-15 + */ + public LinearUnit toExponent(final int exponent) { + return new LinearUnit(this.getBase().toExponent(exponent), Math.pow(this.conversionFactor, exponent)); + } + @Override public String toString() { return super.toString() + String.format(" (equal to %s * base)", this.getConversionFactor()); -- cgit v1.2.3 From 43feeeab69b723e02694a2d93eaa44c3007665e1 Mon Sep 17 00:00:00 2001 From: Adrien Hopkins Date: Fri, 25 Jan 2019 17:12:18 -0500 Subject: Added unit prefixes and made SI reuse base units. --- src/unitConverter/unit/SI.java | 25 ++++++++++++++++- src/unitConverter/unit/SIPrefix.java | 49 ++++++++++++++++++++++++++++++++++ src/unitConverter/unit/UnitPrefix.java | 31 +++++++++++++++++++++ src/unitConverter/unit/UnitSystem.java | 9 ++++++- 4 files changed, 112 insertions(+), 2 deletions(-) create mode 100755 src/unitConverter/unit/SIPrefix.java create mode 100755 src/unitConverter/unit/UnitPrefix.java diff --git a/src/unitConverter/unit/SI.java b/src/unitConverter/unit/SI.java index 4486bf9..cda42e7 100644 --- a/src/unitConverter/unit/SI.java +++ b/src/unitConverter/unit/SI.java @@ -16,6 +16,10 @@ */ package unitConverter.unit; +import java.util.HashSet; +import java.util.Objects; +import java.util.Set; + import unitConverter.dimension.StandardDimensions; import unitConverter.dimension.UnitDimension; @@ -28,6 +32,13 @@ import unitConverter.dimension.UnitDimension; public enum SI implements UnitSystem { SI; + /** + * This system's base units. + * + * @since 2019-01-25 + */ + private static final Set baseUnits = new HashSet<>(); + // base units public static final BaseUnit METRE = SI.getBaseUnit(StandardDimensions.LENGTH); public static final BaseUnit KILOGRAM = SI.getBaseUnit(StandardDimensions.MASS); @@ -39,7 +50,19 @@ public enum SI implements UnitSystem { @Override public BaseUnit getBaseUnit(final UnitDimension dimension) { - return new BaseUnit(dimension, this); + // try to find an existing unit before creating a new one + + Objects.requireNonNull(dimension, "dimension must not be null."); + for (final BaseUnit unit : baseUnits) { + // it will be equal since the conditions for equality are dimension and system, + // and system is always SI. + if (unit.getDimension().equals(dimension)) + return unit; + } + // could not find an existing base unit + final BaseUnit unit = new BaseUnit(dimension, this); + baseUnits.add(unit); + return unit; } @Override diff --git a/src/unitConverter/unit/SIPrefix.java b/src/unitConverter/unit/SIPrefix.java new file mode 100755 index 0000000..452d46f --- /dev/null +++ b/src/unitConverter/unit/SIPrefix.java @@ -0,0 +1,49 @@ +/** + * Copyright (C) 2018 Adrien Hopkins + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package unitConverter.unit; + +/** + * @author Adrien Hopkins + * @since 2019-01-14 + */ +public enum SIPrefix implements UnitPrefix { + DECA(10), HECTO(100), KILO(1e3), MEGA(1e6), GIGA(1e9), TERA(1e12), PETA(1e15), EXA(1e18), ZETTA(1e21), YOTTA( + 1e24), DECI(0.1), CENTI(0.01), MILLI( + 1e-3), MICRO(1e-6), NANO(1e-9), PICO(1e-12), FEMTO(1e-15), ATTO(1e-18), ZEPTO(1e-21), YOCTO(1e-24); + + private final double value; + + /** + * Creates the {@code SIPrefix}. + * + * @param value + * value of prefix + * @since 2019-01-14 + */ + private SIPrefix(final double value) { + this.value = value; + } + + /** + * @return value + * @since 2019-01-14 + */ + @Override + public final double getValue() { + return this.value; + } +} diff --git a/src/unitConverter/unit/UnitPrefix.java b/src/unitConverter/unit/UnitPrefix.java new file mode 100755 index 0000000..0dbdc00 --- /dev/null +++ b/src/unitConverter/unit/UnitPrefix.java @@ -0,0 +1,31 @@ +/** + * Copyright (C) 2018 Adrien Hopkins + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package unitConverter.unit; + +/** + * A prefix that can be attached onto the front of any unit, which multiplies it by a certain value + * + * @author Adrien Hopkins + * @since 2019-01-14 + */ +public interface UnitPrefix { + /** + * @return value of this prefix + * @since 2019-01-14 + */ + double getValue(); +} diff --git a/src/unitConverter/unit/UnitSystem.java b/src/unitConverter/unit/UnitSystem.java index 0a50062..d641832 100755 --- a/src/unitConverter/unit/UnitSystem.java +++ b/src/unitConverter/unit/UnitSystem.java @@ -16,6 +16,8 @@ */ package unitConverter.unit; +import java.util.Objects; + import unitConverter.dimension.UnitDimension; /** @@ -31,9 +33,14 @@ public interface UnitSystem { * @param dimension * dimension used by base unit * @return base unit + * @throws NullPointerException + * if dimension is null * @since 2019-01-25 */ - BaseUnit getBaseUnit(UnitDimension dimension); + default BaseUnit getBaseUnit(final UnitDimension dimension) { + Objects.requireNonNull(dimension, "dimension must not be null."); + return new BaseUnit(dimension, this); + } /** * @return name of system -- cgit v1.2.3 From 8ff06e8e5661645c00656c40d15c8d13db665b57 Mon Sep 17 00:00:00 2001 From: Adrien Hopkins Date: Fri, 25 Jan 2019 19:09:47 -0500 Subject: Added code from the previous implementation of the Unit Converter It includes: - a units database to store units - unit prefix classes - a unit converter GUI that accepts some unit math NOTE: A lot of this code will be edited in the near future. --- src/unitConverter/UnitsDatabase.java | 573 ++++++++++++++++++++ src/unitConverter/UnitsFileTest.java | 31 ++ src/unitConverter/converter/package-info.java | 23 - .../converterGUI/DelegateListModel.java | 228 ++++++++ .../converterGUI/FilterComparator.java | 72 +++ src/unitConverter/converterGUI/GridBagBuilder.java | 432 +++++++++++++++ .../converterGUI/UnitConverterGUI.java | 586 +++++++++++++++++++++ src/unitConverter/converterGUI/package-info.java | 23 + src/unitConverter/unit/AbstractUnit.java | 50 +- src/unitConverter/unit/BaseUnit.java | 10 +- src/unitConverter/unit/DefaultUnitPrefix.java | 64 +++ src/unitConverter/unit/NonlinearUnits.java | 56 ++ src/unitConverter/unit/SIPrefix.java | 14 +- src/unitConverter/unit/Unit.java | 2 +- src/unitConverter/unit/UnitPrefix.java | 4 +- unitsfile.txt | 234 ++++++++ 16 files changed, 2367 insertions(+), 35 deletions(-) create mode 100755 src/unitConverter/UnitsDatabase.java create mode 100755 src/unitConverter/UnitsFileTest.java delete mode 100644 src/unitConverter/converter/package-info.java create mode 100755 src/unitConverter/converterGUI/DelegateListModel.java create mode 100755 src/unitConverter/converterGUI/FilterComparator.java create mode 100755 src/unitConverter/converterGUI/GridBagBuilder.java create mode 100755 src/unitConverter/converterGUI/UnitConverterGUI.java create mode 100644 src/unitConverter/converterGUI/package-info.java create mode 100755 src/unitConverter/unit/DefaultUnitPrefix.java create mode 100755 src/unitConverter/unit/NonlinearUnits.java create mode 100755 unitsfile.txt diff --git a/src/unitConverter/UnitsDatabase.java b/src/unitConverter/UnitsDatabase.java new file mode 100755 index 0000000..d479917 --- /dev/null +++ b/src/unitConverter/UnitsDatabase.java @@ -0,0 +1,573 @@ +/** + * Copyright (C) 2018 Adrien Hopkins + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package unitConverter; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileReader; +import java.io.IOException; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Set; + +import unitConverter.dimension.UnitDimension; +import unitConverter.unit.AbstractUnit; +import unitConverter.unit.BaseUnit; +import unitConverter.unit.DefaultUnitPrefix; +import unitConverter.unit.LinearUnit; +import unitConverter.unit.SI; +import unitConverter.unit.Unit; +import unitConverter.unit.UnitPrefix; + +/** + * A database of units. + * + * @author Adrien Hopkins + * @since 2019-01-07 + */ +public final class UnitsDatabase { + /** + * The units in this system. + * + * @since 2019-01-07 + */ + private final Map units; + + /** + * The unit prefixes in this system. + * + * @since 2019-01-14 + */ + private final Map prefixes; + + /** + * Creates the {@code UnitsDatabase}. + * + * @since 2019-01-10 + */ + public UnitsDatabase() { + this.units = new HashMap<>(); + this.prefixes = new HashMap<>(); + } + + /** + * Adds all units from a file, using data from the database to parse them. + *

+ * Each line in the file should consist of a name and an expression (parsed by getUnitFromExpression) separated by + * any number of tab characters. + *

+ *

+ * Allowed exceptions: + *

    + *
  • Any line that begins with the '#' character is considered a comment and ignored.
  • + *
  • Blank lines are also ignored
  • + *
  • If an expression consists of a single exclamation point, instead of parsing it, this method will search the + * database for an existing unit. If no unit is found, an IllegalArgumentException is thrown. This is used to define + * initial units and ensure that the database contains them.
  • + *
+ * + * @param file + * file to read + * @throws IllegalArgumentException + * if the file cannot be parsed, found or read + * @throws NullPointerException + * if file is null + * @since 2019-01-13 + */ + public void addAllFromFile(final File file) { + Objects.requireNonNull(file, "file must not be null."); + try (FileReader fileReader = new FileReader(file); BufferedReader reader = new BufferedReader(fileReader)) { + // while the reader has lines to read, read a line, then parse it, then add it + long lineCounter = 0; + while (reader.ready()) { + final String line = reader.readLine(); + lineCounter++; + + // ignore lines that start with a # sign - they're comments + if (line.startsWith("#") || line.isEmpty()) { + continue; + } + + // divide line into name and expression + final String[] parts = line.split("\t"); + if (parts.length < 2) + throw new IllegalArgumentException(String.format( + "Lines must consist of a unit name and its definition, separated by tab(s) (line %d).", + lineCounter)); + final String name = parts[0]; + final String expression = parts[parts.length - 1]; + + if (name.endsWith(" ")) { + System.err.printf("Warning - line %d's unit name ends in a space", lineCounter); + } + + // if expression is "!", search for an existing unit + // if no unit found, throw an error + if (expression.equals("!")) { + if (!this.containsUnitName(name)) + throw new IllegalArgumentException( + String.format("! used but no unit found (line %d).", lineCounter)); + } else { + if (name.endsWith("-")) { + final UnitPrefix prefix; + try { + prefix = this.getPrefixFromExpression(expression, name.substring(0, name.length() - 1)); + } catch (final IllegalArgumentException e) { + System.err.printf("Parsing error on line %d:%n", lineCounter); + throw e; + } + this.addPrefix(name.substring(0, name.length() - 1), prefix); + } else { + // it's a unit, get the unit + final Unit unit; + try { + unit = this.getUnitFromExpression(expression); + } catch (final IllegalArgumentException e) { + System.err.printf("Parsing error on line %d:%n", lineCounter); + throw e; + } + AbstractUnit.incrementUnitCounter(); + if (unit instanceof BaseUnit) { + AbstractUnit.incrementBaseUnitCounter(); + } + this.addUnit(name, unit); + } + } + } + } catch (final FileNotFoundException e) { + throw new IllegalArgumentException("Could not find file " + file, e); + } catch (final IOException e) { + throw new IllegalArgumentException("Could not read file " + file, e); + } + } + + /** + * Adds a unit prefix to the database using a custom name + * + * @param name + * prefix's name + * @param prefix + * prefix to add + * @throws NullPointerException + * if name or unit is null + * @since 2019-01-14 + */ + public void addPrefix(final String name, final UnitPrefix prefix) { + this.prefixes.put(Objects.requireNonNull(name, "name must not be null."), + Objects.requireNonNull(prefix, "prefix must not be null.")); + } + + /** + * Adds a unit to the database using a custom name + * + * @param name + * unit's name + * @param unit + * unit to add + * @throws NullPointerException + * if unit is null + * @since 2019-01-10 + */ + public void addUnit(final String name, final Unit unit) { + this.units.put(name, Objects.requireNonNull(unit, "unit must not be null.")); + } + + /** + * Tests if the database has a unit with this name, ignoring prefixes + * + * @param name + * name to test + * @return if database contains name + * @since 2019-01-13 + */ + public boolean containsPrefixlessUnitName(final String name) { + return this.units.containsKey(name); + } + + /** + * Tests if the database has a unit prefix with this name + * + * @param name + * name to test + * @return if database contains name + * @since 2019-01-13 + */ + public boolean containsPrefixName(final String name) { + return this.prefixes.containsKey(name); + } + + /** + * Tests if the database has a unit with this name, taking prefixes into consideration + * + * @param name + * name to test + * @return if database contains name + * @since 2019-01-13 + */ + public boolean containsUnitName(final String name) { + // check for prefixes + for (final String prefixName : this.prefixNameSet()) { + if (name.startsWith(prefixName)) + if (this.containsUnitName(name.substring(prefixName.length()))) + return true; + } + return this.units.containsKey(name); + } + + /** + * Gets a unit prefix from the database from its name + * + * @param name + * prefix's name + * @return prefix + * @since 2019-01-10 + */ + public UnitPrefix getPrefix(final String name) { + return this.prefixes.get(name); + } + + /** + * Gets a unit prefix from a prefix expression and a name + *

+ * Currently, prefix expressions are much simpler than unit expressions: They are either a number or the name of + * another prefix + *

+ * + * @param expression + * expression to input + * @param name + * name of prefix if a new prefix is created + * @return prefix + * @throws IllegalArgumentException + * if expression cannot be parsed + * @throws NullPointerException + * if any argument is null + * @since 2019-01-14 + */ + public UnitPrefix getPrefixFromExpression(final String expression, final String name) { + Objects.requireNonNull(expression, "expression must not be null."); + Objects.requireNonNull(name, "name must not be null."); + + try { + return new DefaultUnitPrefix(Double.parseDouble(expression)); + } catch (final NumberFormatException e) { + if (expression.contains("^")) { + final String[] baseAndExponent = expression.split("\\^"); + + final double base; + try { + base = Double.parseDouble(baseAndExponent[0]); + } catch (final NumberFormatException e2) { + throw new IllegalArgumentException("Base of exponientation must be a number."); + } + + final int exponent; + try { + exponent = Integer.parseInt(baseAndExponent[baseAndExponent.length - 1]); + } catch (final NumberFormatException e2) { + throw new IllegalArgumentException("Exponent must be an integer."); + } + + return new DefaultUnitPrefix(Math.pow(base, exponent)); + } else { + if (!this.containsPrefixName(expression)) + throw new IllegalArgumentException("Unrecognized prefix name \"" + expression + "\"."); + return this.getPrefix(expression); + } + } + } + + /** + * Gets a unit from the database from its name, ignoring prefixes. + * + * @param name + * unit's name + * @return unit + * @since 2019-01-10 + */ + public Unit getPrefixlessUnit(final String name) { + return this.units.get(name); + } + + /** + * Gets a unit from the database from its name, looking for prefixes. + * + * @param name + * unit's name + * @return unit + * @since 2019-01-10 + */ + public Unit getUnit(final String name) { + if (name.contains("^")) { + final String[] baseAndExponent = name.split("\\^"); + + LinearUnit base; + try { + base = SI.SI.getBaseUnit(UnitDimension.EMPTY).times(Double.parseDouble(baseAndExponent[0])); + } catch (final NumberFormatException e2) { + final Unit unit = this.getUnit(baseAndExponent[0]); + if (unit instanceof LinearUnit) { + base = (LinearUnit) unit; + } else if (unit instanceof BaseUnit) { + base = ((BaseUnit) unit).asLinearUnit(); + } else + throw new IllegalArgumentException("Base of exponientation must be a linear or base unit."); + } + + final int exponent; + try { + exponent = Integer.parseInt(baseAndExponent[baseAndExponent.length - 1]); + } catch (final NumberFormatException e2) { + throw new IllegalArgumentException("Exponent must be an integer."); + } + + final LinearUnit exponentiated = base.toExponent(exponent); + if (exponentiated.getConversionFactor() == 1) + return exponentiated.getSystem().getBaseUnit(exponentiated.getDimension()); + else + return exponentiated; + } else { + for (final String prefixName : this.prefixNameSet()) { + // check for a prefix + if (name.startsWith(prefixName)) { + // prefix found! Make sure what comes after it is actually a unit! + final String prefixless = name.substring(prefixName.length()); + if (this.containsUnitName(prefixless)) { + // yep, it's a proper prefix! Get the unit! + final Unit unit = this.getUnit(prefixless); + final UnitPrefix prefix = this.getPrefix(prefixName); + + // Prefixes only work with linear and base units, so make sure it's one of those + if (unit instanceof LinearUnit) { + final LinearUnit linearUnit = (LinearUnit) unit; + return linearUnit.times(prefix.getMultiplier()); + } else if (unit instanceof BaseUnit) { + final BaseUnit baseUnit = (BaseUnit) unit; + return baseUnit.times(prefix.getMultiplier()); + } + } + } + } + return this.units.get(name); + } + } + + /** + * Uses the database's unit data to parse an expression into a unit + *

+ * The expression is a series of any of the following: + *

    + *
  • The name of a unit, which multiplies or divides the result based on preceding operators
  • + *
  • The operators '*' and '/', which multiply and divide (note that just putting two units or values next to each + * other is equivalent to multiplication)
  • + *
  • A number which is multiplied or divided
  • + *
+ * This method only works with linear units. + *

+ * If the expression contains just the name of a unit, returns that unit without changing name or symbol. This + * exists for the creation of aliases. + *

+ * + * @param line + * line to parse + * @throws IllegalArgumentException + * if the expression cannot be parsed + * @throws NullPointerException + * if any argument is null + * @since 2019-01-07 + */ + public Unit getUnitFromExpression(final String expression) { + Objects.requireNonNull(expression, "expression must not be null."); + + // parse the expression + // start with an "empty" unit then apply operations on it + LinearUnit unit = SI.SI.getBaseUnit(UnitDimension.EMPTY).asLinearUnit(); + boolean dividing = false; + + // if I'm just creating an alias, just create one instead of going through the parsing process + if (!expression.contains(" ") && !expression.contains("*") && !expression.contains("/") + && !expression.contains("(") && !expression.contains(")") && !expression.contains("^")) { + try { + return SI.SI.getBaseUnit(UnitDimension.EMPTY).times(Double.parseDouble(expression)); + } catch (final NumberFormatException e) { + if (!this.containsUnitName(expression)) + throw new IllegalArgumentException("Unrecognized unit name \"" + expression + "\"."); + return this.getUnit(expression); + } + } + + // \\* means "asterisk", * is reserved + for (final String part : expression.replaceAll("\\*", " \\* ").replaceAll("/", " / ").replaceAll(" \\^", "\\^") + .replaceAll("\\^ ", "\\^").split(" ")) { + if ("".equals(part)) { + continue; + } + // "unit1 unit2" is the same as "unit1 * unit2", so multiplication signs do nothing + if ("*".equals(part)) { + continue; + } + // When I reach a division sign, don't parse a unit, instead tell myself I'm going to divide the next + // thing + if ("/".equals(part) || "per".equals(part)) { + dividing = true; + continue; + } + + try { + final double partAsNumber = Double.parseDouble(part); // if this works, it's a number + // this code should not throw any exceptions, so I'm going to throw AssertionErrors if it does + try { + if (dividing) { + unit = unit.dividedBy(partAsNumber); + dividing = false; + } else { + unit = unit.times(partAsNumber); + } + } catch (final Exception e) { + throw new AssertionError(e); + } + } catch (final NumberFormatException e) { + // it's a unit, try that + + if (part.contains("(") && part.endsWith(")")) { + // the unitsfile is looking for a nonlinear unit + final String[] unitAndValue = part.split("\\("); + + // this will work because I've checked that it contains a ( + final String unitName = unitAndValue[0]; + final String valueStringWithBracket = unitAndValue[unitAndValue.length - 1]; + final String valueString = valueStringWithBracket.substring(0, valueStringWithBracket.length() - 1); + final double value; + + // try to get the value - else throw an error + try { + value = Double.parseDouble(valueString); + } catch (final NumberFormatException e2) { + throw new IllegalArgumentException("Unparseable value " + valueString); + } + + // get this unit in a linear form + if (!this.containsPrefixlessUnitName(unitName)) + throw new IllegalArgumentException("Unrecognized unit name \"" + part + "\"."); + final Unit partUnit = this.getPrefixlessUnit(unitName); + final LinearUnit multiplier = partUnit.getBase().times(partUnit.convertToBase(value)); + + // finally, add it to the expression + if (dividing) { + unit = unit.dividedBy(multiplier); + dividing = false; + } else { + unit = unit.times(multiplier); + } + } else { + // check for exponientation + if (part.contains("^")) { + final String[] valueAndExponent = part.split("\\^"); + // this will always work because of the contains check + final String valueString = valueAndExponent[0]; + final String exponentString = valueAndExponent[valueAndExponent.length - 1]; + + LinearUnit value; + + // first, try to get the value + try { + final double valueAsNumber = Double.parseDouble(valueString); + + value = SI.SI.getBaseUnit(UnitDimension.EMPTY).times(valueAsNumber); + } catch (final NumberFormatException e2) { + + // look for a unit + if (!this.containsUnitName(valueString)) + throw new IllegalArgumentException("Unrecognized unit name \"" + valueString + "\"."); + final Unit valueUnit = this.getUnit(valueString); + + // try to turn the value into a linear unit + if (valueUnit instanceof LinearUnit) { + value = (LinearUnit) valueUnit; + } else if (valueUnit instanceof BaseUnit) { + value = ((BaseUnit) valueUnit).asLinearUnit(); + } else + throw new IllegalArgumentException("Only linear and base units can be exponientated."); + } + + // now, try to get the exponent + final int exponent; + try { + exponent = Integer.parseInt(exponentString); + } catch (final NumberFormatException e2) { + throw new IllegalArgumentException("Exponents must be integers."); + } + + final LinearUnit exponientated = value.toExponent(exponent); + + if (dividing) { + unit = unit.dividedBy(exponientated); + dividing = false; + } else { + unit = unit.times(exponientated); + } + } else { + // no exponent - look for a unit + // the unitsfile is looking for a linear unit + if (!this.containsUnitName(part)) + throw new IllegalArgumentException("Unrecognized unit name \"" + part + "\"."); + Unit other = this.getUnit(part); + if (other instanceof BaseUnit) { + other = ((BaseUnit) other).asLinearUnit(); + } + if (other instanceof LinearUnit) { + if (dividing) { + unit = unit.dividedBy((LinearUnit) other); + dividing = false; + } else { + unit = unit.times((LinearUnit) other); + } + } else + throw new IllegalArgumentException( + "Only linear or base units can be multiplied and divided. If you want to use a non-linear unit, try the format UNITNAME(VALUE)."); + } + } + } + } + + // replace conversion-factor-1 linear units with base units + // this improves the autogenerated names, allowing them to use derived SI names + if (unit != null && unit.getConversionFactor() == 1) + return unit.getSystem().getBaseUnit(unit.getDimension()); + else + return unit; + } + + /** + * @return an immutable set of all of the unit names in this database, ignoring prefixes + * @since 2019-01-14 + */ + public Set prefixlessUnitNameSet() { + return Collections.unmodifiableSet(this.units.keySet()); + } + + /** + * @return an immutable set of all of the prefix names in this database + * @since 2019-01-14 + */ + public Set prefixNameSet() { + return Collections.unmodifiableSet(this.prefixes.keySet()); + } +} diff --git a/src/unitConverter/UnitsFileTest.java b/src/unitConverter/UnitsFileTest.java new file mode 100755 index 0000000..8edeab0 --- /dev/null +++ b/src/unitConverter/UnitsFileTest.java @@ -0,0 +1,31 @@ +/** + * Copyright (C) 2018 Adrien Hopkins + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package unitConverter; + +import static org.junit.jupiter.api.Assertions.fail; + +/** + * @author Adrien Hopkins + * @since 2019-01-02 + */ +class UnitsFileTest { + + // @Test + void testReading() { + fail("Not yet implemented."); + } +} diff --git a/src/unitConverter/converter/package-info.java b/src/unitConverter/converter/package-info.java deleted file mode 100644 index 6148784..0000000 --- a/src/unitConverter/converter/package-info.java +++ /dev/null @@ -1,23 +0,0 @@ -/** - * Copyright (C) 2019 Adrien Hopkins - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ -/** - * All classes that work to convert units. - * - * @author Adrien Hopkins - * @since 2019-01-25 - */ -package unitConverter.converter; \ No newline at end of file diff --git a/src/unitConverter/converterGUI/DelegateListModel.java b/src/unitConverter/converterGUI/DelegateListModel.java new file mode 100755 index 0000000..42bc0dc --- /dev/null +++ b/src/unitConverter/converterGUI/DelegateListModel.java @@ -0,0 +1,228 @@ +/** + * Copyright (C) 2018 Adrien Hopkins + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package unitConverter.converterGUI; + +import java.util.Collection; +import java.util.Iterator; +import java.util.List; +import java.util.ListIterator; + +import javax.swing.AbstractListModel; + +/** + * A list model that delegates to a list. + *

+ * It is recommended to use the delegate methods in DelegateListModel instead of the delegated list's methods because + * the delegate methods handle updating the list. + *

+ * + * @author Adrien Hopkins + * @since 2019-01-14 + */ +final class DelegateListModel extends AbstractListModel implements List { + /** + * @since 2019-01-14 + */ + private static final long serialVersionUID = 8985494428224810045L; + + /** + * The list that this model is a delegate to. + * + * @since 2019-01-14 + */ + private final List delegate; + + /** + * Creates the {@code DelegateListModel}. + * + * @param delegate + * list to delegate + * @since 2019-01-14 + */ + public DelegateListModel(final List delegate) { + this.delegate = delegate; + } + + @Override + public boolean add(final E element) { + final int index = this.delegate.size(); + final boolean success = this.delegate.add(element); + this.fireIntervalAdded(this, index, index); + return success; + } + + @Override + public void add(final int index, final E element) { + this.delegate.add(index, element); + this.fireIntervalAdded(this, index, index); + } + + @Override + public boolean addAll(final Collection c) { + boolean changed = false; + for (final E e : c) { + if (this.add(e)) { + changed = true; + } + } + return changed; + } + + @Override + public boolean addAll(final int index, final Collection c) { + for (final E e : c) { + this.add(index, e); + } + return !c.isEmpty(); // Since this is a list, it will always change if c has elements. + } + + @Override + public void clear() { + final int oldSize = this.delegate.size(); + this.delegate.clear(); + if (oldSize >= 1) { + this.fireIntervalRemoved(this, 0, oldSize - 1); + } + } + + @Override + public boolean contains(final Object elem) { + return this.delegate.contains(elem); + } + + @Override + public boolean containsAll(final Collection c) { + for (final Object e : c) { + if (!c.contains(e)) + return false; + } + return true; + } + + @Override + public E get(final int index) { + return this.delegate.get(index); + } + + @Override + public E getElementAt(final int index) { + return this.delegate.get(index); + } + + @Override + public int getSize() { + return this.delegate.size(); + } + + @Override + public int indexOf(final Object elem) { + return this.delegate.indexOf(elem); + } + + @Override + public boolean isEmpty() { + return this.delegate.isEmpty(); + } + + @Override + public Iterator iterator() { + return this.delegate.iterator(); + } + + @Override + public int lastIndexOf(final Object elem) { + return this.delegate.lastIndexOf(elem); + } + + @Override + public ListIterator listIterator() { + return this.delegate.listIterator(); + } + + @Override + public ListIterator listIterator(final int index) { + return this.delegate.listIterator(index); + } + + @Override + public E remove(final int index) { + final E returnValue = this.delegate.get(index); + this.delegate.remove(index); + this.fireIntervalRemoved(this, index, index); + return returnValue; + } + + @Override + public boolean remove(final Object o) { + final int index = this.delegate.indexOf(o); + final boolean returnValue = this.delegate.remove(o); + this.fireIntervalRemoved(this, index, index); + return returnValue; + } + + @Override + public boolean removeAll(final Collection c) { + boolean changed = false; + for (final Object e : c) { + if (this.remove(e)) { + changed = true; + } + } + return changed; + } + + @Override + public boolean retainAll(final Collection c) { + final int oldSize = this.size(); + final boolean returnValue = this.delegate.retainAll(c); + this.fireIntervalRemoved(this, this.size(), oldSize - 1); + return returnValue; + } + + @Override + public E set(final int index, final E element) { + final E returnValue = this.delegate.get(index); + this.delegate.set(index, element); + this.fireContentsChanged(this, index, index); + return returnValue; + } + + @Override + public int size() { + return this.delegate.size(); + } + + @Override + public List subList(final int fromIndex, final int toIndex) { + return this.delegate.subList(fromIndex, toIndex); + } + + @Override + public Object[] toArray() { + return this.delegate.toArray(); + } + + @Override + public T[] toArray(final T[] a) { + return this.delegate.toArray(a); + } + + @Override + public String toString() { + return this.delegate.toString(); + } +} diff --git a/src/unitConverter/converterGUI/FilterComparator.java b/src/unitConverter/converterGUI/FilterComparator.java new file mode 100755 index 0000000..ab25f2f --- /dev/null +++ b/src/unitConverter/converterGUI/FilterComparator.java @@ -0,0 +1,72 @@ +/** + * Copyright (C) 2018 Adrien Hopkins + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package unitConverter.converterGUI; + +import java.util.Comparator; + +/** + * @author Adrien Hopkins + * @since 2019-01-15 + */ +public final class FilterComparator implements Comparator { + + private final String filter; + private final Comparator comparator; + + /** + * Creates the {@code FilterComparator}. + * + * @param filter + * @since 2019-01-15 + */ + public FilterComparator(final String filter) { + this(filter, null); + } + + /** + * Creates the {@code FilterComparator}. + * + * @param filter + * @param comparator + * @since 2019-01-15 + */ + public FilterComparator(final String filter, final Comparator comparator) { + this.filter = filter; + this.comparator = comparator; + } + + @Override + public int compare(final String arg0, final String arg1) { + // elements that start with the filter always go first + if (arg0.startsWith(this.filter) && !arg1.startsWith(this.filter)) + return -1; + else if (!arg0.startsWith(this.filter) && arg1.startsWith(this.filter)) + return 1; + + // elements that contain the filter but don't start with them go next + if (arg0.contains(this.filter) && !arg1.contains(this.filter)) + return -1; + else if (!arg0.contains(this.filter) && !arg1.contains(this.filter)) + return 1; + + // other elements go last + if (this.comparator == null) + return arg0.compareTo(arg1); + else + return this.comparator.compare(arg0, arg1); + } +} diff --git a/src/unitConverter/converterGUI/GridBagBuilder.java b/src/unitConverter/converterGUI/GridBagBuilder.java new file mode 100755 index 0000000..7a0615a --- /dev/null +++ b/src/unitConverter/converterGUI/GridBagBuilder.java @@ -0,0 +1,432 @@ +/** + * Copyright (C) 2018 Adrien Hopkins + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package unitConverter.converterGUI; + +import java.awt.GridBagConstraints; +import java.awt.Insets; + +/** + * @author Adrien Hopkins + * @since 2018-11-30 + */ +final class GridBagBuilder { + /** + * Specifies the cell containing the leading edge of the component's display area, where the first cell in a row has + * gridx=0. The leading edge of a component's display area is its left edge for a horizontal, + * left-to-right container and its right edge for a horizontal, right-to-left container. The value + * RELATIVE specifies that the component be placed immediately following the component that was added + * to the container just before this component was added. + *

+ * The default value is RELATIVE. gridx should be a non-negative value. + * + * @serial + * @see #clone() + * @see java.awt.GridBagConstraints#gridy + * @see java.awt.ComponentOrientation + */ + private final int gridx; + + /** + * Specifies the cell at the top of the component's display area, where the topmost cell has gridy=0. + * The value RELATIVE specifies that the component be placed just below the component that was added to + * the container just before this component was added. + *

+ * The default value is RELATIVE. gridy should be a non-negative value. + * + * @serial + * @see #clone() + * @see java.awt.GridBagConstraints#gridx + */ + private final int gridy; + + /** + * Specifies the number of cells in a row for the component's display area. + *

+ * Use REMAINDER to specify that the component's display area will be from gridx to the + * last cell in the row. Use RELATIVE to specify that the component's display area will be from + * gridx to the next to the last one in its row. + *

+ * gridwidth should be non-negative and the default value is 1. + * + * @serial + * @see #clone() + * @see java.awt.GridBagConstraints#gridheight + */ + private final int gridwidth; + + /** + * Specifies the number of cells in a column for the component's display area. + *

+ * Use REMAINDER to specify that the component's display area will be from gridy to the + * last cell in the column. Use RELATIVE to specify that the component's display area will be from + * gridy to the next to the last one in its column. + *

+ * gridheight should be a non-negative value and the default value is 1. + * + * @serial + * @see #clone() + * @see java.awt.GridBagConstraints#gridwidth + */ + private final int gridheight; + + /** + * Specifies how to distribute extra horizontal space. + *

+ * The grid bag layout manager calculates the weight of a column to be the maximum weightx of all the + * components in a column. If the resulting layout is smaller horizontally than the area it needs to fill, the extra + * space is distributed to each column in proportion to its weight. A column that has a weight of zero receives no + * extra space. + *

+ * If all the weights are zero, all the extra space appears between the grids of the cell and the left and right + * edges. + *

+ * The default value of this field is 0. weightx should be a non-negative value. + * + * @serial + * @see #clone() + * @see java.awt.GridBagConstraints#weighty + */ + private double weightx; + + /** + * Specifies how to distribute extra vertical space. + *

+ * The grid bag layout manager calculates the weight of a row to be the maximum weighty of all the + * components in a row. If the resulting layout is smaller vertically than the area it needs to fill, the extra + * space is distributed to each row in proportion to its weight. A row that has a weight of zero receives no extra + * space. + *

+ * If all the weights are zero, all the extra space appears between the grids of the cell and the top and bottom + * edges. + *

+ * The default value of this field is 0. weighty should be a non-negative value. + * + * @serial + * @see #clone() + * @see java.awt.GridBagConstraints#weightx + */ + private double weighty; + + /** + * This field is used when the component is smaller than its display area. It determines where, within the display + * area, to place the component. + *

+ * There are three kinds of possible values: orientation relative, baseline relative and absolute. Orientation + * relative values are interpreted relative to the container's component orientation property, baseline relative + * values are interpreted relative to the baseline and absolute values are not. The absolute values are: + * CENTER, NORTH, NORTHEAST, EAST, SOUTHEAST, + * SOUTH, SOUTHWEST, WEST, and NORTHWEST. The orientation + * relative values are: PAGE_START, PAGE_END, LINE_START, + * LINE_END, FIRST_LINE_START, FIRST_LINE_END, LAST_LINE_START + * and LAST_LINE_END. The baseline relative values are: BASELINE, + * BASELINE_LEADING, BASELINE_TRAILING, ABOVE_BASELINE, + * ABOVE_BASELINE_LEADING, ABOVE_BASELINE_TRAILING, BELOW_BASELINE, + * BELOW_BASELINE_LEADING, and BELOW_BASELINE_TRAILING. The default value is + * CENTER. + * + * @serial + * @see #clone() + * @see java.awt.ComponentOrientation + */ + private int anchor; + + /** + * This field is used when the component's display area is larger than the component's requested size. It determines + * whether to resize the component, and if so, how. + *

+ * The following values are valid for fill: + * + *

    + *
  • NONE: Do not resize the component. + *
  • HORIZONTAL: Make the component wide enough to fill its display area horizontally, but do not + * change its height. + *
  • VERTICAL: Make the component tall enough to fill its display area vertically, but do not change + * its width. + *
  • BOTH: Make the component fill its display area entirely. + *
+ *

+ * The default value is NONE. + * + * @serial + * @see #clone() + */ + private int fill; + + /** + * This field specifies the external padding of the component, the minimum amount of space between the component and + * the edges of its display area. + *

+ * The default value is new Insets(0, 0, 0, 0). + * + * @serial + * @see #clone() + */ + private Insets insets; + + /** + * This field specifies the internal padding of the component, how much space to add to the minimum width of the + * component. The width of the component is at least its minimum width plus ipadx pixels. + *

+ * The default value is 0. + * + * @serial + * @see #clone() + * @see java.awt.GridBagConstraints#ipady + */ + private int ipadx; + + /** + * This field specifies the internal padding, that is, how much space to add to the minimum height of the component. + * The height of the component is at least its minimum height plus ipady pixels. + *

+ * The default value is 0. + * + * @serial + * @see #clone() + * @see java.awt.GridBagConstraints#ipadx + */ + private int ipady; + + /** + * @param gridx + * x position + * @param gridy + * y position + * @since 2018-11-30 + */ + public GridBagBuilder(final int gridx, final int gridy) { + this(gridx, gridy, 1, 1); + } + + /** + * @param gridx + * x position + * @param gridy + * y position + * @param gridwidth + * number of cells occupied horizontally + * @param gridheight + * number of cells occupied vertically + * @since 2018-11-30 + */ + public GridBagBuilder(final int gridx, final int gridy, final int gridwidth, final int gridheight) { + this(gridx, gridy, gridwidth, gridheight, 0.0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.NONE, + new Insets(0, 0, 0, 0), 0, 0); + } + + /** + * @param gridx + * x position + * @param gridy + * y position + * @param gridwidth + * number of cells occupied horizontally + * @param gridheight + * number of cells occupied vertically + * @param weightx + * @param weighty + * @param anchor + * @param fill + * @param insets + * @param ipadx + * @param ipady + * @since 2018-11-30 + */ + private GridBagBuilder(final int gridx, final int gridy, final int gridwidth, final int gridheight, + final double weightx, final double weighty, final int anchor, final int fill, final Insets insets, + final int ipadx, final int ipady) { + super(); + this.gridx = gridx; + this.gridy = gridy; + this.gridwidth = gridwidth; + this.gridheight = gridheight; + this.weightx = weightx; + this.weighty = weighty; + this.anchor = anchor; + this.fill = fill; + this.insets = (Insets) insets.clone(); + this.ipadx = ipadx; + this.ipady = ipady; + } + + /** + * @return {@code GridBagConstraints} created by this builder + * @since 2018-11-30 + */ + public GridBagConstraints build() { + return new GridBagConstraints(this.gridx, this.gridy, this.gridwidth, this.gridheight, this.weightx, + this.weighty, this.anchor, this.fill, this.insets, this.ipadx, this.ipady); + } + + /** + * @return anchor + * @since 2018-11-30 + */ + public int getAnchor() { + return this.anchor; + } + + /** + * @return fill + * @since 2018-11-30 + */ + public int getFill() { + return this.fill; + } + + /** + * @return gridheight + * @since 2018-11-30 + */ + public int getGridheight() { + return this.gridheight; + } + + /** + * @return gridwidth + * @since 2018-11-30 + */ + public int getGridwidth() { + return this.gridwidth; + } + + /** + * @return gridx + * @since 2018-11-30 + */ + public int getGridx() { + return this.gridx; + } + + /** + * @return gridy + * @since 2018-11-30 + */ + public int getGridy() { + return this.gridy; + } + + /** + * @return insets + * @since 2018-11-30 + */ + public Insets getInsets() { + return this.insets; + } + + /** + * @return ipadx + * @since 2018-11-30 + */ + public int getIpadx() { + return this.ipadx; + } + + /** + * @return ipady + * @since 2018-11-30 + */ + public int getIpady() { + return this.ipady; + } + + /** + * @return weightx + * @since 2018-11-30 + */ + public double getWeightx() { + return this.weightx; + } + + /** + * @return weighty + * @since 2018-11-30 + */ + public double getWeighty() { + return this.weighty; + } + + /** + * @param anchor + * anchor to set + * @since 2018-11-30 + */ + public GridBagBuilder setAnchor(final int anchor) { + this.anchor = anchor; + return this; + } + + /** + * @param fill + * fill to set + * @since 2018-11-30 + */ + public GridBagBuilder setFill(final int fill) { + this.fill = fill; + return this; + } + + /** + * @param insets + * insets to set + * @since 2018-11-30 + */ + public GridBagBuilder setInsets(final Insets insets) { + this.insets = insets; + return this; + } + + /** + * @param ipadx + * ipadx to set + * @since 2018-11-30 + */ + public GridBagBuilder setIpadx(final int ipadx) { + this.ipadx = ipadx; + return this; + } + + /** + * @param ipady + * ipady to set + * @since 2018-11-30 + */ + public GridBagBuilder setIpady(final int ipady) { + this.ipady = ipady; + return this; + } + + /** + * @param weightx + * weightx to set + * @since 2018-11-30 + */ + public GridBagBuilder setWeightx(final double weightx) { + this.weightx = weightx; + return this; + } + + /** + * @param weighty + * weighty to set + * @since 2018-11-30 + */ + public GridBagBuilder setWeighty(final double weighty) { + this.weighty = weighty; + return this; + } +} diff --git a/src/unitConverter/converterGUI/UnitConverterGUI.java b/src/unitConverter/converterGUI/UnitConverterGUI.java new file mode 100755 index 0000000..0068312 --- /dev/null +++ b/src/unitConverter/converterGUI/UnitConverterGUI.java @@ -0,0 +1,586 @@ +/** + * Copyright (C) 2018 Adrien Hopkins + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package unitConverter.converterGUI; + +import java.awt.BorderLayout; +import java.awt.GridLayout; +import java.io.File; +import java.math.BigDecimal; +import java.math.MathContext; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; +import java.util.function.Predicate; + +import javax.swing.BorderFactory; +import javax.swing.JButton; +import javax.swing.JFrame; +import javax.swing.JList; +import javax.swing.JOptionPane; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.JSlider; +import javax.swing.JTabbedPane; +import javax.swing.JTextArea; +import javax.swing.JTextField; +import javax.swing.ListModel; +import javax.swing.ListSelectionModel; + +import unitConverter.UnitsDatabase; +import unitConverter.dimension.StandardDimensions; +import unitConverter.dimension.UnitDimension; +import unitConverter.unit.AbstractUnit; +import unitConverter.unit.NonlinearUnits; +import unitConverter.unit.SI; +import unitConverter.unit.Unit; +import unitConverter.unit.UnitPrefix; + +/** + * @author Adrien Hopkins + * @since 2018-12-27 + */ +final class UnitConverterGUI { + private static class Presenter { + /** The presenter's associated view. */ + private final View view; + + /** The units known by the program. */ + private final UnitsDatabase units; + + /** The names of all of the units */ + private final List unitNames; + + /** The names of all of the units, but filtered */ + private final DelegateListModel unitNamesFiltered; + + /** The names of all of the prefixes */ + private final List prefixNames; + + /** The names of all of the prefixes */ + private final DelegateListModel prefixNamesFiltered; + + private final Comparator prefixNameComparator; + + private int significantFigures = 6; + + /** + * Creates the presenter. + * + * @param view + * presenter's associated view + * @since 2018-12-27 + */ + Presenter(final View view) { + this.view = view; + + // load initial units + this.units = new UnitsDatabase(); + this.units.addUnit("metre", SI.METRE); + this.units.addUnit("kilogram", SI.KILOGRAM); + this.units.addUnit("gram", SI.KILOGRAM.dividedBy(1000)); + this.units.addUnit("second", SI.SECOND); + this.units.addUnit("ampere", SI.AMPERE); + this.units.addUnit("kelvin", SI.KELVIN); + this.units.addUnit("mole", SI.MOLE); + this.units.addUnit("candela", SI.CANDELA); + this.units.addUnit("bit", SI.SI.getBaseUnit(StandardDimensions.INFORMATION)); + this.units.addUnit("unit", SI.SI.getBaseUnit(UnitDimension.EMPTY)); + // nonlinear units - must be loaded manually + this.units.addUnit("tempCelsius", NonlinearUnits.CELSIUS); + this.units.addUnit("tempFahrenheit", NonlinearUnits.FAHRENHEIT); + + this.units.addAllFromFile(new File("unitsfile.txt")); + + // a comparator that can be used to compare prefix names + // any name that does not exist is less than a name that does. + // otherwise, they are compared by value + this.prefixNameComparator = (o1, o2) -> { + if (!Presenter.this.units.containsPrefixName(o1)) + return -1; + else if (!Presenter.this.units.containsPrefixName(o2)) + return 1; + + final UnitPrefix p1 = Presenter.this.units.getPrefix(o1); + final UnitPrefix p2 = Presenter.this.units.getPrefix(o2); + + if (p1.getMultiplier() < p2.getMultiplier()) + return -1; + else if (p1.getMultiplier() > p2.getMultiplier()) + return 1; + + return o1.compareTo(o2); + }; + + this.unitNames = new ArrayList<>(this.units.prefixlessUnitNameSet()); + this.unitNames.sort(null); // sorts it using Comparable + + this.unitNamesFiltered = new DelegateListModel<>(new ArrayList<>(this.units.prefixlessUnitNameSet())); + this.unitNamesFiltered.sort(null); // sorts it using Comparable + + this.prefixNames = new ArrayList<>(this.units.prefixNameSet()); + this.prefixNames.sort(this.prefixNameComparator); // sorts it using my comparator + + this.prefixNamesFiltered = new DelegateListModel<>(new ArrayList<>(this.units.prefixNameSet())); + this.prefixNamesFiltered.sort(this.prefixNameComparator); // sorts it using my comparator + + System.out.printf("Successfully loaded %d units (%d base units)", AbstractUnit.getUnitCount(), + AbstractUnit.getBaseUnitCount()); + } + + public final void convert() { + final String fromUnitString = this.view.getFromText(); + final String toUnitString = this.view.getToText(); + + // try to parse from + final Unit from; + try { + from = this.units.getUnitFromExpression(fromUnitString); + } catch (final IllegalArgumentException e) { + this.view.showErrorDialog("Parse Error", "Could not recognize text in From entry: " + e.getMessage()); + return; + } + + final double value; + // try to parse to + final Unit to; + try { + to = this.units.getUnitFromExpression(toUnitString); + } catch (final IllegalArgumentException e) { + this.view.showErrorDialog("Parse Error", "Could not recognize text in To entry: " + e.getMessage()); + return; + } + + // if I can't convert, leave + if (!from.canConvertTo(to)) { + this.view.showErrorDialog("Conversion Error", + String.format("Cannot convert between %s and %s", fromUnitString, toUnitString)); + return; + } + + value = to.convertFromBase(from.convertToBase(1)); + + // round value + final BigDecimal bigValue = new BigDecimal(value).round(new MathContext(this.significantFigures)); + String output = bigValue.toString(); + + // remove trailing zeroes + if (output.contains(".")) { + while (output.endsWith("0")) { + output = output.substring(0, output.length() - 1); + } + if (output.endsWith(".")) { + output = output.substring(0, output.length() - 1); + } + } + + this.view.setOutputText(String.format("%s = %s %s", fromUnitString, output, toUnitString)); + } + + /** + * Filters the filtered model for units + * + * @param filter + * filter to use + * @since 2019-01-15 + */ + private final void filterFilteredPrefixModel(final Predicate filter) { + this.prefixNamesFiltered.clear(); + for (final String prefixName : this.prefixNames) { + if (filter.test(prefixName)) { + this.prefixNamesFiltered.add(prefixName); + } + } + } + + /** + * Filters the filtered model for units + * + * @param filter + * filter to use + * @since 2019-01-15 + */ + private final void filterFilteredUnitModel(final Predicate filter) { + this.unitNamesFiltered.clear(); + for (final String unitName : this.unitNames) { + if (filter.test(unitName)) { + this.unitNamesFiltered.add(unitName); + } + } + } + + /** + * @return a list model of all of the unit keys + * @since 2019-01-14 + */ + public final ListModel keyListModel() { + return this.unitNamesFiltered; + } + + public final void prefixFilterUpdated() { + final String filter = this.view.getPrefixFilterText(); + if (filter.equals("")) { + this.filterFilteredPrefixModel(t -> true); + } else { + this.filterFilteredPrefixModel(t -> t.contains(filter)); + } + this.prefixNamesFiltered.sort(new FilterComparator(filter)); + } + + /** + * @return a list model of all fo the prefix names + * @since 2019-01-15 + */ + public final ListModel prefixNameListModel() { + return this.prefixNamesFiltered; + } + + public final void prefixSelected() { + final int index = this.view.getPrefixListSelection(); + if (index == -1) + return; + else { + final String prefixName = this.prefixNamesFiltered.get(index); + final UnitPrefix prefix = this.units.getPrefix(prefixName); + + this.view.setPrefixTextBoxText(String.format("%s%nMultiplier: %s", prefixName, prefix.getMultiplier())); + } + } + + /** + * @param significantFigures + * new value of significantFigures + * @since 2019-01-15 + */ + public final void setSignificantFigures(final int significantFigures) { + this.significantFigures = significantFigures; + } + + public final void unitFilterUpdated() { + final String filter = this.view.getUnitFilterText(); + if (filter.equals("")) { + this.filterFilteredUnitModel(t -> true); + } else { + this.filterFilteredUnitModel(t -> t.contains(filter)); + } + this.unitNamesFiltered.sort(new FilterComparator(filter)); + } + + /** + * + * @since 2019-01-15 + */ + public void unitNameSelected() { + final int index = this.view.getUnitListSelection(); + if (index == -1) + return; + else { + final String unitName = this.unitNamesFiltered.get(index); + final Unit unit = this.units.getUnit(unitName); + + this.view.setUnitTextBoxText(unit.toString()); + } + } + } + + private static class View { + /** The view's frame. */ + private final JFrame frame; + /** The view's associated presenter. */ + private final Presenter presenter; + + private final JList unitNameList; + private final JList prefixNameList; + private final JTextField unitFilterEntry; + private final JTextArea unitTextBox; + private final JTextField prefixFilterEntry; + private final JTextArea prefixTextBox; + private final JTextField fromEntry; + private final JTextField toEntry; + private final JTextArea output; + + /** + * Creates the {@code View}. + * + * @since 2019-01-14 + */ + public View() { + this.presenter = new Presenter(this); + this.frame = new JFrame("Unit Converter"); + this.frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); + + this.unitNameList = new JList<>(this.presenter.keyListModel()); + this.prefixNameList = new JList<>(this.presenter.prefixNameListModel()); + this.unitFilterEntry = new JTextField(); + this.unitTextBox = new JTextArea(); + this.prefixFilterEntry = new JTextField(); + this.prefixTextBox = new JTextArea(); + this.fromEntry = new JTextField(); + this.toEntry = new JTextField(); + this.output = new JTextArea(2, 32); + + this.initComponents(); + + this.frame.pack(); + } + + public String getFromText() { + return this.fromEntry.getText(); + } + + /** + * @return text in prefix filter + * @since 2019-01-15 + */ + public String getPrefixFilterText() { + return this.prefixFilterEntry.getText(); + } + + /** + * @return index of selected prefix + * @since 2019-01-15 + */ + public int getPrefixListSelection() { + return this.prefixNameList.getSelectedIndex(); + } + + public String getToText() { + return this.toEntry.getText(); + } + + /** + * @return text in unit filter + * @see javax.swing.text.JTextComponent#getText() + */ + public String getUnitFilterText() { + return this.unitFilterEntry.getText(); + } + + /** + * @return index of selected unit + * @since 2019-01-15 + */ + public int getUnitListSelection() { + return this.unitNameList.getSelectedIndex(); + } + + /** + * Starts up the application. + * + * @since 2018-12-27 + */ + public final void init() { + this.frame.setVisible(true); + } + + /** + * Initializes the view's components. + * + * @since 2018-12-27 + */ + private final void initComponents() { + final JPanel masterPanel = new JPanel(); + this.frame.add(masterPanel); + + masterPanel.setLayout(new BorderLayout()); + + { // pane with all of the tabs + final JTabbedPane masterPane = new JTabbedPane(); + masterPanel.add(masterPane, BorderLayout.CENTER); + + { // panel for unit conversion + final JPanel convertPanel = new JPanel(); + masterPane.addTab("Convert Units", convertPanel); + + convertPanel.setLayout(new GridLayout(5, 1)); + + { // panel for units to convert from + final JPanel fromPanel = new JPanel(); + convertPanel.add(fromPanel); + + fromPanel.setBorder(BorderFactory.createTitledBorder("From")); + fromPanel.setLayout(new GridLayout(1, 1)); + + { // entry for units + fromPanel.add(this.fromEntry); + } + } + + { // panel for units to convert to + final JPanel toPanel = new JPanel(); + convertPanel.add(toPanel); + + toPanel.setBorder(BorderFactory.createTitledBorder("To")); + toPanel.setLayout(new GridLayout(1, 1)); + + { // entry for units + toPanel.add(this.toEntry); + } + } + + { // button to convert + final JButton convertButton = new JButton("Convert!"); + convertPanel.add(convertButton); + + convertButton.addActionListener(e -> this.presenter.convert()); + } + + { // output of conversion + final JPanel outputPanel = new JPanel(); + convertPanel.add(outputPanel); + + outputPanel.setBorder(BorderFactory.createTitledBorder("Output")); + outputPanel.setLayout(new GridLayout(1, 1)); + + { // output + outputPanel.add(this.output); + this.output.setEditable(false); + } + } + + { + final JPanel sigDigPanel = new JPanel(); + convertPanel.add(sigDigPanel); + + sigDigPanel.setBorder(BorderFactory.createTitledBorder("Significant Digits")); + + { // slider + final JSlider sigDigSlider = new JSlider(0, 12); + sigDigPanel.add(sigDigSlider); + + sigDigSlider.setMajorTickSpacing(4); + sigDigSlider.setMinorTickSpacing(1); + sigDigSlider.setSnapToTicks(true); + sigDigSlider.setPaintTicks(true); + sigDigSlider.setPaintLabels(true); + + sigDigSlider.addChangeListener( + e -> this.presenter.setSignificantFigures(sigDigSlider.getValue())); + } + } + } + + { // panel to look up units + final JPanel unitLookupPanel = new JPanel(); + masterPane.addTab("Unit Viewer", unitLookupPanel); + + unitLookupPanel.setLayout(new GridLayout()); + + { // panel for listing and searching + final JPanel listPanel = new JPanel(); + unitLookupPanel.add(listPanel); + + listPanel.setLayout(new BorderLayout()); + + { + listPanel.add(this.unitFilterEntry, BorderLayout.PAGE_START); + this.unitFilterEntry.addCaretListener(e -> this.presenter.unitFilterUpdated()); + } + + { // a list of units + listPanel.add(new JScrollPane(this.unitNameList), BorderLayout.CENTER); + this.unitNameList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); // temp + this.unitNameList.addListSelectionListener(e -> { + this.presenter.unitNameSelected(); + }); + } + } + + { // the text box for unit's toString + unitLookupPanel.add(this.unitTextBox); + this.unitTextBox.setEditable(false); + this.unitTextBox.setLineWrap(true); + } + } + + { // panel to look up prefixes + final JPanel prefixLookupPanel = new JPanel(); + masterPane.addTab("Prefix Viewer", prefixLookupPanel); + + prefixLookupPanel.setLayout(new GridLayout(1, 2)); + + { + final JPanel prefixListPanel = new JPanel(); + prefixLookupPanel.add(prefixListPanel); + + prefixListPanel.setLayout(new BorderLayout()); + + { + prefixListPanel.add(this.prefixFilterEntry, BorderLayout.PAGE_START); + this.prefixFilterEntry.addCaretListener(e -> this.presenter.prefixFilterUpdated()); + } + + { // a list of prefixes + prefixListPanel.add(new JScrollPane(this.prefixNameList), BorderLayout.CENTER); + this.prefixNameList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); // temp + this.prefixNameList.addListSelectionListener(e -> { + this.presenter.prefixSelected(); + }); + } + } + + { // the text box for prefix's toString + prefixLookupPanel.add(this.prefixTextBox); + this.unitTextBox.setEditable(false); + } + } + } + } + + public void setOutputText(final String text) { + this.output.setText(text); + } + + /** + * Sets the text of the prefix text box. + * + * @param text + * text to set + * @since 2019-01-15 + */ + public void setPrefixTextBoxText(final String text) { + this.prefixTextBox.setText(text); + } + + /** + * Sets the text of the unit text box. + * + * @param t + * text to set + * @see javax.swing.text.JTextComponent#setText(java.lang.String) + */ + public void setUnitTextBoxText(final String t) { + this.unitTextBox.setText(t); + } + + /** + * Shows an error dialog. + * + * @param title + * title of dialog + * @param message + * message in dialog + * @since 2019-01-14 + */ + public void showErrorDialog(final String title, final String message) { + JOptionPane.showMessageDialog(this.frame, message, title, JOptionPane.ERROR_MESSAGE); + } + } + + public static void main(final String[] args) { + new View().init(); + } +} diff --git a/src/unitConverter/converterGUI/package-info.java b/src/unitConverter/converterGUI/package-info.java new file mode 100644 index 0000000..9f7fa57 --- /dev/null +++ b/src/unitConverter/converterGUI/package-info.java @@ -0,0 +1,23 @@ +/** + * Copyright (C) 2019 Adrien Hopkins + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +/** + * All classes that work to convert units. + * + * @author Adrien Hopkins + * @since 2019-01-25 + */ +package unitConverter.converterGUI; \ No newline at end of file diff --git a/src/unitConverter/unit/AbstractUnit.java b/src/unitConverter/unit/AbstractUnit.java index 62c07a2..d3d6dbd 100644 --- a/src/unitConverter/unit/AbstractUnit.java +++ b/src/unitConverter/unit/AbstractUnit.java @@ -26,7 +26,55 @@ import unitConverter.dimension.UnitDimension; * @author Adrien Hopkins * @since 2019-01-25 */ -abstract class AbstractUnit implements Unit { +public abstract class AbstractUnit implements Unit { + /** + * The number of units created, including base units. + * + * @since 2019-01-02 + */ + private static long unitCount = 0; + + /** + * The number of base units created. + * + * @since 2019-01-02 + */ + private static long baseUnitCount = 0; + + /** + * @return number of base units created + * @since 2019-01-02 + */ + public static final long getBaseUnitCount() { + return baseUnitCount; + } + + /** + * @return number of units created + * @since 2019-01-02 + */ + public static final long getUnitCount() { + return unitCount; + } + + /** + * Increments the number of base units. + * + * @since 2019-01-15 + */ + public static final void incrementBaseUnitCounter() { + baseUnitCount++; + } + + /** + * Increments the number of units. + * + * @since 2019-01-15 + */ + public static final void incrementUnitCounter() { + unitCount++; + } + /** * The dimension, or what the unit measures. * diff --git a/src/unitConverter/unit/BaseUnit.java b/src/unitConverter/unit/BaseUnit.java index 46316bf..204b1cd 100755 --- a/src/unitConverter/unit/BaseUnit.java +++ b/src/unitConverter/unit/BaseUnit.java @@ -53,6 +53,14 @@ public final class BaseUnit extends AbstractUnit { this.isFullBase = dimension.isBase(); } + /** + * @return this unit as a {@code LinearUnit} + * @since 2019-01-25 + */ + public LinearUnit asLinearUnit() { + return this.times(1); + } + @Override public double convertFromBase(final double value) { return value; @@ -134,7 +142,7 @@ public final class BaseUnit extends AbstractUnit { * @since 2019-01-15 */ public BaseUnit toExponent(final int exponent) { - return this.toExponent(exponent); + return this.getSystem().getBaseUnit(this.getDimension().toExponent(exponent)); } @Override diff --git a/src/unitConverter/unit/DefaultUnitPrefix.java b/src/unitConverter/unit/DefaultUnitPrefix.java new file mode 100755 index 0000000..1cce413 --- /dev/null +++ b/src/unitConverter/unit/DefaultUnitPrefix.java @@ -0,0 +1,64 @@ +/** + * Copyright (C) 2018 Adrien Hopkins + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package unitConverter.unit; + +import java.util.Objects; + +/** + * @author Adrien Hopkins + * @since 2019-01-14 + */ +public final class DefaultUnitPrefix implements UnitPrefix { + private final double multiplier; + + /** + * Creates the {@code DefaultUnitPrefix}. + * + * @param multiplier + * @since 2019-01-14 + */ + public DefaultUnitPrefix(final double multiplier) { + this.multiplier = multiplier; + } + + @Override + public boolean equals(final Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (!(obj instanceof DefaultUnitPrefix)) + return false; + final DefaultUnitPrefix other = (DefaultUnitPrefix) obj; + return Double.doubleToLongBits(this.multiplier) == Double.doubleToLongBits(other.multiplier); + } + + @Override + public double getMultiplier() { + return this.multiplier; + } + + @Override + public int hashCode() { + return Objects.hash(this.multiplier); + } + + @Override + public String toString() { + return String.format("Unit prefix equal to %s", this.multiplier); + } +} diff --git a/src/unitConverter/unit/NonlinearUnits.java b/src/unitConverter/unit/NonlinearUnits.java new file mode 100755 index 0000000..f7e257c --- /dev/null +++ b/src/unitConverter/unit/NonlinearUnits.java @@ -0,0 +1,56 @@ +/** + * Copyright (C) 2018 Adrien Hopkins + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package unitConverter.unit; + +/** + * Some major nonlinear units. + * + * @author Adrien Hopkins + * @since 2019-01-14 + */ +public final class NonlinearUnits { + public static final Unit CELSIUS = new AbstractUnit(SI.KELVIN) { + + @Override + public double convertFromBase(final double value) { + return value - 273.15; + } + + @Override + public double convertToBase(final double value) { + return value + 273.15; + } + }; + + public static final Unit FAHRENHEIT = new AbstractUnit(SI.KELVIN) { + + @Override + public double convertFromBase(final double value) { + return 1.8 * value - 459.67; + } + + @Override + public double convertToBase(final double value) { + return (value + 459.67) / 1.8; + } + }; + + // You may NOT get a NonlinearUnits instance. + private NonlinearUnits() { + throw new AssertionError(); + } +} diff --git a/src/unitConverter/unit/SIPrefix.java b/src/unitConverter/unit/SIPrefix.java index 452d46f..39f1a8c 100755 --- a/src/unitConverter/unit/SIPrefix.java +++ b/src/unitConverter/unit/SIPrefix.java @@ -25,17 +25,17 @@ public enum SIPrefix implements UnitPrefix { 1e24), DECI(0.1), CENTI(0.01), MILLI( 1e-3), MICRO(1e-6), NANO(1e-9), PICO(1e-12), FEMTO(1e-15), ATTO(1e-18), ZEPTO(1e-21), YOCTO(1e-24); - private final double value; + private final double multiplier; /** * Creates the {@code SIPrefix}. * - * @param value - * value of prefix + * @param multiplier + * prefix's multiplier * @since 2019-01-14 */ - private SIPrefix(final double value) { - this.value = value; + private SIPrefix(final double multiplier) { + this.multiplier = multiplier; } /** @@ -43,7 +43,7 @@ public enum SIPrefix implements UnitPrefix { * @since 2019-01-14 */ @Override - public final double getValue() { - return this.value; + public final double getMultiplier() { + return this.multiplier; } } diff --git a/src/unitConverter/unit/Unit.java b/src/unitConverter/unit/Unit.java index 3e7f9da..9e1375a 100755 --- a/src/unitConverter/unit/Unit.java +++ b/src/unitConverter/unit/Unit.java @@ -87,7 +87,7 @@ public interface Unit { * @return base unit associated with this unit * @since 2018-12-22 */ - Unit getBase(); + BaseUnit getBase(); /** * @return dimension measured by this unit diff --git a/src/unitConverter/unit/UnitPrefix.java b/src/unitConverter/unit/UnitPrefix.java index 0dbdc00..cb50fd9 100755 --- a/src/unitConverter/unit/UnitPrefix.java +++ b/src/unitConverter/unit/UnitPrefix.java @@ -24,8 +24,8 @@ package unitConverter.unit; */ public interface UnitPrefix { /** - * @return value of this prefix + * @return this prefix's multiplier * @since 2019-01-14 */ - double getValue(); + double getMultiplier(); } diff --git a/unitsfile.txt b/unitsfile.txt new file mode 100755 index 0000000..78e51f7 --- /dev/null +++ b/unitsfile.txt @@ -0,0 +1,234 @@ +# A file for the units in my unit converter program + +# SI Base Units +# ! means "look for an existing unit which I will load at the start" +# This is necessary because every unit must be defined by others, and I need somewhere to start. + +metre ! +kilogram ! +second ! +ampere ! +kelvin ! +mole ! +candela ! + +# Symbols and aliases for base units + +meter metre +m metre +kg kilogram +s second +A ampere +K kelvin +mol mole +cd candela + +# the bit and byte, units of information + +bit ! +b bit +byte 8 bit +B byte + +# SI prefixes + +deca- 10 +deka- deca +hecto- 100 +kilo- 1e3 +mega- 1e6 +giga- 1e9 +tera- 1e12 +peta- 1e15 +exa- 1e18 +zetta- 1e21 +yotta- 1e24 + +deci- 1e-1 +centi- 1e-2 +milli- 1e-3 +micro- 1e-6 +nano- 1e-9 +pico- 1e-12 +femto- 1e-15 +atto- 1e-18 +zepto- 1e-21 +yocto- 1e-24 + +D- deca +h- hecto +H- hecto +k- kilo +K- kilo +M- mega +G- giga +T- tera +P- peta +E- exa +Z- zetta +Y- yotta + +d- deci +c- centi +m- milli +u- micro +n- nano +p- pico +f- femto +a- atto +z- zepto +y- yocto + +# Binary prefixes (i.e. metric but 1024 replaces 1000) + +kibi- 1024^1 +mebi- 1024^2 +gibi- 1024^3 +tebi- 1024^4 +pebi- 1024^5 +exbi- 1024^6 +Ki- kibi +Mi- mebi +Gi- gibi +Ti- tebi +Pi- pebi +Ei- exbi + +# Derived SI units +# Note: it is best to have these before any non-SI units + +newton kg m / s^2 +N newton +pascal N / m^2 +Pa pascal +joule N m +J joule +watt J/s +W watt +coulomb A s +C coulomb +volt W/A +V volt +ohm V/A +siemens A/V +S siemens +farad C/V +F farad +weber V s +Wb weber +henry V s / A +H henry +tesla Wb / m^2 +T tesla +hertz s^-1 +Hz hertz + +# Angle units and constants + +# Tau is the circle constant, equal to a circle's diameter divided by its radius +tau 6.28318530717958 +# Another common circle constant +pi tau / 2 + +radian m / m +rad radian +steradian m^2 / m^2 +sr steradian +degree 360 / tau * radian +deg degree +° degree + +# Nonlinear units, which are not supported by the file reader and must be defined manually +# Use tempC(100) for 100 degrees Celsius + +tempCelsius ! +tempFahrenheit ! +tempC tempCelsius +tempF tempFahrenheit + +# Common time units +minute 60 second +min minute +hour 3600 second +h hour +day 86400 second +d day +julianyear 365.25 day +gregorianyear 365.2425 day + +# Other non-SI "metric" units +litre 0.001 m^3 +liter litre +l litre +L litre +tonne 1000 kg +t tonne +are 100 m^2 +hectare hectoare +arcminute 1 / 60 degree +arcmin arcminute +arcsecond 1 / 60 arcminute +arcsec arcsecond + +# constants +waterdensity 1 kilogram / litre + +# Imperial length units +foot 0.3048 m +ft foot +inch 1 / 12 foot +in inch +yard 3 foot +yd yard +mile 1760 yard +mi mile + +# Imperial weight units +pound 0.45359237 kg +lb pound +ounce pound / 16 +oz ounce +stone 14 lb +UShundredweight 100 lb +UKhundredweight 8 stone +USimperialton 20 UShundredweight +UKimperialton 10 UKhundredweight + +# Imperial volume units +UKfluidounce ounce / waterdensity +UKfloz UKfluidounce +UKcup 10 UKfloz +UKpint 2 UKcup +UKquart 2 UKpint +UKgallon 4 UKquart +UKgal UKgallon + +USgallon 231 inch^3 +USgal USgallon +USquart USgallon / 4 +USpint USquart / 2 +UScup USpint / 2 +USfluidounce UScup / 8 +USfloz USfluidounce +UStablespoon USfluidounce / 2 +UStbsp UStablespoon +USteaspoon UStablespoon / 3 +UStsp USteaspoon + +# Metric versions! +# tsp = 5 mL, tbsp = 15 mL, floz = 30 mL, cup = 240 mL, pint = 480 mL, quart = 960 mL, gallon = 3840 mL +# only metrictsp, metrictbsp and metriccup are common, the rest are derived from the US formulae with 240 mL cup +metricteaspoon 5 mL +teaspoon metricteaspoon +tsp metricteaspoon +metrictablespoon 3 metricteaspoon +tablespoon metrictablespoon +tbsp metrictablespoon +metricfluidounce 2 metrictablespoon +metriccup 8 metricfluidounce +cup metriccup +metricpint 2 metriccup +pint metricpint +metricquart 2 metricpint +quart metricquart +metricgallon 4 metricquart \ No newline at end of file -- cgit v1.2.3 From fceb0002659311f7d7c6481908d1c7f917160e6e Mon Sep 17 00:00:00 2001 From: Adrien Hopkins Date: Fri, 25 Jan 2019 19:28:29 -0500 Subject: Created a README file. --- README.org | 51 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 README.org diff --git a/README.org b/README.org new file mode 100644 index 0000000..9fa913d --- /dev/null +++ b/README.org @@ -0,0 +1,51 @@ +* What is it? + This is a unit converter, which allows you to convert between different units, and includes a GUI which can read unit data from a file (using some unit math) and convert between units that you type in, and has a unit and prefix viewer to check the units that have been loaded in. +* Features + - Convert between units and expressions of units + - linear or base unit can use unit prefixes (including non-metric units!) + - and prefixes are defined in an editable data file, in a simple and intuitive format. + - Viewer and Prefix Viewer which allow you to search through all of the available units and prefixes and learn details about them. + - All of SI included in default text file + - Choose your precision +* How to Convert +To convert units, simply enter the first unit in the From box and the second unit in the To box. Press Convert and a result will appear in the output box. Both boxes accept unit expressions, so you can input things like ‘1.7 m’, ‘85 km/h’ and ‘35 A * 14 s’ into either box. +*Warning*: The space between the number and the unit name is required, or else the whole expression will be interpreted as a unit name. Spaces are not required for operators. +Use the slider at the bottom to choose the maximum precision of the result, in significant digits, not decimal places. +* Units Files and Expressions +As mentioned previously, all units are loaded from a units file. The format for lines consists of a name for the unit, some tabs (not spaces), and an expression. The following operations are supported in expressions + - Multiplication using spaces ( ) or asterisks (*) + - Division using the forward slash (/) + - Exponentiation using the caret (^) +Every argument can be either a linear unit, a base unit or a number, except exponents which must be integers. There is no way to use brackets to manipulate order of operations. + +Example (Explanation provided after # sign, this will *not* work in a real units file): +inch 25.4 mm # Define ‘inch’ as equal to the product of 25.4 and ‘mm’ +mph mile / hour # Define ‘mph’ as equal to the quotient of ‘mile’ and ‘hour’ +litre 0.001 m^3 # Define ‘litre’ as equal to the product of 0.001 and the unit ‘m’ raised to the exponent 3 + +Lines that start with the number sign (#) and blank lines will be ignored. + +Unit prefixes are defined differently. When a unit name ends with the dash (-) character, it is interpreted as a prefix. Prefix expressions are not as powerful as unit expressions, they can only be: + - a number (1000) + - an exponent (10^3) + - the name of another prefix without the dash (kilo) +* Unit Prefixes +In SI, you can have a unit prefix, which you attach to the start of a unit and it multiplies the unit by a certain value. For example, milli-metre = 0.001 * metre. This unit converter supports unit prefixes, and you can put any number of prefixes before a linear or base unit to transform it (this includes non-SI units). +You can use more than one prefix at once (yottayottametre = really really long distance), but you may not convert prefixes alone. If you want to do that, use ‘unit’ or ‘u’ (ku = 1000000 mu). You can also use u for converting from numbers (tau = 6.28319 u). +*Warning*: While the standard symbol for ‘deca’ is ‘da’, this program uses ‘D’ instead since ‘da’ can be confused with ‘deciatto’. Also, you may use capital letters for the symbols of ‘hecto’ and ‘kilo’. +*Warning*: The standard prefixes will never use 1024 instead of 1000, even when operating on bits and bytes. Use ‘Ki’, ‘Mi’, ‘Gi’, ‘Ti’ and so on instead. +* Nonlinear Units +Sometimes, units cannot be converted from and to by simply multiplying and dividing. A common example of this is the Celsius and Fahrenheit temperature scales, which require multiplication and addition to convert to each other (and to their base, Kelvin). +To use nonlinear units, use the following syntax: +FROM: unit1(value) +TO: unit2 +Nonlinear units cannot: + - multiply, divide or exponentiate + - use prefixes + - be defined by unit files +To define a nonlinear unit, make an anonymous inner type (or any other subclass) of AbstractUnit, and define the conversion methods. +* Unit and Prefix Viewers +The unit and prefix viewers can be used to see the available units (without prefixes) and prefixes. Upon opening them, you will see a list of units or prefixes on your left. Using the text box above, the list can be filtered. When a unit is clicked on, details about will be displayed on the right. +* Copyright and Licences +The Unit Converter program is Copyright (C) 2018, 2019 Adrien Hopkins. It is released under the terms of the AGPL v3 licence. +This document is Copyright (C) 2019 Adrien Hopkins. It is released under the terms of the CC BY-SA (Creative Commons Attribution-ShareAlike) licence. -- cgit v1.2.3 From 5358e7f04c3d33a17ff84f6c274c521c8f0dd4c9 Mon Sep 17 00:00:00 2001 From: Adrien Hopkins Date: Sat, 26 Jan 2019 13:35:19 -0500 Subject: Added a changelog and changed the tests. --- CHANGELOG.org | 11 +++++++++ src/unitConverter/UnitsFileTest.java | 31 ------------------------ src/unitConverter/unit/UnitTest.java | 47 ++++++++++++++++++++++++++++++++++++ 3 files changed, 58 insertions(+), 31 deletions(-) create mode 100644 CHANGELOG.org delete mode 100755 src/unitConverter/UnitsFileTest.java create mode 100755 src/unitConverter/unit/UnitTest.java diff --git a/CHANGELOG.org b/CHANGELOG.org new file mode 100644 index 0000000..1dbe268 --- /dev/null +++ b/CHANGELOG.org @@ -0,0 +1,11 @@ +* Changelog +All notable changes in this project will be shown in this file. + +** v0.1.0 +NOTE: At this stage, the API is subject to significant change. +*** Added + - Unit interface, implemented and supporting classes + - UnitPrefix interface, implemented and supporting classes + - UnitDimension and supporting classes + - UnitDatabase to store and parse units + - A GUI for unit conversion diff --git a/src/unitConverter/UnitsFileTest.java b/src/unitConverter/UnitsFileTest.java deleted file mode 100755 index 8edeab0..0000000 --- a/src/unitConverter/UnitsFileTest.java +++ /dev/null @@ -1,31 +0,0 @@ -/** - * Copyright (C) 2018 Adrien Hopkins - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ -package unitConverter; - -import static org.junit.jupiter.api.Assertions.fail; - -/** - * @author Adrien Hopkins - * @since 2019-01-02 - */ -class UnitsFileTest { - - // @Test - void testReading() { - fail("Not yet implemented."); - } -} diff --git a/src/unitConverter/unit/UnitTest.java b/src/unitConverter/unit/UnitTest.java new file mode 100755 index 0000000..7e16123 --- /dev/null +++ b/src/unitConverter/unit/UnitTest.java @@ -0,0 +1,47 @@ +/** + * Copyright (C) 2018 Adrien Hopkins + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package unitConverter.unit; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.api.Test; + +import unitConverter.dimension.StandardDimensions; + +/** + * Testing the various Unit classes + * + * @author Adrien Hopkins + * @since 2019-01-26 + */ +class UnitTest { + @Test + void testConversion() { + final BaseUnit metre = SI.METRE; + final Unit inch = metre.times(0.0254); + + assertEquals(1.9, inch.convertToBase(75), 0.01); + } + + @Test + void testEquals() { + final BaseUnit metre = SI.METRE; + final Unit meter = SI.SI.getBaseUnit(StandardDimensions.LENGTH); + + assertEquals(metre, meter); + } +} -- cgit v1.2.3 From da01eec8c59477da649767f3ed72c98fe1bbb301 Mon Sep 17 00:00:00 2001 From: Adrien Hopkins Date: Sat, 26 Jan 2019 14:07:29 -0500 Subject: Added more @since tags, edited some documentation There is now a @since tag for version as well as date. --- src/unitConverter/UnitsDatabase.java | 35 +++++--- .../converterGUI/DelegateListModel.java | 4 + .../converterGUI/FilterComparator.java | 25 +++++- src/unitConverter/converterGUI/GridBagBuilder.java | 47 +++++++++++ .../converterGUI/UnitConverterGUI.java | 95 ++++++++++++++++++++-- src/unitConverter/dimension/BaseDimension.java | 5 +- .../dimension/OtherBaseDimension.java | 2 + src/unitConverter/dimension/SIBaseDimension.java | 2 + .../dimension/StandardDimensions.java | 1 + src/unitConverter/dimension/UnitDimension.java | 11 +++ src/unitConverter/dimension/UnitDimensionTest.java | 21 +++++ src/unitConverter/unit/AbstractUnit.java | 14 +++- src/unitConverter/unit/BaseUnit.java | 11 ++- src/unitConverter/unit/DefaultUnitPrefix.java | 3 + src/unitConverter/unit/LinearUnit.java | 14 +++- src/unitConverter/unit/NonlinearUnits.java | 1 + src/unitConverter/unit/SI.java | 2 + src/unitConverter/unit/SIPrefix.java | 5 ++ src/unitConverter/unit/Unit.java | 7 ++ src/unitConverter/unit/UnitPrefix.java | 2 + src/unitConverter/unit/UnitSystem.java | 5 +- src/unitConverter/unit/UnitTest.java | 2 +- 22 files changed, 288 insertions(+), 26 deletions(-) diff --git a/src/unitConverter/UnitsDatabase.java b/src/unitConverter/UnitsDatabase.java index d479917..4816db1 100755 --- a/src/unitConverter/UnitsDatabase.java +++ b/src/unitConverter/UnitsDatabase.java @@ -41,12 +41,14 @@ import unitConverter.unit.UnitPrefix; * * @author Adrien Hopkins * @since 2019-01-07 + * @since v0.1.0 */ public final class UnitsDatabase { /** * The units in this system. * * @since 2019-01-07 + * @since v0.1.0 */ private final Map units; @@ -54,6 +56,7 @@ public final class UnitsDatabase { * The unit prefixes in this system. * * @since 2019-01-14 + * @since v0.1.0 */ private final Map prefixes; @@ -61,6 +64,7 @@ public final class UnitsDatabase { * Creates the {@code UnitsDatabase}. * * @since 2019-01-10 + * @since v0.1.0 */ public UnitsDatabase() { this.units = new HashMap<>(); @@ -90,6 +94,7 @@ public final class UnitsDatabase { * @throws NullPointerException * if file is null * @since 2019-01-13 + * @since v0.1.0 */ public void addAllFromFile(final File file) { Objects.requireNonNull(file, "file must not be null."); @@ -128,7 +133,7 @@ public final class UnitsDatabase { if (name.endsWith("-")) { final UnitPrefix prefix; try { - prefix = this.getPrefixFromExpression(expression, name.substring(0, name.length() - 1)); + prefix = this.getPrefixFromExpression(expression); } catch (final IllegalArgumentException e) { System.err.printf("Parsing error on line %d:%n", lineCounter); throw e; @@ -168,6 +173,7 @@ public final class UnitsDatabase { * @throws NullPointerException * if name or unit is null * @since 2019-01-14 + * @since v0.1.0 */ public void addPrefix(final String name, final UnitPrefix prefix) { this.prefixes.put(Objects.requireNonNull(name, "name must not be null."), @@ -184,6 +190,7 @@ public final class UnitsDatabase { * @throws NullPointerException * if unit is null * @since 2019-01-10 + * @since v0.1.0 */ public void addUnit(final String name, final Unit unit) { this.units.put(name, Objects.requireNonNull(unit, "unit must not be null.")); @@ -196,6 +203,7 @@ public final class UnitsDatabase { * name to test * @return if database contains name * @since 2019-01-13 + * @since v0.1.0 */ public boolean containsPrefixlessUnitName(final String name) { return this.units.containsKey(name); @@ -208,6 +216,7 @@ public final class UnitsDatabase { * name to test * @return if database contains name * @since 2019-01-13 + * @since v0.1.0 */ public boolean containsPrefixName(final String name) { return this.prefixes.containsKey(name); @@ -220,6 +229,7 @@ public final class UnitsDatabase { * name to test * @return if database contains name * @since 2019-01-13 + * @since v0.1.0 */ public boolean containsUnitName(final String name) { // check for prefixes @@ -238,13 +248,14 @@ public final class UnitsDatabase { * prefix's name * @return prefix * @since 2019-01-10 + * @since v0.1.0 */ public UnitPrefix getPrefix(final String name) { return this.prefixes.get(name); } /** - * Gets a unit prefix from a prefix expression and a name + * Gets a unit prefix from a prefix expression *

* Currently, prefix expressions are much simpler than unit expressions: They are either a number or the name of * another prefix @@ -252,18 +263,16 @@ public final class UnitsDatabase { * * @param expression * expression to input - * @param name - * name of prefix if a new prefix is created * @return prefix * @throws IllegalArgumentException * if expression cannot be parsed * @throws NullPointerException * if any argument is null * @since 2019-01-14 + * @since v0.1.0 */ - public UnitPrefix getPrefixFromExpression(final String expression, final String name) { + public UnitPrefix getPrefixFromExpression(final String expression) { Objects.requireNonNull(expression, "expression must not be null."); - Objects.requireNonNull(name, "name must not be null."); try { return new DefaultUnitPrefix(Double.parseDouble(expression)); @@ -301,6 +310,7 @@ public final class UnitsDatabase { * unit's name * @return unit * @since 2019-01-10 + * @since v0.1.0 */ public Unit getPrefixlessUnit(final String name) { return this.units.get(name); @@ -313,6 +323,7 @@ public final class UnitsDatabase { * unit's name * @return unit * @since 2019-01-10 + * @since v0.1.0 */ public Unit getUnit(final String name) { if (name.contains("^")) { @@ -377,21 +388,19 @@ public final class UnitsDatabase { *

  • The name of a unit, which multiplies or divides the result based on preceding operators
  • *
  • The operators '*' and '/', which multiply and divide (note that just putting two units or values next to each * other is equivalent to multiplication)
  • + *
  • The operator '^' which exponentiates. Exponents must be integers.
  • *
  • A number which is multiplied or divided
  • * * This method only works with linear units. - *

    - * If the expression contains just the name of a unit, returns that unit without changing name or symbol. This - * exists for the creation of aliases. - *

    * - * @param line - * line to parse + * @param expression + * expression to parse * @throws IllegalArgumentException * if the expression cannot be parsed * @throws NullPointerException * if any argument is null * @since 2019-01-07 + * @since v0.1.0 */ public Unit getUnitFromExpression(final String expression) { Objects.requireNonNull(expression, "expression must not be null."); @@ -558,6 +567,7 @@ public final class UnitsDatabase { /** * @return an immutable set of all of the unit names in this database, ignoring prefixes * @since 2019-01-14 + * @since v0.1.0 */ public Set prefixlessUnitNameSet() { return Collections.unmodifiableSet(this.units.keySet()); @@ -566,6 +576,7 @@ public final class UnitsDatabase { /** * @return an immutable set of all of the prefix names in this database * @since 2019-01-14 + * @since v0.1.0 */ public Set prefixNameSet() { return Collections.unmodifiableSet(this.prefixes.keySet()); diff --git a/src/unitConverter/converterGUI/DelegateListModel.java b/src/unitConverter/converterGUI/DelegateListModel.java index 42bc0dc..0e9b342 100755 --- a/src/unitConverter/converterGUI/DelegateListModel.java +++ b/src/unitConverter/converterGUI/DelegateListModel.java @@ -32,10 +32,12 @@ import javax.swing.AbstractListModel; * * @author Adrien Hopkins * @since 2019-01-14 + * @since v0.1.0 */ final class DelegateListModel extends AbstractListModel implements List { /** * @since 2019-01-14 + * @since v0.1.0 */ private static final long serialVersionUID = 8985494428224810045L; @@ -43,6 +45,7 @@ final class DelegateListModel extends AbstractListModel implements List * The list that this model is a delegate to. * * @since 2019-01-14 + * @since v0.1.0 */ private final List delegate; @@ -52,6 +55,7 @@ final class DelegateListModel extends AbstractListModel implements List * @param delegate * list to delegate * @since 2019-01-14 + * @since v0.1.0 */ public DelegateListModel(final List delegate) { this.delegate = delegate; diff --git a/src/unitConverter/converterGUI/FilterComparator.java b/src/unitConverter/converterGUI/FilterComparator.java index ab25f2f..27ec3ab 100755 --- a/src/unitConverter/converterGUI/FilterComparator.java +++ b/src/unitConverter/converterGUI/FilterComparator.java @@ -17,14 +17,29 @@ package unitConverter.converterGUI; import java.util.Comparator; +import java.util.Objects; /** + * A comparator that compares strings using a filter. + * * @author Adrien Hopkins * @since 2019-01-15 + * @since v0.1.0 */ public final class FilterComparator implements Comparator { - + /** + * The filter that the comparator is filtered by. + * + * @since 2019-01-15 + * @since v0.1.0 + */ private final String filter; + /** + * The comparator to use if the arguments are otherwise equal. + * + * @since 2019-01-15 + * @since v0.1.0 + */ private final Comparator comparator; /** @@ -32,6 +47,7 @@ public final class FilterComparator implements Comparator { * * @param filter * @since 2019-01-15 + * @since v0.1.0 */ public FilterComparator(final String filter) { this(filter, null); @@ -41,11 +57,16 @@ public final class FilterComparator implements Comparator { * Creates the {@code FilterComparator}. * * @param filter + * string to filter by * @param comparator + * comparator to fall back to if all else fails, null is compareTo. * @since 2019-01-15 + * @since v0.1.0 + * @throws NullPointerException + * if filter is null */ public FilterComparator(final String filter, final Comparator comparator) { - this.filter = filter; + this.filter = Objects.requireNonNull(filter, "filter must not be null."); this.comparator = comparator; } diff --git a/src/unitConverter/converterGUI/GridBagBuilder.java b/src/unitConverter/converterGUI/GridBagBuilder.java index 7a0615a..e036677 100755 --- a/src/unitConverter/converterGUI/GridBagBuilder.java +++ b/src/unitConverter/converterGUI/GridBagBuilder.java @@ -20,11 +20,16 @@ import java.awt.GridBagConstraints; import java.awt.Insets; /** + * A builder for Java's {@link java.awt.GridBagConstraints} class. + * * @author Adrien Hopkins * @since 2018-11-30 + * @since v0.1.0 */ final class GridBagBuilder { /** + * The built {@code GridBagConstraints}'s {@code gridx} property. + *

    * Specifies the cell containing the leading edge of the component's display area, where the first cell in a row has * gridx=0. The leading edge of a component's display area is its left edge for a horizontal, * left-to-right container and its right edge for a horizontal, right-to-left container. The value @@ -41,6 +46,8 @@ final class GridBagBuilder { private final int gridx; /** + * The built {@code GridBagConstraints}'s {@code gridy} property. + *

    * Specifies the cell at the top of the component's display area, where the topmost cell has gridy=0. * The value RELATIVE specifies that the component be placed just below the component that was added to * the container just before this component was added. @@ -54,6 +61,8 @@ final class GridBagBuilder { private final int gridy; /** + * The built {@code GridBagConstraints}'s {@code gridwidth} property. + *

    * Specifies the number of cells in a row for the component's display area. *

    * Use REMAINDER to specify that the component's display area will be from gridx to the @@ -69,6 +78,8 @@ final class GridBagBuilder { private final int gridwidth; /** + * The built {@code GridBagConstraints}'s {@code gridheight} property. + *

    * Specifies the number of cells in a column for the component's display area. *

    * Use REMAINDER to specify that the component's display area will be from gridy to the @@ -84,6 +95,8 @@ final class GridBagBuilder { private final int gridheight; /** + * The built {@code GridBagConstraints}'s {@code weightx} property. + *

    * Specifies how to distribute extra horizontal space. *

    * The grid bag layout manager calculates the weight of a column to be the maximum weightx of all the @@ -103,6 +116,8 @@ final class GridBagBuilder { private double weightx; /** + * The built {@code GridBagConstraints}'s {@code weighty} property. + *

    * Specifies how to distribute extra vertical space. *

    * The grid bag layout manager calculates the weight of a row to be the maximum weighty of all the @@ -122,6 +137,8 @@ final class GridBagBuilder { private double weighty; /** + * The built {@code GridBagConstraints}'s {@code anchor} property. + *

    * This field is used when the component is smaller than its display area. It determines where, within the display * area, to place the component. *

    @@ -145,6 +162,8 @@ final class GridBagBuilder { private int anchor; /** + * The built {@code GridBagConstraints}'s {@code fill} property. + *

    * This field is used when the component's display area is larger than the component's requested size. It determines * whether to resize the component, and if so, how. *

    @@ -167,6 +186,8 @@ final class GridBagBuilder { private int fill; /** + * The built {@code GridBagConstraints}'s {@code insets} property. + *

    * This field specifies the external padding of the component, the minimum amount of space between the component and * the edges of its display area. *

    @@ -178,6 +199,8 @@ final class GridBagBuilder { private Insets insets; /** + * The built {@code GridBagConstraints}'s {@code ipadx} property. + *

    * This field specifies the internal padding of the component, how much space to add to the minimum width of the * component. The width of the component is at least its minimum width plus ipadx pixels. *

    @@ -190,6 +213,8 @@ final class GridBagBuilder { private int ipadx; /** + * The built {@code GridBagConstraints}'s {@code ipady} property. + *

    * This field specifies the internal padding, that is, how much space to add to the minimum height of the component. * The height of the component is at least its minimum height plus ipady pixels. *

    @@ -207,6 +232,7 @@ final class GridBagBuilder { * @param gridy * y position * @since 2018-11-30 + * @since v0.1.0 */ public GridBagBuilder(final int gridx, final int gridy) { this(gridx, gridy, 1, 1); @@ -222,6 +248,7 @@ final class GridBagBuilder { * @param gridheight * number of cells occupied vertically * @since 2018-11-30 + * @since v0.1.0 */ public GridBagBuilder(final int gridx, final int gridy, final int gridwidth, final int gridheight) { this(gridx, gridy, gridwidth, gridheight, 0.0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.NONE, @@ -245,6 +272,7 @@ final class GridBagBuilder { * @param ipadx * @param ipady * @since 2018-11-30 + * @since v0.1.0 */ private GridBagBuilder(final int gridx, final int gridy, final int gridwidth, final int gridheight, final double weightx, final double weighty, final int anchor, final int fill, final Insets insets, @@ -266,6 +294,7 @@ final class GridBagBuilder { /** * @return {@code GridBagConstraints} created by this builder * @since 2018-11-30 + * @since v0.1.0 */ public GridBagConstraints build() { return new GridBagConstraints(this.gridx, this.gridy, this.gridwidth, this.gridheight, this.weightx, @@ -275,6 +304,7 @@ final class GridBagBuilder { /** * @return anchor * @since 2018-11-30 + * @since v0.1.0 */ public int getAnchor() { return this.anchor; @@ -283,6 +313,7 @@ final class GridBagBuilder { /** * @return fill * @since 2018-11-30 + * @since v0.1.0 */ public int getFill() { return this.fill; @@ -291,6 +322,7 @@ final class GridBagBuilder { /** * @return gridheight * @since 2018-11-30 + * @since v0.1.0 */ public int getGridheight() { return this.gridheight; @@ -299,6 +331,7 @@ final class GridBagBuilder { /** * @return gridwidth * @since 2018-11-30 + * @since v0.1.0 */ public int getGridwidth() { return this.gridwidth; @@ -307,6 +340,7 @@ final class GridBagBuilder { /** * @return gridx * @since 2018-11-30 + * @since v0.1.0 */ public int getGridx() { return this.gridx; @@ -315,6 +349,7 @@ final class GridBagBuilder { /** * @return gridy * @since 2018-11-30 + * @since v0.1.0 */ public int getGridy() { return this.gridy; @@ -323,6 +358,7 @@ final class GridBagBuilder { /** * @return insets * @since 2018-11-30 + * @since v0.1.0 */ public Insets getInsets() { return this.insets; @@ -331,6 +367,7 @@ final class GridBagBuilder { /** * @return ipadx * @since 2018-11-30 + * @since v0.1.0 */ public int getIpadx() { return this.ipadx; @@ -339,6 +376,7 @@ final class GridBagBuilder { /** * @return ipady * @since 2018-11-30 + * @since v0.1.0 */ public int getIpady() { return this.ipady; @@ -347,6 +385,7 @@ final class GridBagBuilder { /** * @return weightx * @since 2018-11-30 + * @since v0.1.0 */ public double getWeightx() { return this.weightx; @@ -355,6 +394,7 @@ final class GridBagBuilder { /** * @return weighty * @since 2018-11-30 + * @since v0.1.0 */ public double getWeighty() { return this.weighty; @@ -364,6 +404,7 @@ final class GridBagBuilder { * @param anchor * anchor to set * @since 2018-11-30 + * @since v0.1.0 */ public GridBagBuilder setAnchor(final int anchor) { this.anchor = anchor; @@ -374,6 +415,7 @@ final class GridBagBuilder { * @param fill * fill to set * @since 2018-11-30 + * @since v0.1.0 */ public GridBagBuilder setFill(final int fill) { this.fill = fill; @@ -384,6 +426,7 @@ final class GridBagBuilder { * @param insets * insets to set * @since 2018-11-30 + * @since v0.1.0 */ public GridBagBuilder setInsets(final Insets insets) { this.insets = insets; @@ -394,6 +437,7 @@ final class GridBagBuilder { * @param ipadx * ipadx to set * @since 2018-11-30 + * @since v0.1.0 */ public GridBagBuilder setIpadx(final int ipadx) { this.ipadx = ipadx; @@ -404,6 +448,7 @@ final class GridBagBuilder { * @param ipady * ipady to set * @since 2018-11-30 + * @since v0.1.0 */ public GridBagBuilder setIpady(final int ipady) { this.ipady = ipady; @@ -414,6 +459,7 @@ final class GridBagBuilder { * @param weightx * weightx to set * @since 2018-11-30 + * @since v0.1.0 */ public GridBagBuilder setWeightx(final double weightx) { this.weightx = weightx; @@ -424,6 +470,7 @@ final class GridBagBuilder { * @param weighty * weighty to set * @since 2018-11-30 + * @since v0.1.0 */ public GridBagBuilder setWeighty(final double weighty) { this.weighty = weighty; diff --git a/src/unitConverter/converterGUI/UnitConverterGUI.java b/src/unitConverter/converterGUI/UnitConverterGUI.java index 0068312..b54e0da 100755 --- a/src/unitConverter/converterGUI/UnitConverterGUI.java +++ b/src/unitConverter/converterGUI/UnitConverterGUI.java @@ -52,6 +52,7 @@ import unitConverter.unit.UnitPrefix; /** * @author Adrien Hopkins * @since 2018-12-27 + * @since v0.1.0 */ final class UnitConverterGUI { private static class Presenter { @@ -83,6 +84,7 @@ final class UnitConverterGUI { * @param view * presenter's associated view * @since 2018-12-27 + * @since v0.1.0 */ Presenter(final View view) { this.view = view; @@ -141,6 +143,17 @@ final class UnitConverterGUI { AbstractUnit.getBaseUnitCount()); } + /** + * Runs whenever the convert button is pressed. + * + *

    + * Reads and parses a unit expression from the from and to boxes, then converts {@code from} to {@code to}. Any + * errors are shown in JOptionPanes. + *

    + * + * @since 2019-01-26 + * @since v0.1.0 + */ public final void convert() { final String fromUnitString = this.view.getFromText(); final String toUnitString = this.view.getToText(); @@ -196,6 +209,7 @@ final class UnitConverterGUI { * @param filter * filter to use * @since 2019-01-15 + * @since v0.1.0 */ private final void filterFilteredPrefixModel(final Predicate filter) { this.prefixNamesFiltered.clear(); @@ -212,6 +226,7 @@ final class UnitConverterGUI { * @param filter * filter to use * @since 2019-01-15 + * @since v0.1.0 */ private final void filterFilteredUnitModel(final Predicate filter) { this.unitNamesFiltered.clear(); @@ -225,11 +240,21 @@ final class UnitConverterGUI { /** * @return a list model of all of the unit keys * @since 2019-01-14 + * @since v0.1.0 */ public final ListModel keyListModel() { return this.unitNamesFiltered; } + /** + * Runs whenever the prefix filter is changed. + *

    + * Filters the prefix list then sorts it using a {@code FilterComparator}. + *

    + * + * @since 2019-01-15 + * @since v0.1.0 + */ public final void prefixFilterUpdated() { final String filter = this.view.getPrefixFilterText(); if (filter.equals("")) { @@ -241,13 +266,22 @@ final class UnitConverterGUI { } /** - * @return a list model of all fo the prefix names + * @return a list model of all of the prefix names * @since 2019-01-15 */ public final ListModel prefixNameListModel() { return this.prefixNamesFiltered; } + /** + * Runs whenever a prefix is selected in the viewer. + *

    + * Shows its information in the text box to the right. + *

    + * + * @since 2019-01-15 + * @since v0.1.0 + */ public final void prefixSelected() { final int index = this.view.getPrefixListSelection(); if (index == -1) @@ -269,6 +303,15 @@ final class UnitConverterGUI { this.significantFigures = significantFigures; } + /** + * Runs whenever the unit filter is changed. + *

    + * Filters the unit list then sorts it using a {@code FilterComparator}. + *

    + * + * @since 2019-01-15 + * @since v0.1.0 + */ public final void unitFilterUpdated() { final String filter = this.view.getUnitFilterText(); if (filter.equals("")) { @@ -280,8 +323,13 @@ final class UnitConverterGUI { } /** + * Runs whenever a unit is selected in the viewer. + *

    + * Shows its information in the text box to the right. + *

    * * @since 2019-01-15 + * @since v0.1.0 */ public void unitNameSelected() { final int index = this.view.getUnitListSelection(); @@ -302,26 +350,37 @@ final class UnitConverterGUI { /** The view's associated presenter. */ private final Presenter presenter; + /** The list of unit names in the unit viewer */ private final JList unitNameList; + /** The list of prefix names in the prefix viewer */ private final JList prefixNameList; + /** The unit search box in the unit viewer */ private final JTextField unitFilterEntry; + /** The text box for unit data in the unit viewer */ private final JTextArea unitTextBox; + /** The prefix search box in the prefix viewer */ private final JTextField prefixFilterEntry; + /** The text box for prefix data in the prefix viewer */ private final JTextArea prefixTextBox; + /** The "From" entry in the conversion panel */ private final JTextField fromEntry; + /** The "To" entry in the conversion panel */ private final JTextField toEntry; + /** The output area in the conversion panel */ private final JTextArea output; /** * Creates the {@code View}. * * @since 2019-01-14 + * @since v0.1.0 */ public View() { this.presenter = new Presenter(this); this.frame = new JFrame("Unit Converter"); this.frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); + // create the components this.unitNameList = new JList<>(this.presenter.keyListModel()); this.prefixNameList = new JList<>(this.presenter.prefixNameListModel()); this.unitFilterEntry = new JTextField(); @@ -332,11 +391,17 @@ final class UnitConverterGUI { this.toEntry = new JTextField(); this.output = new JTextArea(2, 32); + // create more components this.initComponents(); this.frame.pack(); } + /** + * @return text in "From" box in converter panel + * @since 2019-01-15 + * @since v0.1.0 + */ public String getFromText() { return this.fromEntry.getText(); } @@ -344,6 +409,7 @@ final class UnitConverterGUI { /** * @return text in prefix filter * @since 2019-01-15 + * @since v0.1.0 */ public String getPrefixFilterText() { return this.prefixFilterEntry.getText(); @@ -352,11 +418,17 @@ final class UnitConverterGUI { /** * @return index of selected prefix * @since 2019-01-15 + * @since v0.1.0 */ public int getPrefixListSelection() { return this.prefixNameList.getSelectedIndex(); } + /** + * @return text in "To" box in converter panel + * @since 2019-01-26 + * @since v0.1.0 + */ public String getToText() { return this.toEntry.getText(); } @@ -372,6 +444,7 @@ final class UnitConverterGUI { /** * @return index of selected unit * @since 2019-01-15 + * @since v0.1.0 */ public int getUnitListSelection() { return this.unitNameList.getSelectedIndex(); @@ -381,6 +454,7 @@ final class UnitConverterGUI { * Starts up the application. * * @since 2018-12-27 + * @since v0.1.0 */ public final void init() { this.frame.setVisible(true); @@ -390,6 +464,7 @@ final class UnitConverterGUI { * Initializes the view's components. * * @since 2018-12-27 + * @since v0.1.0 */ private final void initComponents() { final JPanel masterPanel = new JPanel(); @@ -451,7 +526,7 @@ final class UnitConverterGUI { } } - { + { // panel for specifying precision final JPanel sigDigPanel = new JPanel(); convertPanel.add(sigDigPanel); @@ -485,7 +560,7 @@ final class UnitConverterGUI { listPanel.setLayout(new BorderLayout()); - { + { // unit search box listPanel.add(this.unitFilterEntry, BorderLayout.PAGE_START); this.unitFilterEntry.addCaretListener(e -> this.presenter.unitFilterUpdated()); } @@ -512,13 +587,13 @@ final class UnitConverterGUI { prefixLookupPanel.setLayout(new GridLayout(1, 2)); - { + { // panel for listing and seaching final JPanel prefixListPanel = new JPanel(); prefixLookupPanel.add(prefixListPanel); prefixListPanel.setLayout(new BorderLayout()); - { + { // prefix search box prefixListPanel.add(this.prefixFilterEntry, BorderLayout.PAGE_START); this.prefixFilterEntry.addCaretListener(e -> this.presenter.prefixFilterUpdated()); } @@ -540,6 +615,14 @@ final class UnitConverterGUI { } } + /** + * Sets the text in the output of the conversion panel. + * + * @param text + * text to set + * @since 2019-01-15 + * @since v0.1.0 + */ public void setOutputText(final String text) { this.output.setText(text); } @@ -550,6 +633,7 @@ final class UnitConverterGUI { * @param text * text to set * @since 2019-01-15 + * @since v0.1.0 */ public void setPrefixTextBoxText(final String text) { this.prefixTextBox.setText(text); @@ -574,6 +658,7 @@ final class UnitConverterGUI { * @param message * message in dialog * @since 2019-01-14 + * @since v0.1.0 */ public void showErrorDialog(final String title, final String message) { JOptionPane.showMessageDialog(this.frame, message, title, JOptionPane.ERROR_MESSAGE); diff --git a/src/unitConverter/dimension/BaseDimension.java b/src/unitConverter/dimension/BaseDimension.java index 6a727a9..0c09dce 100755 --- a/src/unitConverter/dimension/BaseDimension.java +++ b/src/unitConverter/dimension/BaseDimension.java @@ -21,17 +21,20 @@ package unitConverter.dimension; * * @author Adrien Hopkins * @since 2018-12-22 + * @since v0.1.0 */ public interface BaseDimension { /** * @return the dimension's name - * @since 2019-01-25 + * @since 2018-12-22 + * @since v0.1.0 */ String getName(); /** * @return a short string (usually one character) that represents this base dimension * @since 2018-12-22 + * @since v0.1.0 */ String getSymbol(); } diff --git a/src/unitConverter/dimension/OtherBaseDimension.java b/src/unitConverter/dimension/OtherBaseDimension.java index 2ab19b3..8c6d25d 100755 --- a/src/unitConverter/dimension/OtherBaseDimension.java +++ b/src/unitConverter/dimension/OtherBaseDimension.java @@ -23,6 +23,7 @@ import java.util.Objects; * * @author Adrien Hopkins * @since 2019-01-14 + * @since v0.1.0 */ public enum OtherBaseDimension implements BaseDimension { INFORMATION("Info"), CURRENCY("$$"); @@ -36,6 +37,7 @@ public enum OtherBaseDimension implements BaseDimension { * @param symbol * dimension's symbol * @since 2018-12-11 + * @since v0.1.0 */ private OtherBaseDimension(final String symbol) { this.symbol = Objects.requireNonNull(symbol, "symbol must not be null."); diff --git a/src/unitConverter/dimension/SIBaseDimension.java b/src/unitConverter/dimension/SIBaseDimension.java index b731b00..928d8d6 100755 --- a/src/unitConverter/dimension/SIBaseDimension.java +++ b/src/unitConverter/dimension/SIBaseDimension.java @@ -23,6 +23,7 @@ import java.util.Objects; * * @author Adrien Hopkins * @since 2018-12-11 + * @since v0.1.0 */ public enum SIBaseDimension implements BaseDimension { LENGTH("L"), MASS("M"), TIME("T"), ELECTRIC_CURRENT("I"), TEMPERATURE("\u0398"), // u0398 is the theta symbol @@ -37,6 +38,7 @@ public enum SIBaseDimension implements BaseDimension { * @param symbol * dimension's symbol * @since 2018-12-11 + * @since v0.1.0 */ private SIBaseDimension(final String symbol) { this.symbol = Objects.requireNonNull(symbol, "symbol must not be null."); diff --git a/src/unitConverter/dimension/StandardDimensions.java b/src/unitConverter/dimension/StandardDimensions.java index c830f00..b3edb7d 100755 --- a/src/unitConverter/dimension/StandardDimensions.java +++ b/src/unitConverter/dimension/StandardDimensions.java @@ -21,6 +21,7 @@ package unitConverter.dimension; * * @author Adrien Hopkins * @since 2018-12-11 + * @since v0.1.0 */ public final class StandardDimensions { // base dimensions diff --git a/src/unitConverter/dimension/UnitDimension.java b/src/unitConverter/dimension/UnitDimension.java index ba2a750..40e5bbc 100755 --- a/src/unitConverter/dimension/UnitDimension.java +++ b/src/unitConverter/dimension/UnitDimension.java @@ -29,12 +29,14 @@ import java.util.Set; * * @author Adrien Hopkins * @since 2018-12-11 + * @since v0.1.0 */ public final class UnitDimension { /** * The unit dimension where every exponent is zero * * @since 2018-12-12 + * @since v0.1.0 */ public static final UnitDimension EMPTY = new UnitDimension(new HashMap<>()); @@ -45,6 +47,7 @@ public final class UnitDimension { * dimension to get * @return unit dimension * @since 2018-12-11 + * @since v0.1.0 */ public static final UnitDimension getBase(final BaseDimension dimension) { final Map map = new HashMap<>(); @@ -56,6 +59,7 @@ public final class UnitDimension { * The base dimensions that make up this dimension. * * @since 2018-12-11 + * @since v0.1.0 */ final Map exponents; @@ -65,6 +69,7 @@ public final class UnitDimension { * @param exponents * base dimensions that make up this dimension * @since 2018-12-11 + * @since v0.1.0 */ private UnitDimension(final Map exponents) { this.exponents = new HashMap<>(exponents); @@ -77,6 +82,7 @@ public final class UnitDimension { * other dimension * @return quotient of two dimensions * @since 2018-12-11 + * @since v0.1.0 */ public UnitDimension dividedBy(final UnitDimension other) { final Map map = new HashMap<>(this.exponents); @@ -119,6 +125,7 @@ public final class UnitDimension { /** * @return a set of all of the base dimensions with non-zero exponents that make up this dimension. * @since 2018-12-12 + * @since v0.1.0 */ public final Set getBaseSet() { final Set dimensions = new HashSet<>(); @@ -140,6 +147,7 @@ public final class UnitDimension { * dimension to check * @return exponent for that dimension * @since 2018-12-12 + * @since v0.1.0 */ public int getExponent(final BaseDimension dimension) { return this.exponents.getOrDefault(dimension, 0); @@ -153,6 +161,7 @@ public final class UnitDimension { /** * @return true if this dimension is a base, i.e. it has one exponent of one and no other nonzero exponents * @since 2019-01-15 + * @since v0.1.0 */ public boolean isBase() { int oneCount = 0; @@ -174,6 +183,7 @@ public final class UnitDimension { * other dimension * @return product of two dimensions * @since 2018-12-11 + * @since v0.1.0 */ public UnitDimension times(final UnitDimension other) { final Map map = new HashMap<>(this.exponents); @@ -196,6 +206,7 @@ public final class UnitDimension { * exponent * @return result of exponientation * @since 2019-01-15 + * @since v0.1.0 */ public UnitDimension toExponent(final int exp) { final Map map = new HashMap<>(this.exponents); diff --git a/src/unitConverter/dimension/UnitDimensionTest.java b/src/unitConverter/dimension/UnitDimensionTest.java index 603320b..86db1b8 100755 --- a/src/unitConverter/dimension/UnitDimensionTest.java +++ b/src/unitConverter/dimension/UnitDimensionTest.java @@ -30,22 +30,43 @@ import static unitConverter.dimension.StandardDimensions.VOLUME; import org.junit.jupiter.api.Test; /** + * Tests for {@link UnitDimension}. + * * @author Adrien Hopkins * @since 2018-12-12 + * @since v0.1.0 */ class UnitDimensionTest { + /** + * Tests {@link UnitDimension#equals} + * + * @since 2018-12-12 + * @since v0.1.0 + */ @Test void testEquals() { assertEquals(LENGTH, LENGTH); assertFalse(LENGTH.equals(QUANTITY)); } + /** + * Tests {@code UnitDimension}'s exponentiation + * + * @since 2019-01-15 + * @since v0.1.0 + */ @Test void testExponents() { assertEquals(1, LENGTH.getExponent(SIBaseDimension.LENGTH)); assertEquals(3, VOLUME.getExponent(SIBaseDimension.LENGTH)); } + /** + * Tests {@code UnitDimension}'s multiplication and division. + * + * @since 2018-12-12 + * @since v0.1.0 + */ @Test void testMultiplicationAndDivision() { assertEquals(AREA, LENGTH.times(LENGTH)); diff --git a/src/unitConverter/unit/AbstractUnit.java b/src/unitConverter/unit/AbstractUnit.java index d3d6dbd..24814e8 100644 --- a/src/unitConverter/unit/AbstractUnit.java +++ b/src/unitConverter/unit/AbstractUnit.java @@ -24,13 +24,15 @@ import unitConverter.dimension.UnitDimension; * The default abstract implementation of the {@code Unit} interface. * * @author Adrien Hopkins - * @since 2019-01-25 + * @since 2018-12-22 + * @since v0.1.0 */ public abstract class AbstractUnit implements Unit { /** * The number of units created, including base units. * * @since 2019-01-02 + * @since v0.1.0 */ private static long unitCount = 0; @@ -38,12 +40,14 @@ public abstract class AbstractUnit implements Unit { * The number of base units created. * * @since 2019-01-02 + * @since v0.1.0 */ private static long baseUnitCount = 0; /** * @return number of base units created * @since 2019-01-02 + * @since v0.1.0 */ public static final long getBaseUnitCount() { return baseUnitCount; @@ -52,6 +56,7 @@ public abstract class AbstractUnit implements Unit { /** * @return number of units created * @since 2019-01-02 + * @since v0.1.0 */ public static final long getUnitCount() { return unitCount; @@ -61,6 +66,7 @@ public abstract class AbstractUnit implements Unit { * Increments the number of base units. * * @since 2019-01-15 + * @since v0.1.0 */ public static final void incrementBaseUnitCounter() { baseUnitCount++; @@ -70,6 +76,7 @@ public abstract class AbstractUnit implements Unit { * Increments the number of units. * * @since 2019-01-15 + * @since v0.1.0 */ public static final void incrementUnitCounter() { unitCount++; @@ -79,6 +86,7 @@ public abstract class AbstractUnit implements Unit { * The dimension, or what the unit measures. * * @since 2018-12-22 + * @since v0.1.0 */ private final UnitDimension dimension; @@ -87,6 +95,7 @@ public abstract class AbstractUnit implements Unit { * unit. * * @since 2018-12-22 + * @since v0.1.0 */ private final BaseUnit base; @@ -94,6 +103,7 @@ public abstract class AbstractUnit implements Unit { * The system that this unit is a part of. * * @since 2018-12-23 + * @since v0.1.0 */ private final UnitSystem system; @@ -105,6 +115,7 @@ public abstract class AbstractUnit implements Unit { * @throws NullPointerException * if name, symbol or base is null * @since 2018-12-22 + * @since v0.1.0 */ public AbstractUnit(final BaseUnit base) { this.base = Objects.requireNonNull(base, "base must not be null."); @@ -125,6 +136,7 @@ public abstract class AbstractUnit implements Unit { * @throws NullPointerException * if name, symbol or dimension is null * @since 2018-12-23 + * @since v0.1.0 */ AbstractUnit(final UnitDimension dimension, final UnitSystem system) { // try to set this as a base unit diff --git a/src/unitConverter/unit/BaseUnit.java b/src/unitConverter/unit/BaseUnit.java index 204b1cd..d4a1e9c 100755 --- a/src/unitConverter/unit/BaseUnit.java +++ b/src/unitConverter/unit/BaseUnit.java @@ -26,12 +26,14 @@ import unitConverter.dimension.UnitDimension; * * @author Adrien Hopkins * @since 2018-12-23 + * @since v0.1.0 */ public final class BaseUnit extends AbstractUnit { /** * Is this unit a full base (i.e. m, s, ... but not N, J, ...) * * @since 2019-01-15 + * @since v0.1.0 */ private final boolean isFullBase; @@ -47,6 +49,7 @@ public final class BaseUnit extends AbstractUnit { * @param symbol * symbol of unit * @since 2018-12-23 + * @since v0.1.0 */ BaseUnit(final UnitDimension dimension, final UnitSystem system) { super(dimension, system); @@ -56,6 +59,7 @@ public final class BaseUnit extends AbstractUnit { /** * @return this unit as a {@code LinearUnit} * @since 2019-01-25 + * @since v0.1.0 */ public LinearUnit asLinearUnit() { return this.times(1); @@ -82,6 +86,7 @@ public final class BaseUnit extends AbstractUnit { * @throws NullPointerException * if other is null * @since 2018-12-22 + * @since v0.1.0 */ public BaseUnit dividedBy(final BaseUnit other) { Objects.requireNonNull(other, "other must not be null."); @@ -97,6 +102,7 @@ public final class BaseUnit extends AbstractUnit { * amount to divide by * @return quotient * @since 2018-12-23 + * @since v0.1.0 */ public LinearUnit dividedBy(final double divisor) { return new LinearUnit(this, 1 / divisor); @@ -113,6 +119,7 @@ public final class BaseUnit extends AbstractUnit { * @throws NullPointerException * if other is null * @since 2018-12-22 + * @since v0.1.0 */ public BaseUnit times(final BaseUnit other) { Objects.requireNonNull(other, "other must not be null."); @@ -127,7 +134,8 @@ public final class BaseUnit extends AbstractUnit { * @param multiplier * amount to multiply by * @return product - * @since 2018-12-23B + * @since 2018-12-23 + * @since v0.1.0 */ public LinearUnit times(final double multiplier) { return new LinearUnit(this, multiplier); @@ -140,6 +148,7 @@ public final class BaseUnit extends AbstractUnit { * exponent * @return result of exponentiation * @since 2019-01-15 + * @since v0.1.0 */ public BaseUnit toExponent(final int exponent) { return this.getSystem().getBaseUnit(this.getDimension().toExponent(exponent)); diff --git a/src/unitConverter/unit/DefaultUnitPrefix.java b/src/unitConverter/unit/DefaultUnitPrefix.java index 1cce413..d19161b 100755 --- a/src/unitConverter/unit/DefaultUnitPrefix.java +++ b/src/unitConverter/unit/DefaultUnitPrefix.java @@ -19,8 +19,11 @@ package unitConverter.unit; import java.util.Objects; /** + * The default implementation of {@code UnitPrefix}, which contains a multiplier and nothing else. + * * @author Adrien Hopkins * @since 2019-01-14 + * @since v0.1.0 */ public final class DefaultUnitPrefix implements UnitPrefix { private final double multiplier; diff --git a/src/unitConverter/unit/LinearUnit.java b/src/unitConverter/unit/LinearUnit.java index e2c9eb2..6514ff4 100644 --- a/src/unitConverter/unit/LinearUnit.java +++ b/src/unitConverter/unit/LinearUnit.java @@ -24,13 +24,15 @@ import unitConverter.dimension.UnitDimension; * A unit that is equal to a certain number multiplied by its base. * * @author Adrien Hopkins - * @since 2019-01-25 + * @since 2018-12-22 + * @since v0.1.0 */ public final class LinearUnit extends AbstractUnit { /** * The value of one of this unit in this unit's base unit * * @since 2018-12-22 + * @since v0.1.0 */ private final double conversionFactor; @@ -43,6 +45,7 @@ public final class LinearUnit extends AbstractUnit { * @param conversionFactor * value of one of this unit in its base * @since 2018-12-23 + * @since v0.1.0 */ LinearUnit(final BaseUnit base, final double conversionFactor) { super(base); @@ -57,6 +60,7 @@ public final class LinearUnit extends AbstractUnit { * @param system * system unit is part of * @since 2019-01-25 + * @since v0.1.0 */ LinearUnit(final UnitDimension dimension, final UnitSystem system, final double conversionFactor) { super(dimension, system); @@ -80,6 +84,7 @@ public final class LinearUnit extends AbstractUnit { * scalar to divide by * @return quotient * @since 2018-12-23 + * @since v0.1.0 */ public LinearUnit dividedBy(final double divisor) { return new LinearUnit(this.getBase(), this.getConversionFactor() / divisor); @@ -94,6 +99,7 @@ public final class LinearUnit extends AbstractUnit { * @throws NullPointerException * if other is null * @since 2018-12-22 + * @since v0.1.0 */ public LinearUnit dividedBy(final LinearUnit other) { Objects.requireNonNull(other, "other must not be null"); @@ -103,7 +109,8 @@ public final class LinearUnit extends AbstractUnit { /** * @return conversionFactor - * @since 2019-01-25 + * @since 2018-12-22 + * @since v0.1.0 */ public final double getConversionFactor() { return this.conversionFactor; @@ -116,6 +123,7 @@ public final class LinearUnit extends AbstractUnit { * scalar to multiply by * @return product * @since 2018-12-23 + * @since v0.1.0 */ public LinearUnit times(final double multiplier) { return new LinearUnit(this.getBase(), this.getConversionFactor() * multiplier); @@ -130,6 +138,7 @@ public final class LinearUnit extends AbstractUnit { * @throws NullPointerException * if other is null * @since 2018-12-22 + * @since v0.1.0 */ public LinearUnit times(final LinearUnit other) { Objects.requireNonNull(other, "other must not be null"); @@ -144,6 +153,7 @@ public final class LinearUnit extends AbstractUnit { * exponent to exponientate unit to * @return exponientated unit * @since 2019-01-15 + * @since v0.1.0 */ public LinearUnit toExponent(final int exponent) { return new LinearUnit(this.getBase().toExponent(exponent), Math.pow(this.conversionFactor, exponent)); diff --git a/src/unitConverter/unit/NonlinearUnits.java b/src/unitConverter/unit/NonlinearUnits.java index f7e257c..ec1874c 100755 --- a/src/unitConverter/unit/NonlinearUnits.java +++ b/src/unitConverter/unit/NonlinearUnits.java @@ -21,6 +21,7 @@ package unitConverter.unit; * * @author Adrien Hopkins * @since 2019-01-14 + * @since v0.1.0 */ public final class NonlinearUnits { public static final Unit CELSIUS = new AbstractUnit(SI.KELVIN) { diff --git a/src/unitConverter/unit/SI.java b/src/unitConverter/unit/SI.java index cda42e7..54eb4c5 100644 --- a/src/unitConverter/unit/SI.java +++ b/src/unitConverter/unit/SI.java @@ -28,6 +28,7 @@ import unitConverter.dimension.UnitDimension; * * @author Adrien Hopkins * @since 2018-12-23 + * @since v0.1.0 */ public enum SI implements UnitSystem { SI; @@ -36,6 +37,7 @@ public enum SI implements UnitSystem { * This system's base units. * * @since 2019-01-25 + * @since v0.1.0 */ private static final Set baseUnits = new HashSet<>(); diff --git a/src/unitConverter/unit/SIPrefix.java b/src/unitConverter/unit/SIPrefix.java index 39f1a8c..54625fb 100755 --- a/src/unitConverter/unit/SIPrefix.java +++ b/src/unitConverter/unit/SIPrefix.java @@ -17,8 +17,11 @@ package unitConverter.unit; /** + * The SI prefixes. + * * @author Adrien Hopkins * @since 2019-01-14 + * @since v0.1.0 */ public enum SIPrefix implements UnitPrefix { DECA(10), HECTO(100), KILO(1e3), MEGA(1e6), GIGA(1e9), TERA(1e12), PETA(1e15), EXA(1e18), ZETTA(1e21), YOTTA( @@ -33,6 +36,7 @@ public enum SIPrefix implements UnitPrefix { * @param multiplier * prefix's multiplier * @since 2019-01-14 + * @since v0.1.0 */ private SIPrefix(final double multiplier) { this.multiplier = multiplier; @@ -41,6 +45,7 @@ public enum SIPrefix implements UnitPrefix { /** * @return value * @since 2019-01-14 + * @since v0.1.0 */ @Override public final double getMultiplier() { diff --git a/src/unitConverter/unit/Unit.java b/src/unitConverter/unit/Unit.java index 9e1375a..54f1423 100755 --- a/src/unitConverter/unit/Unit.java +++ b/src/unitConverter/unit/Unit.java @@ -25,6 +25,7 @@ import unitConverter.dimension.UnitDimension; * * @author Adrien Hopkins * @since 2018-12-22 + * @since v0.1.0 */ public interface Unit { /** @@ -34,6 +35,7 @@ public interface Unit { * unit to test with * @return true if the units are compatible * @since 2019-01-13 + * @since v0.1.0 */ default boolean canConvertTo(final Unit other) { return Objects.equals(this.getBase(), other.getBase()); @@ -53,6 +55,7 @@ public interface Unit { * value expressed in base unit * @return value expressed in this unit * @since 2018-12-22 + * @since v0.1.0 */ double convertFromBase(double value); @@ -70,6 +73,7 @@ public interface Unit { * value expressed in this unit * @return value expressed in base unit * @since 2018-12-22 + * @since v0.1.0 */ double convertToBase(double value); @@ -86,18 +90,21 @@ public interface Unit { * * @return base unit associated with this unit * @since 2018-12-22 + * @since v0.1.0 */ BaseUnit getBase(); /** * @return dimension measured by this unit * @since 2018-12-22 + * @since v0.1.0 */ UnitDimension getDimension(); /** * @return system that this unit is a part of * @since 2018-12-23 + * @since v0.1.0 */ UnitSystem getSystem(); } diff --git a/src/unitConverter/unit/UnitPrefix.java b/src/unitConverter/unit/UnitPrefix.java index cb50fd9..a0c1b7c 100755 --- a/src/unitConverter/unit/UnitPrefix.java +++ b/src/unitConverter/unit/UnitPrefix.java @@ -21,11 +21,13 @@ package unitConverter.unit; * * @author Adrien Hopkins * @since 2019-01-14 + * @since v0.1.0 */ public interface UnitPrefix { /** * @return this prefix's multiplier * @since 2019-01-14 + * @since v0.1.0 */ double getMultiplier(); } diff --git a/src/unitConverter/unit/UnitSystem.java b/src/unitConverter/unit/UnitSystem.java index d641832..ce8c249 100755 --- a/src/unitConverter/unit/UnitSystem.java +++ b/src/unitConverter/unit/UnitSystem.java @@ -25,6 +25,7 @@ import unitConverter.dimension.UnitDimension; * * @author Adrien Hopkins * @since 2018-12-23 + * @since v0.1.0 */ public interface UnitSystem { /** @@ -36,6 +37,7 @@ public interface UnitSystem { * @throws NullPointerException * if dimension is null * @since 2019-01-25 + * @since v0.1.0 */ default BaseUnit getBaseUnit(final UnitDimension dimension) { Objects.requireNonNull(dimension, "dimension must not be null."); @@ -44,7 +46,8 @@ public interface UnitSystem { /** * @return name of system - * @since 2019-01-25 + * @since 2018-12-23 + * @since v0.1.0 */ String getName(); } diff --git a/src/unitConverter/unit/UnitTest.java b/src/unitConverter/unit/UnitTest.java index 7e16123..c3237eb 100755 --- a/src/unitConverter/unit/UnitTest.java +++ b/src/unitConverter/unit/UnitTest.java @@ -26,7 +26,7 @@ import unitConverter.dimension.StandardDimensions; * Testing the various Unit classes * * @author Adrien Hopkins - * @since 2019-01-26 + * @since 2018-12-22 */ class UnitTest { @Test -- cgit v1.2.3 From ff30d9ab06bfc93012b90d24af57505b3d9c70d4 Mon Sep 17 00:00:00 2001 From: Adrien Hopkins Date: Sat, 26 Jan 2019 14:14:03 -0500 Subject: Edited README to add more newlines. --- README.org | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/README.org b/README.org index 9fa913d..175db07 100644 --- a/README.org +++ b/README.org @@ -8,19 +8,24 @@ - All of SI included in default text file - Choose your precision * How to Convert -To convert units, simply enter the first unit in the From box and the second unit in the To box. Press Convert and a result will appear in the output box. Both boxes accept unit expressions, so you can input things like ‘1.7 m’, ‘85 km/h’ and ‘35 A * 14 s’ into either box. +To convert units, simply enter the first unit in the From box and the second unit in the To box. Press Convert and a result will appear in the output box. Both boxes accept unit expressions, so you can input things like ‘1.7 m’, ‘85 km/h’ and ‘35 A * 14 s’ into either box. + *Warning*: The space between the number and the unit name is required, or else the whole expression will be interpreted as a unit name. Spaces are not required for operators. + Use the slider at the bottom to choose the maximum precision of the result, in significant digits, not decimal places. * Units Files and Expressions -As mentioned previously, all units are loaded from a units file. The format for lines consists of a name for the unit, some tabs (not spaces), and an expression. The following operations are supported in expressions +As mentioned previously, all units are loaded from a units file. The format for lines consists of a name for the unit, some tabs (not spaces), and an expression. The following operations are supported in expressions: - Multiplication using spaces ( ) or asterisks (*) - Division using the forward slash (/) - Exponentiation using the caret (^) Every argument can be either a linear unit, a base unit or a number, except exponents which must be integers. There is no way to use brackets to manipulate order of operations. Example (Explanation provided after # sign, this will *not* work in a real units file): -inch 25.4 mm # Define ‘inch’ as equal to the product of 25.4 and ‘mm’ + +inch 25.4 mm # Define ‘inch’ as equal to the product of 25.4 and ‘mm’ + mph mile / hour # Define ‘mph’ as equal to the quotient of ‘mile’ and ‘hour’ + litre 0.001 m^3 # Define ‘litre’ as equal to the product of 0.001 and the unit ‘m’ raised to the exponent 3 Lines that start with the number sign (#) and blank lines will be ignored. @@ -31,21 +36,27 @@ Unit prefixes are defined differently. When a unit name ends with the dash (-) - the name of another prefix without the dash (kilo) * Unit Prefixes In SI, you can have a unit prefix, which you attach to the start of a unit and it multiplies the unit by a certain value. For example, milli-metre = 0.001 * metre. This unit converter supports unit prefixes, and you can put any number of prefixes before a linear or base unit to transform it (this includes non-SI units). -You can use more than one prefix at once (yottayottametre = really really long distance), but you may not convert prefixes alone. If you want to do that, use ‘unit’ or ‘u’ (ku = 1000000 mu). You can also use u for converting from numbers (tau = 6.28319 u). +You can use more than one prefix at once (yottayottametre = really really long distance), but you may not convert prefixes alone. If you want to do that, use ‘unit’ or ‘u’ (ku = 1000000 mu). You can also use u for converting from numbers (pi = 3.141593 u). + *Warning*: While the standard symbol for ‘deca’ is ‘da’, this program uses ‘D’ instead since ‘da’ can be confused with ‘deciatto’. Also, you may use capital letters for the symbols of ‘hecto’ and ‘kilo’. + *Warning*: The standard prefixes will never use 1024 instead of 1000, even when operating on bits and bytes. Use ‘Ki’, ‘Mi’, ‘Gi’, ‘Ti’ and so on instead. * Nonlinear Units Sometimes, units cannot be converted from and to by simply multiplying and dividing. A common example of this is the Celsius and Fahrenheit temperature scales, which require multiplication and addition to convert to each other (and to their base, Kelvin). + To use nonlinear units, use the following syntax: FROM: unit1(value) TO: unit2 + Nonlinear units cannot: - multiply, divide or exponentiate - use prefixes - be defined by unit files + To define a nonlinear unit, make an anonymous inner type (or any other subclass) of AbstractUnit, and define the conversion methods. * Unit and Prefix Viewers The unit and prefix viewers can be used to see the available units (without prefixes) and prefixes. Upon opening them, you will see a list of units or prefixes on your left. Using the text box above, the list can be filtered. When a unit is clicked on, details about will be displayed on the right. * Copyright and Licences The Unit Converter program is Copyright (C) 2018, 2019 Adrien Hopkins. It is released under the terms of the AGPL v3 licence. + This document is Copyright (C) 2019 Adrien Hopkins. It is released under the terms of the CC BY-SA (Creative Commons Attribution-ShareAlike) licence. -- cgit v1.2.3 From a6a66e6fb11675e0ea738ad8d0b5a9ba7d2fac2b Mon Sep 17 00:00:00 2001 From: Adrien Hopkins Date: Sun, 27 Jan 2019 16:05:55 -0500 Subject: HOTFIX: Added equals and hashCode to the unit classes --- src/unitConverter/unit/BaseUnit.java | 18 ++++++++++++++++++ src/unitConverter/unit/LinearUnit.java | 18 ++++++++++++++++++ 2 files changed, 36 insertions(+) diff --git a/src/unitConverter/unit/BaseUnit.java b/src/unitConverter/unit/BaseUnit.java index d4a1e9c..fe36c45 100755 --- a/src/unitConverter/unit/BaseUnit.java +++ b/src/unitConverter/unit/BaseUnit.java @@ -108,6 +108,24 @@ public final class BaseUnit extends AbstractUnit { return new LinearUnit(this, 1 / divisor); } + @Override + public boolean equals(final Object obj) { + if (!(obj instanceof BaseUnit)) + return false; + final BaseUnit other = (BaseUnit) obj; + return Objects.equals(this.getSystem(), other.getSystem()) + && Objects.equals(this.getDimension(), other.getDimension()); + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = result * prime + this.getSystem().hashCode(); + result = result * prime + this.getDimension().hashCode(); + return result; + } + /** * Multiplies this unit by another unit. * diff --git a/src/unitConverter/unit/LinearUnit.java b/src/unitConverter/unit/LinearUnit.java index 6514ff4..b786b3b 100644 --- a/src/unitConverter/unit/LinearUnit.java +++ b/src/unitConverter/unit/LinearUnit.java @@ -107,6 +107,15 @@ public final class LinearUnit extends AbstractUnit { return new LinearUnit(base, this.getConversionFactor() / other.getConversionFactor()); } + @Override + public boolean equals(final Object obj) { + if (!(obj instanceof LinearUnit)) + return false; + final LinearUnit other = (LinearUnit) obj; + return Objects.equals(this.getBase(), other.getBase()) + && Objects.equals(this.getConversionFactor(), other.getConversionFactor()); + } + /** * @return conversionFactor * @since 2018-12-22 @@ -116,6 +125,15 @@ public final class LinearUnit extends AbstractUnit { return this.conversionFactor; } + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = result * prime + this.getBase().hashCode(); + result = result * prime + Double.hashCode(this.getConversionFactor()); + return result; + } + /** * Multiplies this unit by a scalar. * -- cgit v1.2.3