001/* 002 * ============================================================================ 003 * Copyright © 2002-2024 by Thomas Thrien. 004 * All Rights Reserved. 005 * ============================================================================ 006 * Licensed to the public under the agreements of the GNU Lesser General Public 007 * License, version 3.0 (the "License"). You may obtain a copy of the License at 008 * 009 * http://www.gnu.org/licenses/lgpl.html 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 013 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 014 * License for the specific language governing permissions and limitations 015 * under the License. 016 */ 017 018package org.tquadrat.foundation.value.api; 019 020import static java.lang.String.format; 021import static java.util.Locale.ROOT; 022import static org.apiguardian.api.API.Status.STABLE; 023import static org.tquadrat.foundation.lang.Objects.hash; 024import static org.tquadrat.foundation.lang.Objects.requireNonNullArgument; 025import static org.tquadrat.foundation.lang.Objects.requireNotEmptyArgument; 026 027import java.io.Serial; 028import java.math.BigDecimal; 029import java.util.function.BiPredicate; 030 031import org.apiguardian.api.API; 032import org.tquadrat.foundation.annotation.ClassVersion; 033import org.tquadrat.foundation.exception.UnexpectedExceptionError; 034 035/** 036 * <p>{@summary An abstract base implementation for the interface 037 * {@link DimensionedValue} 038 * that is intended as the base for concrete implementations of value 039 * types.}</p> 040 * <p>The {@code validator} argument of the constructors is an instance of 041 * {@link BiPredicate} 042 * that takes the 043 * {@linkplain Dimension unit} 044 * with that the instance is initialised, and the value, converted to the base 045 * unit. It returns {@code true} if the given combination is valid, otherwise 046 * it returns {@code false}.</p> 047 * 048 * @extauthor Thomas Thrien - thomas.thrien@tquadrat.org 049 * @version $Id: ValueBase.java 1105 2024-02-28 12:58:46Z tquadrat $ 050 * @since 0.0.4 051 * 052 * @param <D> The dimension. 053 * @param <I> The implementing type. 054 * 055 * @UMLGraph.link 056 */ 057@ClassVersion( sourceVersion = "$Id: ValueBase.java 1105 2024-02-28 12:58:46Z tquadrat $" ) 058@API( status = STABLE, since = "0.0.4" ) 059public abstract non-sealed class ValueBase<D extends Dimension, I extends DimensionedValue<D>> implements DimensionedValue<D> 060{ 061 /*-----------*\ 062 ====** Constants **======================================================== 063 \*-----------*/ 064 /** 065 * The default validator. 066 */ 067 @SuppressWarnings( "rawtypes" ) 068 protected static final BiPredicate DEFAULT_VALIDATOR = ( unit, value) -> true; 069 070 /*------------*\ 071 ====** Attributes **======================================================= 072 \*------------*/ 073 /** 074 * The unit for the value. 075 * 076 * @serial 077 */ 078 private D m_Unit; 079 080 /** 081 * The validation function. 082 * 083 * @serial 084 */ 085 private final BiPredicate<D,BigDecimal> m_Validator; 086 087 /** 088 * The numerical value for this instance. 089 * 090 * @serial 091 */ 092 private final BigDecimal m_Value; 093 094 /*------------------------*\ 095 ====** Static Initialisations **=========================================== 096 \*------------------------*/ 097 /** 098 * The serial version UID for objects of this class: {@value}. 099 */ 100 @Serial 101 private static final long serialVersionUID = -2075489505691464486L; 102 103 /*--------------*\ 104 ====** Constructors **===================================================== 105 \*--------------*/ 106 /** 107 * Creates a new {@code ValueBase} instance. 108 * 109 * @param unit The unit. 110 * @param value The value; only absolute (positive) values are allowed, 111 * a sign will be stripped. 112 * @param validator The validator function; can be {@code null}. 113 */ 114 protected ValueBase( final D unit, final BigDecimal value, final BiPredicate<D,BigDecimal> validator ) 115 { 116 requireNonNullArgument( value, "value" ); 117 setUnit( unit ); 118 final var conversion = unit.toBase(); 119 m_Validator = requireNonNullArgument( validator, "validator" ); 120 m_Value = validate( conversion.apply( value ) ).abs().stripTrailingZeros(); 121 } // ValueBase() 122 123 /** 124 * Creates a new {@code ValueBase} instance. 125 * 126 * @param unit The unit. 127 * @param value The value; it must be possible to parse the given 128 * String into a 129 * {@link BigDecimal}. 130 * @param validator The validator function. 131 * @throws NumberFormatException The provided value cannot be converted 132 * into a {@code BigDecimal}. 133 */ 134 protected ValueBase( final D unit, final String value, final BiPredicate<D,BigDecimal> validator ) throws NumberFormatException 135 { 136 this( unit, new BigDecimal( requireNotEmptyArgument( value, "value" ) ), validator ); 137 } // ValueBase() 138 139 /** 140 * Creates a new {@code ValueBase} instance. 141 * 142 * @param <N> The type of {@code value}. 143 * @param unit The unit. 144 * @param value The value. 145 * @param validator The validator function. 146 */ 147 protected <N extends Number> ValueBase( final D unit, final N value, final BiPredicate<D,BigDecimal> validator ) 148 { 149 this( unit, new BigDecimal( requireNonNullArgument( value, "value" ).toString() ), validator ); 150 } // ValueBase() 151 152 /*---------*\ 153 ====** Methods **========================================================== 154 \*---------*/ 155 /** 156 * {@inheritDoc} 157 */ 158 @Override 159 public final BigDecimal baseValue() { return m_Value; } 160 161 /** 162 * {@inheritDoc} 163 */ 164 @Override 165 public I clone() 166 { 167 final I retValue; 168 try 169 { 170 @SuppressWarnings( "unchecked" ) 171 final var clone = (I) super.clone(); 172 retValue = clone; 173 } 174 catch( final CloneNotSupportedException e ) 175 { 176 throw new UnexpectedExceptionError( e ); 177 } 178 179 //---* Done *---------------------------------------------------------- 180 return retValue; 181 } // clone() 182 183 /** 184 * {@inheritDoc} 185 */ 186 @Override 187 public final I copy() 188 { 189 final var retValue = clone(); 190 191 //---* Done *---------------------------------------------------------- 192 return retValue; 193 } // copy() 194 195 /** 196 * {@inheritDoc} 197 */ 198 @Override 199 public final I copy( final D unit ) 200 { 201 final var retValue = copy(); 202 retValue.setUnit( unit ); // Does the null check ... 203 204 //---* Done *---------------------------------------------------------- 205 return retValue; 206 } // copy() 207 208 /** 209 * {@inheritDoc} 210 */ 211 @Override 212 public final boolean equals( final Object obj ) 213 { 214 var retValue = this == obj; 215 if( !retValue && (obj instanceof final ValueBase<?,?> other ) ) 216 { 217 if( baseUnit() == other.baseUnit() ) 218 { 219 retValue = baseValue().compareTo( other.baseValue() ) == 0; 220 } 221 } 222 223 //---* Done *---------------------------------------------------------- 224 return retValue; 225 } // equals() 226 227 /** 228 * {@inheritDoc} 229 */ 230 @Override 231 public final D getUnit() { return m_Unit; } 232 233 /** 234 * {@inheritDoc} 235 */ 236 @Override 237 public final int hashCode() { return hash( baseValue(), baseUnit() ); } 238 239 /** 240 * {@inheritDoc} 241 */ 242 @Override 243 public final void setUnit( final D dimension ) { m_Unit = requireNonNullArgument( dimension, "dimension" ); } 244 245 /** 246 * {@inheritDoc} 247 */ 248 @SuppressWarnings( "CallToNumericToString" ) 249 @Override 250 public final String toString() { return format( ROOT, "%s %s", convert( m_Unit ).toString(), m_Unit.unitSymbol() ); } 251 252 /** 253 * <p>{@summary Validates the given value, based on the type of the 254 * dimension.}</p> 255 * <p>This method will be called by the constructors always with the 256 * {@linkplain #baseUnit() base unit} 257 * value just before the value will be initialised.</p> 258 * 259 * @param value The value in base units. 260 * @return The value. 261 * @throws IllegalArgumentException The validation failed. 262 */ 263 private final BigDecimal validate( final BigDecimal value ) throws IllegalArgumentException 264 { 265 if( !m_Validator.test( m_Unit, requireNonNullArgument( value, "value" ) ) ) 266 { 267 //noinspection CallToNumericToString 268 throw new IllegalArgumentException( "Value %s is invalide".formatted( value.toString() ) ); 269 } 270 271 //---* Done *---------------------------------------------------------- 272 return value; 273 } // validate() 274} 275// class ValueBase 276 277/* 278 * End of File 279 */