
com.causecode.util.Hashids Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of nucleus Show documentation
Show all versions of nucleus Show documentation
nucleus is a Grails-Groovy plugin which contains utility methods, classes and endpoints.
package com.causecode.util;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Hashids designed for Generating short hashes from numbers (like YouTube and Bitly), obfuscate
* database IDs, use them as forgotten password hashes, invitation codes, store shard numbers
* This is implementation of http://hashids.org v0.3.3 version.
*
* @author fanweixiao [email protected]
* @since 0.3.3
*/
public class Hashids {
private static final String DEFAULT_ALPHABET = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890";
private String salt = "";
private String alphabet = "";
private String seps = "cfhistuCFHISTU";
private int minHashLength = 0;
private double sepDiv = 3.5;
private int guardDiv = 12;
private int minAlphabetLength = 16;
private String guards;
public Hashids() throws Exception {
this("");
}
public Hashids(String salt) throws Exception {
this(salt, 0);
}
public Hashids(String salt, int minHashLength) throws Exception {
this(salt, minHashLength, DEFAULT_ALPHABET);
}
public Hashids(String salt, int minHashLength, String alphabet) throws Exception {
this.salt = salt;
if(minHashLength < 0)
this.minHashLength = 0;
else
this.minHashLength = minHashLength;
this.alphabet = alphabet;
String uniqueAlphabet = "";
for(int i = 0; i < this.alphabet.length(); i++){
if(!uniqueAlphabet.contains("" + this.alphabet.charAt(i))){
uniqueAlphabet += "" + this.alphabet.charAt(i);
}
}
this.alphabet = uniqueAlphabet;
if(this.alphabet.length() < this.minAlphabetLength){
throw new IllegalArgumentException("alphabet must contain at least " + this.minAlphabetLength + " unique characters");
}
if(this.alphabet.contains(" ")){
throw new IllegalArgumentException("alphabet cannot contains spaces");
}
// seps should contain only characters present in alphabet;
// alphabet should not contains seps
for(int i = 0; i < this.seps.length(); i++){
int j = this.alphabet.indexOf(this.seps.charAt(i));
if(j == -1){
this.seps = this.seps.substring(0, i) + " " + this.seps.substring(i + 1);
} else {
this.alphabet = this.alphabet.substring(0, j) + " " + this.alphabet.substring(j + 1);
}
}
this.alphabet = this.alphabet.replaceAll("\\s+", "");
this.seps = this.seps.replaceAll("\\s+", "");
this.seps = this.consistentShuffle(this.seps, this.salt);
if((this.seps.equals("")) || ((this.alphabet.length() / this.seps.length()) > this.sepDiv)){
int seps_len = (int)Math.ceil(this.alphabet.length() / this.sepDiv);
if(seps_len == 1){
seps_len++;
}
if(seps_len > this.seps.length()){
int diff = seps_len - this.seps.length();
this.seps += this.alphabet.substring(0, diff);
this.alphabet = this.alphabet.substring(diff);
} else {
this.seps = this.seps.substring(0, seps_len);
}
}
this.alphabet = this.consistentShuffle(this.alphabet, this.salt);
// use double to round up
int guardCount = (int)Math.ceil((double)this.alphabet.length() / this.guardDiv);
if(this.alphabet.length() < 3){
this.guards = this.seps.substring(0, guardCount);
this.seps = this.seps.substring(guardCount);
} else {
this.guards = this.alphabet.substring(0, guardCount);
this.alphabet = this.alphabet.substring(guardCount);
}
}
/**
* @deprecated
* should use encode() since v1.0
*/
@Deprecated
public String encrypt(long... numbers){
return encode(numbers);
}
/**
* @deprecated
* should use decode() since v1.0
*/
@Deprecated
public long[] decrypt(String hash){
return decode(hash);
}
/**
* @deprecated
* should use encodeHex() since v1.0
*/
@Deprecated
public String encryptHex(String hexa){
return encodeHex(hexa);
}
/**
* @deprecated
* should use decodeHex() since v1.0
*/
@Deprecated
public String decryptHex(String hash){
return decodeHex(hash);
}
/**
* Encrypt numbers to string
*
* @param numbers the numbers to encrypt
* @return the encrypt string
*/
public String encode(long... numbers){
String retval = "";
if(numbers.length == 0) {
return retval;
}
return this._encode(numbers);
}
/**
* Decrypt string to numbers
*
* @param hash the encrypt string
* @return decryped numbers
*/
public long[] decode(String hash){
long[] ret = {};
if(hash.equals(""))
return ret;
return this._decode(hash, this.alphabet);
}
/**
* Encrypt hexa to string
*
* @param hexa the hexa to encrypt
* @return the encrypt string
*/
public String encodeHex(String hexa){
if(!hexa.matches("^[0-9a-fA-F]+$"))
return "";
List matched = new ArrayList();
Matcher matcher = Pattern.compile("[\\w\\W]{1,12}").matcher(hexa);
while (matcher.find())
matched.add(Long.parseLong("1" + matcher.group(), 16));
// conversion
long[] result = new long[matched.size()];
for(int i = 0; i < matched.size(); i++) result[i] = matched.get(i);
return this._encode(result);
}
/**
* Decrypt string to numbers
*
* @param hash the encrypt string
* @return decryped numbers
*/
public String decodeHex(String hash){
String result = "";
long[] numbers = this.decrypt(hash);
for (long number : numbers) {
result += Long.toHexString(number).substring(1);
}
return result;
}
private String _encode(long... numbers){
int numberHashInt = 0;
for(int i = 0; i < numbers.length; i++){
numberHashInt += (numbers[i] % (i+100));
}
String alphabet = this.alphabet;
char ret = alphabet.toCharArray()[numberHashInt % alphabet.length()];
char lottery = ret;
long num;
int sepsIndex, guardIndex;
String buffer, ret_str = ret + "";
char guard;
for(int i = 0; i < numbers.length; i++){
num = numbers[i];
buffer = lottery + this.salt + alphabet;
alphabet = this.consistentShuffle(alphabet, buffer.substring(0, alphabet.length()));
String last = this.hash(num, alphabet);
ret_str += last;
if(i + 1 < numbers.length){
num %= ((int)last.toCharArray()[0] + i);
sepsIndex = (int)(num % this.seps.length());
ret_str += this.seps.toCharArray()[sepsIndex];
}
}
if(ret_str.length() < this.minHashLength){
guardIndex = (numberHashInt + (int)(ret_str.toCharArray()[0])) % this.guards.length();
guard = this.guards.toCharArray()[guardIndex];
ret_str = guard + ret_str;
if(ret_str.length() < this.minHashLength){
guardIndex = (numberHashInt + (int)(ret_str.toCharArray()[2])) % this.guards.length();
guard = this.guards.toCharArray()[guardIndex];
ret_str += guard;
}
}
int halfLen = alphabet.length() / 2;
while(ret_str.length() < this.minHashLength){
alphabet = this.consistentShuffle(alphabet, alphabet);
ret_str = alphabet.substring(halfLen) + ret_str + alphabet.substring(0, halfLen);
int excess = ret_str.length() - this.minHashLength;
if(excess > 0){
int start_pos = excess / 2;
ret_str = ret_str.substring(start_pos, start_pos + this.minHashLength);
}
}
return ret_str;
}
private long[] _decode(String hash, String alphabet){
ArrayList ret = new ArrayList();
int i = 0;
String regexp = "[" + this.guards + "]";
String hashBreakdown = hash.replaceAll(regexp, " ");
String[] hashArray = hashBreakdown.split(" ");
if(hashArray.length == 3 || hashArray.length == 2){
i = 1;
}
hashBreakdown = hashArray[i];
char lottery = hashBreakdown.toCharArray()[0];
hashBreakdown = hashBreakdown.substring(1);
hashBreakdown = hashBreakdown.replaceAll("[" + this.seps + "]", " ");
hashArray = hashBreakdown.split(" ");
String subHash, buffer;
for (String aHashArray : hashArray) {
subHash = aHashArray;
buffer = lottery + this.salt + alphabet;
alphabet = this.consistentShuffle(alphabet, buffer.substring(0, alphabet.length()));
ret.add(this.unhash(subHash, alphabet));
}
//transform from List to long[]
long[] arr = new long[ret.size()];
for(int k = 0; k < arr.length; k++){
arr[k] = ret.get(k);
}
return arr;
}
/* Private methods */
private String consistentShuffle(String alphabet, String salt){
if(salt.length() <= 0)
return alphabet;
char[] arr = salt.toCharArray();
int asc_val, j;
char tmp;
for(int i = alphabet.length() - 1, v = 0, p = 0; i > 0; i--, v++){
v %= salt.length();
asc_val = (int)arr[v];
p += asc_val;
j = (asc_val + v + p) % i;
tmp = alphabet.charAt(j);
alphabet = alphabet.substring(0, j) + alphabet.charAt(i) + alphabet.substring(j + 1);
alphabet = alphabet.substring(0, i) + tmp + alphabet.substring(i + 1);
}
return alphabet;
}
private String hash(long input, String alphabet){
String hash = "";
int alphabetLen = alphabet.length();
char[] arr = alphabet.toCharArray();
do {
hash = arr[(int)(input % alphabetLen)] + hash;
input /= alphabetLen;
} while(input > 0);
return hash;
}
private Long unhash(String input, String alphabet){
long number = 0, pos;
char[] input_arr = input.toCharArray();
for(int i = 0; i < input.length(); i++){
pos = alphabet.indexOf(input_arr[i]);
number += pos * Math.pow(alphabet.length(), input.length() - i - 1);
}
return number;
}
public static int checkedCast(long value) {
int result = (int) value;
if (result != value) {
// don't use checkArgument here, to avoid boxing
throw new IllegalArgumentException("Out of range: " + value);
}
return result;
}
/**
* Get version
*
* @return version
*/
public String getVersion() {
return "1.0.0";
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy