View Javadoc
1   /*
2    * *************************************************************************************************************************************************************
3    *
4    * TheseFoolishThings: Miscellaneous utilities
5    * http://tidalwave.it/projects/thesefoolishthings
6    *
7    * Copyright (C) 2009 - 2025 by Tidalwave s.a.s. (http://tidalwave.it)
8    *
9    * *************************************************************************************************************************************************************
10   *
11   * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License.
12   * You may obtain a copy of the License at
13   *
14   *     http://www.apache.org/licenses/LICENSE-2.0
15   *
16   * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
17   * CONDITIONS OF ANY KIND, either express or implied.  See the License for the specific language governing permissions and limitations under the License.
18   *
19   * *************************************************************************************************************************************************************
20   *
21   * git clone https://bitbucket.org/tidalwave/thesefoolishthings-src
22   * git clone https://github.com/tidalwave-it/thesefoolishthings-src
23   *
24   * *************************************************************************************************************************************************************
25   */
26  package it.tidalwave.util;
27  
28  import javax.annotation.Nonnull;
29  import javax.annotation.concurrent.Immutable;
30  import java.util.Comparator;
31  import java.util.Set;
32  import java.util.TreeSet;
33  import java.util.concurrent.ConcurrentHashMap;
34  import java.io.Serializable;
35  import it.tidalwave.util.annotation.VisibleForTesting;
36  import lombok.AccessLevel;
37  import lombok.EqualsAndHashCode;
38  import lombok.Getter;
39  import lombok.RequiredArgsConstructor;
40  import lombok.ToString;
41  
42  /***************************************************************************************************************************************************************
43   *
44   * @author  Fabrizio Giudici
45   * @since   1.11.0
46   * @stereotype flyweight
47   *
48   **************************************************************************************************************************************************************/
49  @Immutable @RequiredArgsConstructor(access = AccessLevel.PRIVATE) @EqualsAndHashCode @ToString
50  public class Key<T> implements StringValue, Comparable<Key<?>>, Serializable
51    {
52      private static final long serialVersionUID = 2817490298518793579L;
53  
54      // FIXME: a Set would be enough.
55      @VisibleForTesting static final ConcurrentHashMap<Key<?>, Key<?>> INSTANCES = new ConcurrentHashMap<>();
56  
57      @Getter @Nonnull
58      private final String name;
59  
60      @Getter @Nonnull
61      private final Class<T> type;
62  
63      /***********************************************************************************************************************************************************
64       * Create a new instance with the given name.
65       *
66       * @param   name        the name
67       * @deprecated use {@link #of(String, Class)}
68       **********************************************************************************************************************************************************/
69      @Deprecated
70      public Key (@Nonnull final String name)
71        {
72          this.name = name;
73          type = (Class<T>)ReflectionUtils.getTypeArguments(Key.class, getClass()).get(0);
74        }
75  
76      /***********************************************************************************************************************************************************
77       * Creates an instance with the given name and type. If an identical key already exists, that existing instance is
78       * returned. It is allowed to have two keys with the same name and different types (e.g. {@code Key.of("foo",
79       * String.class)} and {@code Key.of("foo", Integer.class)}): they are considered as two distinct keys. This feature
80       * allows to treat the same data both as typed and type-agnostic at the same time; for instance, a collection of
81       * properties could be read from a configuration file in type-agnostic way (if there is no type information in
82       * the file) and later managed as typed only when needed.
83       *
84       * @param <T>     the static type
85       * @param name    the name
86       * @param type    the dynamic type
87       * @return        the key
88       * @since         3.2-ALPHA-2
89       **********************************************************************************************************************************************************/
90      @Nonnull @SuppressWarnings("unchecked")
91      public static <T> Key<T> of (@Nonnull final String name, @Nonnull final Class<T> type)
92        {
93          final var newKey = new Key<T>(name, type);
94          final var key = (Key<T>)INSTANCES.putIfAbsent(newKey, newKey);
95          return key != null ? key : newKey;
96        }
97  
98      /***********************************************************************************************************************************************************
99       * Creates an instance with the given name. Type is considered as unknown (and assumed as {@link Object}). Please
100      * see {@link #of(String, Class)} for more information.
101      *
102      * @param name    the name
103      * @return        the key
104      * @since         3.2-ALPHA-4
105      **********************************************************************************************************************************************************/
106     @Nonnull
107     public static Key<Object> of (@Nonnull final String name)
108       {
109         return of(name, Object.class);
110       }
111 
112     /***********************************************************************************************************************************************************
113      * Returns all the keys registered in the system.
114      *
115      * @return    a mutable and sorted set of keys.
116      * @since     3.2-ALPHA-2
117      **********************************************************************************************************************************************************/
118     @Nonnull
119     public static Set<Key<?>> allKeys()
120       {
121         return new TreeSet<>(INSTANCES.values());
122       }
123 
124     /***********************************************************************************************************************************************************
125      * Create a new instance with the given name.
126      *
127      * @param   name        the name
128      * @deprecated use {@link #of(String, Class)}
129      **********************************************************************************************************************************************************/
130     public Key (@Nonnull final StringValue name)
131       {
132         this(name.stringValue());
133       }
134 
135     /***********************************************************************************************************************************************************
136      * {@inheritDoc}
137      **********************************************************************************************************************************************************/
138     @Override @Nonnull
139     public String stringValue()
140       {
141         return name;
142       }
143 
144     /***********************************************************************************************************************************************************
145      * {@inheritDoc}
146      **********************************************************************************************************************************************************/
147     @Override
148     public int compareTo (@Nonnull final Key<?> other)
149       {
150         final Comparator<Key<?>> byType = Comparator.comparing(k -> k.getType().getName());
151         final Comparator<Key<?>> byName = Comparator.comparing(Key::getName);
152         return byName.thenComparing(byType).compare(this, other);
153       }
154   }