blob: 696f7d61bc0823f0801222b9a900cfcd9a8c808b [file] [log] [blame]
/*
* 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);
}
};
}