001/* 002 * ============================================================================ 003 * Copyright © 2002-2026 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.lang.value; 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.UnlikelyExceptionError; 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 {@true} if the given combination is valid, otherwise 046 * it returns {@false}.</p> 047 * 048 * @author Thomas Thrien - thomas.thrien@tquadrat.org 049 * @version $Id: ValueBase.java 1295 2026-10-18 12:34:27Z tquadrat $ 050 * @since 0.25.1 051 * 052 * @param <D> The dimension. 053 * @param <I> The implementing type. 054 * 055 * @UMLGraph.link 056 */ 057@SuppressWarnings( "SerializableDeserializableClassInSecureContext" ) 058@ClassVersion( sourceVersion = "$Id: ValueBase.java 1295 2026-10-18 12:34:27Z tquadrat $" ) 059@API( status = STABLE, since = "0.25.1" ) 060public abstract non-sealed class ValueBase<D extends Dimension, I extends DimensionedValue<D>> implements DimensionedValue<D> 061{ 062 /*-----------*\ 063 ====** Constants **======================================================== 064 \*-----------*/ 065 /** 066 * <p>{@summary The default validator.}</p> 067 */ 068 @SuppressWarnings( "rawtypes" ) 069 protected static final BiPredicate DEFAULT_VALIDATOR = ( _, _ ) -> true; 070 071 /*------------*\ 072 ====** Attributes **======================================================= 073 \*------------*/ 074 /** 075 * <p>{@summary The unit for the value.}</p> 076 * 077 * @serial 078 */ 079 private D m_Unit; 080 081 /** 082 * <p>{@summary The validation function.}</p> 083 * 084 * @serial 085 */ 086 private final BiPredicate<D,BigDecimal> m_Validator; 087 088 /** 089 * <p>{@summary The numerical value for this instance.}</p> 090 * 091 * @serial 092 */ 093 private final BigDecimal m_Value; 094 095 /*------------------------*\ 096 ====** Static Initialisations **=========================================== 097 \*------------------------*/ 098 /** 099 * <p>{@summary The serial version UID for objects of this class: 100 * {@value}.}</p> 101 * 102 * @hidden 103 */ 104 @Serial 105 private static final long serialVersionUID = -2075489505691464486L; 106 107 /*--------------*\ 108 ====** Constructors **===================================================== 109 \*--------------*/ 110 /** 111 * <p>{@summary {@constructor}}</p> 112 * 113 * @param unit The unit. 114 * @param value The value; only absolute (positive) values are allowed, 115 * a sign will be stripped. 116 * @param validator The validator function; can be {@null}. 117 */ 118 protected ValueBase( final D unit, final BigDecimal value, final BiPredicate<D,BigDecimal> validator ) 119 { 120 requireNonNullArgument( value, "value" ); 121 setUnit( unit ); 122 final var conversion = unit.toBase(); 123 m_Validator = requireNonNullArgument( validator, "validator" ); 124 m_Value = validate( conversion.apply( value ) ).abs().stripTrailingZeros(); 125 } // ValueBase() 126 127 /** 128 * Creates a new {@code ValueBase} instance. 129 * 130 * @param unit The unit. 131 * @param value The value; it must be possible to parse the given 132 * String into a 133 * {@link BigDecimal}. 134 * @param validator The validator function. 135 * @throws NumberFormatException The provided value cannot be converted 136 * into a {@code BigDecimal}. 137 */ 138 protected ValueBase( final D unit, final String value, final BiPredicate<D,BigDecimal> validator ) throws NumberFormatException 139 { 140 this( unit, new BigDecimal( requireNotEmptyArgument( value, "value" ) ), validator ); 141 } // ValueBase() 142 143 /** 144 * Creates a new {@code ValueBase} instance. 145 * 146 * @param <N> The type of {@code value}. 147 * @param unit The unit. 148 * @param value The value. 149 * @param validator The validator function. 150 */ 151 protected <N extends Number> ValueBase( final D unit, final N value, final BiPredicate<D,BigDecimal> validator ) 152 { 153 this( unit, new BigDecimal( requireNonNullArgument( value, "value" ).toString() ), validator ); 154 } // ValueBase() 155 156 /*---------*\ 157 ====** Methods **========================================================== 158 \*---------*/ 159 /** 160 * {@inheritDoc} 161 */ 162 @Override 163 public final BigDecimal baseValue() { return m_Value; } 164 165 /** 166 * {@inheritDoc} 167 */ 168 @Override 169 public I clone() 170 { 171 final I retValue; 172 try 173 { 174 @SuppressWarnings( "unchecked" ) 175 final var clone = (I) super.clone(); 176 retValue = clone; 177 } 178 catch( final CloneNotSupportedException e ) 179 { 180 throw new UnlikelyExceptionError( e ); 181 } 182 183 //---* Done *---------------------------------------------------------- 184 return retValue; 185 } // clone() 186 187 /** 188 * {@inheritDoc} 189 */ 190 @Override 191 public final I copy() 192 { 193 final var retValue = clone(); 194 195 //---* Done *---------------------------------------------------------- 196 return retValue; 197 } // copy() 198 199 /** 200 * {@inheritDoc} 201 */ 202 @Override 203 public final I copy( final D unit ) 204 { 205 final var retValue = copy(); 206 retValue.setUnit( unit ); // Does the null check ... 207 208 //---* Done *---------------------------------------------------------- 209 return retValue; 210 } // copy() 211 212 /** 213 * {@inheritDoc} 214 */ 215 @Override 216 public final boolean equals( final Object obj ) 217 { 218 var retValue = this == obj; 219 if( !retValue && (obj instanceof final ValueBase<?,?> other ) ) 220 { 221 if( baseUnit() == other.baseUnit() ) 222 { 223 retValue = baseValue().compareTo( other.baseValue() ) == 0; 224 } 225 } 226 227 //---* Done *---------------------------------------------------------- 228 return retValue; 229 } // equals() 230 231 /** 232 * {@inheritDoc} 233 */ 234 @Override 235 public final D getUnit() { return m_Unit; } 236 237 /** 238 * {@inheritDoc} 239 */ 240 @Override 241 public final int hashCode() { return hash( baseValue(), baseUnit() ); } 242 243 /** 244 * {@inheritDoc} 245 */ 246 @Override 247 public final void setUnit( final D dimension ) { m_Unit = requireNonNullArgument( dimension, "dimension" ); } 248 249 /** 250 * {@inheritDoc} 251 */ 252 @SuppressWarnings( "CallToNumericToString" ) 253 @Override 254 public final String toString() { return format( ROOT, "%s %s", convert( m_Unit ).toString(), m_Unit.unitSymbol() ); } 255 256 /** 257 * <p>{@summary Validates the given value, based on the type of the 258 * dimension.}</p> 259 * <p>This method will be called by the constructors always with the 260 * {@linkplain #baseUnit() base unit} 261 * value just before the value will be initialised.</p> 262 * 263 * @param value The value in base units. 264 * @return The value. 265 * @throws IllegalArgumentException The validation failed. 266 */ 267 private final BigDecimal validate( final BigDecimal value ) throws IllegalArgumentException 268 { 269 if( !m_Validator.test( m_Unit, requireNonNullArgument( value, "value" ) ) ) 270 { 271 //noinspection CallToNumericToString 272 throw new IllegalArgumentException( "Value %s is invalid".formatted( value.toString() ) ); 273 } 274 275 //---* Done *---------------------------------------------------------- 276 return value; 277 } // validate() 278} 279// class ValueBase 280 281/* 282 * End of File 283 */