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 }