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 AbsQuantity<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      @SuppressWarnings("checkstyle:visibilitymodifier")
28      protected static final Map<Class<?>, Map<String, Reference<?, ?, ?>>> REFERENCES = new LinkedHashMap<>();
29  
30      /** The id. */
31      private final String id;
32  
33      /** The explanation. */
34      private final String name;
35  
36      /** The offset w.r.t. the offset reference, is ZERO when offsetReference is null. */
37      private final Q offset;
38  
39      /** The reference to which the offset is relative, can be null. */
40      private final R offsetReference;
41  
42      /**
43       * Define a new reference point for the absolute quantity. Prevent duplicate registration of the same id within the same
44       * Reference subclass.
45       * @param id the id
46       * @param name the name or explanation
47       * @param offset the offset w.r.t. the offsetReference, should be ZERO when offsetReference is null
48       * @param offsetReference the reference to which the offset is relative, can be null
49       * @throws IllegalArgumentException if an id is already registered for this Reference subclass
50       */
51      public AbstractReference(final String id, final String name, final Q offset, final R offsetReference)
52      {
53          Throw.whenNull(id, "id");
54          Throw.whenNull(name, "name");
55          Throw.whenNull(offset, "offset");
56  
57          this.id = id;
58          this.name = name;
59          this.offset = offset;
60          this.offsetReference = offsetReference;
61  
62          // Register in the per-class map for THIS concrete Reference subclass.
63          final Class<?> refClass = getClass();
64          final Map<String, Reference<?, ?, ?>> map = mapFor(refClass);
65  
66          Throw.when(map.containsKey(id), IllegalArgumentException.class, "Reference id '%s' already registered for %s", id,
67                  refClass.getSimpleName());
68  
69          map.put(id, this);
70      }
71  
72      /**
73       * Get or create the inner map for a specific Reference subclass.
74       * @param referenceClass the reference class to look up
75       * @return the existing or new reference map for the the Reference subclass
76       */
77      protected static Map<String, Reference<?, ?, ?>> mapFor(final Class<?> referenceClass)
78      {
79          return REFERENCES.computeIfAbsent(referenceClass, k -> new LinkedHashMap<>());
80      }
81  
82      /**
83       * Fetch a reference by class and id. Returns null when not found.
84       * @param referenceClass the concrete Reference subclass
85       * @param id the id
86       * @return the reference instance or null
87       * @param <R> the reference subclass type
88       */
89      @SuppressWarnings("unchecked")
90      public static <R extends Reference<R, ?, ?>> R get(final Class<R> referenceClass, final String id)
91      {
92          final Map<String, Reference<?, ?, ?>> map = REFERENCES.get(referenceClass);
93          if (map == null)
94          {
95              return null;
96          }
97          return (R) map.get(id);
98      }
99  
100     /**
101      * Check existence of id in a specific Reference subclass.
102      * @param referenceClass the reference subclass to check
103      * @param id the id to check
104      * @return whether the id exists for the Reference subclass
105      */
106     public static boolean containsId(final Class<?> referenceClass, final String id)
107     {
108         final Map<String, Reference<?, ?, ?>> map = REFERENCES.get(referenceClass);
109         return map != null && map.containsKey(id);
110     }
111 
112     /**
113      * Return a safe copy (snapshot) of the registry for a Reference subclass.
114      * @param referenceClass the reference subclass to retrieve
115      * @return a safe copy of the reference map
116      */
117     public static Map<String, Reference<?, ?, ?>> snapshotMap(final Class<?> referenceClass)
118     {
119         final Map<String, Reference<?, ?, ?>> map = REFERENCES.get(referenceClass);
120         return map == null ? Map.of() : new LinkedHashMap<>(map);
121     }
122 
123     /**
124      * Return a safe copy of the static reference map for this Reference subclass.
125      * @return a safe copy of the static reference map for this subclass
126      */
127     @Override
128     public Map<String, Reference<?, ?, ?>> getReferenceMap()
129     {
130         return snapshotMap(getClass());
131     }
132 
133     /**
134      * Instance-level unregister; removes this reference from the per-class registry. Intended primarily for unit tests to clean
135      * up temporary references. Existing objects that hold a direct pointer to this instance continue to work.
136      * @return true if this reference was removed from the registry; false if it was not present
137      */
138     @Override
139     public boolean unregister()
140     {
141         final Map<String, Reference<?, ?, ?>> map = mapFor(getClass());
142         synchronized (map)
143         {
144             // remove(key, value): only remove if the map still points at *this* instance
145             return map.remove(getId(), this);
146         }
147     }
148 
149     /**
150      * Return a strongly typed absolute quantity belonging to this reference.
151      * @param quantity the relative quantity that indicates the 'distance' to this reference point 
152      * @return a strongly typed absolute quantity belonging to this reference
153      */
154     @Override
155     public abstract A instantiate(Q quantity);
156     
157     /**
158      * Return the offset w.r.t. the offset reference, or zero when the offset is not defined.
159      * @return the offset expressed in the relative quantity
160      */
161     @Override
162     public Q getOffset()
163     {
164         return this.offset;
165     }
166 
167     /**
168      * Return the offset reference for the offset, or null when the offset reference is not defined.
169      * @return the offset reference
170      */
171     @Override
172     public R getOffsetReference()
173     {
174         return this.offsetReference;
175     }
176 
177     @Override
178     public String getId()
179     {
180         return this.id;
181     }
182 
183     /** @return description of this reference point */
184     @Override
185     public String getName()
186     {
187         return this.name;
188     }
189 
190     @Override
191     public int hashCode()
192     {
193         return Objects.hash(this.id);
194     }
195 
196     @SuppressWarnings("checkstyle:needbraces")
197     @Override
198     public boolean equals(final Object obj)
199     {
200         if (this == obj)
201             return true;
202         if (obj == null)
203             return false;
204         if (getClass() != obj.getClass())
205             return false;
206         AbstractReference<?, ?, ?> other = (AbstractReference<?, ?, ?>) obj;
207         return Objects.equals(this.id, other.id);
208     }
209 
210     @Override
211     public String toString()
212     {
213         return this.id;
214     }
215 }