/*
   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.generator;

import com.google.common.base.Function;
import com.google.common.base.Functions;
import com.google.common.collect.ImmutableCollection;
import com.google.common.collect.ImmutableList;
import com.sun.tools.javac.code.Attribute;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import javax.annotation.Nullable;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.AnnotationValue;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.Name;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.SimpleAnnotationValueVisitor7;
import javax.lang.model.util.Types;

public final class AnnotationMirrors {
  private AnnotationMirrors() {}

  // safe unchecked: known return type list or empty immutable list
  @SuppressWarnings("unchecked")
  public static List<? extends AnnotationMirror> from(TypeMirror type) {
    return (List<? extends AnnotationMirror>) GetTypeAnnotations.get(type);
  }

  private enum GetTypeAnnotations {
    ;
    @Nullable
    private static final Method METHOD;
    static {
      @Nullable Method method = null;
      try {
        method = TypeMirror.class.getMethod("getAnnotationMirrors");
      } catch (Exception ex) {
      }
      METHOD = method;
    }

    static Object get(Object input) {
      if (METHOD != null) {
        try {
          return METHOD.invoke(input);
        } catch (Exception ex) {
        }
      }
      return ImmutableList.of();
    }
  }

  public static CharSequence toCharSequence(AnnotationMirror value) {
    PrintVisitor printer = new PrintVisitor();
    printer.visitAnnotation(value, null);
    return printer.builder;
  }

  public static CharSequence toCharSequence(AnnotationValue value) {
    PrintVisitor printer = new PrintVisitor();
    printer.visit(value, null);
    return printer.builder;
  }

  public static CharSequence toCharSequence(
      AnnotationMirror value,
      Function<String, String> unresovedImportsResolver) {
    PrintVisitor printer = new PrintVisitor(unresovedImportsResolver);
    printer.visitAnnotation(value, null);
    return printer.builder;
  }

  public static ImmutableList<TypeMirror> getTypesFromMirrors(
      String annotationQualifiedName,
      String annotationAttributeName,
      List<? extends AnnotationMirror> annotationMirrors) {
    ImmutableList.Builder<TypeMirror> builder = ImmutableList.builder();
    for (AnnotationMirror annotationMirror : annotationMirrors) {
      TypeElement element = (TypeElement) annotationMirror.getAnnotationType().asElement();
      if (element.getQualifiedName().contentEquals(annotationQualifiedName)) {
        collectTypesFromAnnotationAttribute(annotationAttributeName, builder, annotationMirror);
      }
    }
    return builder.build();
  }

  public static ImmutableList<TypeMirror> getTypesFromMirrors(
      Types types,
      TypeMirror annotationType,
      String annotationAttributeName,
      List<? extends AnnotationMirror> annotationMirrors) {
    ImmutableList.Builder<TypeMirror> builder = ImmutableList.builder();
    for (AnnotationMirror annotationMirror : annotationMirrors) {
      if (types.isSameType(annotationMirror.getAnnotationType(), annotationType)) {
        collectTypesFromAnnotationAttribute(annotationAttributeName, builder, annotationMirror);
      }
    }
    return builder.build();
  }

  private static void collectTypesFromAnnotationAttribute(
      String annotationValueName,
      final ImmutableCollection.Builder<TypeMirror> builder,
      AnnotationMirror annotationMirror) {

    for (Entry<? extends ExecutableElement, ? extends AnnotationValue> e : annotationMirror
        .getElementValues()
        .entrySet()) {

      if (e.getKey().getSimpleName().contentEquals(annotationValueName)) {
        e.getValue().accept(new SimpleAnnotationValueVisitor7<Void, Void>() {
          @Override
          public Void visitArray(List<? extends AnnotationValue> vals, Void p) {
            for (AnnotationValue annotationValue : vals) {
              annotationValue.accept(this, p);
            }
            return null;
          }

          @Override
          public Void visitType(TypeMirror t, Void p) {
            builder.add(t);
            return null;
          }
        }, null);
      }
    }
  }

  @Nullable
  public static AnnotationMirror findAnnotation(
      List<? extends AnnotationMirror> annotationMirrors,
      Class<? extends Annotation> annotationType) {
    String annotationTypeName = annotationType.getCanonicalName();
    return findAnnotation(annotationMirrors, annotationTypeName);
  }

  @Nullable
  public static AnnotationMirror findAnnotation(
      List<? extends AnnotationMirror> annotationMirrors,
      String annotationTypeName) {
    for (AnnotationMirror annotation : annotationMirrors) {
      if (((TypeElement) annotation.getAnnotationType().asElement())
          .getQualifiedName()
          .contentEquals(annotationTypeName)) {
        return annotation;
      }
    }
    return null;
  }

  public static boolean isAnnotationPresent(
      List<? extends AnnotationMirror> annotationMirrors,
      Class<? extends Annotation> annotationType) {
    return findAnnotation(annotationMirrors, annotationType) != null;
  }

  private static final class PrintVisitor extends SimpleAnnotationValueVisitor7<Void, Void> {
    private static final String ATTRIBUTE_VALUE = "value";
    private static final String CONSTANT_MAX_VALUE = ".MAX_VALUE";
    private static final String CONSTANT_MIN_VALUE = ".MIN_VALUE";
    private static final String CONSTANT_NAN = ".NaN";
    private static final String CONSTANT_NEGATIVE_INFINITY = ".NEGATIVE_INFINITY";
    private static final String CONSTANT_POSITIVE_INFINITY = ".POSITIVE_INFINITY";

    final StringBuilder builder = new StringBuilder();
    final Function<String, String> unresovedImportsResolver;

    PrintVisitor() {
      this(Functions.<String>identity());
    }

    PrintVisitor(Function<String, String> unresovedImportsResolver) {
      this.unresovedImportsResolver = unresovedImportsResolver;
    }

    void visitValue(AnnotationValue value) {
      value.accept(this, null);
    }

    @Override
    public Void visitBoolean(boolean b, Void p) {
      builder.append(b);
      return null;
    }

    @Override
    public Void visitInt(int i, Void p) {
      appendLiteral(i);
      return null;
    }

    @Override
    public Void visitDouble(double d, Void p) {
      appendLiteral(d);
      return null;
    }

    @Override
    public Void visitFloat(float f, Void p) {
      appendLiteral(f);
      return null;
    }

    @Override
    public Void visitLong(long l, Void p) {
      appendLiteral(l);
      return null;
    }

    @Override
    public Void visitShort(short s, Void p) {
      appendLiteral(s);
      return null;
    }

    @Override
    public Void visitByte(byte b, Void p) {
      appendLiteral(b);
      return null;
    }

    @Override
    public Void visitChar(char c, Void p) {
      builder.append(StringLiterals.toLiteral(c));
      return null;
    }

    @Override
    public Void visitString(String s, Void p) {
      builder.append(StringLiterals.toLiteral(s));
      return null;
    }

    @Override
    public Void visitType(TypeMirror t, Void p) {
      builder.append(t).append(".class");
      return null;
    }

    @Override
    public Void visitArray(List<? extends AnnotationValue> vals, Void p) {
      if (vals.size() == 1) {
        visitValue(vals.get(0));
        return null;
      }
      builder.append('{');
      boolean notFirst = false;
      for (AnnotationValue v : vals) {
        if (notFirst) {
          builder.append(", ");
        }
        notFirst = true;
        visitValue(v);
      }
      builder.append('}');
      return null;
    }

    @Override
    public Void visitEnumConstant(VariableElement c, Void p) {
      builder.append(c.getEnclosingElement()).append('.').append(c.getSimpleName());
      return null;
    }

    @Override
    public Void visitAnnotation(AnnotationMirror a, Void p) {
      builder.append('@').append(a.getAnnotationType());

      Map<? extends ExecutableElement, ? extends AnnotationValue> values = a.getElementValues();
      if (!values.isEmpty()) {
        builder.append('(');
        boolean notFirst = false;
        for (Entry<? extends ExecutableElement, ? extends AnnotationValue> e : values.entrySet()) {
          if (notFirst) {
            builder.append(", ");
          }
          notFirst = true;
          Name name = e.getKey().getSimpleName();
          boolean onlyValue = values.size() == 1 && name.contentEquals(ATTRIBUTE_VALUE);
          if (!onlyValue) {
            builder.append(name).append(" = ");
          }
          printValue(e.getValue());
        }
        builder.append(')');
      }
      return null;
    }

    private void printValue(AnnotationValue value) {
      // doing string comparison here because this class may not be available in Javac JDK7
      if (Compiler.JAVAC.isPresent()
          && "com.sun.tools.javac.code.Attribute.UnresolvedClass".equals(value.getClass().getCanonicalName())) {
        Attribute.UnresolvedClass unresolved = ((Attribute.UnresolvedClass) value);
        String typeString = unresolved.classType.tsym.name.toString();
        builder.append(unresovedImportsResolver.apply(typeString)).append(".class");
      } else {
        visitValue(value);
      }
    }

    private void appendLiteral(float value) {
      if (value == Float.NEGATIVE_INFINITY) {
        appendConstant(Float.class, CONSTANT_NEGATIVE_INFINITY);
      } else if (value == Float.POSITIVE_INFINITY) {
        appendConstant(Float.class, CONSTANT_POSITIVE_INFINITY);
      } else if (value == Float.MAX_VALUE) {
        appendConstant(Float.class, CONSTANT_MAX_VALUE);
      } else if (value == Float.MIN_VALUE) {
        appendConstant(Float.class, CONSTANT_MIN_VALUE);
      } else if (Float.isNaN(value)) {
        appendConstant(Float.class, CONSTANT_NAN);
      } else {
        builder.append(Float.toString(value));
      }
    }

    private void appendConstant(Class<?> type, String accessor) {
      builder.append(type.getCanonicalName()).append(accessor);
    }

    private void appendLiteral(double value) {
      if (value == Double.NEGATIVE_INFINITY) {
        appendConstant(Double.class, CONSTANT_NEGATIVE_INFINITY);
      } else if (value == Double.POSITIVE_INFINITY) {
        appendConstant(Double.class, CONSTANT_POSITIVE_INFINITY);
      } else if (value == Double.MAX_VALUE) {
        appendConstant(Double.class, CONSTANT_MAX_VALUE);
      } else if (value == Double.MIN_VALUE) {
        appendConstant(Double.class, CONSTANT_MIN_VALUE);
      } else if (Double.isNaN(value)) {
        appendConstant(Double.class, CONSTANT_NAN);
      } else {
        builder.append(Double.toString(value));
      }
    }

    private void appendLiteral(long value) {
      if (value == Long.MAX_VALUE) {
        appendConstant(Long.class, CONSTANT_MAX_VALUE);
      } else if (value == Long.MIN_VALUE) {
        appendConstant(Long.class, CONSTANT_MIN_VALUE);
      } else {
        builder.append(Long.toString(value));
      }
    }

    private void appendLiteral(int value) {
      if (value == Integer.MAX_VALUE) {
        appendConstant(Integer.class, CONSTANT_MAX_VALUE);
      } else if (value == Integer.MIN_VALUE) {
        appendConstant(Integer.class, CONSTANT_MIN_VALUE);
      } else {
        builder.append(Integer.toString(value));
      }
    }

    private void appendLiteral(short value) {
      if (value == Short.MAX_VALUE) {
        appendConstant(Short.class, CONSTANT_MAX_VALUE);
      } else if (value == Integer.MIN_VALUE) {
        appendConstant(Short.class, CONSTANT_MIN_VALUE);
      } else {
        builder.append(Short.toString(value));
      }
    }

    private void appendLiteral(byte value) {
      if (value == Byte.MAX_VALUE) {
        appendConstant(Byte.class, CONSTANT_MAX_VALUE);
      } else if (value == Byte.MIN_VALUE) {
        appendConstant(Byte.class, CONSTANT_MIN_VALUE);
      } else {
        builder.append(Integer.toHexString(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.criteria.constraints;

/**
 * Criteria for comparables (like {@code >, <=, >} and ranges).
 */
public class ComparableCriteria<R, V extends Comparable<V>>
        extends ObjectCriteria<R, V> {

  public ComparableCriteria(CriteriaContext<R> context) {
    super(context);
  }

  /**]
   * Checks that attribute is less than (but not equal to) {@code upper}.
   *
Use {@link #isAtMost(Comparable)} for less or equal comparison

   */
  public R isLessThan(V upper) {
    return create(e -> Expressions.call(Operators.LESS_THAN, e, Expressions.literal(upper)));
  }

  /**
   * Checks that attribute is greater than (but not equal to)  {@code lower}.
   *
Use {@link #isAtLeast(Comparable)} for greater or equal comparison

   */
  public R isGreaterThan(V lower) {
    return create(e -> Expressions.call(Operators.GREATER_THAN, e, Expressions.literal(lower)));
  }

  /**
   * Checks that attribute is less than or equal to {@code upperInclusive}.
   */
  public R isAtMost(V upperInclusive) {
    return create(e -> Expressions.call(Operators.LESS_THAN_OR_EQUAL, e, Expressions.literal(upperInclusive)));
  }

  /**
   * Checks that attribute is greater or equal to {@code lowerInclusive}.
   */
  public R isAtLeast(V lowerInclusive) {
    return create(e -> Expressions.call(Operators.GREATER_THAN_OR_EQUAL, e, Expressions.literal(lowerInclusive)));
  }

  public static class Self<V extends Comparable<V>> extends ComparableCriteria<Self<V>, V> {
    public Self(CriteriaContext<ComparableCriteria.Self<V>> context) {
      super(context);
    }
  }

}


/*
   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;

import com.google.common.base.Preconditions;
import com.google.common.util.concurrent.ListeningExecutorService;
import com.google.common.util.concurrent.MoreExecutors;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.TypeAdapterFactory;
import com.mongodb.DB;
import com.mongodb.MongoClient;
import com.mongodb.MongoClientURI;
import com.mongodb.ReadPreference;
import com.mongodb.WriteConcern;
import com.mongodb.client.MongoDatabase;
import org.bson.codecs.configuration.CodecRegistries;
import org.bson.codecs.configuration.CodecRegistry;
import org.immutables.mongo.bson4gson.GsonCodecs;
import org.immutables.mongo.repository.Repositories.Repository;
import org.immutables.mongo.types.TypeAdapters;

import javax.annotation.Nullable;
import javax.annotation.concurrent.NotThreadSafe;
import javax.annotation.concurrent.ThreadSafe;
import java.lang.reflect.Field;
import java.lang.reflect.Member;
import java.util.ServiceLoader;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

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

/**
 * {@link RepositorySetup} combines driver's database, thread-pool and serialization configuration
 * ({@link CodecRegistry}) to configure repositories extended from {@link Repositories.Repository}. Setup can
 * and usually should be shared between repositories which accesses the same database and share
 * other resources.
 * @see Repository
 */
@ThreadSafe
public final class RepositorySetup {

  final ListeningExecutorService executor;
  final CodecRegistry codecRegistry;
  final MongoDatabase database;
  final FieldNamingStrategy fieldNamingStrategy;

  private RepositorySetup(ListeningExecutorService executor, MongoDatabase database,
                          CodecRegistry codecRegistry,
                          FieldNamingStrategy fieldNamingStrategy) {
    this.executor = executor;
    this.database = database;
    this.codecRegistry = codecRegistry;
    this.fieldNamingStrategy = Preconditions.checkNotNull(fieldNamingStrategy, "fieldNamingStrategy");
  }

  /**
   *
Customize how fields are (re)named in mongo queries.
   *
   *
When building criterias (eg. by using {@link org.immutables.mongo.repository.Repositories.Finder}) immutables
   * doesn't have runtime information about final object format in the data-store
   * (serialization is delegated to {@link CodecRegistry}). Therefore, it is important that fields in the
   * query and mongo database match. This interface allows to hook codec logic into query construction so
   * immutables is aware of correct naming.
   *
   *
Example

   *
Below is an example for gson which is
   * using @SerializedName
   * to control member naming.
   *

   *  final Gson gson = ...
   *  FieldNamingStrategy gsonStrategy = new FieldNamingStrategy() {
   *       @Override
   *       public String translateName(Member member) {
   *         return gson.fieldNamingStrategy().translateName((Field) member);
   *       }
   *  };
   * }
   *

   *
   *
Don't forget to employ same codec(s) for POJO serialization and current naming strategy.
   */
  public interface FieldNamingStrategy {

    /**
     * Provide a name for this {@code member} when used in queries.
     *
     * @param member current property (can be field / method) which is being translated
     * @return name of the property when serialized
     */
    String translateName(Member member);

    /**
     * Default implementation which uses same name of the property in queries.
     */
    FieldNamingStrategy DEFAULT = new FieldNamingStrategy() {
      @Override
      public String translateName(Member member) {
        return member.getName();
      }
    };

    /**
     * Uses {@link Gson#fieldNamingStrategy()} to translate names.
     */
    class GsonNamingStrategy implements FieldNamingStrategy {
      private final Gson gson;

      private GsonNamingStrategy(Gson gson) {
        this.gson = Preconditions.checkNotNull(gson, "gson");
      }

      @Override
      public String translateName(Member member) {
        return gson.fieldNamingStrategy().translateName((Field) member);
      }
    }
  }

  /**
   * Builder for {@link RepositorySetup}.
   * @see Builder#database(MongoDatabase)
   * @see Builder#executor(ListeningExecutorService)
   * @return new builder
   */
  public static Builder builder() {
    return new Builder();
  }

  /**
   * Configures and builds unmodifiable instance of {@link RepositorySetup}.
   */
  @NotThreadSafe
  public static class Builder {
    @Nullable
    private ListeningExecutorService executor;

    @Nullable
    private MongoDatabase database;

    @Nullable
    private CodecRegistry codecRegistry;

    @Nullable
    private FieldNamingStrategy fieldNamingStrategy;

    private Builder() {}

    /**
     * Configures repository to use specified executor for all operations.
     * @param executor executor that will be used by repositories.
     * @return {@code this}
     * @see Executors
     * @see MoreExecutors#listeningDecorator(java.util.concurrent.ExecutorService)
     */
    public Builder executor(ListeningExecutorService executor) {
      this.executor = checkNotNull(executor);
      return this;
    }

    /**
     * Configures repository to lookup {@link com.mongodb.client.MongoCollection collection} from the specified
     * {@code database} handle. Repository will inherit {@link WriteConcern} and
     * {@link ReadPreference} settings that was configured on supplied instance.
     * @param database database handle.
     * @return {@code this}
     * @see MongoClient#getDB(String)
     */
    public Builder database(MongoDatabase database) {
      this.database = checkNotNull(database);
      return this;
    }

    /**
     * Configures current repository setup with {@link Gson} serialization.
     *
     *

     * You could use service providers for {@link TypeAdapterFactory} interface to register type
     * adapters. This mechanism, while optional, used a lot in immutables and allows for easy
     * registration if launched in a simple flat classpath.
     *
     *

     * GsonBuilder gsonBuilder = new GsonBuilder();
     * gsonBuilder.registerTypeAdapterFactory(new TypeAdapters()); // BSON specific type adapters
     * ...
     * for (TypeAdapterFactory factory : ServiceLoader.load(TypeAdapterFactory)) {
     *   gsonBuilder.registerTypeAdapterFactory(factory);
     * }
     * Gson gson = gsonBuilder.create();
     *

     *
     * The factory registration shown above is done by default when using
     * {@link RepositorySetup#forUri(String)} to create setup.
     * @param gson configured {@link Gson} instance
     * @see #codecRegistry(CodecRegistry) for other serialization options
     * @return {@code this}
     */
    public Builder gson(Gson gson) {
      checkNotNull(gson, "gson");

      // Will be used as a factory for BSON types (if Gson does not have one). By default, uses
      // TypeAdapter(s) from Gson if they're explicitly defined (not a ReflectiveTypeAdapter).
      // Otherwise delegate to BSON codec.
      TypeAdapterFactory bsonAdapterFactory = GsonCodecs.delegatingTypeAdapterFactory(
              MongoClient.getDefaultCodecRegistry()
      );

      // Appending new TypeAdapterFactory to allow Gson and Bson adapters to co-exists.
      // Depending on the type we may need to use one or another. For instance,
      // Date should be serialized by Gson (even if Bson has codec for it).
      // But ObjectId / Decimal128 by BSON (if Gson doesn't have a type adapter for it).
      // Document or BsonDocument should only be handled by BSON (it's unlikely that users have direct dependency on them in POJOs).
      // So newGson is a way to extend existing Gson instance with "BSON TypeAdapter(s)"
      Gson newGson = gson.newBuilder()
              .registerTypeAdapterFactory(bsonAdapterFactory)
              .create();

      // expose new Gson as CodecRegistry. Using fromRegistries() for caching
      CodecRegistry codecRegistry = CodecRegistries.fromRegistries(GsonCodecs.codecRegistryFromGson(newGson));

      return codecRegistry(codecRegistry, new FieldNamingStrategy.GsonNamingStrategy(gson));
    }

    /**
     * Register custom codec registry to serialize and deserialize entities using
     *  BSON.
     *
     *
Also allows user to register custom naming strategy if POJOs have different property names
     * when serialized (important for correct querying).
     *
     * @param registry existing registry
     * @param fieldNamingStrategy allows to customize property names in queries
     * @return {@code this} same instance of the builder
     * @throws NullPointerException if one of arguments is null
     */
    public Builder codecRegistry(CodecRegistry registry, FieldNamingStrategy fieldNamingStrategy) {
      this.codecRegistry = checkNotNull(registry, "registry");
      this.fieldNamingStrategy = checkNotNull(fieldNamingStrategy, "fieldNamingStrategy");
      return this;
    }

    /**
     * Register custom codec registry to serialize and deserialize entities using
     * BSON.
     *
     *
Please note that if you employ custom property naming (eg {@code @PropertyName} for
     * jackson
     * or {@code @SerializedName} for gson) you need
     * to register {@link FieldNamingStrategy} otherwise mongo queries might not work correctly. Use
     * {@link #codecRegistry(CodecRegistry, FieldNamingStrategy)} for that. For additional information
     * consult documentation on {@link FieldNamingStrategy}.
     *
     * @param registry set of codecs to be used for serialization
     * @return {@code this} same instance of the builder
     * @throws NullPointerException if one of arguments is null
     */
    public Builder codecRegistry(CodecRegistry registry) {
      return codecRegistry(registry, FieldNamingStrategy.DEFAULT);
    }

    /**
     * Builds unmodifiable instance of {@link RepositorySetup}.
     * @return repository setup instance.
     */
    public RepositorySetup build() {
      checkState(executor != null, "executor is not set");
      checkState(database != null, "database is not set");
      checkState(codecRegistry != null, "codecRegistry is not set");
      checkState(fieldNamingStrategy != null, "fieldNamingStrategy is not set");

      return new RepositorySetup(executor, database, codecRegistry, fieldNamingStrategy);
    }
  }

  /**
   * Create setup using MongoDB client uri.
   *

   *
URI should contain database path segment

   *
New internal {@link MongoClient} will be created

   *
New internal executor will be created (with shutdown on jvm exit)

   *
New {@link Gson} instance will be created configured with type adapter factory
   * providers

   *

   *

   * Setup created by this factory methods should be reused to configure collection repositories for
   * the same MongoDB database.
   *

   * This constructor designed for ease of use in sample scenarious. For more flexibility consider
   * using {@link #builder()} with custom constructed {@link ListeningExecutorService
   * executor} and {@link DB database} handle.
   * @param uri string that will be parsed as {@link MongoClientURI}.
   * @see MongoClientURI
   * @return repository setup instance.
   */
  public static RepositorySetup forUri(String uri) {
    MongoClientURI clientUri = new MongoClientURI(uri);
    @Nullable String databaseName = clientUri.getDatabase();
    checkArgument(databaseName != null, "URI should contain database path segment");

    return builder()
        .database(newMongoClient(clientUri).getDatabase(databaseName))
        .executor(newExecutor())
        .gson(createGson())
        .build();
  }

  private static Gson createGson() {
    GsonBuilder gsonBuilder = new GsonBuilder();
    // there are no longer auto-registed from class-path, but from here or if added manually.
    gsonBuilder.registerTypeAdapterFactory(new TypeAdapters());

    for (TypeAdapterFactory factory : ServiceLoader.load(TypeAdapterFactory.class)) {
      gsonBuilder.registerTypeAdapterFactory(factory);
    }
    return gsonBuilder.create();
  }

  private static MongoClient newMongoClient(MongoClientURI clientUri) {
      return new MongoClient(clientUri);
  }

  private static final int DEFAULT_THREAD_POOL_CORE_SIZE = 5;
  private static final int DEFAULT_THREAD_POOL_MAXIMUM_SIZE = 15;
  private static final long DEFAULT_THREAD_POOL_KEEP_ALIVE_MILLIS = TimeUnit.MINUTES.toMillis(1);

  private static final ThreadFactory DEFAULT_THREAD_FACTORY =
      new ThreadFactoryBuilder()
          .setNameFormat(RepositorySetup.class.getPackage().getName() + "-%s")
          .setDaemon(true)
          .build();

  private static ListeningExecutorService newExecutor() {
    return MoreExecutors.listeningDecorator(
        MoreExecutors.getExitingExecutorService(
            new ThreadPoolExecutor(
                DEFAULT_THREAD_POOL_CORE_SIZE,
                DEFAULT_THREAD_POOL_MAXIMUM_SIZE,
                DEFAULT_THREAD_POOL_KEEP_ALIVE_MILLIS,
                TimeUnit.MILLISECONDS,
                new LinkedBlockingQueue<Runnable>(),
                DEFAULT_THREAD_FACTORY)));
  }
}


/*
 * Copyright 2013-2018 the original author or authors.
 *
 * 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.fixture.encoding.defs;

import java.util.*;
import java.util.stream.*;
import org.immutables.encode.Encoding;
import org.immutables.encode.Encoding.StandardNaming;

@Encoding
final class OptionalList2<T> {

  @Encoding.Impl
  private final List<T> list = null;

  public boolean equals(OptionalList2<T> other) {
    return Objects.equals(this.list, other.list);
  }

  @Encoding.Expose
  public Optional<List<T>> get() {
    return Optional.ofNullable(this.list);
  }

  @Override
  public int hashCode() {
    return Objects.hashCode(this.list);
  }

  @Override
  public String toString() {
    return Objects.toString(this.list);
  }

  @Encoding.Of
  static <T> List<T> initFromOptional(Optional<List<T>> optional) {
    Objects.requireNonNull(optional, "optional"); // TODO: Adding this line causes methods to be
// created twice in generated code

    return optional
        .map(elements -> createSafeList(elements)) // TODO: Method reference name mangling doesn't
// happen in generated code
        .orElse(null);
  }

  // TODO: @Encoding.Init duplicate method 'initFromVarargs'. Cannot have more than one init method
  // TODO: Needed to reproduce withX(T...) + withX(Optional<List<T>>)
//    @Encoding.Init
//    static <T> List<T> initFromVarargs(T... elements) {
//        return elements == null ? null : createSafeList(Arrays.asList(elements));
//    }

  // TODO: @Encoding.Init duplicate method 'initFromIterable'. Cannot have more than one init method
  // TODO: Needed to reproduce withX(Iterable<T>) + withX(Optional<List<T>>)
//    @Encoding.Init
//    static <T> List<T> initFromIterable(Iterable<T> iterable) {
//        return elements == null ? null : createSafeList(iterable);
//    }

  private static <T> List<T> createSafeList(Iterable<T> iterable) {
    List<T> list = StreamSupport.stream(iterable.spliterator(), false)
        .map(element -> Objects.requireNonNull(element, "element"))
        .collect(Collectors.toList());

    return Collections.unmodifiableList(list);
  }

  @Encoding.Builder
  static final class Builder<T> {

    private List<T> list;

    @Encoding.Naming(standard = StandardNaming.ADD_ALL)
    @Encoding.Init
    void addAll(Iterable<T> iterable) {
      Objects.requireNonNull(iterable, "iterable");

      if (this.list == null) {
        this.list = new ArrayList<>();
      }

      StreamSupport.stream(iterable.spliterator(), false)
          .map(element -> Objects.requireNonNull(element, "element"))
          .forEach(element -> this.list.add(element));
    }

    @Encoding.Naming(standard = StandardNaming.ADD)
    @Encoding.Init
    void addSingle(T element) {
      if (this.list == null) {
        this.list = new ArrayList<>();
      }

      this.list.add(Objects.requireNonNull(element, "element"));
    }

    // TODO: Adding this method results in a silent generation failure; the class just doesn't get
// created
    // TODO: Needed to reproduce (addX(T...))
    @Encoding.Naming(standard = StandardNaming.ADD)
    @Encoding.Init
    @SafeVarargs
    final void addVarargs(T... elements) {
      if (this.list == null) {
        this.list = new ArrayList<>();
      }

      Stream.of(elements)
          .forEach(element -> this.list.add(Objects.requireNonNull(element, "element")));
    }

    @Encoding.Build
    List<T> build() {
      return this.list;
    }

    @Encoding.Naming(standard = StandardNaming.INIT)
    @Encoding.Init
    void setFromIterable(Iterable<T> iterable) {
      if (this.list == null) {
        this.list = null;
      } else {
        this.list = StreamSupport.stream(iterable.spliterator(), false)
            .map(element -> Objects.requireNonNull(element, "element"))
            .collect(Collectors.toList());
      }
    }

    @Encoding.Naming(standard = StandardNaming.INIT)
    @Encoding.Init
    @Encoding.Copy
    void setFromOptional(Optional<List<T>> optional) {
      this.list = optional.orElse(null);
    }

  }

}


/*
   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.fixture.encoding.defs;

import java.util.Optional;
import com.google.common.collect.ImmutableMap;
import java.util.Map;
import javax.annotation.Nullable;
import org.immutables.encode.Encoding;

@Encoding
class OptionalMap<K, V> {

  // implemented as nullable map field
  @Encoding.Impl
  private final ImmutableMap<K, V> map = null;

  // Expose (and detect) the field as Optional of Map
  @Encoding.Expose
  public Optional<Map<K, V>> get() {
    return Optional.<Map<K, V>>ofNullable(map);
  }

  // alternatively it can be exposed as Optional of ImmutableMap
  @Encoding.Expose
  public Optional<ImmutableMap<K, V>> getImmutable() {
    return Optional.ofNullable(map);
  }

  // as field is nullable, so null-safe hashcode required
  @Override
  public int hashCode() {
    return java.util.Objects.hashCode(map);
  }

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

  boolean equals(OptionalMap<K, V> obj) {
    return java.util.Objects.equals(this.map, obj.map);
  }

  @Encoding.Of
  static @Nullable <K, V> ImmutableMap<K, V> init(Optional<? extends Map<K, V>> map) {
    return map.isPresent()
        ? ImmutableMap.copyOf(map.get())
        : null;
  }

  @Encoding.Builder
  static class Builder<K, V> {
    private @Nullable ImmutableMap.Builder<K, V> builder = null;

    @Encoding.Naming("put*")
    @Encoding.Init
    void put(K row, V value) {
      nonnullBuilder().put(row, value);
    }

    @Encoding.Naming("putAll*")
    @Encoding.Init
    void putAll(Map<? extends K, ? extends V> entries) {
      nonnullBuilder().putAll(entries);
    }

    @Encoding.Naming("set*")
    @Encoding.Init
    @Encoding.Copy
    void set(Optional<? extends Map<K, V>> entries) {
      this.builder = null;
      if (entries.isPresent()) {
        nonnullBuilder().putAll(entries.get());
      }
    }

    private ImmutableMap.Builder<K, V> nonnullBuilder() {
      return builder == null ? builder = ImmutableMap.<K, V>builder() : builder;
    }

    @Encoding.Build
    @Nullable
    ImmutableMap<K, V> build() {
      return builder != null ? builder.build() : null;
    }
  }
}


/*
   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.generator;

import com.google.common.base.Verify;
import com.google.common.collect.*;
import java.util.*;
import javax.annotation.concurrent.NotThreadSafe;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.TypeParameterElement;
import javax.lang.model.type.*;
import static com.google.common.base.Verify.verify;

@NotThreadSafe
public class TypeHierarchyCollector {
  private final List<TypeElement> extendedClasses = Lists.newArrayList();
  private final Set<TypeElement> implementedInterfaces = Sets.newLinkedHashSet();
  private final Set<String> extendedClassNames = Sets.newLinkedHashSet();
  private final Set<String> implementedInterfaceNames = Sets.newLinkedHashSet();
  protected final Set<String> unresolvedYetArguments = Sets.newHashSet();

  /**
   * overridable stringify.
   * @param input the input
   * @param context the context
   * @return the string
   */
  protected String stringify(DeclaredType input, TypevarContext context) {
    return toTypeElement(input).getQualifiedName().toString();
  }

  public final class TypevarContext {
    public final List<String> parameters;
    public final List<String> arguments;

    TypevarContext(TypeElement element, String renderedTypeString) {
      List<? extends TypeParameterElement> typeParameters = element.getTypeParameters();
      if (!typeParameters.isEmpty()) {
        this.arguments = SourceTypes.extract(renderedTypeString).getValue();

        this.parameters = Lists.newArrayList();
        for (TypeParameterElement p : typeParameters) {
          parameters.add(p.getSimpleName().toString());
        }
        // we allow having no arguments in a string as raw type/unspecified argument scenario
        Verify.verify(arguments.isEmpty() || (parameters.size() == arguments.size()), parameters + " =/> " + arguments);
      } else {
        this.parameters = Collections.emptyList();
        this.arguments = Collections.emptyList();
      }
    }
  }

  public void collectFrom(TypeMirror typeMirror) {
    if (typeMirror.getKind() == TypeKind.DECLARED) {
      collectHierarchyMirrors(typeMirror, typeMirror.toString());
    }
  }

  private DeclaredType toDeclaredType(TypeMirror typeMirror) {
    verify(typeMirror.getKind() == TypeKind.DECLARED || typeMirror.getKind() == TypeKind.ERROR);
    return (DeclaredType) typeMirror;
  }

  public ImmutableList<TypeElement> extendedClasses() {
    return ImmutableList.copyOf(extendedClasses);
  }

  public ImmutableSet<TypeElement> implementedInterfaces() {
    return ImmutableSet.copyOf(implementedInterfaces);
  }

  public ImmutableSet<String> unresolvedYetArguments() {
    return ImmutableSet.copyOf(unresolvedYetArguments);
  }

  public ImmutableSet<String> extendedClassNames() {
    return ImmutableSet.copyOf(extendedClassNames);
  }

  public ImmutableSet<String> implementedInterfaceNames() {
    return ImmutableSet.copyOf(implementedInterfaceNames);
  }

  private void collectHierarchyMirrors(TypeMirror typeMirror, String stringRepresentation) {
    if (typeMirror.getKind() != TypeKind.DECLARED
        || typeMirror.toString().equals(Object.class.getName())) {
      return;
    }

    DeclaredType declaredType = toDeclaredType(typeMirror);
    TypeElement e = toTypeElement(declaredType);
    TypevarContext context = new TypevarContext(e, stringRepresentation);

    collectInterfacesMirrors(declaredType, context);

    TypeMirror superclassMirror = e.getSuperclass();
    if (superclassMirror.getKind() != TypeKind.NONE) {
      DeclaredType superclass = toDeclaredType(superclassMirror);
      String stringified = stringify(superclass, context);

      if (!stringified.equals(Object.class.getName())) {
        extendedClasses.add(toTypeElement(superclass));
        extendedClassNames.add(stringified);
        collectHierarchyMirrors(superclass, stringified);
      }
    }

    for (TypeMirror m : e.getInterfaces()) {
      collectUnresolvedInterface(m, context);
      collectInterfacesMirrors(m, context);
    }
  }

  private void collectUnresolvedInterface(TypeMirror typeMirror, TypevarContext context) {
    if (typeMirror.getKind() == TypeKind.ERROR) {
      DeclaredType declaredType = toDeclaredType(typeMirror);
      String stringified = stringify(declaredType, context);
      implementedInterfaceNames.add(stringified);
    }
  }

  private void collectInterfacesMirrors(TypeMirror typeMirror, TypevarContext context) {
    if (typeMirror.getKind() != TypeKind.DECLARED) {
      return;
    }
    DeclaredType declaredType = toDeclaredType(typeMirror);
    TypeElement e = toTypeElement(declaredType);

    if (e.getKind().isInterface()) {
      implementedInterfaces.add(e);
      String stringified = stringify(declaredType, context);
      TypevarContext nestedContext = new TypevarContext(e, stringified);
      implementedInterfaceNames.add(stringified);
      for (TypeMirror m : e.getInterfaces()) {
        collectInterfacesMirrors(m, nestedContext);
      }
    }
  }

  private static TypeElement toTypeElement(DeclaredType input) {
    return (TypeElement) input.asElement();
  }
}


package org.immutables.value.processor.meta;

import org.immutables.value.Value.Immutable;
import org.immutables.value.Value.Style;
import org.immutables.value.Value.Style.BuilderVisibility;

/**
 * API for the template to use.
 */
@Immutable
@Style(stagedBuilder = true, builderVisibility = BuilderVisibility.PACKAGE)
public abstract class AttributeBuilderDescriptor {

  public enum ValueToBuilderTarget {
    /**
     * Would look like {@code ValueObject.Builder builderCopy = valueInstance.toBuilder();}
     */
    VALUE_INSTANCE,
    /**
     * Would look like {@code ValueObject.Builder builderCopy = ValueObject.newBuilderFrom(valueInstance);}
     */
    VALUE_TYPE,
    /**
     * Would look like {@code ValueObject.Builder builderCopy = ValueObject.builder().merge(valueInstance);}
     */
    BUILDER_INSTANCE,
    /**
     * Would look like {@code ValueObject.Builder builderCopy = ValueObject.Builder.from(valueInstance);}
     */
    BUILDER_TYPE,

    /**
     * Would look like {@coode ValueObject.Builder builderCopy = new ValueObject.Builder(valueInstance);}
     */
    BUILDER_CONSTRUCTOR
  }

  public abstract ValueToBuilderTarget getValueToBuilderTarget();

  /**
   * A method name which when invoked will return a builder with all the properties set from
   * the value object.  This method will be invoked based on {@link #getValueToBuilderTarget()}
   *
   * @return method to create a value instance. No () included.
   */
  protected abstract String getValueToBuilderMethod();

  /**
   * Uses {@link #getValueToBuilderTarget()} to determine appropriate format for
   * creating a new builder from a value object.
   *
   * The template needs to still query {@link #isCopyMethodOnValueInstance()} to determine
   * whether to use {@code [expression].[n.getQualifiedValueToBuilderMethod]()} vs
   * {@code [n.getQualifiedValueToBuilderMethod]([expression])}
   *
   * @return method to use for converting a value to a builder.
   */
  public String getQualifiedValueToBuilderMethod() {
    switch (getValueToBuilderTarget()) {

      case VALUE_INSTANCE:
        return getValueToBuilderMethod();
      case VALUE_TYPE:
        return String.format("%s.%s", getQualifiedValueTypeName(), getValueToBuilderMethod());
      case BUILDER_INSTANCE:
        return String
            .format("%s().%s", getQualifiedBuilderConstructorMethod(), getValueToBuilderMethod());
      case BUILDER_TYPE:
        return String.format("%s.%s", getQualifiedBuilderTypeName(), getValueToBuilderMethod());
      case BUILDER_CONSTRUCTOR:
        return getQualifiedBuilderConstructorMethod();
      default:
        throw new UnsupportedOperationException(
            String.format("Could not handle %s", getValueToBuilderTarget()));
    }
  }

  /**
   * Helper for the template.
   *
   * @return if {@link #getValueToBuilderTarget()} ==  {@link ValueToBuilderTarget#VALUE_INSTANCE}
   */
  public boolean isCopyMethodOnValueInstance() {
    return getValueToBuilderTarget() == ValueToBuilderTarget.VALUE_INSTANCE;
  }

  /**
   * A method to be invoked on the builder instance which returns a new value instance.
   *
   * @return a method which constructs a new value instance.
   */
  public abstract String getBuildMethod();

  /**
   * A fully qualified type for the value object.
   *
   * @return fully qualified name of the value type.
   */
  public abstract String getQualifiedValueTypeName();

  /**
   * A fully qualified type for the builder object.
   *
   * @return fully qualified name of the builder type.
   */
  public abstract String getQualifiedBuilderTypeName();

  /**
   * Use to generate helper methods for a specific builder/value in a unique namespace.
   *
   * @return fully qualified name of the value type joined by underscores.
   */
  public String getQualifiedValueTypeNameWithUnderscores() {
    return getQualifiedValueTypeName().replaceAll("\\.", "_");
  }

  /**
   * A fully qualified path and method which creates a new instance of a builder.
   *
   * If the builder is constructed from a no-arg constructor, the {@code new} keyword
   * should be prepended with a space.
   *
   * @return static path which invoked will create a new empty builder. No () included.
   */
  public abstract String getQualifiedBuilderConstructorMethod();
}


/*
   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.gson.stream;

import com.fasterxml.jackson.core.JsonLocation;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonStreamContext;
import com.fasterxml.jackson.databind.util.TokenBuffer;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonToken;
import java.io.IOException;
import java.io.Reader;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Callable;
import javax.annotation.Nullable;
import javax.annotation.concurrent.NotThreadSafe;
import static com.fasterxml.jackson.core.JsonToken.*;

/**
 * {@link JsonReader} impementation backed by Jackson's {@link JsonParser}.
 * Provides measurable JSON parsing improvements over Gson's native implementation.
 * Error reporting might differ, however.
 */
@NotThreadSafe
public class JsonParserReader extends JsonReader implements Callable<JsonParser> {
  private static final Reader UNSUPPORTED_READER = new Reader() {
    @Override
    public int read(char[] buffer, int offset, int count) {
      throw new UnsupportedOperationException();
    }

    @Override
    public void close() {
      throw new UnsupportedOperationException();
    }
  };

  private final JsonParser parser;

  public JsonParserReader(JsonParser parser) {
    super(UNSUPPORTED_READER);
    this.parser = parser;
  }

  public JsonParser getParser() {
    return parser;
  }

  @Nullable
  private com.fasterxml.jackson.core.JsonToken peek;

  private void clearPeek() {
    peek = null;
  }

  private void requirePeek() throws IOException {
    if (peek == null) {
      peek = parser.nextToken();
    }
  }

  @Override
  public void beginArray() throws IOException {
    requirePeek();
    expect(START_ARRAY);
    clearPeek();
  }

  @Override
  public void endArray() throws IOException {
    requirePeek();
    expect(END_ARRAY);
    clearPeek();
  }

  @Override
  public void beginObject() throws IOException {
    requirePeek();
    expect(START_OBJECT);
    clearPeek();
  }

  @Override
  public void endObject() throws IOException {
    requirePeek();
    expect(END_OBJECT);
    clearPeek();
  }

  @Override
  public boolean hasNext() throws IOException {
    requirePeek();
    com.fasterxml.jackson.core.JsonToken token = peek;
    return token != END_OBJECT & token != END_ARRAY;
  }

  @Override
  public JsonToken peek() throws IOException {
    requirePeek();
    return toGsonToken(peek);
  }

  private void expect(com.fasterxml.jackson.core.JsonToken expected) {
    if (peek != expected) {
      throw new IllegalStateException("Expected " + expected + " but was " + peek);
    }
  }

  @Override
  public String nextName() throws IOException {
    requirePeek();
    expect(FIELD_NAME);
    String name = parser.getText();
    clearPeek();
    return name;
  }

  @Override
  public String nextString() throws IOException {
    requirePeek();
    if (!peek.isScalarValue()) {
      throw new IllegalStateException("Expected scalar value for string but was " + peek);
    }
    String value = parser.getText();
    clearPeek();
    return value;
  }

  @Override
  public boolean nextBoolean() throws IOException {
    requirePeek();
    boolean value = parser.getBooleanValue();
    clearPeek();
    return value;
  }

  @Override
  public void nextNull() throws IOException {
    requirePeek();
    expect(VALUE_NULL);
    clearPeek();
  }

  @Override
  public double nextDouble() throws IOException {
    requirePeek();
    double value = parser.getDoubleValue();
    clearPeek();
    return value;

  }

  @Override
  public long nextLong() throws IOException {
    requirePeek();
    long value = parser.getLongValue();
    clearPeek();
    return value;
  }

  @Override
  public int nextInt() throws IOException {
    requirePeek();
    int value = parser.getIntValue();
    clearPeek();
    return value;
  }

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

  @Override
  public void skipValue() throws IOException {
    requirePeek();
    parser.skipChildren();
    clearPeek();
  }

  public void promoteNameToValue() {
    throw new UnsupportedOperationException();
  }

  private static JsonToken toGsonToken(com.fasterxml.jackson.core.JsonToken token) {
    switch (token) {
    case START_ARRAY:
      return JsonToken.BEGIN_ARRAY;
    case END_ARRAY:
      return JsonToken.END_ARRAY;
    case START_OBJECT:
      return JsonToken.BEGIN_OBJECT;
    case END_OBJECT:
      return JsonToken.END_OBJECT;
    case FIELD_NAME:
      return JsonToken.NAME;
    case VALUE_FALSE:
      return JsonToken.BOOLEAN;
    case VALUE_TRUE:
      return JsonToken.BOOLEAN;
    case VALUE_NULL:
      return JsonToken.NULL;
    case VALUE_NUMBER_INT:
      return JsonToken.NUMBER;
    case VALUE_NUMBER_FLOAT:
      return JsonToken.NUMBER;
    case VALUE_STRING:
    case VALUE_EMBEDDED_OBJECT:
      return JsonToken.STRING;
    default: // Not semantically equivalent
      return JsonToken.NULL;
    }
  }

  /**
   * Implements {@link Callable} mostly as a marker interface.
   * Better use {@link #getParser()} to get parser.
   * @return unwrapped {@link JsonParser}
   */
  @Override
  public JsonParser call() throws Exception {
    return parser;
  }

  /**
   * Reads current value including objects and array as effiecient token buffer.
   * Use of Jackson's own mechanisms is important to preserve custom elements
   * such as special embedded objects in BSON or other data formats.
   * @return {@link TokenBuffer}
   * @throws IOException if error occured
   */
  public final TokenBuffer nextTokenBuffer() throws IOException {
    TokenBuffer buffer = new TokenBuffer(parser);
    // if token is consumed, but undelying parser is still sitting on this token, we move forward
    requirePeek();
    buffer.copyCurrentStructure(parser);
    // when we will return to reading from reader, state will be cleared and nextToken after
    clearPeek();
    return buffer;
  }

  protected final void consumePeek() {
    clearPeek();
  }

  @Override
  public String getPath() {
    return toJsonPath(parser.getParsingContext());
  }

  @Override
  public String toString() {
    return getClass().getSimpleName() + "(" + parser + ")";
  }

  public String[] getLocationInfo() {
    return new String[] {
        "path " + getPath(),
        "token " + getTokenString(),
        "at " + getLocationString()
    };
  }

  private String getTokenString() {
    if (parser.getCurrentToken() != null) {
      try {
        return "'" + parser.getText() + "'";
      } catch (Exception ex) {
        return "?";
      }
    }
    return "";
  }

  private String getLocationString() {
    JsonLocation l = parser.getCurrentLocation();
    List<String> parts = new ArrayList<>(4);
    parts.add("line: " + l.getLineNr());
    parts.add("column: " + l.getColumnNr());
    if (l.getByteOffset() >= 0) {
      parts.add("byte offset: " + l.getByteOffset());
    }
    return parts.toString();
  }

  static String toJsonPath(JsonStreamContext context) {
    StringBuilder builder = new StringBuilder();
    for (JsonStreamContext c = context; c != null; c = c.getParent()) {
      if (c.inArray()) {
        builder.insert(0, "[" + c.getCurrentIndex() + "]");
      } else if (c.inObject()) {
        @Nullable String name = c.getCurrentName();
        if (name == null || name.isEmpty()) {
          builder.insert(0, "[]");
        } else if (isAsciiIdentifierPath(name)) {
          builder.insert(0, "." + name);
        } else {
          builder.insert(0, "['" + name + "']");
        }
      } else if (c.inRoot()) {
        builder.insert(0, "$");
      }
    }
    return builder.toString();
  }

  private static boolean isAsciiIdentifierPath(String name) {
    for (int i = 0; i < name.length(); i++) {
      char c = name.charAt(i);
      if ((c != '_')
          && (c < 'A' || c > 'Z')
          && (c < 'a' || c > 'z')
          && (c < '0' || c > '9' || i == 0)) {
        return false;
      }
    }
    return true;
  }
}


// Licensed to the Software Freedom Conservancy (SFC) under one
// or more contributor license agreements.  See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership.  The SFC licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License.  You may obtain a copy of the License at
//
//   http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied.  See the License for the
// specific language governing permissions and limitations
// under the License.

package org.openqa.selenium.htmlunit;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

import org.openqa.selenium.Alert;
import org.openqa.selenium.ElementNotInteractableException;
import org.openqa.selenium.NoAlertPresentException;

import com.gargoylesoftware.htmlunit.Page;
import com.gargoylesoftware.htmlunit.WebClient;
import com.gargoylesoftware.htmlunit.WebWindow;

/**
 * Implementation of {@link Alert}.
 */
public class HtmlUnitAlert implements Alert {

  HtmlUnitDriver driver;
  private AlertHolder holder_;
  private boolean quitting_;
  private Lock lock = new ReentrantLock();
  private Condition condition = lock.newCondition();
  private WebWindow webWindow_;

  HtmlUnitAlert(HtmlUnitDriver driver) {
    this.driver = driver;
    WebClient webClient = driver.getWebClient();
    webClient.setAlertHandler(this::alertHandler);
    webClient.setPromptHandler(this::promptHandler);
    webClient.setConfirmHandler(this::confirmHandler);
    webClient.setOnbeforeunloadHandler(this::onbeforeunloadHandler);
  }

  private void alertHandler(Page page, String message) {
    if (quitting_) {
      return;
    }
    webWindow_ = page.getEnclosingWindow();
    holder_ = new AlertHolder(message);
    awaitCondition();
  }

  private boolean confirmHandler(Page page, String message) {
    if (quitting_) {
      return false;
    }
    webWindow_ = page.getEnclosingWindow();
    holder_ = new AlertHolder(message);
    AlertHolder localHolder = holder_;
    awaitCondition();
    return localHolder.isAccepted();
  }

  private void awaitCondition() {
    lock.lock();
    try {
      if (driver.isProcessAlert()) {
        try {
          condition.await(5, TimeUnit.SECONDS);
        }
        catch (InterruptedException e) {
          throw new RuntimeException(e);
        }
      }
    }
    finally {
      lock.unlock();
    }
  }

  private String promptHandler(Page page, String message, String defaultMessage) {
    if (quitting_) {
      return null;
    }
    webWindow_ = page.getEnclosingWindow();
    holder_ = new PromptHolder(message, defaultMessage);
    PromptHolder localHolder = (PromptHolder) holder_;
    awaitCondition();
    return localHolder.value;
  }

  private boolean onbeforeunloadHandler(Page page, String returnValue) {
    if (quitting_) {
      return true;
    }
    webWindow_ = page.getEnclosingWindow();
    holder_ = new AlertHolder(returnValue);
    AlertHolder localHolder = holder_;
    awaitCondition();
    return localHolder.isAccepted();
  }

  WebWindow getWebWindow() {
    return webWindow_;
  }

  public void setAutoAccept(boolean autoAccept) {
    this.quitting_ = autoAccept;
  }

  @Override
  public void dismiss() {
    lock.lock();
    condition.signal();
    holder_ = null;
    lock.unlock();
  }

  @Override
  public void accept() {
    lock.lock();
    holder_.accept();
    condition.signal();
    holder_ = null;
    lock.unlock();
  }

  @Override
  public String getText() {
    if (holder_ == null) {
      throw new NoAlertPresentException();
    }
    return holder_.message;
  }

  @Override
  public void sendKeys(String keysToSend) {
    holder_.sendKeys(keysToSend);
  }

  /**
   * Closes the current window.
   */
  void close() {
    lock.lock();
    condition.signal();
    setAutoAccept(true);
    lock.unlock();
    holder_ = null;
  }

  boolean isLocked() {
    return holder_ != null;
  }

  private static class AlertHolder {
    String message;
    boolean accepted;

    AlertHolder(String message) {
      this.message = message;
    }

    void sendKeys(String keysToSend) {
      if (keysToSend != null) {
        throw new ElementNotInteractableException("alert is not interactable");
      }
      throw new IllegalArgumentException();
    }

    void accept() {
      accepted = true;
    }

    boolean isAccepted() {
      return accepted;
    }
  }

  private static class PromptHolder extends AlertHolder {

    String defaultMessage;
    String value;

    public PromptHolder(String message, String defaultMessage) {
      super(message);
      this.defaultMessage = defaultMessage;
    }

    @Override
    void sendKeys(String keysToSend) {
      if (keysToSend == null) {
        keysToSend = defaultMessage;
      }
      this.value = keysToSend;
    }

    @Override
    void accept() {
      if (value == null) {
        value = defaultMessage;
      }
    }
  }

}


// Licensed to the Software Freedom Conservancy (SFC) under one
// or more contributor license agreements.  See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership.  The SFC licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License.  You may obtain a copy of the License at
//
//   http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied.  See the License for the
// specific language governing permissions and limitations
// under the License.

package org.openqa.selenium.htmlunit;

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;

import org.openqa.selenium.ScriptTimeoutException;
import org.openqa.selenium.UnhandledAlertException;
import org.openqa.selenium.WebDriverException;

import com.gargoylesoftware.htmlunit.ScriptException;
import com.gargoylesoftware.htmlunit.ScriptResult;
import com.gargoylesoftware.htmlunit.html.HtmlPage;

import net.sourceforge.htmlunit.corejs.javascript.Function;
import net.sourceforge.htmlunit.corejs.javascript.NativeJavaObject;

/**
 * Injects an asynchronous script into the current page for execution. The script should signal that
 * it is finished by invoking the callback function, which will always be the last argument passed
 * to the injected script.
 */
class AsyncScriptExecutor {

  private final HtmlPage page;
  private final long timeoutMillis;
  private AsyncScriptResult asyncResult;

  /**
   * Prepares a new asynchronous script for execution.
   *
   * @param page The page to inject the script into.
   * @param timeoutMillis How long to wait for the script to complete, in milliseconds.
   */
  AsyncScriptExecutor(HtmlPage page, long timeoutMillis) {
    this.page = page;
    this.timeoutMillis = timeoutMillis;
  }

  void alertTriggered(String message) {
    asyncResult.alert(message);
  }

  /**
   * Injects an asynchronous script for execution and waits for its result.
   *
   * @param scriptBody The script body.
   * @param parameters The script parameters, which can be referenced using the {@code arguments}
   *        JavaScript object.
   * @return The script result.
   */
  public Object execute(String scriptBody, Object[] parameters) {
    try {
      asyncResult = new AsyncScriptResult();
      Function function = createInjectedScriptFunction(scriptBody, asyncResult);

      try {
        page.executeJavaScriptFunction(function, function, parameters,
            page.getDocumentElement());
      } catch (ScriptException e) {
        throw new WebDriverException(e);
      }

      try {
        return asyncResult.waitForResult(timeoutMillis);
      } catch (InterruptedException e) {
        throw new WebDriverException(e);
      }
    }
    finally {
      asyncResult = null;
    }
  }

  private Function createInjectedScriptFunction(String userScript, AsyncScriptResult asyncResult) {
    String script =
        "function() {" +
            "  var self = this, timeoutId;" +
            "  var cleanUp = function() {" +
            "    window.clearTimeout(timeoutId);" +
            "    if (window.detachEvent) {" +
            "      window.detachEvent('onunload', catchUnload);" +
            "    } else {" +
            "      window.removeEventListener('unload', catchUnload, false);" +
            "    }" +
            "  };" +
            "  var self = this, timeoutId, catchUnload = function() {" +
            "    cleanUp();" +
            "    self.host.unload();" +
            "  };" +
            // Convert arguments into an actual array, then add the callback object.
            "  arguments = Array.prototype.slice.call(arguments, 0);" +
            "  arguments.push(function(value) {" +
            "    cleanUp();" +
            "    self.host.callback(typeof value == 'undefined' ? null : value);" +
            "  });" +
            // Add an event listener to trap unload events; page loads are not supported with async
            // script execution.
            "  if (window.attachEvent) {" +
            "    window.attachEvent('onunload', catchUnload);" +
            "  } else {" +
            "    window.addEventListener('unload', catchUnload, false);" +
            "  }" +
            // Execute the user's script
            "  (function() {" + userScript + "}).apply(null, arguments);" +
            // Register our timeout for the script. If the script invokes the callback immediately
            // (e.g. it's not really async), then this will still fire. That's OK because the host
            // object should ignore the extra timeout.
            "  timeoutId = window.setTimeout(function() {" +
            "    self.host.timeout();" +
            "  }, " + timeoutMillis + ");" +
            "}";

    // Compile our script.
    ScriptResult result = page.executeJavaScript(script);
    Function function = (Function) result.getJavaScriptResult();

    // Finally, update the script with the callback host object.
    function.put("host", function, new NativeJavaObject(function, asyncResult, null));

    return function;
  }

  /**
   * Host object used to capture the result of an asynchronous script.
   *
   *

   * This class has public visibility so it can be correctly wrapped in a {@link NativeJavaObject}.
   *
   * @see AsyncScriptExecutor
   */
  public static class AsyncScriptResult {

    private final CountDownLatch latch = new CountDownLatch(1);

    private volatile Object value;
    private volatile boolean isTimeout;
    private volatile String alertMessage;
    private volatile boolean unloadDetected;

    /**
     * Waits for the script to signal it is done by calling {@link #callback(Object) callback}.
     *
     * @return The script result.
     * @throws InterruptedException If this thread is interrupted before a result is ready.
     */
    Object waitForResult(long timeoutMillis) throws InterruptedException {
      long startTimeNanos = System.nanoTime();
      latch.await();
      if (isTimeout) {
        long elapsedTimeNanos = System.nanoTime() - startTimeNanos;
        long elapsedTimeMillis = TimeUnit.NANOSECONDS.toMillis(elapsedTimeNanos);
        throw new ScriptTimeoutException(
            "Timed out waiting for async script result after " + elapsedTimeMillis + "ms");
      }
      if (alertMessage != null) {
        throw new UnhandledAlertException("Alert found", alertMessage);
      }

      if (unloadDetected) {
        throw new WebDriverException(
            "Detected a page unload event; executeAsyncScript does not work across page loads");
      }
      return value;
    }

    /**
     * Callback function to be exposed in JavaScript.
     *
     *

     * This method has public visibility for Rhino and should never be called by code outside of
     * Rhino.
     *
     * @param callbackValue The asynchronous script result.
     */
    public void callback(Object callbackValue) {
      if (latch.getCount() > 0) {
        this.value = callbackValue;
        latch.countDown();
      }
    }

    /**
     * Function exposed in JavaScript to signal a timeout. Has no effect if called after the
     * {@link #callback(Object) callback} function.
     *
     *

     * This method has public visibility for Rhino and should never be called by code outside of
     * Rhino.
     */
    public void timeout() {
      if (latch.getCount() > 0) {
        isTimeout = true;
        latch.countDown();
      }
    }

    /**
     * Function to signal an alert.
     */
    private void alert(String message) {
      if (latch.getCount() > 0) {
        this.alertMessage = message;
        latch.countDown();
      }
    }

    /**
     * Function exposed to JavaScript to signal that a page unload event was fired. WebDriver's
     * asynchronous script execution model does not permit new page loads.
     *
     *

     * This method has public visibility for Rhino and should never be called by code outside of
     * Rhino.
     */
    public void unload() {
      if (latch.getCount() > 0) {
        unloadDetected = true;
        latch.countDown();
      }
    }
  }
}


// Licensed to the Software Freedom Conservancy (SFC) under one
// or more contributor license agreements.  See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership.  The SFC licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License.  You may obtain a copy of the License at
//
//   http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied.  See the License for the
// specific language governing permissions and limitations
// under the License.

package org.openqa.selenium.htmlunit.logging;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.logging.Level;

import org.openqa.selenium.logging.LogEntries;
import org.openqa.selenium.logging.LogEntry;
import org.openqa.selenium.logging.LogType;
import org.openqa.selenium.logging.Logs;

import com.gargoylesoftware.htmlunit.WebClient;
import com.gargoylesoftware.htmlunit.WebConsole.Logger;

/**
 * An implementation of the {@link Logs} interface for HtmlUnit.
 * At the moment this is empty.
 */
public class HtmlUnitLogs implements Logs {
    final HtmlUnitDriverLogger logger;

    public HtmlUnitLogs(WebClient webClient) {
        logger = new HtmlUnitDriverLogger();
        webClient.getWebConsole().setLogger(logger);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public LogEntries get(String logType) {
        if (logType == LogType.BROWSER) {
            return new LogEntries(logger.getContentAndFlush());
        }

        return new LogEntries(Collections.emptyList());
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Set<String> getAvailableLogTypes() {
        return Collections.EMPTY_SET;
    }

    private static class HtmlUnitDriverLogger implements Logger {
        private static final int BUFFER_SIZE = 1000;

        private LogEntry[] buffer = new LogEntry[BUFFER_SIZE];
        private int insertPos = 0;
        private boolean isFull = false;

        private void append(LogEntry entry) {
            buffer[insertPos] = entry;
            insertPos++;
            if (insertPos == BUFFER_SIZE) {
                insertPos = 0;
                isFull = true;
            }
        }

        private List<LogEntry> getContentAndFlush() {
            List<LogEntry> result;
            if (isFull) {
                result = new ArrayList<>(BUFFER_SIZE);
                int i = insertPos;
                for (; i < BUFFER_SIZE; i++) {
                    result.add(buffer[i]);
                }
            }
            else {
                result = new ArrayList<>(insertPos);
            }

            for (int i = 0; i < insertPos; i++) {
                result.add(buffer[i]);
            }

            insertPos = 0;
            isFull = false;

            return result;
        }

        @Override
        public void warn(final Object message) {
            append(new LogEntry(Level.WARNING, System.currentTimeMillis(), message == null ? "" : message.toString()));
        }

        @Override
        public void trace(final Object message) {
            append(new LogEntry(Level.FINEST, System.currentTimeMillis(), message == null ? "" : message.toString()));
        }

        @Override
        public void info(final Object message) {
            append(new LogEntry(Level.INFO, System.currentTimeMillis(), message == null ? "" : message.toString()));
        }

        @Override
        public void error(final Object message) {
            append(new LogEntry(Level.SEVERE, System.currentTimeMillis(), message == null ? "" : message.toString()));
        }

        @Override
        public void debug(final Object message) {
            append(new LogEntry(Level.FINE, System.currentTimeMillis(), message == null ? "" : message.toString()));
        }

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

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

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

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

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


// Licensed to the Software Freedom Conservancy (SFC) under one
// or more contributor license agreements.  See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership.  The SFC licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License.  You may obtain a copy of the License at
//
//   http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied.  See the License for the
// specific language governing permissions and limitations
// under the License.

package org.openqa.selenium.htmlunit;

import java.util.HashMap;
import java.util.Map;

import org.openqa.selenium.Keys;

import com.gargoylesoftware.htmlunit.javascript.host.event.KeyboardEvent;

/**
 * Maps {@link Keys} to their {@link KeyboardEvent} constant.
 */
public class HtmlUnitKeyboardMapping {

  private static final Map<Character, Integer> specialKeysMap = new HashMap<>();

  static {
    addMapping(Keys.CANCEL, KeyboardEvent.DOM_VK_CANCEL);
    addMapping(Keys.HELP, KeyboardEvent.DOM_VK_HELP);
    addMapping(Keys.BACK_SPACE, KeyboardEvent.DOM_VK_BACK_SPACE);
    addMapping(Keys.TAB, KeyboardEvent.DOM_VK_TAB);
    addMapping(Keys.CLEAR, KeyboardEvent.DOM_VK_CLEAR);
    addMapping(Keys.RETURN, KeyboardEvent.DOM_VK_RETURN);

    // No match for Keys.ENTER
    addMapping(Keys.ENTER, KeyboardEvent.DOM_VK_RETURN);
    addMapping(Keys.SHIFT, KeyboardEvent.DOM_VK_SHIFT);
    // No match for Keys.LEFT_SHIFT
    addMapping(Keys.LEFT_SHIFT, KeyboardEvent.DOM_VK_SHIFT);
    addMapping(Keys.CONTROL, KeyboardEvent.DOM_VK_CONTROL);
    // No match for Keys.LEFT_CONTROL
    addMapping(Keys.LEFT_CONTROL, KeyboardEvent.DOM_VK_CONTROL);
    addMapping(Keys.ALT, KeyboardEvent.DOM_VK_ALT);
    // No match for Keys.LEFT_ALT
    addMapping(Keys.LEFT_ALT, KeyboardEvent.DOM_VK_ALT);
    addMapping(Keys.PAUSE, KeyboardEvent.DOM_VK_PAUSE);
    addMapping(Keys.ESCAPE, KeyboardEvent.DOM_VK_ESCAPE);
    addMapping(Keys.SPACE, KeyboardEvent.DOM_VK_SPACE);
    addMapping(Keys.PAGE_UP, KeyboardEvent.DOM_VK_PAGE_UP);
    addMapping(Keys.PAGE_DOWN, KeyboardEvent.DOM_VK_PAGE_DOWN);
    addMapping(Keys.END, KeyboardEvent.DOM_VK_END);
    addMapping(Keys.HOME, KeyboardEvent.DOM_VK_HOME);
    addMapping(Keys.LEFT, KeyboardEvent.DOM_VK_LEFT);
    addMapping(Keys.ARROW_LEFT, KeyboardEvent.DOM_VK_LEFT);
    addMapping(Keys.UP, KeyboardEvent.DOM_VK_UP);
    addMapping(Keys.ARROW_UP, KeyboardEvent.DOM_VK_UP);
    addMapping(Keys.RIGHT, KeyboardEvent.DOM_VK_RIGHT);
    addMapping(Keys.ARROW_RIGHT, KeyboardEvent.DOM_VK_RIGHT);
    addMapping(Keys.DOWN, KeyboardEvent.DOM_VK_CLEAR);
    addMapping(Keys.ARROW_DOWN, KeyboardEvent.DOM_VK_DOWN);
    addMapping(Keys.INSERT, KeyboardEvent.DOM_VK_INSERT);
    addMapping(Keys.DELETE, KeyboardEvent.DOM_VK_DELETE);
    addMapping(Keys.SEMICOLON, KeyboardEvent.DOM_VK_SEMICOLON);
    addMapping(Keys.EQUALS, KeyboardEvent.DOM_VK_EQUALS);
    addMapping(Keys.NUMPAD0, KeyboardEvent.DOM_VK_NUMPAD0);
    addMapping(Keys.NUMPAD1, KeyboardEvent.DOM_VK_NUMPAD1);
    addMapping(Keys.NUMPAD2, KeyboardEvent.DOM_VK_NUMPAD2);
    addMapping(Keys.NUMPAD3, KeyboardEvent.DOM_VK_NUMPAD3);
    addMapping(Keys.NUMPAD4, KeyboardEvent.DOM_VK_NUMPAD4);
    addMapping(Keys.NUMPAD5, KeyboardEvent.DOM_VK_NUMPAD5);
    addMapping(Keys.NUMPAD6, KeyboardEvent.DOM_VK_NUMPAD6);
    addMapping(Keys.NUMPAD7, KeyboardEvent.DOM_VK_NUMPAD7);
    addMapping(Keys.NUMPAD8, KeyboardEvent.DOM_VK_NUMPAD8);
    addMapping(Keys.NUMPAD9, KeyboardEvent.DOM_VK_NUMPAD9);
    addMapping(Keys.MULTIPLY, KeyboardEvent.DOM_VK_MULTIPLY);
    addMapping(Keys.ADD, KeyboardEvent.DOM_VK_ADD);
    addMapping(Keys.SEPARATOR, KeyboardEvent.DOM_VK_SEPARATOR);
    addMapping(Keys.SUBTRACT, KeyboardEvent.DOM_VK_SUBTRACT);
    addMapping(Keys.DECIMAL, KeyboardEvent.DOM_VK_DECIMAL);
    addMapping(Keys.DIVIDE, KeyboardEvent.DOM_VK_DIVIDE);
    addMapping(Keys.F1, KeyboardEvent.DOM_VK_F1);
    addMapping(Keys.F2, KeyboardEvent.DOM_VK_F2);
    addMapping(Keys.F3, KeyboardEvent.DOM_VK_F3);
    addMapping(Keys.F4, KeyboardEvent.DOM_VK_F4);
    addMapping(Keys.F5, KeyboardEvent.DOM_VK_F5);
    addMapping(Keys.F6, KeyboardEvent.DOM_VK_F6);
    addMapping(Keys.F7, KeyboardEvent.DOM_VK_F7);
    addMapping(Keys.F8, KeyboardEvent.DOM_VK_F8);
    addMapping(Keys.F9, KeyboardEvent.DOM_VK_F9);
    addMapping(Keys.F10, KeyboardEvent.DOM_VK_F10);
    addMapping(Keys.F11, KeyboardEvent.DOM_VK_F11);
    addMapping(Keys.F12, KeyboardEvent.DOM_VK_F12);
    addMapping(Keys.META, KeyboardEvent.DOM_VK_META);
    // No match for Keys.COMMAND
    // No match for Keys.ZENKAKU_HANKAKU
  }

  private static void addMapping(Keys keys, int value) {
    specialKeysMap.put(keys.charAt(0), value);
  }

  static boolean isSpecialKey(char ch) {
    return ch >= '\uE000' && ch <= '\uF8FF';
  }

  /**
   * Returns the equivalent constant in {@link KeyboardEvent}.
   */
  static int getKeysMapping(char ch) {
    Integer i = specialKeysMap.get(ch);
    if (i == null) {
      return 0;
    }
    return i;
  }
}