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