View Javadoc

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 }