/*
   Copyright 2014-2018 Immutables Authors and Contributors

   Licensed 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 org.immutables.value.processor.meta;

import com.google.common.base.Joiner;
import com.google.common.base.Strings;
import com.google.common.collect.Lists;
import java.util.List;
import java.util.Set;
import javax.annotation.Nullable;
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.PackageElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.util.ElementFilter;
import org.immutables.generator.Naming;
import org.immutables.generator.Naming.Preference;
import org.immutables.generator.SourceExtraction;
import org.immutables.generator.SourceTypes;
import org.immutables.value.Value;
import org.immutables.value.processor.meta.Proto.DeclaringType;
import org.immutables.value.processor.meta.Proto.Protoclass;
import org.immutables.value.processor.meta.Reporter.About;
import org.immutables.value.processor.meta.Styles.PackageNaming;
import org.immutables.value.processor.meta.Styles.UsingName.TypeNames;
import org.immutables.value.processor.meta.ValueMirrors.Style.ImplementationVisibility;
import static com.google.common.base.Preconditions.checkState;
import static com.google.common.base.Verify.verify;

@Value.Enclosing
@Value.Immutable
public abstract class Constitution {
  private static final String NA_ERROR = "!should_not_be_used_in_generated_code!";
  private static final String NEW_KEYWORD = "new";
  private static final Joiner DOT_JOINER = Joiner.on('.').skipNulls();

  public abstract Protoclass protoclass();

  @Value.Lazy
  public Generics generics() {
    return new Generics(protoclass(),
        protoclass().kind().isConstructor()
            ? protoclass().sourceElement().getEnclosingElement()
            : protoclass().sourceElement());
  }

  @Value.Derived
  public Visibility implementationVisibility() {
    if (style().visibility() == ImplementationVisibility.PRIVATE
        && !protoclass().features().builder()
        && !protoclass().kind().isNested()) {
      protoclass()
          .report()
          .warning(About.INCOMPAT,
              "effective Style.visibility cannot be PRIVATE when builder is disabled and is not nested,"
              + " automatically switching visibility to PACKAGE because top level implementation class is required");
      return Visibility.PACKAGE;
    }
    return protoclass().visibility().forImplementation(style().visibility());
  }

  @Value.Derived
  public Visibility builderVisibility() {
    return protoclass().visibility().forBuilder(style().builderVisibility());
  }

  public boolean isImplementationHidden() {
    return implementationVisibility().isPrivate();
  }

  public boolean returnsAbstractValueType() {
    return isImplementationHidden()
        || style().visibility() == ValueMirrors.Style.ImplementationVisibility.SAME_NON_RETURNED
        || style().overshadowImplementation()
        || (style().implementationNestedInBuilder()
        && implementationVisibility().isMoreRestrictiveThan(builderVisibility()));
  }

  public boolean isImplementationPrimary() {
    return protoclass().visibility().isMoreRestrictiveThan(implementationVisibility());
  }

  @Value.Derived
  public String implementationPackage() {
    PackageNaming naming = protoclass().styles().packageGenerated();
    return naming.apply(definingPackage());
  }

  public String definingPackage() {
    return protoclass().packageOf().name();
  }

  @Value.Derived
  public TypeNames names() {
    return protoclass().createTypeNames();
  }

  @Value.Lazy
  public NameForms typePreferablyAbstract() {
    if (protoclass().kind().isValue()) {
      return isImplementationPrimary()
          ? typeImmutable()
          : typeAbstract();
    }
    return typeValue();
  }

  @Value.Lazy
  public NameForms typeDocument() {
    if (protoclass().kind().isValue()) {
      return isAbstractPrimary()
          ? typeAbstract()
          : typeImmutable();
    }
    return typeValue();
  }

  @Value.Lazy
  public NameForms typeModifiable() {
    checkState(protoclass().kind().isModifiable());
    String simple = names().typeModifiable();
    return ImmutableConstitution.NameForms.builder()
        .simple(simple)
        .relativeRaw(inPackage(simple))
        .genericArgs(generics().args())
        .packageOf(implementationPackage())
        .visibility(implementationVisibility())
        .build();
  }

  @Value.Lazy
  public AppliedNameForms factoryCreate() {
    return typeModifiable().applied(names().create());
  }

  private boolean isAbstractPrimary() {
    return returnsAbstractValueType()
        || !isImplementationPrimary();
  }

  public boolean isSimple() {
    return protoclass().kind().isValue()
        && !protoclass().kind().isNested()
        && implementationVisibility().isPublic()
        && !returnsAbstractValueType();
  }

  /**
   * Value is the canonical outside look of the value type. It should be either
   * {@link #typeAbstract()} or {@link #typeImmutable()}.
   * For factory it is a special surrogate.
   * @return canonical value type name forms
   */
  @Value.Lazy
  public NameForms typeValue() {
    if (protoclass().kind().isValue()) {
      return returnsAbstractValueType()
          ? typeAbstract()
          : typeImmutable();
    }
    if (isFactory()) {

      if (protoclass().kind().isConstructor()) {
        TypeElement enclosingType = (TypeElement) protoclass().sourceElement().getEnclosingElement();

        return ImmutableConstitution.NameForms.builder()
            .simple(enclosingType.getSimpleName().toString())
            .relativeRaw(enclosingType.getQualifiedName().toString())
            .genericArgs(generics().args())
            .relativeAlreadyQualified(true)
            .packageOf(NA_ERROR)
            .visibility(protoclass().visibility())
            .build();
      }

      ExecutableElement method = (ExecutableElement) protoclass().sourceElement();
      String type = method.getReturnType().toString();

      return ImmutableConstitution.NameForms.builder()
          .simple(NA_ERROR)
          .relativeRaw(type)
          .packageOf(NA_ERROR)
          .relativeAlreadyQualified(true)
          .visibility(protoclass().visibility())
          .build();
    }
    return typeEnclosing();
  }

  @Value.Derived
  public boolean hasImmutableInBuilder() {
    return isOutsideBuilder() && isTopLevelValue();
  }

  public boolean hasTopLevelBuilder() {
    return isFactory() || (isTopLevelValue() && isOutsideBuilder());
  }

  private boolean isFactory() {
    return protoclass().kind().isFactory();
  }

  public boolean hasTopLevelImmutable() {
    return isTopLevelValue()
        && !hasImmutableInBuilder();
  }

  public boolean isOutsideBuilder() {
    return isFactory()
        || (protoclass().features().builder()
        && (isImplementationHidden()
        || style().implementationNestedInBuilder()));
  }

  private boolean isTopLevelValue() {
    return protoclass().kind().isValue()
        && !protoclass().kind().isNested();
  }

  public boolean hasEnclosingNonvalue() {
    return protoclass().kind().isEnclosing()
        && !protoclass().kind().isValue();
  }

  /**
   * Actual abstract value type that is definitive model for the value type.
   * @return abstract value type name forms
   */
  @Value.Lazy
  public NameForms typeAbstract() {
    if (protoclass().kind().isConstructor()) {
      return typeValue();
    }

    List<String> classSegments = Lists.newArrayListWithExpectedSize(2);
    Element e = SourceNames.collectClassSegments(protoclass().sourceElement(), classSegments);
    verify(e instanceof PackageElement);

    String packageOf = ((PackageElement) e).getQualifiedName().toString();
    String relative = DOT_JOINER.join(classSegments);
    boolean relativeAlreadyQualified = false;

    if (!implementationPackage().equals(packageOf)) {
      relative = DOT_JOINER.join(packageOf, relative);
      relativeAlreadyQualified = true;
    }

    return ImmutableConstitution.NameForms.builder()
        .simple(names().typeAbstract)
        .relativeRaw(relative)
        .packageOf(packageOf)
        .genericArgs(generics().args())
        .relativeAlreadyQualified(relativeAlreadyQualified)
        .visibility(protoclass().visibility())
        .build();
  }

  public StyleInfo style() {
    return protoclass().styles().style();
  }

  /**
   * Package relative path
   * @param topLevel
   * @param nested
   * @return
   */
  private String inPackage(String topLevel, String... nested) {
    return DOT_JOINER.join(null, topLevel, (Object[]) nested);
  }

  /**
   * Actual immutable value type generated implementation.
   * @return immutable implementation type name forms
   */
  @Value.Lazy
  public NameForms typeImmutable() {
    String simple, relative;

    if (protoclass().kind().isNested()) {
      String enclosingSimpleName = typeImmutableEnclosingSimpleName();
      simple = names().typeImmutableNested();
      relative = inPackage(enclosingSimpleName, simple);
    } else if (hasImmutableInBuilder()) {
      simple = names().typeImmutable;
      relative = inPackage(typeBuilderSimpleName(), simple);
    } else {
      simple = names().typeImmutable;
      relative = inPackage(simple);
    }

    return ImmutableConstitution.NameForms.builder()
        .simple(simple)
        .relativeRaw(relative)
        .genericArgs(generics().args())
        .packageOf(implementationPackage())
        .visibility(implementationVisibility())
        .build();
  }

  /**
   * Walks to the enclosing type's simple names and applies naming convention.
   * This shortcut/fix shows deficiency of model (it's probably more complicated than needed).
   * @return enclosing immutable name
   */
  @Value.Lazy
  String typeImmutableEnclosingSimpleName() {
    DeclaringType declaringType = protoclass().enclosingOf().get();
    String enclosingSimpleName = declaringType.element().getSimpleName().toString();
    String enclosingRawName = names().rawFromAbstract(enclosingSimpleName);
    // Here we checking for having both enclosing and value
    // if we had protoclass it would be kind().isEnclosing() && kind().isValue()
    Naming naming = declaringType.isImmutable()
        ? names().namings.typeImmutable
        : names().namings.typeImmutableEnclosing;

    return naming.apply(enclosingRawName);
  }

  private String typeBuilderSimpleName() {
    boolean isOutside = isOutsideBuilder();
    Naming typeBuilderNaming = names().namings.typeBuilder;
    if (isOutside) {
      // For outer builder we can override with constant builder naming, but not the default.
      boolean isPlainDefault =
          isConstantNamingEquals(typeBuilderNaming, protoclass().environment().defaultStyles().typeBuilder());

      if (isPlainDefault) {
        typeBuilderNaming = typeBuilderNaming.requireNonConstant(Preference.SUFFIX);
      }
    }

    return Naming.Usage.CAPITALIZED.apply(typeBuilderNaming.apply(names().raw));
  }

  @Value.Lazy
  public AppliedNameForms factoryBuilder() {
    InnerBuilderDefinition innerBuilder = innerBuilder();
    if (innerBuilder.isExtending) {
      return typeBuilder().applied(NEW_KEYWORD);
    }
    return factoryImplementationBuilder();
  }

  private AppliedNameForms factoryImplementationBuilder() {
    boolean isOutside = isOutsideBuilder();
    Naming methodBuilderNaming = isOutside
        ? names().namings.newBuilder
        : names().namings.builder;

    boolean haveConstructorOnBuilder = isOutside
        || isConstantNamingEquals(methodBuilderNaming, NEW_KEYWORD);

    NameForms typeNameForms = haveConstructorOnBuilder
        ? typeBuilder()
        : typeImmutable();

    return typeNameForms.applied(methodBuilderNaming.apply(names().raw));
  }

  private boolean isConstantNamingEquals(Naming naming, String name) {
    return naming.isConstant()
        && naming.apply("").equals(name);
  }

  @Value.Lazy
  public AppliedNameForms factoryOf() {
    if (isFactory()) {
      TypeElement enclosingType = (TypeElement) protoclass().sourceElement().getEnclosingElement();

      String invoke = protoclass().kind().isConstructor()
          ? "new"
          : protoclass().sourceElement().getSimpleName().toString();

      return ImmutableConstitution.NameForms.builder()
          .simple(enclosingType.getSimpleName().toString())
          .relativeRaw(enclosingType.getQualifiedName().toString())
          .genericArgs(generics().args())
          .relativeAlreadyQualified(true)
          .packageOf(NA_ERROR)
          .visibility(protoclass().visibility())
          .build()
          .applied(invoke);
    }

    return applyFactoryNaming(names().namings.of);
  }

  @Value.Lazy
  public AppliedNameForms factoryInstance() {
    return applyFactoryNaming(names().namings.instance);
  }

  @Value.Lazy
  public AppliedNameForms factoryCopyOf() {
    return applyFactoryNaming(names().namings.copyOf);
  }

  private AppliedNameForms applyFactoryNaming(Naming naming) {
    String raw = names().raw;

    boolean hasForwardingFactoryMethods = isImplementationHidden()
        && protoclass().kind().isNested();

    NameForms nameForms = hasForwardingFactoryMethods
        ? typeEnclosingFactory()
        : typeImmutable();

    if (hasForwardingFactoryMethods) {
      naming = naming.requireNonConstant(Preference.PREFIX);
    }

    String applyName = Naming.Usage.LOWERIZED.apply(naming.apply(raw));

    return nameForms.applied(applyName);
  }

  @Value.Lazy
  public NameForms typeEnclosingFactory() {
    String enclosingSimpleName = typeImmutableEnclosingSimpleName();
    return ImmutableConstitution.NameForms.builder()
        .simple(enclosingSimpleName)
        .relativeRaw(enclosingSimpleName)
        .packageOf(implementationPackage())
        .visibility(protoclass().declaringVisibility())
        .build();
  }

  @Value.Lazy
  public NameForms typeEnclosing() {
    String name = protoclass().kind().isDefinedValue()
        ? names().typeImmutable
        : names().typeImmutableEnclosing();

    return ImmutableConstitution.NameForms.builder()
        .simple(name)
        .relativeRaw(name)
        .packageOf(implementationPackage())
        .visibility(implementationEnclosingVisibility())
        .build();
  }

  private Visibility implementationEnclosingVisibility() {
    return implementationVisibility().max(Visibility.PACKAGE);
  }

  @Value.Lazy
  public NameForms typeWith() {
    String simple, relative;

    if (protoclass().kind().isNested()) {
      String enclosingSimpleName = typeImmutableEnclosingSimpleName();
      simple = names().typeWith();
      relative = inPackage(enclosingSimpleName, simple);
    } else if (hasImmutableInBuilder()) {
      simple = names().typeWith();
      relative = inPackage(typeBuilderSimpleName(), simple);
    } else {
      simple = names().typeWith();
      relative = inPackage(simple);
    }

    return ImmutableConstitution.NameForms.builder()
        .simple(simple)
        .relativeRaw(relative)
        .genericArgs(generics().args())
        .packageOf(implementationPackage())
        .visibility(implementationVisibility())
        .build();
  }

  @Value.Lazy
  public NameForms typeBuilder() {
    InnerBuilderDefinition innerBuilder = innerBuilder();
    if (innerBuilder.isExtending) {
      NameForms typeAbstract = typeAbstract();
      return ImmutableConstitution.NameForms.copyOf(typeAbstract)
          .withRelativeRaw(DOT_JOINER.join(typeAbstract.relativeRaw(), innerBuilder.simpleName))
          .withSimple(innerBuilder.simpleName);
    }
    return typeImplementationBuilder();
  }

  @Value.Lazy
  public NameForms typeImplementationBuilder() {
    TypeNames names = names();

    boolean outside = isOutsideBuilder();
    boolean nested = protoclass().kind().isNested();

    String simple = typeBuilderSimpleName();
    String relative;

    if (outside && nested) {
      relative = inPackage(typeImmutableEnclosingSimpleName(), simple);
    } else if (outside) {
      relative = inPackage(simple);
    } else if (nested) {
      relative = inPackage(inPackage(typeImmutableEnclosingSimpleName(), names.typeImmutableNested(), simple));
    } else {
      relative = inPackage(inPackage(names.typeImmutable, simple));
    }

    Visibility visibility = builderVisibility();

    return ImmutableConstitution.NameForms.builder()
        .simple(simple)
        .relativeRaw(relative)
        .genericArgs(generics().args())
        .packageOf(implementationPackage())
        .visibility(visibility)
        .build();
  }

  @Value.Immutable
  public static abstract class AppliedNameForms extends AbstractNameForms {
    public abstract NameForms forms();

    public abstract String applied();

    @Override
    @Value.Derived
    public String simple() {
      return isNew()
          ? (NEW_KEYWORD + ' ' + forms().simple())
          : applied();
    }

    @Override
    public String relativeRaw() {
      return isNew()
          ? (NEW_KEYWORD + ' ' + forms().relativeRaw())
          : (forms().relativeRaw() + '.' + applied());
    }

    @Override
    public String relative() {
      return combineApplied(false);
    }

    @Value.Derived
    public boolean isNew() {
      return NEW_KEYWORD.equals(applied());
    }

    @Override
    public String toString() {
      if (relativeAlreadyQualified()) {
        return relative();
      }
      return combineApplied(true);
    }

    @Override
    public String genericArgs() {
      return forms().genericArgs();
    }

    @Override
    public String packageOf() {
      return forms().packageOf();
    }

    @Override
    public Visibility visibility() {
      return forms().visibility();
    }

    @Override
    public boolean relativeAlreadyQualified() {
      return forms().relativeAlreadyQualified();
    }

    private String combineApplied(boolean qualifyWithPackage) {
      String base = forms().relativeRaw();
      if (qualifyWithPackage) {
        base = qualifyWithPackage(base);
      }
      return isNew()
          ? (NEW_KEYWORD + ' ' + base + genericArgs())
          : (base + '.' + genericArgs() + applied());
    }
  }

  public static abstract class AbstractNameForms {
    private static final String PUBLIC_MODIFIER_PREFIX = "public ";
    private static final String PRIVATE_MODIFIER_PREFIX = "private ";

    public abstract String simple();

    public abstract String relativeRaw();

    public abstract String packageOf();

    public abstract Visibility visibility();

    @Value.Default
    public String absolute(){
      return DOT_JOINER.join(packageOf(), relative());
    }

    @Value.Default
    public String absoluteRaw(){
      return DOT_JOINER.join(packageOf(), relativeRaw());
    }

    @Value.Default
    public String genericArgs() {
      return "";
    }

    @Value.Default
    public boolean relativeAlreadyQualified() {
      return false;
    }

    public String relative() {
      return relativeRaw() + genericArgs();
    }

    /**
     * Access prefix. Includes trailing space separator if not empty (package private).
     * @return access keyword text
     */
    public String access() {
      switch (visibility()) {
      case PRIVATE:
        return PRIVATE_MODIFIER_PREFIX;
      case PUBLIC:
        return PUBLIC_MODIFIER_PREFIX;
      default:
        return "";
      }
    }

    protected String qualifyWithPackage(String reference) {
      return DOT_JOINER.join(Strings.emptyToNull(packageOf()), reference);
    }
  }

  @Value.Immutable
  public static abstract class NameForms extends AbstractNameForms {

    /**
     * Fully qualified type name
     */
    @Override
    public String toString() {
      return relativeAlreadyQualified()
          ? relative()
          : qualifyWithPackage(relative());
    }

    public AppliedNameForms applied(String input) {
      return ImmutableConstitution.AppliedNameForms.builder()
          .forms(this)
          .applied(input)
          .build();
    }
  }

  @Value.Lazy
  public InnerBuilderDefinition innerBuilder() {
    return new InnerBuilderDefinition();
  }

  public final class InnerBuilderDefinition {
    public final boolean isAccessibleFields;
    public final boolean isPresent;
    public final boolean isExtending;
    public final boolean isSuper;
    public final boolean isInterface;
    public final Visibility visibility;
    public final @Nullable String simpleName;
    public final @Nullable Generics generics;

    InnerBuilderDefinition() {
      @Nullable TypeElement builderElement = findBuilderElement();
      // The following series of checks designed
      // to not validate inner builder if it's disabled,
      // but at the same time we need such validation
      // if we are using "extending" builder which is still allowed
      // on demand even if builder feature is disabled
      boolean extending = false;

      if (builderElement != null) {
        extending = isExtending(builderElement);
      }

      if (builderElement != null && !protoclass().features().builder() && !extending) {
        builderElement = null;
      }

      if (builderElement != null && !isValidInnerBuilder(builderElement)) {
        builderElement = null;
      }

      if (builderElement != null) {
        this.isAccessibleFields = AccessibleFieldsMirror.find(builderElement).isPresent();
        this.isPresent = true;
        this.isInterface = builderElement.getKind() == ElementKind.INTERFACE;
        this.isExtending = extending;
        this.isSuper = !extending;
        this.simpleName = builderElement.getSimpleName().toString();
        this.visibility = Visibility.of(builderElement);
        this.generics = new Generics(protoclass(), builderElement);
        if (isExtending) {
          lateValidateExtending(builderElement);
        }
        if (isSuper) {
          lateValidateSuper(builderElement);
        }
      } else {
        this.isAccessibleFields = false;
        this.isPresent = false;
        this.isInterface = false;
        this.isExtending = false;
        this.isSuper = false;
        this.visibility = Visibility.PRIVATE;
        this.simpleName = null;
        this.generics = Generics.empty();
      }
    }

    private void lateValidateSuper(TypeElement t) {
      List<String> undeclaredParams = Lists.newArrayList();
      for (String v : this.generics.vars()) {
        if (!generics().hasParameter(v)) {
          undeclaredParams.add(v);
        }
      }

      if (!undeclaredParams.isEmpty()) {
        protoclass()
            .report()
            .withElement(t)
            .error("Inner type %s%s uses generic parameter %s which are not present in value's declaration: %s",
                t.getSimpleName(),
                this.generics.args(),
                Joiner.on(", ").join(undeclaredParams),
                generics());
      }
    }

    private void lateValidateExtending(TypeElement t) {
      if (t.getModifiers().contains(Modifier.ABSTRACT)) {
        protoclass()
            .report()
            .withElement(t)
            .error("Extending %s shouldn't be abstract, it has to be instantiable",
                t.getSimpleName());
      }

      if (!this.generics.def().equals(generics().def())) {
        protoclass()
            .report()
            .withElement(t)
            .error("Inner type %s should have the same type parameters as abstract value type: %s",
                t.getSimpleName(),
                generics().def());
      }

      if (protoclass().styles().style().stagedBuilder()) {
        protoclass()
            .report()
            .withElement(t)
            .warning(About.INCOMPAT,
                "Extending %s shouldn't be used with stagedBuilder style attribute, they are incompartible:"
                + " Staged builder generate series of staged interfaces, but extending builder actually"
                + " extends implementation and do not provide type safety for setting first attribute,"
                + " as well as stagedBuilder forces generated builder interfaces to leak in code using the builder"
                + " and hence defeating the purpose of using extending builder.",
                t.getSimpleName());
      }
    }

    private boolean isExtending(TypeElement element) {
      if (element.getKind() == ElementKind.CLASS) {
        String superclassString = SourceExtraction.getSuperclassString(element);
        String rawSuperclass = SourceTypes.extract(superclassString).getKey();
        // If we are extending yet to be generated builder, we detect it by having the same name
        // as relative name of builder type
        return rawSuperclass.endsWith(typeImplementationBuilder().relativeRaw());
      }
      return false;
    }

    @Nullable
    private TypeElement findBuilderElement() {
      Protoclass protoclass = protoclass();
      if (!protoclass.kind().isValue()) {
        return null;
      }
      for (Element t : protoclass.sourceElement().getEnclosedElements()) {
        ElementKind kind = t.getKind();
        if (kind.isClass() || kind.isInterface()) {
          String simpleName = t.getSimpleName().toString();
          Naming typeInnerBuilderNaming = names().namings.typeInnerBuilder;

          if (!typeInnerBuilderNaming.detect(simpleName).isEmpty()) {
            return (TypeElement) t;
          }
        }
      }
      return null;
    }

    private boolean isValidInnerBuilder(Element t) {
      ElementKind kind = t.getKind();
      if (kind != ElementKind.CLASS
          && kind != ElementKind.INTERFACE) {
        protoclass()
            .report()
            .withElement(t)
            .warning(About.INCOMPAT,
                "Inner type %s is %s - not supported as Builder extend/super type",
                t.getSimpleName(),
                kind.name().toLowerCase());

        return false;
      }

      Set<Modifier> modifiers = t.getModifiers();

      if (!modifiers.contains(Modifier.STATIC)
          || modifiers.contains(Modifier.PRIVATE)) {
        protoclass()
            .report()
            .withElement(t)
            .warning(About.INCOMPAT,
                "Inner type %s should be static non-private to be supported as Builder extend/super type",
                t.getSimpleName());

        return false;
      }

      if (kind == ElementKind.CLASS
          && !hasAccessibleConstructor(t)) {
        protoclass()
            .report()
            .withElement(t)
            .warning(About.INCOMPAT,
                "%s should have non-private no-argument constructor to be supported as Builder extend/super type",
                t.getSimpleName());

        return false;
      }

      return true;
    }

    private boolean hasAccessibleConstructor(Element type) {
      List<ExecutableElement> constructors = ElementFilter.constructorsIn(type.getEnclosedElements());

      if (constructors.isEmpty()) {
        // It is unclear (not checked) if we will have syntethic no-arg constructor
        // included, so we will assume no constructor to equate having a single constructors.
        return true;
      }

      for (ExecutableElement c : constructors) {
        if (c.getParameters().isEmpty()) {
          return !Visibility.of(c).isPrivate();
        }
      }

      return false;
    }
  }
}


/*
   Copyright 2015 Immutables Authors and Contributors

   Licensed 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 org.immutables.mongo.types;

import com.google.gson.Gson;
import com.google.gson.TypeAdapter;
import com.google.gson.TypeAdapterFactory;
import com.google.gson.reflect.TypeToken;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonToken;
import com.google.gson.stream.JsonWriter;
import org.bson.BsonBinary;
import org.bson.BsonRegularExpression;
import org.bson.types.Decimal128;
import org.bson.types.ObjectId;
import org.immutables.metainf.Metainf;
import org.immutables.mongo.bson4gson.BsonReader;
import org.immutables.mongo.bson4gson.BsonWriter;

import javax.annotation.Nullable;
import java.io.IOException;
import java.math.BigDecimal;
import java.util.regex.Pattern;

import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;

/**
 * Built-in BSON type adapters. Also contains reusable delegate-adapters to easily implement own
 * wrapper if needed. Supports {@link Id}, {@link TimeInstant}, {@link Binary}, {@link Pattern}
 * types which correspond to {@code ObjectID}, {@code UTCDate}, {@code Binary}, {@code Regex} native
 * BSON types, not available in JSON.
 * <p>
 * This class also expose simple type adapters to BSON special types, so you can easily write
 * adapters for your own wrapper types.
 * @see #binaryAdapter()
 * @see #objectIdAdapter()
 * @see #objectIdAdapter()
 */
@Metainf.Service
public final class TypeAdapters implements TypeAdapterFactory {
  private static final TypeToken<Id> ID_TYPE_TOKEN = TypeToken.get(Id.class);
  private static final TypeToken<TimeInstant> TIME_INSTANT_TYPE_TOKEN = TypeToken.get(TimeInstant.class);
  private static final TypeToken<Binary> BINARY_TYPE_TOKEN = TypeToken.get(Binary.class);
  private static final TypeToken<Pattern> PATTERN_TYPE_TOKEN = TypeToken.get(Pattern.class);
  private static final TypeToken<Decimal128> DECIMAL128_TYPE_TOKEN = TypeToken.get(Decimal128.class);

  // safe unchecked, typecheck performed by type token equality
  @SuppressWarnings("unchecked")
  @Override
  @Nullable
  public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
    if (ID_TYPE_TOKEN.equals(type)) {
      return (TypeAdapter<T>) WRAPPED_ID_ADAPTER;
    }
    if (TIME_INSTANT_TYPE_TOKEN.equals(type)) {
      return (TypeAdapter<T>) WRAPPED_TIME_INSTANT_ADAPTER;
    }
    if (BINARY_TYPE_TOKEN.equals(type)) {
      return (TypeAdapter<T>) WRAPPED_BINARY_ADAPTER;
    }
    if (PATTERN_TYPE_TOKEN.equals(type)) {
      return (TypeAdapter<T>) PATTERN_ADAPTER;
    }
    if (DECIMAL128_TYPE_TOKEN.equals(type)) {
      return (TypeAdapter<T>) DECIMAL128_ADAPTER;
    }
    return null;
  }

  /**
   * Use this adapter to easily delagate marshaling of custom time instant wrapper.
   * @return {@code Long <==> UTCDate} adapter
   */
  public static TypeAdapter<Long> timeInstantAdapter() {
    return TIME_INSTANT_ADAPTER;
  }

  /**
   * Use this adapter to easily delagate marshaling of custom ObjectID wrapper.
   * @return {@code byte[] <==> ObjectID} adapter
   */
  public static TypeAdapter<byte[]> objectIdAdapter() {
    return OBJECT_ID_ADAPTER;
  }

  /**
   * Use this adapter to easily delagate marshaling of custom Binary wrapper.
   * @return {@code byte[] <==> Binary} adapter
   */
  public static TypeAdapter<byte[]> binaryAdapter() {
    return BINARY_ADAPTER;
  }

  /**
   * Use this adapter (not registered by factory, default Gson adapter is used otherwise) to
   * delagate marshaling of {@link BigDecimal} to {@link Decimal128} type in mongo.
   * @return {@code BigDecimal <==> Decimal128} adapter
   */
  public static TypeAdapter<BigDecimal> decimalAdapter() {
    return DECIMAL_ADAPTER;
  }

  private static final TypeAdapter<TimeInstant> WRAPPED_TIME_INSTANT_ADAPTER = new TypeAdapter<TimeInstant>() {
    @Override
    public void write(JsonWriter out, TimeInstant value) throws IOException {
      if (out instanceof BsonWriter) {
        TIME_INSTANT_ADAPTER.write(out, value.value());
      } else {
        out.value(value.toString());
      }
    }

    @Override
    public TimeInstant read(JsonReader in) throws IOException {
      return TimeInstant.of(TIME_INSTANT_ADAPTER.read(in));
    }

    @Override
    public String toString() {
      return "TypeAdapters.(TimeInstant)";
    }
  };

  private static final TypeAdapter<Id> WRAPPED_ID_ADAPTER = new TypeAdapter<Id>() {
    @Override
    public void write(JsonWriter out, Id value) throws IOException {
      if (out instanceof BsonWriter) {
        OBJECT_ID_ADAPTER.write(out, value.value());
      } else {
        out.value(value.toString());
      }
    }

    @Override
    public Id read(JsonReader in) throws IOException {
      return Id.from(OBJECT_ID_ADAPTER.read(in));
    }

    @Override
    public String toString() {
      return "TypeAdapters.(Id)";
    }
  };

  private static final TypeAdapter<Binary> WRAPPED_BINARY_ADAPTER = new TypeAdapter<Binary>() {
    @Override
    public void write(JsonWriter out, Binary value) throws IOException {
      if (out instanceof BsonWriter) {
        BINARY_ADAPTER.write(out, value.value());
      } else {
        out.value(value.toString());
      }
    }

    @Override
    public Binary read(JsonReader in) throws IOException {
      return Binary.create(BINARY_ADAPTER.read(in));
    }

    @Override
    public String toString() {
      return "TypeAdapters.(Binary)";
    }
  };

  private static final TypeAdapter<Pattern> PATTERN_ADAPTER = new TypeAdapter<Pattern>() {
    @Override
    public void write(JsonWriter out, Pattern value) throws IOException {
      if (value == null) {
        out.nullValue();
      } else if (out instanceof BsonWriter) {
        ((BsonWriter) out).unwrap()
                .writeRegularExpression(new BsonRegularExpression(value.pattern()));
      } else {
        out.value(value.toString());
      }
    }

    @Override
    public Pattern read(JsonReader in) throws IOException {
      if (in.peek() == JsonToken.NULL) {
        in.nextNull();
        return null;
      }
      if (in instanceof BsonReader) {
        final String pattern = ((BsonReader) in).unwrap()
                .readRegularExpression().getPattern();
        return Pattern.compile(pattern);
      }
      return Pattern.compile(in.nextString());
    }

    @Override
    public String toString() {
      return "TypeAdapters.(Pattern)";
    }
  };

  private static final TypeAdapter<Decimal128> DECIMAL128_ADAPTER = new TypeAdapter<Decimal128>() {
    @Override
    public void write(JsonWriter out, Decimal128 value) throws IOException {
      if (value == null) {
        out.nullValue();
      } else if (out instanceof BsonWriter) {
        ((BsonWriter) out).unwrap().writeDecimal128(value);
      } else {
        out.value(value.toString());
      }
    }

    @Override
    public Decimal128 read(JsonReader in) throws IOException {
      if (in.peek() == JsonToken.NULL) {
        in.nextNull();
        return null;
      }
      if (in instanceof BsonReader) {
        return ((BsonReader) in).unwrap().readDecimal128();
      }
      return Decimal128.parse(in.nextString());
    }

    @Override
    public String toString() {
      return "TypeAdapters.(Decimal128)";
    }
  };

  private static final TypeAdapter<BigDecimal> DECIMAL_ADAPTER = new TypeAdapter<BigDecimal>() {
    @Override
    public void write(JsonWriter out, BigDecimal value) throws IOException {
      checkArgument(out instanceof BsonWriter, "Should be BsonWriter, not some other JsonWriter");
      checkNotNull(value, "Value could not be null, delegate to #nullSafe() adapter if needed");
      ((BsonWriter) out).value(value);
    }

    @Override
    public BigDecimal read(JsonReader in) throws IOException {
      checkArgument(in instanceof BsonReader, "Should be BsonReader, not some other JsonReader");
      return ((BsonReader) in).unwrap().readDecimal128().bigDecimalValue();
    }

    @Override
    public String toString() {
      return "TypeAdapters.decimalAdapter()";
    }
  };

  private static final TypeAdapter<Long> TIME_INSTANT_ADAPTER = new TypeAdapter<Long>() {
    @Override
    public void write(JsonWriter out, Long value) throws IOException {
      checkArgument(out instanceof BsonWriter, "Should be BsonWriter, not some other JsonWriter");
      checkNotNull(value, "Value could not be null, delegate to #nullSafe() adapter if needed");
      ((BsonWriter) out).unwrap()
              .writeDateTime(value);
    }

    @Override
    public Long read(JsonReader in) throws IOException {
      checkArgument(in instanceof BsonReader, "Should be BsonReader, not some other JsonReader");
      return ((BsonReader) in).unwrap().readDateTime();
    }

    @Override
    public String toString() {
      return "TypeAdapters.timeInstantAdapter()";
    }
  };

  private static final TypeAdapter<byte[]> OBJECT_ID_ADAPTER = new TypeAdapter<byte[]>() {
    @Override
    public void write(JsonWriter out, byte[] value) throws IOException {
      checkArgument(out instanceof BsonWriter, "Should be BsonWriter, not some other JsonWriter");
      checkNotNull(value, "Value could not be null, delegate to #nullSafe() adapter if needed");
      ((BsonWriter) out).unwrap().writeObjectId(new ObjectId(value));
    }

    @Override
    public byte[] read(JsonReader in) throws IOException {
      checkArgument(in instanceof BsonReader, "Should be BsonReader, not some other JsonReader");
      return ((BsonReader) in).unwrap().readObjectId().toByteArray();
    }

    @Override
    public String toString() {
      return "TypeAdapters.objectIdAdapter()";
    }
  };

  private static final TypeAdapter<byte[]> BINARY_ADAPTER = new TypeAdapter<byte[]>() {
    @Override
    public void write(JsonWriter out, byte[] value) throws IOException {
      checkArgument(out instanceof BsonWriter, "Should be BsonWriter, not some other JsonWriter");
      checkNotNull(value, "Value could not be null, delegate to #nullSafe() adapter if needed");
      ((BsonWriter) out).unwrap().writeBinaryData(new BsonBinary(value));
    }

    @Override
    public byte[] read(JsonReader in) throws IOException {
      checkArgument(in instanceof BsonReader, "Should be BsonReader, not some other JsonReader");
      return ((BsonReader) in).unwrap().readBinaryData().getData();
    }

    @Override
    public String toString() {
      return "TypeAdapters.binaryAdapter()";
    }
  };
}


/*
   Copyright 2013-2018 Immutables Authors and Contributors

   Licensed 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 org.immutables.ordinal;

import com.google.common.collect.ForwardingSet;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.primitives.Longs;
import java.util.BitSet;
import java.util.Collection;
import java.util.List;
import java.util.Set;
import static com.google.common.base.Preconditions.*;

/**
 * Immutable set that take advantage of elements being an {@link OrdinalValue}s to provide
 * compact storage and efficient {@link Set#contains(Object)} and
 * {@link Set#containsAll(Collection)} operations.
 * @see OrdinalValue
 * @see OrdinalDomain
 * @see BitSet BitSet for similar internal implementation
 * @param <E> element type
 */
public abstract class ImmutableOrdinalSet<E extends OrdinalValue<E>>
    extends ForwardingSet<E> {

  ImmutableOrdinalSet() {}

  @SuppressWarnings("rawtypes")
  private static final ImmutableOrdinalSet<?> EMPTY_SET = new EmptyImmutableOrdinalSet();

  /**
   * Returns singleton empty immutable ordinal set
   * @param <E> element type
   * @return empty set
   */
  @SuppressWarnings("unchecked")
  public static <E extends OrdinalValue<E>> ImmutableOrdinalSet<E> of() {
    // safe unchecked: will contain no elements
    return (ImmutableOrdinalSet<E>) EMPTY_SET;
  }

  /**
   * Creates immutable ordinal set from 1 or more elements.
   * All elements expected to have same {@link OrdinalValue#domain()} as the first element,
   * otherwise exception will be thrown.
   * @param <E> element type
   * @param first first element
   * @param rest the rest of elements
   * @return empty set
   */
  @SafeVarargs
  public static <E extends OrdinalValue<E>> ImmutableOrdinalSet<E> of(
      E first, E... rest) {
    OrdinalDomain<E> domain = first.domain();
    if (rest.length == 0) {
      return new SingletonImmutableOrdinalSet<>(first);
    }
    OrdinalValue<?>[] array = new OrdinalValue<?>[1 + rest.length];
    array[0] = first;
    System.arraycopy(rest, 0, array, 1, rest.length);
    return new RegularImmutableOrdinalSet<>(domain, array);
  }

  /**
   * Creates immutable ordinal set from iterable of elements.
   * All elements expected to have same {@link OrdinalValue#domain()} as the first element,
   * otherwise exception will be thrown.
   * @param <E> the element type
   * @param elements the elements, no nulls allowed
   * @return the immutable ordinal set
   */
  @SuppressWarnings("unchecked")
  // Safe unchecked, elements are defined to be of E or subtypes of E
  // which is allowed for immutable collection
  public static <E extends OrdinalValue<E>> ImmutableOrdinalSet<E> copyOf(Iterable<? extends E> elements) {
    if (elements instanceof ImmutableOrdinalSet) {
      return (ImmutableOrdinalSet<E>) elements;
    }
    return constructFromArray(Iterables.toArray(elements, OrdinalValue.class));
  }

  /**
   * Creates immutable ordinal set from array of elements.
   * All elements expected to have same {@link OrdinalValue#domain()} as the first element,
   * otherwise exception will be thrown.
   * @param <E> the element type
   * @param elements the elements, no nulls allowed
   * @return the immutable ordinal set
   */
  public static <E extends OrdinalValue<E>> ImmutableOrdinalSet<E> copyOf(E[] elements) {
    return constructFromArray(elements);
  }

  @SuppressWarnings("unchecked")
  // Safe unchecked as element is known to be of type E
  private static <E extends OrdinalValue<E>> ImmutableOrdinalSet<E> constructFromArray(OrdinalValue<?>[] array) {
    switch (array.length) {
    case 0:
      return of();
    case 1:
      // Safe unchecked as element is known to be of type E
      return new SingletonImmutableOrdinalSet<>((E) array[0]);
    default:
      return new RegularImmutableOrdinalSet<>(((E) array[0]).domain(), array);
    }
  }

  /**
   * Will throw an exception and leave the collection unmodified.
   * @throws UnsupportedOperationException always
   * @deprecated Modification operation are not supported.
   */
  @Deprecated
  @Override
  public final boolean add(E e) {
    throw new UnsupportedOperationException();
  }

  /**
   * Will throw an exception and leave the collection unmodified.
   * @throws UnsupportedOperationException always
   * @deprecated Modification operation are not supported.
   */
  @Deprecated
  @Override
  public final boolean remove(Object object) {
    throw new UnsupportedOperationException();
  }

  /**
   * Will throw an exception and leave the collection unmodified.
   * @throws UnsupportedOperationException always
   * @deprecated Modification operation are not supported.
   */
  @Deprecated
  @Override
  public final boolean addAll(Collection<? extends E> newElements) {
    throw new UnsupportedOperationException();
  }

  /**
   * Will throw an exception and leave the collection unmodified.
   * @throws UnsupportedOperationException always
   * @deprecated Modification operation are not supported.
   */
  @Deprecated
  @Override
  public final boolean removeAll(Collection<?> oldElements) {
    throw new UnsupportedOperationException();
  }

  /**
   * Will throw an exception and leave the collection unmodified.
   * @throws UnsupportedOperationException always
   * @deprecated Modification operation are not supported.
   */
  @Deprecated
  @Override
  public final boolean retainAll(Collection<?> elementsToKeep) {
    throw new UnsupportedOperationException();
  }

  /**
   * Will throw an exception and leave the collection unmodified.
   * @throws UnsupportedOperationException always
   * @deprecated Modification operation are not supported.
   */
  @Deprecated
  @Override
  public final void clear() {
    throw new UnsupportedOperationException();
  }

  private static class EmptyImmutableOrdinalSet<E extends OrdinalValue<E>>
      extends ImmutableOrdinalSet<E> {
    @Override
    protected Set<E> delegate() {
      return ImmutableSet.of();
    }

    @Override
    public boolean isEmpty() {
      return true;
    }

    @Override
    public int size() {
      return 0;
    }

    @Override
    public boolean containsAll(Collection<?> collection) {
      return collection.isEmpty();
    }

    @Override
    public boolean containsAny(Collection<?> collection) {
      return false;
    }

    @Override
    public void incrementCounters(int[] countersByOrdinal) {}
  }

  private static class SingletonImmutableOrdinalSet<E extends OrdinalValue<E>>
      extends ImmutableOrdinalSet<E> {
    private final E element;

    SingletonImmutableOrdinalSet(E element) {
      this.element = checkNotNull(element);
    }

    @Override
    public boolean contains(Object object) {
      return element.equals(object);
    }

    @Override
    public boolean containsAll(Collection<?> collection) {
      if (collection instanceof SingletonImmutableOrdinalSet) {
        return element.equals(((SingletonImmutableOrdinalSet<?>) collection).element);
      }
      return super.containsAll(collection);
    }

    @Override
    public boolean containsAny(Collection<?> collection) {
      return collection.contains(element);
    }

    @Override
    protected Set<E> delegate() {
      return ImmutableSet.of(element);
    }

    @Override
    public int size() {
      return 1;
    }

    @Override
    public boolean isEmpty() {
      return false;
    }

    @Override
    public void incrementCounters(int[] counters) {
      counters[element.ordinal()]++;
    }
  }

  private static class RegularImmutableOrdinalSet<E extends OrdinalValue<E>> extends ImmutableOrdinalSet<E> {
    private static final int BITS_PER_WORD = Longs.BYTES * Byte.SIZE;
    private static final int POWER_OF_TWO_WORD_BITS = 6;

    private final OrdinalDomain<E> domain;
    private final long[] vector;
    private final int size;

    RegularImmutableOrdinalSet(OrdinalDomain<E> domain, OrdinalValue<?>[] elements) {
      int maxOrdinal = 0;
      int count = 0;
      for (OrdinalValue<?> e : elements) {
        checkArgument(e.domain().equals(domain), "Element has different domain %s", e);
        maxOrdinal = Math.max(maxOrdinal, e.ordinal());
        count++;
      }
      this.domain = domain;
      this.size = count;
      this.vector = new long[(maxOrdinal >>> POWER_OF_TWO_WORD_BITS) + 1];
      fillVector(elements);
    }

    private void fillVector(OrdinalValue<?>[] elements) {
      for (OrdinalValue<?> e : elements) {
        int ordinal = e.ordinal();
        int wordIndex = ordinal >>> POWER_OF_TWO_WORD_BITS;
        int bitIndex = ordinal - (wordIndex << POWER_OF_TWO_WORD_BITS);
        long word = vector[wordIndex];
        if (((word >>> bitIndex) & 1) != 0) {
          checkArgument(false, "Duplicate element %s", e);
        }
        vector[wordIndex] = word | (1L << bitIndex);
      }
    }

    @Override
    protected Set<E> delegate() {
      ImmutableSet.Builder<E> builder = ImmutableSet.builder();

      for (int i = 0; i < vector.length; i++) {
        long word = vector[i];
        int wordOrdinal = i * BITS_PER_WORD;
        for (int bitIndex = 0; bitIndex < BITS_PER_WORD; bitIndex++) {
          if (((word >>> bitIndex) & 1) != 0) {
            builder.add(domain.get(wordOrdinal + bitIndex));
          }
        }
      }

      return builder.build();
    }

    @Override
    public boolean contains(Object object) {
      if (object instanceof OrdinalValue<?>) {
        OrdinalValue<?> value = (OrdinalValue<?>) object;
        if (value.domain().equals(domain)) {
          return containsOrdinal(value.ordinal());
        }
      }
      return false;
    }

    private boolean containsOrdinal(int ordinal) {
      int wordIndex = ordinal >>> POWER_OF_TWO_WORD_BITS;
      int bitIndex = ordinal - (wordIndex << POWER_OF_TWO_WORD_BITS);
      return (wordIndex < vector.length) && ((vector[wordIndex] >>> bitIndex) & 1) != 0;
    }

    private boolean containsAllOrdinals(RegularImmutableOrdinalSet<?> ordinalSet) {
      long[] otherVector = ordinalSet.vector;
      long[] vector = this.vector;

      if (vector.length < otherVector.length) {
        // If other set contains more words - then it contains higher ordinals that this
        // just don't possess, so containsAll will be false
        return false;
      }

      for (int i = 0; i < otherVector.length; i++) {
        long v = vector[i];
        long ov = otherVector[i];
        if ((v & ov) != ov) {
          return false;
        }
      }

      return true;
    }

    private boolean containsAnyOrdinal(RegularImmutableOrdinalSet<?> ordinalSet) {
      long[] otherVector = ordinalSet.vector;
      long[] vector = this.vector;

      for (int i = 0; i < otherVector.length && i < vector.length; i++) {
        long v = vector[i];
        long ov = otherVector[i];
        if ((v & ov) > 0) {
          return true;
        }
      }

      return false;
    }

    @Override
    public boolean containsAny(Collection<?> collection) {
      int size = collection.size();
      if (size == 0) {
        return false;
      }
      if (size == 1) {
        return contains(Iterables.get(collection, 0));
      }
      if (collection instanceof RegularImmutableOrdinalSet<?>) {
        RegularImmutableOrdinalSet<?> otherSet = (RegularImmutableOrdinalSet<?>) collection;
        return otherSet.domain.equals(domain) && containsAnyOrdinal(otherSet);
      }
      return super.containsAny(collection);
    }

    @Override
    public boolean containsAll(Collection<?> collection) {
      int size = collection.size();
      if (size == 0) {
        return true;
      }
      if (size == 1) {
        return contains(Iterables.get(collection, 0));
      }
      if (collection instanceof RegularImmutableOrdinalSet<?>) {
        RegularImmutableOrdinalSet<?> otherSet = (RegularImmutableOrdinalSet<?>) collection;
        return otherSet.domain.equals(domain) && containsAllOrdinals(otherSet);
      }
      return super.containsAll(collection);
    }

    @Override
    public boolean isEmpty() {
      return false;
    }

    @Override
    public int size() {
      return size;
    }

    @Override
    public void incrementCounters(int[] counters) {
      long[] vector = this.vector;

      for (int i = 0; i < vector.length; i++) {
        long v = vector[i];
        for (int ordinal = i << POWER_OF_TWO_WORD_BITS; v != 0;) {
          int zeroes = Long.numberOfTrailingZeros(v);
          if (zeroes == BITS_PER_WORD) {
            break;
          }
          if (zeroes == BITS_PER_WORD - 1) {
            v = 0;
          } else {
            v >>>= zeroes + 1;
          }
          ordinal += zeroes;
          counters[ordinal++]++;
        }
      }
    }
  }

  /**
   * Coarse grained method to effectively collect containment information without
   * re-packing internal structures to temporary collections.
   * <p>
   * For any contained element, corresponding value in array by ordinal index will be incremented.
   * @param counters array of counters where indexes corresponds to ordinal values
   * @exception RuntimeException if counters array length do not correspond to ordinal indexes of
   *              contained values
   */
  public abstract void incrementCounters(int[] counters);

  public boolean containsAny(Collection<?> collection) {
    for (Object object : collection) {
      if (contains(object)) {
        return true;
      }
    }
    return false;
  }

  /**
   * Build instances of {@link ImmutableOrdinalSet}.
   * @param <E> element type
   * @return builder
   */
  public static <E extends OrdinalValue<E>> Builder<E> builder() {
    return new Builder<>();
  }

  /**
   * Build instances of {@link ImmutableOrdinalSet}.
   * @param <E> element type
   */
  public static final class Builder<E extends OrdinalValue<E>> {
    private final List<E> builder = Lists.newArrayListWithExpectedSize(4);

    Builder() {}

    /**
     * Adds add elements from the iterable.
     * @param elements the elements
     * @return {@code this} builder for chained invocation
     */
    public Builder<E> addAll(Iterable<E> elements) {
      Iterables.addAll(builder, checkNotNull(elements));
      return this;
    }

    /**
     * Adds element.
     * @param element the element
     * @return {@code this} builder for chained invocation
     */
    public Builder<E> add(E element) {
      builder.add(checkNotNull(element));
      return this;
    }

    /**
     * Builds instances of {@link ImmutableOrdinalSet} using all added elements.
     * @return immutable ordinal set
     */
    public ImmutableOrdinalSet<E> build() {
      return ImmutableOrdinalSet.copyOf(builder);
    }
  }
}


/*
   Copyright 2013-2018 Immutables Authors and Contributors

   Licensed 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 org.immutables.mongo.repository.internal;

import com.google.common.base.Function;
import com.google.common.base.MoreObjects;
import com.google.common.base.Preconditions;
import com.google.common.collect.BoundType;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.common.collect.LinkedHashMultimap;
import com.google.common.collect.Multimap;
import com.google.common.collect.Range;
import com.mongodb.QueryOperators;
import org.bson.BsonDocument;
import org.bson.BsonDocumentWriter;
import org.bson.BsonValue;
import org.bson.Document;
import org.bson.codecs.Encoder;
import org.bson.codecs.EncoderContext;
import org.bson.codecs.configuration.CodecRegistry;
import org.bson.conversions.Bson;
import org.immutables.mongo.repository.Repositories;
import org.immutables.mongo.repository.internal.Constraints.ConstraintVisitor;
import javax.annotation.Nullable;
import javax.annotation.concurrent.NotThreadSafe;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.regex.Pattern;
import static com.google.common.base.Preconditions.checkNotNull;

/**
 * Routines and classes used by generated code and bridging code in {@link Repositories}
 */
public final class Support {
  private Support() {}

  /**
   * Given a query / ordering clause will convert it to Bson representation.
   */
  public static Bson convertToBson(final Constraints.ConstraintHost fields) {
    if (fields instanceof JsonQuery) {
      return (Bson) fields;
    }
    return fields.accept(new ConstraintBuilder("")).asDocument();
  }

  /**
   * Builds bson with index definition. The difference with
   * {@link #convertToBson(Constraints.ConstraintHost)} is that this method fails on duplicate
   * fields. Currently only one index is allowed per field.
   * @see Mongo Indexes
   */
  public static Bson convertToIndex(final Constraints.ConstraintHost fields) {
    final Document document = new Document();
    fields.accept(new ConvertToIndex(document));
    return document;
  }

  public static String stringify(final Constraints.ConstraintHost constraints) {
    if (constraints instanceof JsonQuery) {
      return constraints.toString();
    }
    return convertToBson(constraints).toString();
  }

  private static final class ConvertToIndex
      extends Constraints.AbstractConstraintVisitor<ConvertToIndex> {
    private final Document document;

    private ConvertToIndex(Document document) {
      this.document = document;
    }

    @Override
    public ConvertToIndex equal(String name, boolean negate, @Nullable Object value) {
      if (document.containsKey(name)) {
        throw new IllegalArgumentException(
            String.format("Attribute %s is not unique: %s", name, document.get(name)));
      }
      document.put(name, value);
      return this;
    }
  }

  @NotThreadSafe
  public static class ConstraintBuilder extends Constraints.AbstractConstraintVisitor<ConstraintBuilder> {

    private final String keyPrefix;

    private List<Document> conjunctions; // $and
    private final List<Document> disjunctions; // $or

    private ConstraintBuilder(String keyPrefix) {
      this.keyPrefix = Preconditions.checkNotNull(keyPrefix, "keyPrefix");
      this.conjunctions = new ArrayList<>();
      this.disjunctions = new ArrayList<>();
    }

    private ConstraintBuilder newBuilderForKey(String key) {
      return new ConstraintBuilder(keyPrefix + "." + key);
    }

    private void addContraint(String name, Object constraint) {
      final String path = keyPrefix.concat(name);
      conjunctions.add(new Document(path, constraint));
    }

    @Override
    public ConstraintBuilder in(String name, boolean negate, Iterable<?> values) {
      addContraint(name,
          new Document(
              negate ? QueryOperators.NIN : QueryOperators.IN,
              ImmutableSet.copyOf(unwrapBsonableIterable(values))));
      return this;
    }

    @Override
    public ConstraintBuilder equal(String name, boolean negate, @Nullable Object value) {
      addContraint(name, negate ? new Document(QueryOperators.NE, unwrapBsonable(value)) : unwrapBsonable(value));
      return this;
    }

    @Override
    public ConstraintBuilder range(String name, boolean negate, Range<?> range) {

      if (range.hasLowerBound() && range.hasUpperBound()) {
        if (range.lowerEndpoint().equals(range.upperEndpoint()) && !range.isEmpty()) {
          equal(name, negate, range.lowerEndpoint());
        } else {
          Document rangeObject = new Document()
              .append(boundToOperator(true, false, range.lowerBoundType()), unwrapBsonable(range.lowerEndpoint()))
              .append(boundToOperator(false, false, range.upperBoundType()), unwrapBsonable(range.upperEndpoint()));

          addContraint(name, negateConstraint(negate, rangeObject));
        }

      } else if (range.hasLowerBound()) {
        Document rangeObject =
            new Document(
                boundToOperator(true, negate, range.lowerBoundType()),
                unwrapBsonable(range.lowerEndpoint()));

        addContraint(name, rangeObject);

      } else if (range.hasUpperBound()) {
        Document rangeObject =
            new Document(
                boundToOperator(false, negate, range.upperBoundType()),
                unwrapBsonable(range.upperEndpoint()));

        addContraint(name, rangeObject);
      }
      return this;
    }

    private String boundToOperator(boolean lower, boolean negate, BoundType lowerBoundType) {
      boolean closedBound = lowerBoundType == BoundType.CLOSED;
      return comparisonOperators[lower ^ negate ? 1 : 0][closedBound ^ negate ? 1 : 0];
    }

    private static final String[][] comparisonOperators = {
        {QueryOperators.LT, QueryOperators.LTE},
        {QueryOperators.GT, QueryOperators.GTE}
    };

    private Object negateConstraint(boolean negate, Object constraint) {
      return negate ? new Document(QueryOperators.NOT, constraint) : constraint;
    }

    public Document asDocument() {
      final List<Document> result = new ArrayList<>(disjunctions);
      result.add(mergeConjunctions(conjunctions));

      return result.size() == 1 ? result.get(0) : new Document(QueryOperators.OR, result);

    }

    private Document mergeConjunctions(List<Document> conjunctions) {
      final Multimap<String, Document> merged = LinkedHashMultimap.create();

      for (Document doc : conjunctions) {
        Preconditions.checkState(doc.keySet().size() == 1, "Invalid constraint %s", doc);
        final String key = doc.keySet().iterator().next();
        merged.put(key, doc);
      }

      final Document result = new Document();

      for (Map.Entry<String, Collection<Document>> entry : merged.asMap().entrySet()) {
        Preconditions.checkState(!entry.getValue().isEmpty(), "empty constraint: %s", entry);

        if (entry.getValue().size() == 1) {
          result.putAll(entry.getValue().iterator().next());
        } else {
          result.putAll(new Document(QueryOperators.AND, entry.getValue()));
        }
      }

      return result;
    }

    @Override
    public ConstraintBuilder size(String name, boolean negate, int size) {
      addContraint(name, negateConstraint(negate, new Document(QueryOperators.SIZE, size)));
      return this;
    }

    @Override
    public ConstraintBuilder present(String name, boolean negate) {
      addContraint(name, new Document(QueryOperators.EXISTS, !negate));
      return this;
    }

    @Override
    public ConstraintBuilder match(String name, boolean negate, Pattern pattern) {
      addContraint(name, negateConstraint(negate, pattern));
      return this;
    }

    @Override
    public ConstraintBuilder nested(String name, Constraints.ConstraintHost nestedConstraints) {
      conjunctions.add(nestedConstraints.accept(newBuilderForKey(name)).asDocument());
      return this;
    }

    @Override
    public ConstraintBuilder disjunction() {
      disjunctions.add(mergeConjunctions(conjunctions));
      conjunctions = new ArrayList<>();
      return this;
    }

  }

  public static Constraints.ConstraintHost jsonQuery(String query) {
    return new JsonQuery(Document.parse(query));
  }

  public static <T> Object writable(Encoder<T> encoder, T value) {
    return new Adapted<>(encoder, value);
  }

  public static Object writable(Object value) {
    return value;
  }

  @SuppressWarnings("unchecked")
  public static <T extends Comparable<? super T>> Range<Comparable<Object>> writable(
      Encoder<T> encoder,
      Range<T> range) {
    if (range.hasLowerBound() && range.hasUpperBound()) {
      return Range.range(
          (Comparable<Object>) writable(encoder, range.lowerEndpoint()),
          range.lowerBoundType(),
          (Comparable<Object>) writable(encoder, range.upperEndpoint()),
          range.upperBoundType());
    } else if (range.hasLowerBound()) {
      return Range.downTo(
          (Comparable<Object>) writable(encoder, range.lowerEndpoint()),
          range.lowerBoundType());
    } else if (range.hasUpperBound()) {
      return Range.upTo(
          (Comparable<Object>) writable(encoder, range.upperEndpoint()),
          range.upperBoundType());
    }
    throw new AssertionError();
  }

  @SuppressWarnings("unchecked")
  public static <T extends Comparable<? super T>> Range<Comparable<Object>> writable(Range<T> range) {
    return (Range<Comparable<Object>>) writable((Object) range);
  }

  public static Object unwrapBsonable(Object value) {
    if (value instanceof Document) {
      for (Entry<String, Object> entry : ((Document) value).entrySet()) {
        entry.setValue(unwrapBsonable(entry.getValue()));
      }
      return value;
    }

    if (value instanceof Iterable<?>) {
      return ImmutableList.copyOf(unwrapBsonableIterable((Iterable<?>) value));
    }

    if (value instanceof Constraints.ConstraintHost) {
      return convertToBson((Constraints.ConstraintHost) value);
    }

    if (value == null
        || value instanceof Number
        || value instanceof Boolean
        || value instanceof String) {
      return value;
    }

    if (value instanceof Adapted<?>) {
      return ((Adapted<?>) value).toBson();
    }

    return String.valueOf(value);
  }

  private static class JsonQuery implements Constraints.ConstraintHost, Bson {
    private final Document value;

    JsonQuery(Document value) {
      this.value = checkNotNull(value, "value");
    }

    @Override
    public String toString() {
      return value.toJson();
    }

    @Override
    public <V extends ConstraintVisitor<V>> V accept(V visitor) {
      throw new UnsupportedOperationException(
          "Satisfied ConstraintSupport.ConstraintHost only for technical reasons and don't implements accept");
    }

    @Override
    public <TDocument> BsonDocument toBsonDocument(Class<TDocument> tDocumentClass, CodecRegistry codecRegistry) {
      return value.toBsonDocument(tDocumentClass, codecRegistry);
    }
  }

  static final class Adapted<T> implements Comparable<Adapted<T>> {
    final T value;
    final Encoder<T> encoder;

    Adapted(Encoder<T> encoder, T value) {
      this.encoder = encoder;
      this.value = value;
    }

    BsonValue toBson() {
      BsonDocument bson = new BsonDocument();
      org.bson.BsonWriter writer = new BsonDocumentWriter(bson);
      // Bson doesn't allow to write directly scalars / primitives, they have to be embedded in a
      // document.
      writer.writeStartDocument();
      writer.writeName("$");
      encoder.encode(writer, value, EncoderContext.builder().build());
      writer.writeEndDocument();
      writer.flush();
      return bson.get("$");
    }

    @SuppressWarnings("unchecked")
    @Override
    public int compareTo(Adapted<T> o) {
      return ((Comparable<T>) value).compareTo(o.value);
    }

    @Override
    public String toString() {
      return MoreObjects.toStringHelper(this)
          .addValue(value)
          .toString();
    }
  }

  static Iterable<?> unwrapBsonableIterable(Iterable<?> values) {
    return Iterables.transform(values, new Function<Object, Object>() {
      @Override
      public Object apply(Object input) {
        return unwrapBsonable(input);
      }
    });
  }

  public static Object emptyBsonObject() {
    return new Document();
  }

  public static Object bsonObjectAttribute(String name, Object value) {
    return new Document(name, value);
  }
}


/*
   Copyright 2013-2018 Immutables Authors and Contributors

   Licensed 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 org.immutables.mongo.bson4gson;

import com.google.gson.internal.LazilyParsedNumber;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonToken;
import org.bson.AbstractBsonReader;
import org.bson.AbstractBsonReader.State;
import org.bson.BsonType;
import org.immutables.mongo.Wrapper;

import javax.annotation.concurrent.NotThreadSafe;
import java.io.IOException;
import java.io.Reader;

import static com.google.common.base.Preconditions.checkNotNull;

/**
 * Adapter of {@link JsonReader GSON Reader} reading directly from
 * BSON encoded documents.
 * It delegates most of the calls to {@link org.bson.BsonReader} which understands binary JSON
 * representation (default wire protocol
 * between mongo server and client). This class allows to instantiate immutable objects directly
 * from binary stream (eg. {@code byte[]}) bypassing intermediate
 * object instantiation (typically {@code byte[] -> DBObject-> Immutable} ). Internally generated
 * GSON {@link com.google.gson.TypeAdapter} will
 * read incoming bytes as stream API.
 * @see BsonWriter
 * @see Mongo Driver (BSON)
 * @see BSON spec
 */
@NotThreadSafe
public class BsonReader extends JsonReader implements Wrapper<org.bson.BsonReader> {

  private static final Reader UNREADABLE_READER = new Reader() {
    @Override
    public int read(char[] buffer, int offset, int count) throws IOException {
      throw new AssertionError();
    }

    @Override
    public void close() throws IOException {
      throw new AssertionError();
    }
  };

  private final AbstractBsonReader delegate;

  /**
   * In order to be able to get current location of the BSON "cursor" (document or array start/end)
   * enforcing clients to
   * provide {@link AbstractBsonReader} which exposes this information via {@code getState()}
   * method, instead of more generic interface {@link BsonReader}.
   * @param delegate
   * @see AbstractBsonReader#getState()
   */
  BsonReader(org.bson.AbstractBsonReader delegate) {
    super(UNREADABLE_READER);
    this.delegate = checkNotNull(delegate, "delegate");
  }

  @Override
  public org.bson.BsonReader unwrap() {
    return this.delegate;
  }

  private void advance() {
    delegate.readBsonType();
  }

  @Override
  public void beginArray() throws IOException {
    delegate.readStartArray();
  }

  @Override
  public void endArray() throws IOException {
    delegate.readEndArray();
  }

  @Override
  public void beginObject() throws IOException {
    delegate.readStartDocument();
  }

  @Override
  public void endObject() throws IOException {
    delegate.readEndDocument();
  }

  @Override
  public boolean hasNext() throws IOException {
    if (!hasMoreElements()) {
      return false;
    }
    advance();
    return hasMoreElements();
  }

  private boolean hasMoreElements() {
    switch (state()) {
    case END_OF_DOCUMENT:
    case END_OF_ARRAY:
    case DONE:
      return false;
    default:
      return true;
    }
  }

  @Override
  public JsonToken peek() throws IOException {
    switch (state()) {
    case INITIAL:
    case SCOPE_DOCUMENT:
    case TYPE:
      advance();
      return toGsonToken(delegate.getCurrentBsonType());
    case NAME:
      return JsonToken.NAME;
    case END_OF_DOCUMENT:
      return JsonToken.END_OBJECT;
    case END_OF_ARRAY:
      return JsonToken.END_ARRAY;
    case DONE:
      return JsonToken.END_DOCUMENT;
    case VALUE:
      return toGsonToken(delegate.getCurrentBsonType());
    default:
      throw new IllegalStateException("Unexpected state: " + state() + " currentType:" +
          delegate.getCurrentBsonType());
    }
  }

  private State state() {
    return delegate.getState();
  }

  private static JsonToken toGsonToken(BsonType type) {
    switch (type) {
    case END_OF_DOCUMENT:
      return JsonToken.END_DOCUMENT;
    case DOCUMENT:
      return JsonToken.BEGIN_OBJECT;
    case ARRAY:
      return JsonToken.BEGIN_ARRAY;
    case BOOLEAN:
      return JsonToken.BOOLEAN;
    case STRING:
    case SYMBOL:
    case OBJECT_ID:
    case BINARY:
    case REGULAR_EXPRESSION:
      return JsonToken.STRING;
    case DATE_TIME:
    case DOUBLE:
    case INT32:
    case INT64:
    case TIMESTAMP:
    case DECIMAL128:
      return JsonToken.NUMBER;
    case NULL:
      return JsonToken.NULL;
    default:
      // not really sure what to do with this type
      return JsonToken.NULL;
    }
  }

  @Override
  public String nextName() throws IOException {
    return delegate.readName();
  }

  @Override
  public String nextString() throws IOException {
    return scalarToString();
  }

  /**
   * Gson library reads numbers lazily when using generic
   * {@link com.google.gson.internal.bind.TypeAdapters#JSON_ELEMENT} type adapter.
   * Number is read as string and then wrapped inside {@link LazilyParsedNumber}. This inefficiency
   * should only occur if reading numbers with generic JSON element API
   * and not using generated type adapters.
   * @see LazilyParsedNumber
   * @see com.google.gson.internal.bind.TypeAdapters#JSON_ELEMENT
   */
  private String scalarToString() {
    switch (delegate.getCurrentBsonType()) {
    case STRING:
      return delegate.readString();
    case SYMBOL:
      return delegate.readSymbol();
    case INT32:
      return Integer.toString(delegate.readInt32());
    case INT64:
      return Long.toString(delegate.readInt64());
    case DOUBLE:
      return Double.toString(delegate.readDouble());
    case DECIMAL128:
      return delegate.readDecimal128().toString();
    default:
      throw new IllegalStateException(
          "Unknown scalar type to be converted to string: " + delegate.getCurrentBsonType());
    }
  }

  @Override
  public boolean nextBoolean() throws IOException {
    return delegate.readBoolean();
  }

  @Override
  public void nextNull() throws IOException {
    delegate.readNull();
  }

  @Override
  public double nextDouble() throws IOException {
    switch (delegate.getCurrentBsonType()) {
    case INT32:
      return delegate.readInt32();
    case INT64:
      return delegate.readInt64();
    case DECIMAL128:
      return delegate.readDecimal128().bigDecimalValue().doubleValue();
    default:
      return delegate.readDouble();
    }
  }

  @Override
  public long nextLong() throws IOException {
    switch (delegate.getCurrentBsonType()) {
    case DOUBLE:
      return (long) delegate.readDouble();
    case INT32:
      return delegate.readInt32();
    case DECIMAL128:
      return delegate.readDecimal128().bigDecimalValue().longValueExact();
    default:
      return delegate.readInt64();
    }
  }

  @Override
  public int nextInt() throws IOException {
    switch (delegate.getCurrentBsonType()) {
    case DOUBLE:
      return (int) delegate.readDouble();
    case INT64:
      return (int) delegate.readInt64();
    case DECIMAL128:
      return delegate.readDecimal128().bigDecimalValue().intValueExact();
    default:
      return delegate.readInt32();
    }
  }

  @Override
  public void close() throws IOException {
    delegate.close();
  }

  @Override
  public void skipValue() throws IOException {
    delegate.skipValue();
  }

}


package org.immutables.criteria.constraints;

import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;

import javax.annotation.Nullable;
import java.util.Arrays;
import java.util.List;

/**
 * A set of predefined utilities and factories for expressions like {@link Literal} or {@link Call}
 */
public final class Expressions {

  private Expressions() {}

  public static <T> Path<T> path(final String path) {
    Preconditions.checkNotNull(path, "path");

    return new Path<T>() {
      @Override
      public String path() {
        return path;
      }

      @Nullable
      @Override
      public <R, C> R accept(ExpressionVisitor<R, C> visitor, @Nullable C context) {
        return visitor.visit(this, context);
      }
    };
  }

  public static <T> Literal<T> literal(final T value) {
    return new Literal<T>() {
      @Override
      public T value() {
        return value;
      }

      @Nullable
      @Override
      public <R, C> R accept(ExpressionVisitor<R, C> visitor, @Nullable C context) {
        return visitor.visit(this, context);
      }
    };
  }

  public static <T> Expression<T> and(Expression<T> first, Expression<T> second) {
    return and(Arrays.asList(first, second));
  }

  public static <T> Expression<T> and(Iterable<? extends Expression<T>> expressions) {
    return reduce(Operators.AND, expressions);
  }

  public static <T> Expression<T> or(Expression<T> first, Expression<T> second) {
    return or(Arrays.asList(first ,second));
  }

  public static <T> Expression<T> or(Iterable<? extends Expression<T>> expressions) {
    return reduce(Operators.OR, expressions);
  }

  private static <T> Expression<T> reduce(Operator operator, Iterable<? extends Expression<T>> expressions) {
    final Iterable<? extends Expression<T>> filtered = Iterables.filter(expressions, e -> !isNil(e) );
    final int size = Iterables.size(filtered);

    if (size == 0) {
      return nil();
    } else if (size == 1) {
      return filtered.iterator().next();
    }

    return call(operator, expressions);
  }

  /**
   * Combines expressions Disjunctive normal form
   */
  public static <T> Expression<T> dnf(Operator operator, Expression<T> existing, Expression<T> newExpression) {
    if (!(operator == Operators.AND || operator == Operators.OR)) {
      throw new IllegalArgumentException(String.format("Expected %s for operator but got %s",
              Arrays.asList(Operators.AND, Operators.OR), operator));
    }

    if (isNil(existing)) {
      return DnfExpression.<T>create().and(newExpression);
    }

    if (!(existing instanceof DnfExpression)) {
      throw new IllegalStateException(String.format("Expected existing expression to be %s but was %s",
              DnfExpression.class.getName(), existing.getClass().getName()));
    }

    @SuppressWarnings("unchecked")
    final DnfExpression<T> conjunction = (DnfExpression<T>) existing;
    return operator == Operators.AND ? conjunction.and(newExpression) : conjunction.or(newExpression);
  }

  public static <T> Call<T> call(final Operator operator, Expression<?> ... operands) {
    return call(operator, Arrays.asList(operands));
  }

  public static <T> Call<T> call(final Operator operator, final Iterable<? extends Expression<?>> operands) {
    final List<Expression<?>> ops = ImmutableList.copyOf(operands);
    return new Call<T>() {
      @Override
      public List<Expression<?>> getArguments() {
        return ops;
      }

      @Override
      public Operator getOperator() {
        return operator;
      }

      @Nullable
      @Override
      public <R, C> R accept(ExpressionVisitor<R, C> visitor, @Nullable C context) {
        return visitor.visit(this, context);
      }
    };
  }

  /**
   * Used as sentinel for {@code noop} expression.
   */
  @SuppressWarnings("unchecked")
  public static <T> Expression<T> nil() {
    return (Expression<T>) NilExpression.INSTANCE;
  }

  public static boolean isNil(Expression<?> expression) {
    return expression == NilExpression.INSTANCE;
  }

}


/*
   Copyright 2016 Immutables Authors and Contributors

   Licensed 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 org.immutables.value.processor.meta;

import java.lang.annotation.ElementType;
import javax.lang.model.element.TypeElement;
import org.immutables.value.Value;

@Value.Immutable(intern = true, builder = false)
abstract class NullabilityAnnotationInfo {
  @Value.Parameter
  @Value.Auxiliary
  abstract TypeElement element();

  @Value.Derived
  String qualifiedName() {
    return element().getQualifiedName().toString();
  }

  @Value.Derived
  String simpleName() {
    return element().getSimpleName().toString();
  }

  @Value.Lazy
  String asPrefix() {
    return "@" + qualifiedName() + " ";
  }

  String asPrefixOriginal() {
    return asPrefix();
  }

  @Value.Lazy
  String asLocalPrefix() {
    boolean applicableToLocal = Annotations.annotationMatchesTarget(
        element(),
        ElementType.LOCAL_VARIABLE);

    return applicableToLocal
        ? asPrefix()
        : "";
  }

  /** Ad-hoc implementation for type-use level nullable annotation. */
  static NullabilityAnnotationInfo forTypeUse() {
    return new NullabilityAnnotationInfo() {
      @Override
      TypeElement element() {
        throw new UnsupportedOperationException("expecting this will not be accessed");
      }

      @Override
      String qualifiedName() {
        return "";
      }

      @Override
      String simpleName() {
        return "";
      }

      /** Empty as we expect type string extraction to handle this for us where possible. */
      @Override
      String asLocalPrefix() {
        return "";
      }

      /** Empty as we expect type string extraction to handle this for us where possible. */
      @Override
      String asPrefix() {
        return "";
      }
    };
  }
}


/*
   Copyright 2013-2018 Immutables Authors and Contributors

   Licensed 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 org.immutables.mongo.bson4gson;

import com.google.common.base.Preconditions;
import com.google.gson.Gson;
import com.google.gson.TypeAdapter;
import com.google.gson.TypeAdapterFactory;
import com.google.gson.reflect.TypeToken;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonWriter;
import org.bson.AbstractBsonReader;
import org.bson.codecs.Codec;
import org.bson.codecs.DecoderContext;
import org.bson.codecs.EncoderContext;
import org.bson.codecs.configuration.CodecConfigurationException;
import org.bson.codecs.configuration.CodecRegistry;

import java.io.IOException;

/**
 * Set of utilities to bridge BSON and
 * Gson standard classes like
 * {@link TypeAdapter} / {@link Codec}(s).
 */
public final class GsonCodecs {

  private GsonCodecs() {
  }

  /**
   * Build a TypeAdapter from {@link Codec} opposite of {@link #codecFromTypeAdapter(Class, TypeAdapter)}.
   *
   * @param codec existing codec
   * @return type adapter which delegates calls to a codec.
   */
  public static <T> TypeAdapter<T> typeAdapterFromCodec(final Codec<T> codec) {
    Preconditions.checkNotNull(codec, "codec");
    return new TypeAdapter<T>() {
      @Override
      public void write(JsonWriter out, T value) throws IOException {
        BsonWriter writer = (BsonWriter) out;
        org.bson.BsonWriter delegate = writer.unwrap();
        codec.encode(delegate, value, EncoderContext.builder().build());
      }

      @Override
      public T read(JsonReader in) throws IOException {
        BsonReader reader = (BsonReader) in;
        org.bson.BsonReader delegate = reader.unwrap();
        return codec.decode(delegate, DecoderContext.builder().build());
      }
    };
  }

  /**
   * Gson Factory which gives preference to existing adapters from {@code gson} instance. However,
   * if type is not supported it will query {@link CodecRegistry} to create one (if possible).
   *
   *
This allows supporting Bson types by Gson natively (eg. for {@link org.bson.types.ObjectId}).
   *
   * @param registry existing registry which will be used if type is unknown to {@code gson}.
   * @return factory which delegates to {@code registry} for unknown types.
   */
  public static TypeAdapterFactory delegatingTypeAdapterFactory(final CodecRegistry registry) {
    Preconditions.checkNotNull(registry, "registry");
    return new TypeAdapterFactory() {
      @Override
      public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
        boolean hasAdapter;
        try {
          TypeAdapter<T> adapter = gson.getDelegateAdapter(this, type);
          hasAdapter = !isReflectiveTypeAdapter(adapter);
        } catch (IllegalArgumentException e) {
          hasAdapter = false;
        }

        if (hasAdapter) {
          return null;
        }

        try {
          @SuppressWarnings("unchecked")
          Codec<T> codec = (Codec<T>) registry.get(type.getRawType());
          return typeAdapterFromCodec(codec);
        } catch (CodecConfigurationException e1) {
          return null;
        }

      }
    };
  }

  /**
   * Build a codec from {@link TypeAdapter}. Opposite of {@link #typeAdapterFromCodec(Codec)}.
   *
   * @param type type handled by this adapter
   * @param adapter existing adapter
   * @param <T> codec value type
   * @throws CodecConfigurationException if adapter is not supported
   * @return new instance of the codec which handles {@code type}.
   */
  public static <T> Codec<T> codecFromTypeAdapter(Class<T> type, TypeAdapter<T> adapter) {
    if (isReflectiveTypeAdapter(adapter)) {
      throw new CodecConfigurationException(String.format("%s can't be build from %s " +
                      "(for type %s)", TypeAdapterCodec.class.getSimpleName(),
              adapter.getClass().getName(), type.getName()));
    }
    return new TypeAdapterCodec<>(type, adapter);
  }

  /**
   * Given existing {@code Gson} instance builds a {@link CodecRegistry}.
   *
   * @param gson preconfigured instance
   * @return wrapper for {@code gson}.
   */
  public static CodecRegistry codecRegistryFromGson(final Gson gson) {
    Preconditions.checkNotNull(gson, "gson");
    return new CodecRegistry() {
      @Override
      public <T> Codec<T> get(Class<T> clazz) {
        return codecFromTypeAdapter(clazz, gson.getAdapter(clazz));
      }
    };
  }

  static  boolean isReflectiveTypeAdapter(TypeAdapter adapter) {
    Preconditions.checkNotNull(adapter, "adapter");
    return adapter instanceof com.google.gson.internal.bind.ReflectiveTypeAdapterFactory.Adapter;
  }

  /**
   * Codec which delegates all calls to existing type adapter.
   *
   * @param <T> type handled by this codec
   */
  private static class TypeAdapterCodec<T> implements Codec<T> {
    private final Class<T> clazz;
    private final TypeAdapter<T> adapter;

    private TypeAdapterCodec(Class<T> type, TypeAdapter<T> adapter) {
      this.clazz = Preconditions.checkNotNull(type, "type");
      Preconditions.checkArgument(!isReflectiveTypeAdapter(adapter),
              "Type adapter %s for type '%s' is not supported."
                      + " This may happen when using default RepositorySetup.forUri and"
                      + " META-INF/services/..TypeAdapterFactory files are not compiled or accessible."
                      + " Alternatively this may happen if creating custom RepositorySetup with Gson instance,"
                      + " which does not have type adapters registered.", adapter.getClass().getName(), type);
      this.adapter = adapter;
    }

    @Override
    public T decode(org.bson.BsonReader reader, DecoderContext decoderContext) {
      if (!(reader instanceof AbstractBsonReader)) {
        throw new UnsupportedOperationException(String.format("Only readers of type %s supported. Yours is %s",
                AbstractBsonReader.class.getName(), reader.getClass().getName()));
      }

      try {
        return adapter.read(new BsonReader((AbstractBsonReader) reader));
      } catch (IOException e) {
        throw new RuntimeException(String.format("Couldn't read %s", clazz), e);
      }
    }

    @Override
    public void encode(org.bson.BsonWriter writer, T value, EncoderContext encoderContext) {
      try {
        adapter.write(new BsonWriter(writer), value);
      } catch (IOException e) {
        throw new RuntimeException(String.format("Couldn't write value of class %s: %s", clazz.getName(), value), e);
      }
    }

    @Override
    public Class<T> getEncoderClass() {
      return clazz;
    }

  }
}


/*
   Copyright 2013-2018 Immutables Authors and Contributors

   Licensed 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 org.immutables.ordinal;

import com.google.common.collect.AbstractIterator;
import java.util.Iterator;
import javax.annotation.concurrent.ThreadSafe;

/**
 * Ordinal domain represent (potentially growing) set of objects of some kind (among {@code E})
 * which has distinct and contiguous range of {@link OrdinalValue#ordinal() ordinal} values. Equal
 * objects expected to have same ordinal value. Usually, ordinal values will be interned
 * in {@link OrdinalDomain}, but it is not strictly required.
 *

 * Implementations must be thread safe.
 *

 * @param <E> type with ordinal values
 */
@ThreadSafe
public abstract class OrdinalDomain<E extends OrdinalValue<E>> implements Iterable<E> {
  /**
   * Gets element from domain by corresponding ordinal value. It is guaranteed that returned element
   * will have {@link OrdinalValue#ordinal()} value equal to supplied {@code ordinal} parameter.
   * @param ordinal ordinal value
   * @return the element by ordinal value
   * @throws IndexOutOfBoundsException if no such element exists
   */
  public abstract E get(int ordinal);

  /**
   * Current length of ordinal domain. Be caution that length is not required to be stable and could
   * grow, as such it could be used as a hint.
   * @return the domain length: current max ordinal value plus one
   */
  public abstract int length();

  /**
   * Iterator over all present inhabitants of ordinal domain.
   * @return snapshot iterator of elements in ordinal domain.
   */
  @Override
  public Iterator<E> iterator() {
    return new AbstractIterator<E>() {
      private final int length = length();
      private int index = 0;

      @Override
      protected E computeNext() {
        int p = index++;
        if (p < length) {
          return get(p);
        }
        return endOfData();
      }
    };
  }
}