package edu.uw.zookeeper.data; import static com.google.common.base.Preconditions.checkNotNull; import java.util.Comparator; import java.util.Locale; import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.annotation.Nonnull; import com.google.common.base.Converter; import com.google.common.base.Optional; import com.google.common.primitives.UnsignedInteger; /** * Converts a sequential znode label to a prefix and sequence number. */ public abstract class Sequential<T extends CharSequence & Comparable<? super T>, U extends Number> implements Comparable<Sequential<T,?>> { public static final String SEQUENTIAL_FORMAT = "%s%010d"; public static final Pattern OVERFLOW_PATTERN = Pattern.compile(Overflowed.OVERFLOW_SEQUENCE.toString()); public static final Pattern SEQUENCE_PATTERN = Pattern.compile("[0-9]{10}"); public static final Pattern SUFFIX_PATTERN = Pattern.compile("((" + OVERFLOW_PATTERN + ")|(" + SEQUENCE_PATTERN + "))"); public static final Pattern LABEL_PATTERN = Pattern.compile("^(.+?)" + SUFFIX_PATTERN.pattern() + "$"); public static Optional<? extends Sequential<String,?>> maybeFromString(CharSequence input) { final Matcher m = LABEL_PATTERN.matcher(input); if (m.matches()) { final String prefix = m.group(1); final String sequence = m.group(4); final Sequential<String,?> sequential; if (sequence != null) { sequential = Sequenced.valueOf(prefix, UnsignedInteger.valueOf(sequence)); } else { assert (m.group(3) != null); sequential = Overflowed.valueOf(prefix); } return Optional.of(sequential); } else { return Optional.absent(); } } @Serializes(from=String.class, to=Sequential.class) public static Sequential<String,?> fromString(CharSequence input) { Optional<? extends Sequential<String,?>> sequential = maybeFromString(input); if (sequential.isPresent()) { return sequential.get(); } else { throw new IllegalArgumentException(String.valueOf(input)); } } public static String toString(Sequential<?,?> input) { return String.format(Locale.ENGLISH, SEQUENTIAL_FORMAT, input.prefix(), input.sequence().intValue()); } public static Converter<Sequential<?,?>, CharSequence> converter() { return new SequentialConverter(); } public static Comparator<String> comparator() { return new SequentialComparator(); } public static <T extends CharSequence & Comparable<? super T>> Sequential<T,?> fromInt(T prefix, int value) { if (value >= 0) { return sequenced(prefix, UnsignedInteger.fromIntBits(value)); } else { return overflowed(prefix); } } public static <T extends CharSequence & Comparable<? super T>> Sequenced<T> sequenced(T prefix, UnsignedInteger sequence) { return Sequenced.valueOf(prefix, sequence); } public static <T extends CharSequence & Comparable<? super T>> Overflowed<T> overflowed(T prefix) { return Overflowed.valueOf(prefix); } private final T prefix; private Sequential(@Nonnull T prefix) { assert (prefix != null); this.prefix = prefix; } public final T prefix() { return prefix; } /** * Orders by sequence then by prefix. */ @Override public abstract int compareTo(Sequential<T, ?> other); public abstract U sequence(); @Serializes(from=Sequential.class, to=String.class) @Override public String toString() { return toString(this); } @Override public int hashCode() { return sequence().hashCode(); } public static final class Sequenced<T extends CharSequence & Comparable<? super T>> extends Sequential<T, UnsignedInteger> { public static <T extends CharSequence & Comparable<? super T>> Sequenced<T> valueOf(T prefix, UnsignedInteger sequence) { try { checkNotNull(prefix).charAt(0); } catch (IndexOutOfBoundsException e) { throw new IllegalArgumentException("Sequenced prefix can't be empty"); } return new Sequenced<T>(prefix, checkNotNull(sequence)); } private final UnsignedInteger sequence; private Sequenced(@Nonnull T prefix, @Nonnull UnsignedInteger sequence) { super(prefix); this.sequence = sequence; } @Override public UnsignedInteger sequence() { return sequence; } @Override public int compareTo(Sequential<T, ?> other) { int result; if (other instanceof Sequenced) { result = sequence.compareTo(((Sequenced<?>) other).sequence); if (result == 0) { result = prefix().compareTo(other.prefix()); } } else { assert (other instanceof Overflowed); result = -1; } return result; } @Override public boolean equals(Object obj) { if (!(obj instanceof Sequenced)) { return false; } Sequenced<?> other = (Sequenced<?>) obj; return sequence().equals(other.sequence()) && prefix().equals(other.prefix()); } } /** * From the documentation it's not clear what happens when there are * multiple overflows. For now we'll assume that the first one gets the * overflow value and the creation of the rest fails. */ public static final class Overflowed<T extends CharSequence & Comparable<? super T>> extends Sequential<T, Integer> { public static final Integer OVERFLOW_SEQUENCE = Integer.valueOf(Integer.MIN_VALUE + 1); public static <T extends CharSequence & Comparable<? super T>> Overflowed<T> valueOf(T prefix) { try { checkNotNull(prefix).charAt(0); } catch (IndexOutOfBoundsException e) { throw new IllegalArgumentException("Overflowed prefix can't be empty"); } return new Overflowed<T>(checkNotNull(prefix)); } private Overflowed(@Nonnull T prefix) { super(prefix); } @Override public Integer sequence() { return OVERFLOW_SEQUENCE; } @Override public int compareTo(Sequential<T, ?> other) { int result; if (other instanceof Sequenced) { result = 1; } else { assert (other instanceof Overflowed); result = prefix().compareTo(other.prefix()); } return result; } @Override public boolean equals(Object obj) { if (!(obj instanceof Overflowed)) { return false; } return prefix().equals(((Overflowed<?>) obj).prefix()); } } public static final class SequentialConverter extends Converter<Sequential<?,?>, CharSequence> { public SequentialConverter() {} @Override protected Sequential<String, ?> doBackward(CharSequence input) { return fromString(input); } @Override protected String doForward(Sequential<?,?> input) { return input.toString(); } } /** * Uses the inherent ordering of Strings and Sequentials. * For mixed cases, compares the prefix of the Sequential to the String. * if the prefix of the sequential equals the string, * then the sequential is ordered higher. */ public static final class SequentialComparator implements Comparator<String> { public SequentialComparator() {} @Override public int compare(String o1, String o2) { Optional<? extends Sequential<String,?>> s1 = maybeFromString(o1); Optional<? extends Sequential<String,?>> s2 = maybeFromString(o2); if (s1.isPresent()) { if (s2.isPresent()) { return (s1.get().compareTo(s2.get())); } else { int cmp = s1.get().prefix().compareTo(o2); if (cmp == 0) { cmp = 1; } return cmp; } } else { if (s2.isPresent()) { int cmp = o1.compareTo(s2.get().prefix()); if (cmp == 0) { cmp = -1; } return cmp; } else { return o1.compareTo(o2); } } } } }