| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package accord.maelstrom; |
| |
| import java.io.IOException; |
| import java.util.function.BiFunction; |
| import java.util.zip.CRC32; |
| |
| import com.google.gson.TypeAdapter; |
| import com.google.gson.stream.JsonReader; |
| import com.google.gson.stream.JsonWriter; |
| |
| public class Datum implements Comparable<Datum> |
| { |
| public static final boolean COMPARE_BY_HASH = true; |
| |
| public static class Hash implements Comparable<Hash> |
| { |
| final int hash; |
| public Hash(int hash) |
| { |
| this.hash = hash; |
| } |
| |
| @Override |
| public int compareTo(Hash that) |
| { |
| return Integer.compare(this.hash, that.hash); |
| } |
| |
| public String toString() |
| { |
| return "#" + hash; |
| } |
| } |
| |
| public enum Kind |
| { |
| STRING, LONG, DOUBLE, HASH; |
| |
| public MaelstromKey.Routing[] split(int count) |
| { |
| if (count <= 1) |
| throw new IllegalArgumentException(); |
| |
| MaelstromKey.Routing[] result = new MaelstromKey.Routing[count]; |
| if (this != Kind.HASH) |
| throw new UnsupportedOperationException(); |
| |
| int delta = 2 * (Integer.MAX_VALUE / count); |
| int start = Integer.MIN_VALUE; |
| for (int i = 0; i < count; ++i) |
| result[i] = new MaelstromKey.Routing(start + i * delta); |
| |
| return result; |
| } |
| |
| private static final int CHARS = 63; |
| |
| private static String toString(long v) |
| { |
| if (v == 0) return ""; |
| --v; |
| char[] buf = new char[4]; |
| for (int i = 3 ; i >= 0 ; --i) |
| { |
| buf[i] = toChar(v % CHARS); |
| v /= CHARS; |
| } |
| return new String(buf); |
| } |
| |
| private static char toChar(long v) |
| { |
| if (v == 0) return ' '; |
| v -= 1; |
| if (v < 10) return (char) ('0' + v); |
| v -= 10; |
| if (v < 26) return (char) ('A' + v); |
| v -= 26; |
| return (char) ('a' + v); |
| } |
| |
| } |
| |
| public final Kind kind; |
| public final Object value; |
| |
| Datum(Kind kind, Object value) |
| { |
| this.kind = kind; |
| this.value = value; |
| } |
| |
| public Datum(String value) |
| { |
| this(Kind.STRING, value); |
| } |
| |
| public Datum(Long value) |
| { |
| this(Kind.LONG, value); |
| } |
| |
| public Datum(Double value) |
| { |
| this(Kind.DOUBLE, value); |
| } |
| |
| public Datum(Hash value) |
| { |
| this(Kind.HASH, value); |
| } |
| |
| @Override |
| public int hashCode() |
| { |
| return value == null ? 0 : hash(value); |
| } |
| |
| @Override |
| public boolean equals(Object that) |
| { |
| return that == this || (that instanceof Datum && equals((Datum) that)); |
| } |
| |
| public boolean equals(Datum that) |
| { |
| return this.kind.equals(that.kind) && this.value.equals(that.value); |
| } |
| |
| @Override |
| public String toString() |
| { |
| return value == null ? kind + ":+Inf" : value.toString(); |
| } |
| |
| @Override |
| public int compareTo(Datum that) |
| { |
| int c = 0; |
| if (COMPARE_BY_HASH) |
| c = Integer.compare(hash(this.value), hash(that.value)); |
| if (c == 0) c = this.kind.compareTo(that.kind); |
| if (c != 0) return c; |
| if (this.value == null || that.value == null) |
| { |
| if (this.value == null && that.value == null) |
| return 0; |
| return this.value == null ? 1 : -1; |
| } |
| return ((Comparable)this.value).compareTo(that.value); |
| } |
| |
| static int hash(Object object) |
| { |
| if (object == null) |
| return Integer.MAX_VALUE; |
| |
| if (object instanceof Hash) |
| return ((Hash) object).hash; |
| |
| CRC32 crc32c = new CRC32(); |
| int i = object.hashCode(); |
| crc32c.update(i); |
| crc32c.update(i >> 8); |
| crc32c.update(i >> 16); |
| crc32c.update(i >> 24); |
| return (int)crc32c.getValue(); |
| } |
| |
| public static Datum read(JsonReader in) throws IOException |
| { |
| return read(in, Datum::new); |
| } |
| |
| public void write(JsonWriter out) throws IOException |
| { |
| if (!isSimple()) |
| { |
| out.beginArray(); |
| out.value(kind.toString()); |
| if (kind == Kind.HASH) |
| { |
| out.value(value != null); |
| if (value != null) |
| out.value(((Hash)value).hash); |
| } |
| out.endArray(); |
| return; |
| } |
| switch (kind) |
| { |
| default: throw new IllegalStateException(); |
| case LONG: out.value((Long) value); break; |
| case DOUBLE: out.value((Double) value); break; |
| case STRING: out.value((String) value); break; |
| } |
| } |
| |
| public boolean isSimple() |
| { |
| return value != null && kind != Kind.HASH; |
| } |
| |
| protected static <V> V read(JsonReader in, BiFunction<Kind, Object, V> constructor) throws IOException |
| { |
| Datum.Kind type; |
| Object value; |
| switch (in.peek()) |
| { |
| default: |
| throw new IllegalStateException(); |
| case BEGIN_ARRAY: |
| in.beginArray(); |
| type = Kind.valueOf(in.nextString()); |
| if (type == Kind.HASH && in.nextBoolean()) value = new Hash(in.nextInt()); |
| else value = null; |
| in.endArray(); |
| break; |
| case STRING: |
| value = in.nextString(); |
| type = Datum.Kind.STRING; |
| break; |
| case NUMBER: |
| try { value = in.nextLong(); type = Datum.Kind.LONG; } |
| catch (IllegalArgumentException iae) { value = in.nextDouble(); type = Datum.Kind.DOUBLE; } |
| break; |
| } |
| return constructor.apply(type, value); |
| } |
| |
| public static final TypeAdapter<Datum> GSON_ADAPTER = new TypeAdapter<Datum>() |
| { |
| @Override |
| public void write(JsonWriter out, Datum value) throws IOException |
| { |
| value.write(out); |
| } |
| |
| @Override |
| public Datum read(JsonReader in) throws IOException |
| { |
| return Datum.read(in); |
| } |
| }; |
| } |