• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 package org.unicode.cldr.util;
2 
3 import java.nio.charset.Charset;
4 import java.security.MessageDigest;
5 import java.util.Map;
6 import java.util.concurrent.ConcurrentHashMap;
7 
8 /**
9  * Produce an ID for a string based on a long hash. When used properly, the odds
10  * of collision are so low that the ID can be used as a proxy for the
11  * original string. The ID is non-negative. The algorithm uses SHA-1 over the
12  * UTF-8 bytes in the string. Also provides lookup for long previously generated for string.
13  *
14  * @author markdavis
15  */
16 public final class StringId {
17     private static final Map<String, Long> STRING_TO_ID = new ConcurrentHashMap<>();
18     private static final Map<Long, String> ID_TO_STRING = new ConcurrentHashMap<>();
19     private static final MessageDigest digest;
20     private static final Charset UTF_8 = Charset.forName("UTF-8");
21     private static final int RETRY_LIMIT = 9;
22     static {
23         try {
24             digest = MessageDigest.getInstance("SHA-1");
25         } catch (Exception e) {
26             throw new IllegalArgumentException(e); // darn'd checked exceptions
27         }
28     }
29 
30     /**
31      * Get the ID for a string.
32      *
33      * @param string
34      *            input string.
35      * @return a value from 0 to 0x7FFFFFFFFFFFFFFFL.
36      */
getId(CharSequence charSequence)37     public static long getId(CharSequence charSequence) {
38         String string = charSequence.toString();
39         Long resultLong = STRING_TO_ID.get(string);
40         if (resultLong != null) {
41             return resultLong;
42         }
43         int retryCount = RETRY_LIMIT;
44         while (true) {
45             try {
46                 synchronized (digest) {
47                     byte[] hash = digest.digest(string.getBytes(UTF_8));
48                     long result = 0;
49                     for (int i = 0; i < 8; ++i) {
50                         result <<= 8;
51                         result ^= hash[i];
52                     }
53                     // mash the top bit to make things easier
54                     result &= 0x7FFFFFFFFFFFFFFFL;
55                     STRING_TO_ID.put(string, result);
56                     ID_TO_STRING.put(result, string);
57                     return result;
58                 }
59             } catch (RuntimeException e) {
60                 if (--retryCount < 0) {
61                     throw e;
62                 }
63             }
64         }
65     }
66 
67     /**
68      * Get the hex ID for a string.
69      *
70      * @param string
71      *            input string.
72      * @return a string with the hex value
73      */
getHexId(CharSequence string)74     public static String getHexId(CharSequence string) {
75         return Long.toHexString(getId(string));
76     }
77 
78     /**
79      * Get the hex ID for a string.
80      *
81      * @param string
82      *            input string.
83      * @return a string with the hex value
84      */
getStringFromHexId(String string)85     public static String getStringFromHexId(String string) {
86         return getStringFromId(Long.parseLong(string, 16));
87     }
88 
89     /**
90      * Returns string previously used to generate the longValue with getId.
91      * @param longValue
92      * @return String previously used to generate the longValue with getId.
93      */
getStringFromId(long longValue)94     public static String getStringFromId(long longValue) {
95         return ID_TO_STRING.get(longValue);
96     }
97 }