View Javadoc
1   package org.djunits.quantity.def;
2   
3   import java.util.LinkedHashMap;
4   import java.util.Map;
5   import java.util.Objects;
6   
7   import org.djutils.exceptions.Throw;
8   
9   /**
10   * Reference contains information about the reference point or origin / zero point of an absolute quantity.
11   * <p>
12   * Copyright (c) 2025-2026 Delft University of Technology, Jaffalaan 5, 2628 BX Delft, the Netherlands. All rights reserved. See
13   * for project information <a href="https://djunits.org" target="_blank">https://djunits.org</a>. The DJUNITS project is
14   * distributed under a <a href="https://djunits.org/docs/license.html" target="_blank">three-clause BSD-style license</a>.
15   * @author Alexander Verbraeck
16   * @param <R> the reference type itself
17   * @param <A> the absolute quantity type for generics instantiation
18   * @param <Q> the relative quantity type for the offset
19   */
20  public abstract class AbstractReference<R extends AbstractReference<R, A, Q>, A extends AbsBasic<A, Q, R>,
21          Q extends Quantity<Q>> implements Reference<R, A, Q>
22  {
23      /**
24       * Master registry: per concrete Reference subclass we keep a map of id to reference. This prevents name collisions between
25       * different absolute quantities.
26       */
27      private static final Map<Class<?>, Map<String, Reference<?, ?, ?>>> REFERENCES = new LinkedHashMap<>();
28  
29      /** The id. */
30      private final String id;
31  
32      /** The explanation. */
33      private final String name;
34  
35      /** The offset w.r.t. the offset reference, is ZERO when offsetReference is null. */
36      private final Q offset;
37  
38      /** The reference to which the offset is relative, can be null. */
39      private final R offsetReference;
40  
41      /**
42       * Define a new reference point for the absolute quantity. Prevent duplicate registration of the same id within the same
43       * Reference subclass.
44       * @param id the id
45       * @param name the name or explanation
46       * @param offset the offset w.r.t. the offsetReference, should be ZERO when offsetReference is null
47       * @param offsetReference the reference to which the offset is relative, can be null
48       * @throws IllegalArgumentException if an id is already registered for this Reference subclass
49       */
50      public AbstractReference(final String id, final String name, final Q offset, final R offsetReference)
51      {
52          Throw.whenNull(id, "id");
53          Throw.whenNull(name, "name");
54          Throw.whenNull(offset, "offset");
55  
56          this.id = id;
57          this.name = name;
58          this.offset = offset;
59          this.offsetReference = offsetReference;
60  
61          // Register in the per-class map for THIS concrete Reference subclass.
62          final Class<?> refClass = getClass();
63          final Map<String, Reference<?, ?, ?>> map = mapFor(refClass);
64  
65          Throw.when(map.containsKey(id), IllegalArgumentException.class, "Reference id '%s' already registered for %s", id,
66                  refClass.getSimpleName());
67  
68          map.put(id, this);
69      }
70  
71      /**
72       * Get or create the inner map for a specific Reference subclass.
73       * @param referenceClass the reference class to look up
74       * @return the existing or new reference map for the the Reference subclass
75       */
76      protected static Map<String, Reference<?, ?, ?>> mapFor(final Class<?> referenceClass)
77      {
78          return REFERENCES.computeIfAbsent(referenceClass, k -> new LinkedHashMap<>());
79      }
80  
81      /**
82       * Fetch a reference by class and id. Returns null when not found.
83       * @param referenceClass the concrete Reference subclass
84       * @param id the id
85       * @return the reference instance or null
86       * @param <R> the reference subclass type
87       */
88      @SuppressWarnings("unchecked")
89      public static <R extends Reference<R, ?, ?>> R get(final Class<R> referenceClass, final String id)
90      {
91          final Map<String, Reference<?, ?, ?>> map = REFERENCES.get(referenceClass);
92          if (map == null)
93          {
94              return null;
95          }
96          return (R) map.get(id);
97      }
98  
99      /**
100      * Check existence of id in a specific Reference subclass.
101      * @param referenceClass the reference subclass to check
102      * @param id the id to check
103      * @return whether the id exists for the Reference subclass
104      */
105     public static boolean containsId(final Class<?> referenceClass, final String id)
106     {
107         final Map<String, Reference<?, ?, ?>> map = REFERENCES.get(referenceClass);
108         return map != null && map.containsKey(id);
109     }
110 
111     /**
112      * Return a safe copy (snapshot) of the registry for a Reference subclass.
113      * @param referenceClass the reference subclass to retrieve
114      * @return a safe copy of the reference map
115      */
116     public static Map<String, Reference<?, ?, ?>> snapshotMap(final Class<?> referenceClass)
117     {
118         final Map<String, Reference<?, ?, ?>> map = REFERENCES.get(referenceClass);
119         return map == null ? Map.of() : new LinkedHashMap<>(map);
120     }
121 
122     /**
123      * Return a safe copy of the static reference map for this Reference subclass.
124      * @return a safe copy of the static reference map for this subclass
125      */
126     @Override
127     public Map<String, Reference<?, ?, ?>> getReferenceMap()
128     {
129         return snapshotMap(getClass());
130     }
131 
132     /**
133      * Instance-level unregister; removes this reference from the per-class registry. Intended primarily for unit tests to clean
134      * up temporary references. Existing objects that hold a direct pointer to this instance continue to work.
135      * @return true if this reference was removed from the registry; false if it was not present
136      */
137     @Override
138     public boolean unregister()
139     {
140         final Map<String, Reference<?, ?, ?>> map = mapFor(getClass());
141         synchronized (map)
142         {
143             // remove(key, value): only remove if the map still points at *this* instance
144             return map.remove(getId(), this);
145         }
146     }
147 
148     /**
149      * Return a strongly typed absolute quantity belonging to this reference.
150      * @param quantity the relative quantity that indicates the 'distance' to this reference point 
151      * @return a strongly typed absolute quantity belonging to this reference
152      */
153     @Override
154     public abstract A instantiate(Q quantity);
155     
156     /**
157      * Return the offset w.r.t. the offset reference, or zero when the offset is not defined.
158      * @return the offset expressed in the relative quantity
159      */
160     @Override
161     public Q getOffset()
162     {
163         return this.offset;
164     }
165 
166     /**
167      * Return the offset reference for the offset, or null when the offset reference is not defined.
168      * @return the offset reference
169      */
170     @Override
171     public R getOffsetReference()
172     {
173         return this.offsetReference;
174     }
175 
176     @Override
177     public String getId()
178     {
179         return this.id;
180     }
181 
182     /** @return description of this reference point */
183     @Override
184     public String getName()
185     {
186         return this.name;
187     }
188 
189     @Override
190     public int hashCode()
191     {
192         return Objects.hash(this.id);
193     }
194 
195     @SuppressWarnings("checkstyle:needbraces")
196     @Override
197     public boolean equals(final Object obj)
198     {
199         if (this == obj)
200             return true;
201         if (obj == null)
202             return false;
203         if (getClass() != obj.getClass())
204             return false;
205         AbstractReference<?, ?, ?> other = (AbstractReference<?, ?, ?>) obj;
206         return Objects.equals(this.id, other.id);
207     }
208 
209     @Override
210     public String toString()
211     {
212         return this.id;
213     }
214 }