diff --git a/README.md b/README.md
index 301e9ba..4e0af6d 100644
--- a/README.md
+++ b/README.md
@@ -33,7 +33,7 @@ Add these lines to your `pom.xml`.
+ * Examples of valid ULID strings: + * - 0123456789ABCDEFGHJKMNPKRS (26 alphanumeric, case insensitive, except U) + * - 0123456789ABCDEFGHIJKLMNOP (26 alphanumeric, case insensitive, including OIL, except U) + * - 0123456789-ABCDEFGHJK-MNPKRS (26 alphanumeric, case insensitive, except U, with hyphens) + * - 0123456789-ABCDEFGHIJ-KLMNOP (26 alphanumeric, case insensitive, including OIL, except U, with hyphens) + *+ * + * @param ulid a ULID + * @return boolean true if valid + */ + public static boolean isValid(String ulid) { + if (ulid == null) { + return false; + } + char[] chars = ulid.toCharArray(); + return isValidString(chars) && isValidTimestamp(chars); + } + + /** + * Checks if the ULID string is a valid. + * + * See {@link UlidValidator#isValid(String)}. + * + * @param ulid a ULID string + * @throws InvalidUlidException if invalid + */ + public static void validate(String ulid) { + if (!isValid(ulid)) { + throw new InvalidUlidException(String.format("Invalid ULID: %s.", ulid)); + } + } + /** * Checks if the string is a valid ULID. * @@ -50,40 +93,48 @@ public final class UlidValidator { * - 0123456789-ABCDEFGHIJ-KLMNOP (26 alphanumeric, case insensitive, including OIL, except U, with hyphens) * * - * @param ulid a ULID + * @param c a char array * @return boolean true if valid */ - public static boolean isValid(String ulid) { - - if (ulid == null) { - return false; + protected static boolean isValidString(final char[] c) { + int hyphen = 0; + for (int i = 0; i < c.length; i++) { + if (c[i] == '-') { + hyphen++; + continue; + } + if (c[i] == 'U' || c[i] == 'u') { + return false; + } + // ASCII codes: A-Z, 0-9, a-z + if (!((c[i] >= 0x41 && c[i] <= 0x5a) || (c[i] >= 0x30 && c[i] <= 0x39) || (c[i] >= 0x61 && c[i] <= 0x7a))) { + return false; + } } - - char[] chars = removeHyphens(ulid.toCharArray()); - if (chars.length != ULID_CHAR_LENGTH || !isCrockfordBase32(chars)) { - return false; - } - - // Extract time component - final char[] timestampComponent = new char[10]; - System.arraycopy(chars, 0, timestampComponent, 0, 10); - final long timestamp = fromBase32Crockford(timestampComponent); - - return timestamp >= 0 && timestamp <= TIMESTAMP_MAX; - + return (c.length - hyphen) == ULID_LENGTH; } /** - * Checks if the ULID string is a valid. + * Checks if the timestamp is between 0 and 2^48-1 * - * See {@link TsidValidator#isValid(String)}. - * - * @param ulid a ULID string - * @throws InvalidUlidException if invalid + * @param chars a char array + * @return false if invalid. */ - protected static void validate(String ulid) { - if (!isValid(ulid)) { - throw new InvalidUlidException(String.format("Invalid ULID: %s.", ulid)); - } + protected static boolean isValidTimestamp(char[] chars) { + + long time = 0; + + time |= BASE32_VALUES[chars[0x00]] << 45; + time |= BASE32_VALUES[chars[0x01]] << 40; + time |= BASE32_VALUES[chars[0x02]] << 35; + time |= BASE32_VALUES[chars[0x03]] << 30; + time |= BASE32_VALUES[chars[0x04]] << 25; + time |= BASE32_VALUES[chars[0x05]] << 20; + time |= BASE32_VALUES[chars[0x06]] << 15; + time |= BASE32_VALUES[chars[0x07]] << 10; + time |= BASE32_VALUES[chars[0x08]] << 5; + time |= BASE32_VALUES[chars[0x09]]; + + return time >= 0 && time <= TIMESTAMP_MAX; } } diff --git a/src/main/java/com/github/f4b6a3/ulid/util/internal/UlidStruct.java b/src/main/java/com/github/f4b6a3/ulid/util/internal/UlidStruct.java new file mode 100644 index 0000000..321f0b8 --- /dev/null +++ b/src/main/java/com/github/f4b6a3/ulid/util/internal/UlidStruct.java @@ -0,0 +1,250 @@ +/* + * MIT License + * + * Copyright (c) 2020 Fabio Lima + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package com.github.f4b6a3.ulid.util.internal; + +import java.util.UUID; + +import com.github.f4b6a3.ulid.util.UlidValidator; + +/** + * This class represents the structure of a ULID. + * + * It is for internal use and test cases in this library. + */ +public final class UlidStruct { + + public final long time; + public final long random1; + public final long random2; + + protected static final long TIMESTAMP_COMPONENT = 0x0000ffffffffffffL; + protected static final long HALF_RANDOM_COMPONENT = 0x000000ffffffffffL; + + public static final char[] BASE32_CHARS = // + { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', // + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'J', 'K', // + 'M', 'N', 'P', 'Q', 'R', 'S', 'T', 'V', 'W', 'X', 'Y', 'Z' }; + + public static final long[] BASE32_VALUES = new long[128]; + static { + + // Numbers + BASE32_VALUES['0'] = 0x00; + BASE32_VALUES['1'] = 0x01; + BASE32_VALUES['2'] = 0x02; + BASE32_VALUES['3'] = 0x03; + BASE32_VALUES['4'] = 0x04; + BASE32_VALUES['5'] = 0x05; + BASE32_VALUES['6'] = 0x06; + BASE32_VALUES['7'] = 0x07; + BASE32_VALUES['8'] = 0x08; + BASE32_VALUES['9'] = 0x09; + // Lower case + BASE32_VALUES['a'] = 0x0a; + BASE32_VALUES['b'] = 0x0b; + BASE32_VALUES['c'] = 0x0c; + BASE32_VALUES['d'] = 0x0d; + BASE32_VALUES['e'] = 0x0e; + BASE32_VALUES['f'] = 0x0f; + BASE32_VALUES['g'] = 0x10; + BASE32_VALUES['h'] = 0x11; + BASE32_VALUES['j'] = 0x12; + BASE32_VALUES['k'] = 0x13; + BASE32_VALUES['m'] = 0x14; + BASE32_VALUES['n'] = 0x15; + BASE32_VALUES['p'] = 0x16; + BASE32_VALUES['q'] = 0x17; + BASE32_VALUES['r'] = 0x18; + BASE32_VALUES['s'] = 0x19; + BASE32_VALUES['t'] = 0x1a; + BASE32_VALUES['v'] = 0x1b; + BASE32_VALUES['w'] = 0x1c; + BASE32_VALUES['x'] = 0x1d; + BASE32_VALUES['y'] = 0x1e; + BASE32_VALUES['z'] = 0x1f; + // Lower case OIL + BASE32_VALUES['o'] = 0x00; + BASE32_VALUES['i'] = 0x01; + BASE32_VALUES['l'] = 0x01; + // Upper case + BASE32_VALUES['A'] = 0x0a; + BASE32_VALUES['B'] = 0x0b; + BASE32_VALUES['C'] = 0x0c; + BASE32_VALUES['D'] = 0x0d; + BASE32_VALUES['E'] = 0x0e; + BASE32_VALUES['F'] = 0x0f; + BASE32_VALUES['G'] = 0x10; + BASE32_VALUES['H'] = 0x11; + BASE32_VALUES['J'] = 0x12; + BASE32_VALUES['K'] = 0x13; + BASE32_VALUES['M'] = 0x14; + BASE32_VALUES['N'] = 0x15; + BASE32_VALUES['P'] = 0x16; + BASE32_VALUES['Q'] = 0x17; + BASE32_VALUES['R'] = 0x18; + BASE32_VALUES['S'] = 0x19; + BASE32_VALUES['T'] = 0x1a; + BASE32_VALUES['V'] = 0x1b; + BASE32_VALUES['W'] = 0x1c; + BASE32_VALUES['X'] = 0x1d; + BASE32_VALUES['Y'] = 0x1e; + BASE32_VALUES['Z'] = 0x1f; + // Upper case OIL + BASE32_VALUES['O'] = 0x00; + BASE32_VALUES['I'] = 0x01; + BASE32_VALUES['L'] = 0x01; + + } + + public UlidStruct(long time, long random1, long random2) { + this.time = time & TIMESTAMP_COMPONENT; + this.random1 = random1 & HALF_RANDOM_COMPONENT; + this.random2 = random2 & HALF_RANDOM_COMPONENT; + } + + public UlidStruct(UUID uuid) { + final long msb = uuid.getMostSignificantBits(); + final long lsb = uuid.getLeastSignificantBits(); + + this.time = (msb >>> 16); + this.random1 = ((msb & 0x000000000000ffffL) << 24) | (lsb >>> 40); + this.random2 = (lsb & 0x000000ffffffffffL); + } + + public UlidStruct(String string) { + + UlidValidator.validate(string); + + long tm = 0; + long r1 = 0; + long r2 = 0; + final char[] chars = string.toCharArray(); + + tm |= BASE32_VALUES[chars[0x00]] << 45; + tm |= BASE32_VALUES[chars[0x01]] << 40; + tm |= BASE32_VALUES[chars[0x02]] << 35; + tm |= BASE32_VALUES[chars[0x03]] << 30; + tm |= BASE32_VALUES[chars[0x04]] << 25; + tm |= BASE32_VALUES[chars[0x05]] << 20; + tm |= BASE32_VALUES[chars[0x06]] << 15; + tm |= BASE32_VALUES[chars[0x07]] << 10; + tm |= BASE32_VALUES[chars[0x08]] << 5; + tm |= BASE32_VALUES[chars[0x09]]; + + r1 |= BASE32_VALUES[chars[0x0a]] << 35; + r1 |= BASE32_VALUES[chars[0x0b]] << 30; + r1 |= BASE32_VALUES[chars[0x0c]] << 25; + r1 |= BASE32_VALUES[chars[0x0d]] << 20; + r1 |= BASE32_VALUES[chars[0x0e]] << 15; + r1 |= BASE32_VALUES[chars[0x0f]] << 10; + r1 |= BASE32_VALUES[chars[0x10]] << 5; + r1 |= BASE32_VALUES[chars[0x11]]; + + r2 |= BASE32_VALUES[chars[0x12]] << 35; + r2 |= BASE32_VALUES[chars[0x13]] << 30; + r2 |= BASE32_VALUES[chars[0x14]] << 25; + r2 |= BASE32_VALUES[chars[0x15]] << 20; + r2 |= BASE32_VALUES[chars[0x16]] << 15; + r2 |= BASE32_VALUES[chars[0x17]] << 10; + r2 |= BASE32_VALUES[chars[0x18]] << 5; + r2 |= BASE32_VALUES[chars[0x19]]; + + this.time = tm; + this.random1 = r1; + this.random2 = r2; + } + + public String toString() { + + final char[] chars = new char[26]; + + chars[0x00] = BASE32_CHARS[(int) (time >>> 45 & 0b11111)]; + chars[0x01] = BASE32_CHARS[(int) (time >>> 40 & 0b11111)]; + chars[0x02] = BASE32_CHARS[(int) (time >>> 35 & 0b11111)]; + chars[0x03] = BASE32_CHARS[(int) (time >>> 30 & 0b11111)]; + chars[0x04] = BASE32_CHARS[(int) (time >>> 25 & 0b11111)]; + chars[0x05] = BASE32_CHARS[(int) (time >>> 20 & 0b11111)]; + chars[0x06] = BASE32_CHARS[(int) (time >>> 15 & 0b11111)]; + chars[0x07] = BASE32_CHARS[(int) (time >>> 10 & 0b11111)]; + chars[0x08] = BASE32_CHARS[(int) (time >>> 5 & 0b11111)]; + chars[0x09] = BASE32_CHARS[(int) (time & 0b11111)]; + + chars[0x0a] = BASE32_CHARS[(int) (random1 >>> 35 & 0b11111)]; + chars[0x0b] = BASE32_CHARS[(int) (random1 >>> 30 & 0b11111)]; + chars[0x0c] = BASE32_CHARS[(int) (random1 >>> 25 & 0b11111)]; + chars[0x0d] = BASE32_CHARS[(int) (random1 >>> 20 & 0b11111)]; + chars[0x0e] = BASE32_CHARS[(int) (random1 >>> 15 & 0b11111)]; + chars[0x0f] = BASE32_CHARS[(int) (random1 >>> 10 & 0b11111)]; + chars[0x10] = BASE32_CHARS[(int) (random1 >>> 5 & 0b11111)]; + chars[0x11] = BASE32_CHARS[(int) (random1 & 0b11111)]; + + chars[0x12] = BASE32_CHARS[(int) (random2 >>> 35 & 0b11111)]; + chars[0x13] = BASE32_CHARS[(int) (random2 >>> 30 & 0b11111)]; + chars[0x14] = BASE32_CHARS[(int) (random2 >>> 25 & 0b11111)]; + chars[0x15] = BASE32_CHARS[(int) (random2 >>> 20 & 0b11111)]; + chars[0x16] = BASE32_CHARS[(int) (random2 >>> 15 & 0b11111)]; + chars[0x17] = BASE32_CHARS[(int) (random2 >>> 10 & 0b11111)]; + chars[0x18] = BASE32_CHARS[(int) (random2 >>> 5 & 0b11111)]; + chars[0x19] = BASE32_CHARS[(int) (random2 & 0b11111)]; + + return new String(chars); + } + + public UUID toUuid() { + + final long msb = (time << 16) | (random1 >>> 24); + final long lsb = (random1 << 40) | random2; + + return new UUID(msb, lsb); + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + (int) (random1 ^ (random1 >>> 32)); + result = prime * result + (int) (random2 ^ (random2 >>> 32)); + result = prime * result + (int) (time ^ (time >>> 32)); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + UlidStruct other = (UlidStruct) obj; + if (random1 != other.random1) + return false; + if (random2 != other.random2) + return false; + if (time != other.time) + return false; + return true; + } +} diff --git a/src/test/java/com/github/f4b6a3/ulid/TestSuite.java b/src/test/java/com/github/f4b6a3/ulid/TestSuite.java index bc99110..4f5a0cf 100644 --- a/src/test/java/com/github/f4b6a3/ulid/TestSuite.java +++ b/src/test/java/com/github/f4b6a3/ulid/TestSuite.java @@ -8,6 +8,7 @@ import com.github.f4b6a3.ulid.ulid.UlidCreatorTest; import com.github.f4b6a3.ulid.util.UlidConverterTest; import com.github.f4b6a3.ulid.util.UlidUtilTest; import com.github.f4b6a3.ulid.util.UlidValidatorTest; +import com.github.f4b6a3.ulid.util.internal.UlidStructTest; @RunWith(Suite.class) @Suite.SuiteClasses({ @@ -16,6 +17,7 @@ import com.github.f4b6a3.ulid.util.UlidValidatorTest; UlidConverterTest.class, UlidUtilTest.class, UlidValidatorTest.class, + UlidStructTest.class, }) /** diff --git a/src/test/java/com/github/f4b6a3/ulid/util/UlidConverterTest.java b/src/test/java/com/github/f4b6a3/ulid/util/UlidConverterTest.java index 2bfda7e..2fea854 100644 --- a/src/test/java/com/github/f4b6a3/ulid/util/UlidConverterTest.java +++ b/src/test/java/com/github/f4b6a3/ulid/util/UlidConverterTest.java @@ -1,6 +1,8 @@ package com.github.f4b6a3.ulid.util; import static org.junit.Assert.*; + +import java.util.Random; import java.util.UUID; import org.junit.Test; @@ -8,6 +10,7 @@ import org.junit.Test; import com.github.f4b6a3.ulid.UlidCreator; import com.github.f4b6a3.ulid.util.UlidConverter; import com.github.f4b6a3.ulid.util.UlidValidator; +import com.github.f4b6a3.ulid.util.internal.UlidStruct; public class UlidConverterTest { @@ -32,4 +35,84 @@ public class UlidConverterTest { } } + + @Test + public void testToString1() { + + for (int i = 0; i < DEFAULT_LOOP_MAX; i++) { + + Random random = new Random(); + final long time = random.nextLong(); + final long random1 = random.nextLong(); + final long random2 = random.nextLong(); + UlidStruct struct0 = new UlidStruct(time, random1, random2); + + String string1 = struct0.toString(); + UlidStruct struct1 = new UlidStruct(string1); + + assertEquals(struct0.time, struct1.time); + assertEquals(struct0.random1, struct1.random1); + assertEquals(struct0.random2, struct1.random2); + } + } + + @Test + public void testToString2() { + + for (int i = 0; i < DEFAULT_LOOP_MAX; i++) { + + UUID ulid0 = UlidCreator.getUlid(); + UlidStruct struct0 = new UlidStruct(ulid0); + + String string1 = UlidConverter.toString(ulid0); + UlidStruct struct1 = new UlidStruct(string1); + + assertEquals(struct0.time, struct1.time); + assertEquals(struct0.random1, struct1.random1); + assertEquals(struct0.random2, struct1.random2); + } + } + + @Test + public void testToString3() { + + for (int i = 0; i < DEFAULT_LOOP_MAX; i++) { + + UUID ulid0 = UlidCreator.getUlid(); + UlidStruct struct0 = new UlidStruct(ulid0); + + String string1 = struct0.toString(); + UlidStruct struct1 = new UlidStruct(string1); + + assertEquals(struct0.time, struct1.time); + assertEquals(struct0.random1, struct1.random1); + assertEquals(struct0.random2, struct1.random2); + } + } + + @Test + public void testToString4() { + + for (int i = 0; i < DEFAULT_LOOP_MAX; i++) { + + UUID ulid0 = UlidCreator.getUlid(); + UlidStruct struct0 = new UlidStruct(ulid0); + + String string1 = UlidConverter.toString(ulid0); + UlidStruct struct1 = new UlidStruct(string1); + + String string2 = struct0.toString(); + UlidStruct struct2 = new UlidStruct(string2); + + assertEquals(string1, string2); + + assertEquals(struct0.time, struct1.time); + assertEquals(struct0.random1, struct1.random1); + assertEquals(struct0.random2, struct1.random2); + + assertEquals(struct0.time, struct2.time); + assertEquals(struct0.random1, struct2.random1); + assertEquals(struct0.random2, struct2.random2); + } + } } diff --git a/src/test/java/com/github/f4b6a3/ulid/util/internal/UlidStructTest.java b/src/test/java/com/github/f4b6a3/ulid/util/internal/UlidStructTest.java new file mode 100644 index 0000000..eb4d592 --- /dev/null +++ b/src/test/java/com/github/f4b6a3/ulid/util/internal/UlidStructTest.java @@ -0,0 +1,159 @@ +package com.github.f4b6a3.ulid.util.internal; + +import static org.junit.Assert.assertEquals; + +import java.util.Random; +import java.util.UUID; + +import org.junit.Test; + +public class UlidStructTest { + + private static final int DEFAULT_LOOP_MAX = 100_000; + + protected static final char[] ALPHABET_CROCKFORD = "0123456789ABCDEFGHJKMNPQRSTVWXYZ".toCharArray(); + protected static final char[] ALPHABET_JAVA = "0123456789abcdefghijklmnopqrstuv".toCharArray(); // Long.parseUnsignedLong() + + @Test + public void testConstructorLongs() { + for (int i = 0; i < DEFAULT_LOOP_MAX; i++) { + Random random = new Random(); + final long time = random.nextLong(); + final long random1 = random.nextLong(); + final long random2 = random.nextLong(); + UlidStruct struct0 = new UlidStruct(time, random1, random2); // <-- under test + + assertEquals(time & 0xffffffffffffL, struct0.time); + assertEquals(random1 & 0xffffffffffL, struct0.random1); + assertEquals(random2 & 0xffffffffffL, struct0.random2); + } + } + + @Test + public void testConstructorString() { + for (int i = 0; i < DEFAULT_LOOP_MAX; i++) { + Random random = new Random(); + final long time = random.nextLong(); + final long random1 = random.nextLong(); + final long random2 = random.nextLong(); + UlidStruct struct0 = new UlidStruct(time, random1, random2); + + String string1 = toString(struct0); + UlidStruct struct1 = new UlidStruct(string1); // <-- under test + assertEquals(struct0, struct1); + } + } + + @Test + public void testConstructorUuid() { + for (int i = 0; i < DEFAULT_LOOP_MAX; i++) { + Random random = new Random(); + final long msb = random.nextLong(); + final long lsb = random.nextLong(); + final UUID uuid0 = new UUID(msb, lsb); + UlidStruct struct0 = new UlidStruct(uuid0); // <-- under test + + UUID uuid1 = toUuid(struct0); + assertEquals(uuid0, uuid1); + } + } + + @Test + public void testToString() { + + for (int i = 0; i < DEFAULT_LOOP_MAX; i++) { + Random random = new Random(); + final long time = random.nextLong(); + final long random1 = random.nextLong(); + final long random2 = random.nextLong(); + UlidStruct struct0 = new UlidStruct(time, random1, random2); + + String string1 = toString(struct0); + String string2 = struct0.toString(); // <-- under test + assertEquals(string1, string2); + } + } + + @Test + public void testToUuid() { + + for (int i = 0; i < DEFAULT_LOOP_MAX; i++) { + Random random = new Random(); + final long time = random.nextLong(); + final long random1 = random.nextLong(); + final long random2 = random.nextLong(); + UlidStruct struct0 = new UlidStruct(time, random1, random2); + + UUID uuid1 = toUuid(struct0); + UUID uuid2 = struct0.toUuid(); // <-- under test + assertEquals(uuid1, uuid2); + } + } + + public UlidStruct fromString(String string) { + + long time = 0; + long random1 = 0; + long random2 = 0; + + String tm = string.substring(0, 10); + String r1 = string.substring(10, 18); + String r2 = string.substring(18, 26); + + tm = transliterate(tm, ALPHABET_CROCKFORD, ALPHABET_JAVA); + r1 = transliterate(r1, ALPHABET_CROCKFORD, ALPHABET_JAVA); + r2 = transliterate(r2, ALPHABET_CROCKFORD, ALPHABET_JAVA); + + time = Long.parseUnsignedLong(tm, 32); + random1 = Long.parseUnsignedLong(r1, 32); + random2 = Long.parseUnsignedLong(r2, 32); + + return new UlidStruct(time, random1, random2); + } + + public UUID toUuid(UlidStruct struct) { + + long time = struct.time & 0xffffffffffffL; + long random1 = struct.random1 & 0xffffffffffL; + long random2 = struct.random2 & 0xffffffffffL; + + final long msb = (time << 16) | (random1 >>> 24); + final long lsb = (random1 << 40) | random2; + + return new UUID(msb, lsb); + } + + public String toString(UlidStruct struct) { + + final String tzero = "0000000000"; + final String rzero = "00000000"; + + String time = Long.toUnsignedString(struct.time, 32); + String random1 = Long.toUnsignedString(struct.random1, 32); + String random2 = Long.toUnsignedString(struct.random2, 32); + + time = tzero.substring(0, tzero.length() - time.length()) + time; + random1 = rzero.substring(0, rzero.length() - random1.length()) + random1; + random2 = rzero.substring(0, rzero.length() - random2.length()) + random2; + + time = transliterate(time, ALPHABET_JAVA, ALPHABET_CROCKFORD); + random1 = transliterate(random1, ALPHABET_JAVA, ALPHABET_CROCKFORD); + random2 = transliterate(random2, ALPHABET_JAVA, ALPHABET_CROCKFORD); + + return time + random1 + random2; + } + + private static String transliterate(String string, char[] alphabet1, char[] alphabet2) { + char[] output = string.toCharArray(); + for (int i = 0; i < output.length; i++) { + for (int j = 0; j < alphabet1.length; j++) { + if (output[i] == alphabet1[j]) { + output[i] = alphabet2[j]; + break; + } + } + } + return new String(output); + } + +}