com.github.protobufel.grammar.Scopes Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of protobufel-grammar Show documentation
Show all versions of protobufel-grammar Show documentation
ProtoBuf Java Parser and FileDescriptor Builder
//
// Copyright © 2014, David Tesler (https://github.com/protobufel)
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in the
// documentation and/or other materials provided with the distribution.
// * Neither the name of the nor the
// names of its contributors may be used to endorse or promote products
// derived from this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY
// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//
package com.github.protobufel.grammar;
import static com.github.protobufel.grammar.ProtoFileParser.MAX_FIELD_NUMBER;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.NavigableSet;
import java.util.Set;
import java.util.TreeSet;
import com.github.protobufel.grammar.Exceptions.InvalidExtensionRange;
import com.github.protobufel.grammar.ProtoFileParser.ContextLookup;
import com.github.protobufel.grammar.ProtoParser.ExtensionsContext;
import com.google.protobuf.DescriptorProtos.DescriptorProto;
import com.google.protobuf.DescriptorProtos.DescriptorProto.Builder;
import com.google.protobuf.DescriptorProtos.DescriptorProtoOrBuilder;
import com.google.protobuf.DescriptorProtos.EnumDescriptorProto;
import com.google.protobuf.DescriptorProtos.EnumOptions;
import com.google.protobuf.DescriptorProtos.EnumValueDescriptorProto;
import com.google.protobuf.DescriptorProtos.EnumValueOptions;
import com.google.protobuf.DescriptorProtos.FieldDescriptorProto;
import com.google.protobuf.DescriptorProtos.FieldDescriptorProtoOrBuilder;
import com.google.protobuf.DescriptorProtos.FieldOptions;
import com.google.protobuf.DescriptorProtos.FileDescriptorProto;
import com.google.protobuf.DescriptorProtos.FileOptions;
import com.google.protobuf.DescriptorProtos.MessageOptions;
import com.google.protobuf.DescriptorProtos.MethodDescriptorProto;
import com.google.protobuf.DescriptorProtos.MethodOptions;
import com.google.protobuf.DescriptorProtos.OneofDescriptorProto;
import com.google.protobuf.DescriptorProtos.ServiceDescriptorProto;
import com.google.protobuf.DescriptorProtos.ServiceOptions;
import com.google.protobuf.DescriptorProtos.UninterpretedOption;
import com.google.protobuf.Descriptors.Descriptor;
import com.google.protobuf.GeneratedMessage;
/**
* Classes for {@link ProtoFileParser}'s proto elements scope processing, including some uniqueness
* validation.
*
* @author [email protected] David Tesler
*/
class Scopes {
private Scope> currentScope;
private final FileScope rootScope;
private final SymbolScopes symbolScopes;
private final ContextLookup contextLookup;
public Scopes(final FileDescriptorProto.Builder fileBuilder, final ContextLookup contextLookup) {
rootScope = new FileScope(fileBuilder);
currentScope = rootScope;
this.contextLookup = contextLookup;
symbolScopes = new SymbolScopes();
}
public void init() {
symbolScopes.initGlobalScope(getFileBuilder().getPackage());
}
public FileDescriptorProto.Builder getFileBuilder() {
return rootScope.protoBuilder;
}
@SuppressWarnings("unchecked")
public List resolveAllSymbols() {
return (List) symbolScopes.resolveAllSymbols(contextLookup);
}
// FIXME local extension fields with the same extendee, whether resolved or not, mustn't have
// duplicates!
public void validateAllExtensionNumbers() {
final Map> extensionNumbers = new HashMap>(); // multiset
Map> localExtensionNumbers = new HashMap>();
for (final FieldDescriptorProtoOrBuilder extension : rootScope.protoBuilder
.getExtensionOrBuilderList()) {
processExtensionField(extensionNumbers, localExtensionNumbers, extension);
}
localExtensionNumbers = null;
for (final DescriptorProtoOrBuilder childProto : rootScope.protoBuilder
.getMessageTypeOrBuilderList()) {
processAllExtensions(childProto, extensionNumbers);
}
}
protected void processAllExtensions(final DescriptorProtoOrBuilder proto,
final Map> extensionNumbers) {
Map> localExtensionNumbers = new HashMap>();
for (final FieldDescriptorProtoOrBuilder extension : proto.getExtensionOrBuilderList()) {
processExtensionField(extensionNumbers, localExtensionNumbers, extension);
}
localExtensionNumbers = null;
for (final DescriptorProtoOrBuilder childProto : proto.getNestedTypeOrBuilderList()) {
processAllExtensions(childProto, extensionNumbers);
}
}
protected void processExtensionField(final Map> extensionNumbers,
final Map> localExtensionNumbers,
final FieldDescriptorProtoOrBuilder extension) {
if (extension.getExtendee().startsWith(".")) {
// use global cache
lookupExtension(extensionNumbers, extension);
} else {
// use local Message-level cache
lookupExtension(localExtensionNumbers, extension);
}
}
protected void lookupExtension(final Map> extensionNumberCache,
final FieldDescriptorProtoOrBuilder extension) {
Set fieldNumbers = extensionNumberCache.get(extension.getExtendee());
if (fieldNumbers == null) {
fieldNumbers = new HashSet();
fieldNumbers.add(extension.getNumber());
extensionNumberCache.put(extension.getExtendee(), fieldNumbers);
} else if (!fieldNumbers.add(extension.getNumber())) {
contextLookup.reportNonUniqueExtensionNumberError(extension, false);
}
}
@SuppressWarnings("unchecked")
public > T getProtoBuilder() {
return (T) currentScope.protoBuilder;
}
public Scope> popScope() {
currentScope = currentScope.popScope();
return currentScope;
}
public Scope> popScope(final Descriptor type) {
currentScope = currentScope.popScope(type);
return currentScope;
}
public Descriptor getDescriptor() {
return currentScope.getDescriptor();
}
public Scope> getCurrentScope() {
return currentScope;
}
private > T pushScope(final T scope) {
currentScope = scope;
return scope;
}
public DescriptorProto.Builder addMessage(final String messageName) {
final DescriptorProto.Builder builder = currentScope.addMessage().setName(messageName);
pushScope(new MessageScope(builder, currentScope));
symbolScopes.pushScope(messageName);
return builder;
}
public DescriptorProto.Builder addGroup(final String messageName) {
final DescriptorProto.Builder builder = currentScope.addMessage().setName(messageName);
pushScope(new GroupScope(builder, currentScope));
symbolScopes.pushScope(messageName);
return builder;
}
public EnumDescriptorProto.Builder addEnum(final String enumName) {
final EnumDescriptorProto.Builder builder = currentScope.addEnum().setName(enumName);
pushScope(new EnumScope(builder, currentScope));
symbolScopes.addLeaf(enumName);
return builder;
}
public EnumValueDescriptorProto.Builder addEnumValue() {
final EnumValueDescriptorProto.Builder builder = currentScope.addEnumValue();
pushScope(new EnumValueScope(builder, currentScope));
return builder;
}
public FieldDescriptorProto.Builder addField() {
FieldDescriptorProto.Builder builder = null;
// FIXME and then remove try!
try {
builder = currentScope.addField();
} catch (final Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
pushScope(new FieldScope(builder, currentScope));
return builder;
}
public boolean addIfUnresolved(final FieldDescriptorProto.Builder protoBuilder) {
return symbolScopes.addIfUnresolved(protoBuilder);
}
public ServiceDescriptorProto.Builder addService() {
final ServiceDescriptorProto.Builder builder = currentScope.addService();
pushScope(new ServiceScope(builder, currentScope));
return builder;
}
public MethodDescriptorProto.Builder addMethod() {
final MethodDescriptorProto.Builder builder = currentScope.addMethod();
pushScope(new MethodScope(builder, currentScope));
return builder;
}
public void addExtensionRange(final int start, final int end, final ExtensionsContext ctx) {
currentScope.addExtensionRange(start, end, ctx);
}
public void addExtend(final String extendee) {
pushScope(newExtendScope(currentScope, extendee));
}
private > ExtendScope newExtendScope(
final Scope parent, final String extendee) {
return new ExtendScope(parent, extendee);
}
public OneofDescriptorProto.Builder addOneOf() {
final OneofDescriptorProto.Builder builder = currentScope.addOneof();
pushScope(new OneofScope(builder, (MessageScope) currentScope));
return builder;
}
public boolean isOptionNameUnique(final String optionName) {
return currentScope.isOptionNameUnique(optionName);
}
public void verifyField(final FieldDescriptorProto.Builder field) {
if (currentScope instanceof MessageScope) {
final MessageScope scope = (MessageScope) currentScope;
if (!scope.verifyFieldNameUnique(field.getName())) {
contextLookup.reportNonUniqueFieldNameError(field, false);
}
if (!scope.verifyFieldNumberUnique(field.getNumber())) {
contextLookup.reportNonUniqueFieldNumberError(field, false);
}
}
}
public void verifyOneofName(final OneofDescriptorProto.Builder oneof) {
if (currentScope instanceof MessageScope) {
final MessageScope scope = (MessageScope) currentScope;
if (!scope.verifyOneofNameUnique(oneof.getName())) {
contextLookup.reportNonUniqueOneofNameError(oneof, false);
}
}
}
public UninterpretedOption.Builder addCustomOption() {
return currentScope.addCustomOption();
}
public FileOptions.Builder getFileOptions() {
return currentScope.getFileOptions();
}
public MessageOptions.Builder getMessageOptions() {
return currentScope.getMessageOptions();
}
public FieldOptions.Builder getFieldOptions() {
return currentScope.getFieldOptions();
}
public EnumOptions.Builder getEnumOptions() {
return currentScope.getEnumOptions();
}
public EnumValueOptions.Builder getEnumValueOptions() {
return currentScope.getEnumValueOptions();
}
public ServiceOptions.Builder getServiceOptions() {
return currentScope.getServiceOptions();
}
public MethodOptions.Builder getMethodOptions() {
return currentScope.getMethodOptions();
}
// ***************** Member classes START
protected abstract class Scope> {
private static final String NOT_APPLICABLE_IN_CURRENT_SCOPE =
"not applicable in current scope!";
protected final BType protoBuilder;
private Scope> parent;
// validation data
private Set optionNames = null;
private Scope(final BType protoBuilder, final Scope> parent) {
this.protoBuilder = protoBuilder;
this.parent = parent;
}
protected Scope> getParent() {
return parent;
}
public Scope> popScope() {
if (parent == null) {
throw new RuntimeException("cannot pop the root scope");
}
popSymbolScope();
final Scope> result = parent;
parent = null;
return result;
}
/**
* To be overriden by symbol storing scopes. At the moment, the symbol storing scopes are :
*
* - MessageScope
*
- EnumScope
*
- GroupScope
*/
protected void popSymbolScope() {
// NOOP
}
public Scope> popScope(final Descriptor type) {
Scope> scope = this;
while (!scope.getDescriptor().equals(type)) {
scope = scope.popScope();
}
return scope;
}
public Descriptor getDescriptor() {
return protoBuilder.getDescriptorForType();
}
public BType getProtoBuilder() {
return protoBuilder;
}
public boolean isOptionNameUnique(final String optionName) {
if (optionNames == null) {
optionNames = new HashSet
();
}
return optionNames.add(optionName);
}
protected DescriptorProto.Builder addGroup() {
return addMessage();
}
protected DescriptorProto.Builder addMessage() {
throw new RuntimeException(NOT_APPLICABLE_IN_CURRENT_SCOPE);
}
protected EnumDescriptorProto.Builder addEnum() {
throw new RuntimeException(NOT_APPLICABLE_IN_CURRENT_SCOPE);
}
protected FieldDescriptorProto.Builder addExtension() {
throw new RuntimeException(NOT_APPLICABLE_IN_CURRENT_SCOPE);
}
protected OneofDescriptorProto.Builder addOneof() {
throw new RuntimeException(NOT_APPLICABLE_IN_CURRENT_SCOPE);
}
protected void addExtensionRange(final int start, final int end, final ExtensionsContext ctx) {
throw new RuntimeException(NOT_APPLICABLE_IN_CURRENT_SCOPE);
}
protected FieldDescriptorProto.Builder addField() {
throw new RuntimeException(NOT_APPLICABLE_IN_CURRENT_SCOPE);
}
protected EnumValueDescriptorProto.Builder addEnumValue() {
throw new RuntimeException(NOT_APPLICABLE_IN_CURRENT_SCOPE);
}
protected ServiceDescriptorProto.Builder addService() {
throw new RuntimeException(NOT_APPLICABLE_IN_CURRENT_SCOPE);
}
protected MethodDescriptorProto.Builder addMethod() {
throw new RuntimeException(NOT_APPLICABLE_IN_CURRENT_SCOPE);
}
// Options stuff
protected UninterpretedOption.Builder addCustomOption() {
throw new RuntimeException(NOT_APPLICABLE_IN_CURRENT_SCOPE);
}
protected FileOptions.Builder getFileOptions() {
throw new RuntimeException(NOT_APPLICABLE_IN_CURRENT_SCOPE);
}
protected MessageOptions.Builder getMessageOptions() {
throw new RuntimeException(NOT_APPLICABLE_IN_CURRENT_SCOPE);
}
protected FieldOptions.Builder getFieldOptions() {
throw new RuntimeException(NOT_APPLICABLE_IN_CURRENT_SCOPE);
}
protected EnumOptions.Builder getEnumOptions() {
throw new RuntimeException(NOT_APPLICABLE_IN_CURRENT_SCOPE);
}
protected EnumValueOptions.Builder getEnumValueOptions() {
throw new RuntimeException(NOT_APPLICABLE_IN_CURRENT_SCOPE);
}
protected ServiceOptions.Builder getServiceOptions() {
throw new RuntimeException(NOT_APPLICABLE_IN_CURRENT_SCOPE);
}
protected MethodOptions.Builder getMethodOptions() {
throw new RuntimeException(NOT_APPLICABLE_IN_CURRENT_SCOPE);
}
}
protected class FileScope extends Scope {
private FileScope(final FileDescriptorProto.Builder protoBuilder) {
super(protoBuilder, null);
}
@Override
protected EnumDescriptorProto.Builder addEnum() {
return protoBuilder.addEnumTypeBuilder();
}
@Override
protected DescriptorProto.Builder addMessage() {
return protoBuilder.addMessageTypeBuilder();
}
@Override
protected FieldDescriptorProto.Builder addExtension() {
return protoBuilder.addExtensionBuilder();
}
@Override
protected ServiceDescriptorProto.Builder addService() {
return protoBuilder.addServiceBuilder();
}
@Override
protected UninterpretedOption.Builder addCustomOption() {
return protoBuilder.getOptionsBuilder().addUninterpretedOptionBuilder();
}
@Override
protected FileOptions.Builder getFileOptions() {
return protoBuilder.getOptionsBuilder();
}
}
protected class MessageScope extends Scope {
private static final int RESERVED_EXTENSION_RANGE_END = 19999;
private static final int RESERVED_EXTENSION_RANGE_START = 19000;
private IntegerRanges ranges;
// TODO: uniqueness validation data - only validated what FileDescriptor won't, maybe we should!
private Set fieldNames = null;
private Set fieldNumbers = null;
private Set oneofNames = null;
protected MessageScope(final DescriptorProto.Builder protoBuilder, final Scope> parent) {
super(protoBuilder, parent);
}
@Override
public Scope> popScope() {
verifyFieldsNotInExtensionRanges();
return super.popScope();
}
@Override
protected void popSymbolScope() {
// super.popSymbolScope();
symbolScopes.popScope();
}
public boolean verifyOneofNameUnique(final String oneofName) {
if (oneofNames == null) {
oneofNames = new HashSet();
}
return oneofNames.add(oneofName);
}
public boolean verifyFieldNameUnique(final String fieldName) {
if (fieldNames == null) {
fieldNames = new HashSet();
}
return fieldNames.add(fieldName);
}
public boolean verifyFieldNumberUnique(final int fieldNumber) {
if (fieldNumbers == null) {
fieldNumbers = new HashSet();
}
return fieldNumbers.add(fieldNumber);
}
public boolean verifyFieldsNotInExtensionRanges() {
boolean isValid = true;
for (final FieldDescriptorProtoOrBuilder field : protoBuilder.getFieldOrBuilderList()) {
if (isInExtensionRanges(field.getNumber())) {
contextLookup.reportFieldInExtensionRangeEror(field, false);
isValid = false;
}
}
return isValid;
}
private boolean isInExtensionRanges(final int fieldNumber) {
return ranges != null && ranges.contains(fieldNumber);
}
@Override
protected void addExtensionRange(final int start, final int end, final ExtensionsContext ctx) {
checkValidExtensionRange(start, end, ctx);
checkNoExtensionRangeOverlaps(start, end, ctx);
protoBuilder.addExtensionRangeBuilder().setStart(start).setEnd(end + 1);
}
private boolean checkValidExtensionRange(final int start, final int end,
final ExtensionsContext ctx) {
if (start < 0 || start > end || end > MAX_FIELD_NUMBER) {
contextLookup.reportInvalidExtensionRange(
new InvalidExtensionRange("wrong extension range"), ctx);
} else if (start >= RESERVED_EXTENSION_RANGE_START && end <= RESERVED_EXTENSION_RANGE_END) {
contextLookup.reportInvalidExtensionRange(new InvalidExtensionRange(
"cannot use reserved extension range"), ctx);
}
return true;
}
private boolean checkNoExtensionRangeOverlaps(final int start, final int end,
final ExtensionsContext ctx) {
ranges = ranges == null ? new IntegerRanges() : ranges;
if (ranges.addRange(start, end)) {
return true;
}
contextLookup.reportInvalidExtensionRange(new InvalidExtensionRange(start, end), ctx);
return false;
}
@Override
protected EnumDescriptorProto.Builder addEnum() {
return protoBuilder.addEnumTypeBuilder();
}
@Override
protected DescriptorProto.Builder addMessage() {
return protoBuilder.addNestedTypeBuilder();
}
@Override
protected FieldDescriptorProto.Builder addExtension() {
return protoBuilder.addExtensionBuilder();
}
@Override
protected OneofDescriptorProto.Builder addOneof() {
return protoBuilder.addOneofDeclBuilder();
}
@Override
protected FieldDescriptorProto.Builder addField() {
return protoBuilder.addFieldBuilder();
}
@Override
protected UninterpretedOption.Builder addCustomOption() {
return protoBuilder.getOptionsBuilder().addUninterpretedOptionBuilder();
}
@Override
protected MessageOptions.Builder getMessageOptions() {
return protoBuilder.getOptionsBuilder();
}
}
protected class GroupScope extends MessageScope {
protected GroupScope(final DescriptorProto.Builder protoBuilder, final Scope> parent) {
super(protoBuilder, parent);
}
}
/**
* A special scope delegating to its parent scope. All fields and groups of the extend element
* should be within the parent scope; however, at least a location info for extend might require
* this to be in its own scope.
*
* @param
* @author [email protected] David Tesler
*/
protected class ExtendScope> extends Scope {
private final String extendee;
private ExtendScope(final Scope parent, final String extendee) {
super(parent.getProtoBuilder(), parent);
this.extendee = extendee;
}
@Override
protected FieldDescriptorProto.Builder addField() {
return getParent().addExtension().setExtendee(extendee);
}
@Override
protected DescriptorProto.Builder addGroup() {
return getParent().addGroup();
}
@Override
protected Builder addMessage() {
return getParent().addMessage();
}
@Override
protected UninterpretedOption.Builder addCustomOption() {
return getParent().addCustomOption();
}
@Override
protected MessageOptions.Builder getMessageOptions() {
return getParent().getMessageOptions();
}
@Override
protected FileOptions.Builder getFileOptions() {
return getParent().getFileOptions();
}
}
/**
* A special scope delegating to its parent scope. All fields and groups of the extend element
* should be within the parent scope; however, at least a location info for extend might require
* this to be in its own scope.
*
* @param
* @author [email protected] David Tesler
*/
protected class OneofScope extends Scope {
private final int oneofIndex;
private OneofScope(final OneofDescriptorProto.Builder protoBuilder,
final Scope parent) {
super(protoBuilder, parent);
oneofIndex = parent.getProtoBuilder().getOneofDeclCount() - 1;
}
@Override
protected FieldDescriptorProto.Builder addField() {
return getParent().addField().setOneofIndex(oneofIndex);
}
@Override
protected DescriptorProto.Builder addGroup() {
return getParent().addGroup();
}
@Override
protected Builder addMessage() {
return getParent().addMessage();
}
@Override
protected UninterpretedOption.Builder addCustomOption() {
return getParent().addCustomOption();
}
@Override
protected MessageOptions.Builder getMessageOptions() {
return getParent().getMessageOptions();
}
}
protected class EnumScope extends Scope {
private EnumScope(final EnumDescriptorProto.Builder protoBuilder, final Scope> parent) {
super(protoBuilder, parent);
}
@Override
protected EnumValueDescriptorProto.Builder addEnumValue() {
return protoBuilder.addValueBuilder();
}
@Override
protected UninterpretedOption.Builder addCustomOption() {
return protoBuilder.getOptionsBuilder().addUninterpretedOptionBuilder();
}
@Override
protected EnumOptions.Builder getEnumOptions() {
return protoBuilder.getOptionsBuilder();
}
}
protected class FieldScope extends Scope {
private FieldScope(final FieldDescriptorProto.Builder protoBuilder, final Scope> parent) {
super(protoBuilder, parent);
}
@Override
protected UninterpretedOption.Builder addCustomOption() {
return protoBuilder.getOptionsBuilder().addUninterpretedOptionBuilder();
}
@Override
protected FieldOptions.Builder getFieldOptions() {
return protoBuilder.getOptionsBuilder();
}
}
protected class EnumValueScope extends Scope {
private EnumValueScope(final EnumValueDescriptorProto.Builder protoBuilder,
final Scope> parent) {
super(protoBuilder, parent);
}
@Override
protected UninterpretedOption.Builder addCustomOption() {
return protoBuilder.getOptionsBuilder().addUninterpretedOptionBuilder();
}
@Override
protected EnumValueOptions.Builder getEnumValueOptions() {
return protoBuilder.getOptionsBuilder();
}
}
protected class ServiceScope extends Scope {
private ServiceScope(final ServiceDescriptorProto.Builder protoBuilder, final Scope> parent) {
super(protoBuilder, parent);
}
@Override
protected MethodDescriptorProto.Builder addMethod() {
return protoBuilder.addMethodBuilder();
}
@Override
protected UninterpretedOption.Builder addCustomOption() {
return protoBuilder.getOptionsBuilder().addUninterpretedOptionBuilder();
}
@Override
protected ServiceOptions.Builder getServiceOptions() {
return protoBuilder.getOptionsBuilder();
}
}
protected class MethodScope extends Scope {
private MethodScope(final MethodDescriptorProto.Builder protoBuilder, final Scope> parent) {
super(protoBuilder, parent);
}
@Override
protected UninterpretedOption.Builder addCustomOption() {
return protoBuilder.getOptionsBuilder().addUninterpretedOptionBuilder();
}
@Override
protected MethodOptions.Builder getMethodOptions() {
return protoBuilder.getOptionsBuilder();
}
}
private interface IntegralTypePlus> extends Comparable {
public T getCeiling();
}
private static final class IntegerPlus implements IntegralTypePlus {
private final int value;
private final boolean isCeiling;
private IntegerPlus(final int value, final boolean isCeiling) {
this.value = value;
this.isCeiling = isCeiling;
}
public static IntegerPlus valueOf(final int value) {
return new IntegerPlus(value, false);
}
@Override
public int compareTo(final IntegerPlus other) {
return value == other.value ? Boolean.compare(isCeiling, other.isCeiling) : Integer.compare(
value, other.value);
}
public int compareTo(final int value) {
return this.value == value ? Boolean.compare(isCeiling, false) : Integer.compare(this.value,
value);
}
@Override
public IntegerPlus getCeiling() {
return isCeiling ? this : new IntegerPlus(value, true);
}
@Override
public String toString() {
final StringBuilder builder = new StringBuilder();
builder.append("IntegerPlus [value=").append(value).append(", isCeiling=").append(isCeiling)
.append("]");
return builder.toString();
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + (isCeiling ? 1231 : 1237);
result = prime * result + value;
return result;
}
@Override
public boolean equals(final Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (!(obj instanceof IntegerPlus)) {
return false;
}
final IntegerPlus other = (IntegerPlus) obj;
if (isCeiling != other.isCeiling) {
return false;
}
if (value != other.value) {
return false;
}
return true;
}
}
private static final class IntegerRanges {
private final NavigableSet delegate;
public IntegerRanges() {
delegate = new TreeSet();
}
public boolean addRange(final int start, final int end) {
final IntegerPlus rangeStart = IntegerPlus.valueOf(start);
final IntegerPlus rangeEnd =
start == end ? rangeStart.getCeiling() : IntegerPlus.valueOf(end);
if (!delegate.subSet(rangeStart, true, rangeEnd, true).isEmpty()) {
return false;
}
delegate.add(rangeStart);
delegate.add(rangeEnd);
return true;
}
public boolean contains(final int value) {
if (delegate.isEmpty() || delegate.first().compareTo(value) > 0
|| delegate.last().compareTo(value) < 0) {
return false;
}
final NavigableSet headSet = delegate.headSet(IntegerPlus.valueOf(value), true);
return headSet.last().compareTo(value) == 0 || headSet.size() % 2 == 1;
}
}
}