1 package cz.cuni.amis.utils.token;
2
3 import java.lang.ref.WeakReference;
4 import java.util.Arrays;
5 import java.util.HashMap;
6 import java.util.Map;
7
8 /**
9 * Provides a way for string-to-long translation for quick handling of "String" keys inside
10 * maps or sets. The method {@link Tokens#get(long)} or {@link Tokens#get(String)} will
11 * return you an instance of {@link Token} that has {@link Token#hashCode()}
12 * and {@link Token#equals(Object)} correctly specified to be quick (i.e., O(1) time complexity
13 * agains String's O(n) time complexity).
14 * <p><p>
15 * Also notice that {@link Token} is using an array of 'long' ({@link Token#getIds()}) to identify
16 * itself, i.e., you can never run out of ids for Strings (meaning you may truly allocate
17 * an arbitrary number of tokens via get methods ... as much as JVM heap allows you to).
18 * <p><p>
19 * THREAD-SAFE! You do not need to worry that two threads will request the same token
20 * and be provided with different {@link Token} instances.
21 *
22 * @author Jimmy
23 */
24 public class Tokens {
25
26 /**
27 * Last unique identifier.
28 */
29 private volatile static long[] lastIds = new long[0];
30
31 /**
32 * Memory leak ... sticking many "tokens" will result in memory leak as it is never GC()ed from the map.
33 * <p><p>
34 * TODO: we need better {@link Map} implementation for {@link Tokens#tokenMap} is needed, we need
35 * to GC the whole entry whenever a value disappears.
36 *
37 */
38 private static Map<String, WeakReference<Token>> tokenMap = new HashMap<String, WeakReference<Token>>();
39
40 /**
41 * Token representing "null" / "empty token".
42 */
43 public static final Token NULL_TOKEN = get("null");
44
45 /**
46 * Token representing "none" information.
47 */
48 public static final Token NONE_TOKEN = get("@@NONE@@");
49
50 /**
51 * Used for testing! Reinitialize the whole singleton. So future calls to
52 * {@link Tokens#get(long)} or {@link Tokens#get(String)} starts to give different
53 * results!
54 */
55 static void restart() {
56 lastIds = new long[0];
57 tokenMap = new HashMap<String, WeakReference<Token>>();
58 }
59
60 /**
61 * Returns {@link Token} with name "tokenStr", do not use {@link Tokens#NULL_TOKEN} string "null" as param 'tokenStr' that represents null tokens
62 * (tokens without names).
63 * <p><p>
64 * Notice that even though the method is not synchronized, the creation of {@link Token}
65 * is.
66 * <p><p>
67 * THREAD-SAFE!
68 *
69 * @param tokenStr
70 * @return
71 */
72 public static Token get(String tokenStr) {
73 if (tokenStr == null) return NULL_TOKEN;
74 WeakReference<Token> refToken = tokenMap.get(tokenStr);
75 Token token = null;
76 if (refToken != null) {
77 token = refToken.get();
78 }
79 if (token == null) {
80 // we have found out that we need to create a new token
81 // now we have to be synchronized
82 synchronized(tokenMap) {
83 // as get-check-lock does not work, perform another check
84 refToken = tokenMap.get(tokenStr);
85 if (refToken != null) {
86 token = refToken.get();
87 }
88 if (token != null) {
89 // the token exists (racing conditions, somebody was quiker than us)
90 return token;
91 }
92 // the token is still null - create a new one
93 token = newToken(tokenStr);
94 tokenMap.put(tokenStr, new WeakReference<Token>(token));
95 }
96 }
97 return token;
98 }
99
100 /**
101 * Returns {@link Token} of a specified 'id'. Note that 'id' is actual translated
102 * into {@link String} first, meaning that (long)1 and (String)"1" is the same token.
103 * <p><p>
104 * Notice that even though the method is not synchronized, the creation of {@link Token}
105 * is.
106 * <p><p>
107 * THREAD-SAFE!
108 *
109 * @param tokenStr
110 * @return
111 */
112 public static Token get(long id) {
113 return get(String.valueOf(id));
114 }
115
116 /**
117 * Returns {@link Token} of a specified 'id'. Note that 'id' is actual translated
118 * into {@link String} first, meaning that (double)1 and (String)"1" is the same token (but watch out for actual translation of double to string!).
119 * <p><p>
120 * Notice that even though the method is not synchronized, the creation of {@link Token}
121 * is.
122 * <p><p>
123 * THREAD-SAFE!
124 *
125 * @param tokenStr
126 * @return
127 */
128 public static Token get(double id) {
129 return get(String.valueOf(id));
130 }
131
132 /**
133 * Removes 'token' from {@link Tokens#tokenMap}.
134 * @param token
135 */
136 public static void destroy(Token token) {
137 synchronized(tokenMap) {
138 tokenMap.remove(token.getToken());
139 }
140 }
141
142 /**
143 * Factory-method, returns new {@link Token} instance containing unique ids.
144 * <p><p>
145 * THREAD-UNSAFE! Must be called only from synchronized statements/methods.
146 * @param tokenStr
147 * @return
148 */
149 private static Token newToken(String tokenStr) {
150 return new Token(tokenStr, nextLastIds());
151 }
152
153 /**
154 * Issues another unique ids.
155 *
156 * THREAD-UNSAFE! Must be called only from synchronized statments/methods.
157 *
158 * @return array holding next unique identifier
159 */
160 private static long[] nextLastIds() {
161 for (int i = 0; i < lastIds.length; ++i) {
162 if (lastIds[i] == Long.MAX_VALUE) {
163 lastIds[i] = Long.MIN_VALUE;
164 break;
165 }
166 if (lastIds[i] == -1) {
167 lastIds[i] = 0;
168 continue;
169 }
170 lastIds[i] += 1;
171 return lastIds;
172 }
173 lastIds = new long[lastIds.length+1];
174 Arrays.fill(lastIds, 0);
175 return lastIds;
176 }
177
178 }