AbstractReference.java
package org.djunits.quantity.def;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Objects;
import org.djutils.base.Identifiable;
import org.djutils.exceptions.Throw;
/**
* Reference contains information about the reference point or origin / zero point of an absolute quantity.
* <p>
* Copyright (c) 2025-2026 Delft University of Technology, Jaffalaan 5, 2628 BX Delft, the Netherlands. All rights reserved. See
* for project information <a href="https://djunits.org" target="_blank">https://djunits.org</a>. The DJUNITS project is
* distributed under a <a href="https://djunits.org/docs/license.html" target="_blank">three-clause BSD-style license</a>.
* @author Alexander Verbraeck
* @param <R> the reference type itself
* @param <Q> the relative quantity for the offset
*/
public abstract class AbstractReference<R extends AbstractReference<R, Q>, Q extends Quantity<Q, ?>> implements Identifiable
{
/**
* Master registry: per concrete Reference subclass we keep a map of id to reference. This prevents name collisions between
* different absolute quantities.
*/
@SuppressWarnings("checkstyle:visibilitymodifier")
protected static final Map<Class<?>, Map<String, AbstractReference<?, ?>>> REFERENCES = new LinkedHashMap<>();
/** The id. */
private final String id;
/** The explanation. */
private final String name;
/** The offset w.r.t. the offset reference, is ZERO when offsetReference is null. */
private final Q offset;
/** The reference to which the offset is relative, can be null. */
private final R offsetReference;
/**
* Define a new reference point for the absolute quantity. Prevent duplicate registration of the same id within the same
* Reference subclass.
* @param id the id
* @param name the name or explanation
* @param offset the offset w.r.t. the offsetReference, should be ZERO when offsetReference is null
* @param offsetReference the reference to which the offset is relative, can be null
* @throws IllegalArgumentException if an id is already registered for this Reference subclass
*/
public AbstractReference(final String id, final String name, final Q offset, final R offsetReference)
{
Throw.whenNull(id, "id");
Throw.whenNull(name, "name");
Throw.whenNull(offset, "offset");
this.id = id;
this.name = name;
this.offset = offset;
this.offsetReference = offsetReference;
// Register in the per-class map for THIS concrete Reference subclass.
final Class<?> refClass = getClass();
final Map<String, AbstractReference<?, ?>> map = mapFor(refClass);
Throw.when(map.containsKey(id), IllegalArgumentException.class, "Reference id '%s' already registered for %s", id,
refClass.getSimpleName());
map.put(id, this);
}
/**
* Get or create the inner map for a specific Reference subclass.
* @param referenceClass the reference class to look up
* @return the existing or new reference map for the the Reference subclass
*/
protected static Map<String, AbstractReference<?, ?>> mapFor(final Class<?> referenceClass)
{
return REFERENCES.computeIfAbsent(referenceClass, k -> new LinkedHashMap<>());
}
/**
* Fetch a reference by class and id. Returns null when not found.
* @param referenceClass the concrete Reference subclass
* @param id the id
* @return the reference instance or null
* @param <R> the reference subclass type
*/
@SuppressWarnings("unchecked")
public static <R extends AbstractReference<R, ?>> R get(final Class<R> referenceClass, final String id)
{
final Map<String, AbstractReference<?, ?>> map = REFERENCES.get(referenceClass);
if (map == null)
{
return null;
}
return (R) map.get(id);
}
/**
* Check existence of id in a specific Reference subclass.
* @param referenceClass the reference subclass to check
* @param id the id to check
* @return whether the id exists for the Reference subclass
*/
public static boolean containsId(final Class<?> referenceClass, final String id)
{
final Map<String, AbstractReference<?, ?>> map = REFERENCES.get(referenceClass);
return map != null && map.containsKey(id);
}
/**
* Return a safe copy (snapshot) of the registry for a Reference subclass.
* @param referenceClass the reference subclass to retrieve
* @return a safe copy of the reference map
*/
public static Map<String, AbstractReference<?, ?>> snapshotMap(final Class<?> referenceClass)
{
final Map<String, AbstractReference<?, ?>> map = REFERENCES.get(referenceClass);
return map == null ? Map.of() : new LinkedHashMap<>(map);
}
/**
* Return a safe copy of the static reference map for this Reference subclass.
* @return a safe copy of the static reference map for this subclass
*/
public Map<String, AbstractReference<?, ?>> getReferenceMap()
{
return snapshotMap(getClass());
}
/**
* Instance-level unregister; removes this reference from the per-class registry. Intended primarily for unit tests to clean
* up temporary references. Existing objects that hold a direct pointer to this instance continue to work.
* @return true if this reference was removed from the registry; false if it was not present
*/
public boolean unregister()
{
final Map<String, AbstractReference<?, ?>> map = mapFor(getClass());
synchronized (map)
{
// remove(key, value): only remove if the map still points at *this* instance
return map.remove(getId(), this);
}
}
/**
* Return the offset w.r.t. the offset reference, or zero when the offset is not defined.
* @return the offset expressed in the relative quantity
*/
public Q getOffset()
{
return this.offset;
}
/**
* Return the offset reference for the offset, or null when the offset reference is not defined.
* @return the offset reference
*/
public R getOffsetReference()
{
return this.offsetReference;
}
@Override
public String getId()
{
return this.id;
}
/** @return description of this reference point */
public String getName()
{
return this.name;
}
@Override
public int hashCode()
{
return Objects.hash(this.id);
}
@SuppressWarnings("checkstyle:needbraces")
@Override
public boolean equals(final Object obj)
{
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
AbstractReference<?, ?> other = (AbstractReference<?, ?>) obj;
return Objects.equals(this.id, other.id);
}
@Override
public String toString()
{
return this.id;
}
}