/*
 * Copyright 2008-present MongoDB, Inc.
 *
 * 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 com.mongodb;

import com.mongodb.client.model.Collation;
import com.mongodb.lang.Nullable;
import org.bson.codecs.Encoder;

import java.util.List;

/**
 * A builder for a single update request.
 *
 * @mongodb.server.release 2.6
 * @mongodb.driver.manual /reference/command/update
 * @since 2.12
 */
public class BulkUpdateRequestBuilder {
    private final BulkWriteOperation bulkWriteOperation;
    private final DBObject query;
    private final boolean upsert;
    private final Encoder<DBObject> queryCodec;
    private final Encoder<DBObject> replacementCodec;
    private Collation collation;
    private final List<? extends DBObject> arrayFilters;

    BulkUpdateRequestBuilder(final BulkWriteOperation bulkWriteOperation, final DBObject query, final boolean upsert,
                             final Encoder<DBObject> queryCodec, final Encoder<DBObject> replacementCodec,
                             @Nullable final Collation collation, @Nullable final List<? extends DBObject> arrayFilters) {
        this.bulkWriteOperation = bulkWriteOperation;
        this.query = query;
        this.upsert = upsert;
        this.queryCodec = queryCodec;
        this.replacementCodec = replacementCodec;
        this.collation = collation;
        this.arrayFilters = arrayFilters;
    }

    /**
     * Returns the collation
     *
     * @return the collation
     * @since 3.4
     * @mongodb.server.release 3.4
     */
    @Nullable
    public Collation getCollation() {
        return collation;
    }

    /**
     * Sets the collation
     *
     * @param collation the collation
     * @return this
     * @since 3.4
     * @mongodb.server.release 3.4
     */
    @Nullable
    public BulkUpdateRequestBuilder collation(final Collation collation) {
        this.collation = collation;
        return this;
    }

    /**
     * Gets the array filters to apply to the update operation
     * @return the array filters, which may be null
     * @since 3.6
     * @mongodb.server.release 3.6
     */
    @Nullable
    public List<? extends DBObject> getArrayFilters() {
        return arrayFilters;
    }

    /**
     * Adds a request to replace one document in the collection that matches the query with which this builder was created.
     *
     * @param document the replacement document, which must be structured just as a document you would insert.  It can not contain any
     *                 update operators.
     */
    public void replaceOne(final DBObject document) {
        bulkWriteOperation.addRequest(new ReplaceRequest(query, document, upsert, queryCodec, replacementCodec, collation));
    }

    /**
     * Adds a request to update all documents in the collection that match the query with which this builder was created.
     *
     * @param update the update criteria
     */
    public void update(final DBObject update) {
        bulkWriteOperation.addRequest(new UpdateRequest(query, update, true, upsert, queryCodec, collation, arrayFilters));
    }

    /**
     * Adds a request to update one document in the collection that matches the query with which this builder was created.
     *
     * @param update the update criteria
     */
    public void updateOne(final DBObject update) {
        bulkWriteOperation.addRequest(new UpdateRequest(query, update, false, upsert, queryCodec, collation, arrayFilters));
    }
}


/*
 * Copyright 2008-present MongoDB, Inc.
 *
 * 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 com.mongodb.connection;

import com.mongodb.ServerAddress;
import com.mongodb.annotations.Immutable;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import static com.mongodb.assertions.Assertions.notNull;
import static com.mongodb.connection.ServerDescription.getDefaultMaxDocumentSize;

/**
 * A description of a connection to a MongoDB server.
 *
 * @since 3.0
 */
@Immutable
public class ConnectionDescription {
    private final ConnectionId connectionId;
    private final ServerVersion serverVersion;
    private final int maxWireVersion;
    private final ServerType serverType;
    private final int maxBatchCount;
    private final int maxDocumentSize;
    private final int maxMessageSize;
    private final List<String> compressors;

    private static final int DEFAULT_MAX_MESSAGE_SIZE = 0x2000000;   // 32MB
    private static final int DEFAULT_MAX_WRITE_BATCH_SIZE = 512;

    /**
     * Construct a defaulted connection description instance.
     *
     * @param serverId   the server address
     */
    public ConnectionDescription(final ServerId serverId) {
        this(new ConnectionId(serverId), new ServerVersion(), 0, ServerType.UNKNOWN, DEFAULT_MAX_WRITE_BATCH_SIZE,
             getDefaultMaxDocumentSize(), DEFAULT_MAX_MESSAGE_SIZE, Collections.<String>emptyList());
    }

    /**
     * Construct an instance.
     *
     * @param connectionId    the connection id
     * @param serverVersion   the server version
     * @param serverType      the server type
     * @param maxBatchCount   the max batch count
     * @param maxDocumentSize the max document size in bytes
     * @param maxMessageSize  the max message size in bytes
     * @deprecated Prefer {@link #ConnectionDescription(ConnectionId, ServerVersion, int, ServerType, int, int, int, List)}
     */
    @Deprecated
    public ConnectionDescription(final ConnectionId connectionId, final ServerVersion serverVersion,
                                 final ServerType serverType, final int maxBatchCount, final int maxDocumentSize,
                                 final int maxMessageSize) {

        this(connectionId, serverVersion, 0, serverType, maxBatchCount, maxDocumentSize, maxMessageSize, Collections.<String>emptyList());
    }

    /**
     * Construct an instance.
     *
     * @param connectionId    the connection id
     * @param serverVersion   the server version
     * @param serverType      the server type
     * @param maxBatchCount   the max batch count
     * @param maxDocumentSize the max document size in bytes
     * @param maxMessageSize  the max message size in bytes
     * @param compressors     the available compressors on the connection
     * @since 3.5
     * @deprecated Prefer {@link #ConnectionDescription(ConnectionId, ServerVersion, int, ServerType, int, int, int, List)}
     */
    @Deprecated
    public ConnectionDescription(final ConnectionId connectionId, final ServerVersion serverVersion,
                                 final ServerType serverType, final int maxBatchCount, final int maxDocumentSize,
                                 final int maxMessageSize, final List<String> compressors) {
        this(connectionId, serverVersion, 0, serverType, maxBatchCount, maxDocumentSize, maxMessageSize, compressors);
    }

    /**
     * Construct an instance.
     *
     * @param connectionId    the connection id
     * @param serverVersion   the server version
     * @param maxWireVersion  the max wire version
     * @param serverType      the server type
     * @param maxBatchCount   the max batch count
     * @param maxDocumentSize the max document size in bytes
     * @param maxMessageSize  the max message size in bytes
     * @param compressors     the available compressors on the connection
     * @since 3.10
     * @deprecated
     */
    @Deprecated
    public ConnectionDescription(final ConnectionId connectionId, final ServerVersion serverVersion, final int maxWireVersion,
                                 final ServerType serverType, final int maxBatchCount, final int maxDocumentSize,
                                 final int maxMessageSize, final List<String> compressors) {
        this.connectionId = connectionId;
        this.serverType = serverType;
        this.maxBatchCount = maxBatchCount;
        this.maxDocumentSize = maxDocumentSize;
        this.maxMessageSize = maxMessageSize;
        this.serverVersion = serverVersion;
        this.maxWireVersion = maxWireVersion;
        this.compressors = notNull("compressors", Collections.unmodifiableList(new ArrayList<String>(compressors)));
    }

    /**
     * Construct an instance.
     *
     * @param connectionId    the connection id
     * @param maxWireVersion  the max wire version
     * @param serverType      the server type
     * @param maxBatchCount   the max batch count
     * @param maxDocumentSize the max document size in bytes
     * @param maxMessageSize  the max message size in bytes
     * @param compressors     the available compressors on the connection
     * @since 3.10
     */
    public ConnectionDescription(final ConnectionId connectionId, final int maxWireVersion,
                                 final ServerType serverType, final int maxBatchCount, final int maxDocumentSize,
                                 final int maxMessageSize, final List<String> compressors) {
        this(connectionId, new ServerVersion(), maxWireVersion, serverType, maxBatchCount, maxDocumentSize, maxMessageSize, compressors);
    }

    /**
     * Creates a new connection description with the set connection id
     *
     * @param connectionId the connection id
     * @return the new connection description
     * @since 3.8
     */
    public ConnectionDescription withConnectionId(final ConnectionId connectionId) {
        notNull("connectionId", connectionId);
        return new ConnectionDescription(connectionId, serverVersion, maxWireVersion, serverType, maxBatchCount, maxDocumentSize,
                maxMessageSize, compressors);
    }

    /**
     * Gets the server address.
     *
     * @return the server address
     */
    public ServerAddress getServerAddress() {
        return connectionId.getServerId().getAddress();
    }

    /**
     * Gets the id of the connection. If possible, this id will correlate with the connection id that the server puts in its log messages.
     *
     * @return the connection id
     */
    public ConnectionId getConnectionId() {
        return connectionId;
    }

    /**
     * Gets the version of the server.
     *
     * @return the server version
     * @deprecated Use {@link #getMaxWireVersion()} instead
     */
    @Deprecated
    public ServerVersion getServerVersion() {
        return serverVersion;
    }

    /**
     * The latest version of the wire protocol that this MongoDB server is capable of using to communicate with clients.
     *
     * @return the maximum protocol version supported by this server
     * @since 3.10
     * @mongodb.server.release 2.6
     */
    public int getMaxWireVersion() {
        return maxWireVersion;
    }

    /**
     * Gets the server type.
     *
     * @return the server type
     */
    public ServerType getServerType() {
        return serverType;
    }

    /**
     * Gets the max batch count for bulk write operations.
     *
     * @return the max batch count
     */
    public int getMaxBatchCount() {
        return maxBatchCount;
    }

    /**
     * Gets the max document size in bytes for documents to be stored in collections.
     *
     * @return the max document size in bytes
     */
    public int getMaxDocumentSize() {
        return maxDocumentSize;
    }

    /**
     * Gets the max message size in bytes for wire protocol messages to be sent to the server.
     *
     * @return the max message size in bytes.
     */
    public int getMaxMessageSize() {
        return maxMessageSize;
    }

    /**
     * Gets the compressors supported by this connection.
     *
     * @return the non-null list of compressors supported by this connection
     */
    public List<String> getCompressors() {
        return compressors;
    }

    /**
     * Get the default maximum message size.
     *
     * @return the default maximum message size.
     */
    public static int getDefaultMaxMessageSize() {
        return DEFAULT_MAX_MESSAGE_SIZE;
    }

    /**
     * Get the default maximum write batch size.
     *
     * @return the default maximum write batch size.
     */
    public static int getDefaultMaxWriteBatchSize() {
        return DEFAULT_MAX_WRITE_BATCH_SIZE;
    }

    @Override
    public boolean equals(final Object o) {
        if (this == o) {
            return true;
        }
        if (o == null || getClass() != o.getClass()) {
            return false;
        }

        ConnectionDescription that = (ConnectionDescription) o;

        if (maxBatchCount != that.maxBatchCount) {
            return false;
        }
        if (maxDocumentSize != that.maxDocumentSize) {
            return false;
        }
        if (maxMessageSize != that.maxMessageSize) {
            return false;
        }
        if (!connectionId.equals(that.connectionId)) {
            return false;
        }
        if (serverType != that.serverType) {
            return false;
        }
        if (!serverVersion.equals(that.serverVersion)) {
            return false;
        }
        if (maxWireVersion != that.maxWireVersion) {
            return false;
        }
        if (!compressors.equals(that.compressors)) {
            return false;
        }

        return true;
    }

    @Override
    public int hashCode() {
        int result = connectionId.hashCode();
        result = 31 * result + serverVersion.hashCode();
        result = 31 * result + maxBatchCount;
        result = 31 * result + serverType.hashCode();
        result = 31 * result + maxBatchCount;
        result = 31 * result + maxDocumentSize;
        result = 31 * result + maxMessageSize;
        result = 31 * result + compressors.hashCode();
        return result;
    }

    @Override
    public String toString() {
        return "ConnectionDescription{"
                + "connectionId=" + connectionId
                + ", serverVersion=" + serverVersion
                + ", maxWireVersion=" + maxWireVersion
                + ", serverType=" + serverType
                + ", maxBatchCount=" + maxBatchCount
                + ", maxDocumentSize=" + maxDocumentSize
                + ", maxMessageSize=" + maxMessageSize
                + ", compressors=" + compressors
                + '}';
    }
}


/*
 * Copyright 2008-present MongoDB, Inc.
 *
 * 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 com.mongodb;

import com.mongodb.lang.Nullable;
import org.bson.BsonValue;

/**
 * The result of a successful write operation.  If the write was unacknowledged, then {@code wasAcknowledged} will return false and all
 * other methods will throw {@code MongoUnacknowledgedWriteException}.
 *
 * @see com.mongodb.WriteConcern#UNACKNOWLEDGED
 * @since 3.0
 */
public abstract class WriteConcernResult {

    /**
     * Returns true if the write was acknowledged.
     *
     * @return true if the write was acknowledged
     */
    public abstract boolean wasAcknowledged();

    /**
     * Returns the number of documents affected by the write operation.
     *
     * @return the number of documents affected by the write operation
     * @throws UnsupportedOperationException if the write was unacknowledged.
     */
    public abstract int getCount();

    /**
     * Returns true if the write was an update of an existing document.
     *
     * @return true if the write was an update of an existing document
     * @throws UnsupportedOperationException if the write was unacknowledged.
     */
    public abstract boolean isUpdateOfExisting();

    /**
     * Returns the value of _id if this write resulted in an upsert.
     *
     * @return the value of _id if this write resulted in an upsert.
     * @throws UnsupportedOperationException if the write was unacknowledged.
     */
    @Nullable
    public abstract BsonValue getUpsertedId();

    /**
     * Create an acknowledged WriteConcernResult
     *
     * @param count the count of matched documents
     * @param isUpdateOfExisting whether an existing document was updated
     * @param upsertedId if an upsert resulted in an inserted document, this is the _id of that document.  This may be null
     * @return an acknowledged WriteConcernResult
     */
    public static WriteConcernResult acknowledged(final int count, final boolean isUpdateOfExisting, @Nullable final BsonValue upsertedId) {
        return new WriteConcernResult() {
            @Override
            public boolean wasAcknowledged() {
                return true;
            }

            @Override
            public int getCount() {
                return count;
            }

            @Override
            public boolean isUpdateOfExisting() {
                return isUpdateOfExisting;
            }

            @Override
            @Nullable
            public BsonValue getUpsertedId() {
                return upsertedId;
            }

            @Override
            public boolean equals(final Object o) {
                if (this == o) {
                    return true;
                }
                if (o == null || getClass() != o.getClass()) {
                    return false;
                }

                WriteConcernResult that = (WriteConcernResult) o;
                if (!that.wasAcknowledged()) {
                    return false;
                }

                if (count != that.getCount()) {
                    return false;
                }
                if (isUpdateOfExisting != that.isUpdateOfExisting()) {
                    return false;
                }
                if (upsertedId != null ? !upsertedId.equals(that.getUpsertedId()) : that.getUpsertedId() != null) {
                    return false;
                }

                return true;
            }

            @Override
            public int hashCode() {
                int result = count;
                result = 31 * result + (isUpdateOfExisting ? 1 : 0);
                result = 31 * result + (upsertedId != null ? upsertedId.hashCode() : 0);
                return result;
            }

            @Override
            public String toString() {
                return "AcknowledgedWriteResult{"
                       + "count=" + count
                       + ", isUpdateOfExisting=" + isUpdateOfExisting
                       + ", upsertedId=" + upsertedId
                       + '}';
            }
        };
    }

    /**
     * Create an unacknowledged WriteConcernResult
     *
     * @return an unacknowledged WriteConcernResult
     */
    public static WriteConcernResult unacknowledged() {
        return new WriteConcernResult() {
            @Override
            public boolean wasAcknowledged() {
                return false;
            }

            @Override
            public int getCount() {
                throw getUnacknowledgedWriteException();
            }

            @Override
            public boolean isUpdateOfExisting() {
                throw getUnacknowledgedWriteException();
            }

            @Override
            public BsonValue getUpsertedId() {
                throw getUnacknowledgedWriteException();
            }

            @Override
            public boolean equals(final Object o) {
                if (this == o) {
                    return true;
                }
                if (o == null || getClass() != o.getClass()) {
                    return false;
                }

                WriteConcernResult that = (WriteConcernResult) o;
                return !that.wasAcknowledged();
            }

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

            @Override
            public String toString() {
                return "UnacknowledgedWriteResult{}";
            }

            private UnsupportedOperationException getUnacknowledgedWriteException() {
                return new UnsupportedOperationException("Cannot get information about an unacknowledged write");
            }
        };
    }
}


/*
 * Copyright 2008-present MongoDB, Inc.
 *
 * 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.bson;

import org.bson.codecs.Decoder;
import org.bson.codecs.DecoderContext;
import org.bson.codecs.DocumentCodec;
import org.bson.codecs.Encoder;
import org.bson.codecs.EncoderContext;
import org.bson.codecs.configuration.CodecRegistry;
import org.bson.conversions.Bson;
import org.bson.json.JsonReader;
import org.bson.json.JsonWriter;
import org.bson.json.JsonWriterSettings;
import org.bson.types.ObjectId;

import java.io.Serializable;
import java.io.StringWriter;
import java.util.Collection;
import java.util.Date;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import static java.lang.String.format;
import static org.bson.assertions.Assertions.isTrue;
import static org.bson.assertions.Assertions.notNull;

/**
 * A representation of a document as a {@code Map}.  All iterators will traverse the elements in insertion order, as with {@code
 * LinkedHashMap}.
 *
 * @mongodb.driver.manual core/document document
 * @since 3.0.0
 */
public class Document implements Map<String, Object>, Serializable, Bson {
    private static final long serialVersionUID = 6297731997167536582L;

    private final LinkedHashMap<String, Object> documentAsMap;

    /**
     * Creates an empty Document instance.
     */
    public Document() {
        documentAsMap = new LinkedHashMap<String, Object>();
    }

    /**
     * Create a Document instance initialized with the given key/value pair.
     *
     * @param key   key
     * @param value value
     */
    public Document(final String key, final Object value) {
        documentAsMap = new LinkedHashMap<String, Object>();
        documentAsMap.put(key, value);
    }

    /**
     * Creates a Document instance initialized with the given map.
     *
     * @param map initial map
     */
    public Document(final Map<String, Object> map) {
        documentAsMap = new LinkedHashMap<String, Object>(map);
    }

    /**
     * Parses a string in MongoDB Extended JSON format to a {@code Document}
     *
     * @param json the JSON string
     * @return a corresponding {@code Document} object
     * @see org.bson.json.JsonReader
     * @mongodb.driver.manual reference/mongodb-extended-json/ MongoDB Extended JSON
     */
    public static Document parse(final String json) {
        return parse(json, new DocumentCodec());
    }

    /**
     * Parses a string in MongoDB Extended JSON format to a {@code Document}
     *
     * @param json the JSON string
     * @param decoder the {@code Decoder} to use to parse the JSON string into a {@code Document}
     * @return a corresponding {@code Document} object
     * @see org.bson.json.JsonReader
     * @mongodb.driver.manual reference/mongodb-extended-json/ MongoDB Extended JSON
     */
    public static Document parse(final String json, final Decoder<Document> decoder) {
        notNull("codec", decoder);
        JsonReader bsonReader = new JsonReader(json);
        return decoder.decode(bsonReader, DecoderContext.builder().build());
    }

    @Override
    public <C> BsonDocument toBsonDocument(final Class<C> documentClass, final CodecRegistry codecRegistry) {
        return new BsonDocumentWrapper<Document>(this, codecRegistry.get(Document.class));
    }

    /**
     * Put the given key/value pair into this Document and return this.  Useful for chaining puts in a single expression, e.g.
     *

     * doc.append("a", 1).append("b", 2)}
     *

     * @param key   key
     * @param value value
     * @return this
     */
    public Document append(final String key, final Object value) {
        documentAsMap.put(key, value);
        return this;
    }

    /**
     * Gets the value of the given key, casting it to the given {@code Class<T>}.  This is useful to avoid having casts in client code,
     * though the effect is the same.  So to get the value of a key that is of type String, you would write {@code String name =
     * doc.get("name", String.class)} instead of {@code String name = (String) doc.get("x") }.
     *
     * @param key   the key
     * @param clazz the non-null class to cast the value to
     * @param <T>   the type of the class
     * @return the value of the given key, or null if the instance does not contain this key.
     * @throws ClassCastException if the value of the given key is not of type T
     */
    public <T> T get(final Object key, final Class<T> clazz) {
        notNull("clazz", clazz);
        return clazz.cast(documentAsMap.get(key));
    }

    /**
     * Gets the value of the given key, casting it to {@code Class<T>} or returning the default value if null.
     * This is useful to avoid having casts in client code, though the effect is the same.
     *
     * @param key   the key
     * @param defaultValue what to return if the value is null
     * @param <T>   the type of the class
     * @return the value of the given key, or null if the instance does not contain this key.
     * @throws ClassCastException if the value of the given key is not of type T
     * @since 3.5
     */
    @SuppressWarnings("unchecked")
    public <T> T get(final Object key, final T defaultValue) {
        notNull("defaultValue", defaultValue);
        Object value = documentAsMap.get(key);
        return value == null ? defaultValue : (T) value;
    }

    /**
     * Gets the value in an embedded document, casting it to the given {@code Class<T>}.  The list of keys represents a path to the
     * embedded value, drilling down into an embedded document for each key. This is useful to avoid having casts in
     * client code, though the effect is the same.
     *
     * The generic type of the keys list is {@code ?} to be consistent with the corresponding {@code get} methods, but in practice
     * the actual type of the argument should be {@code List<String>}. So to get the embedded value of a key list that is of type String,
     * you would write {@code String name = doc.getEmbedded(List.of("employee", "manager", "name"), String.class)} instead of
     * {@code String name = (String) doc.get("employee", Document.class).get("manager", Document.class).get("name") }.
     *
     * @param keys  the list of keys
     * @param clazz the non-null class to cast the value to
     * @param <T>   the type of the class
     * @return the value of the given embedded key, or null if the instance does not contain this embedded key.
     * @throws ClassCastException if the value of the given embedded key is not of type T
     * @since 3.10
     */
    public <T> T getEmbedded(final List<?> keys, final Class<T> clazz) {
        notNull("keys", keys);
        isTrue("keys", !keys.isEmpty());
        notNull("clazz", clazz);
        return getEmbeddedValue(keys, clazz, null);
    }

    /**
     * Gets the value in an embedded document, casting it to the given {@code Class<T>} or returning the default value if null.
     * The list of keys represents a path to the embedded value, drilling down into an embedded document for each key.
     * This is useful to avoid having casts in client code, though the effect is the same.
     *
     * The generic type of the keys list is {@code ?} to be consistent with the corresponding {@code get} methods, but in practice
     * the actual type of the argument should be {@code List<String>}. So to get the embedded value of a key list that is of type String,
     * you would write {@code String name = doc.getEmbedded(List.of("employee", "manager", "name"), "John Smith")} instead of
     * {@code String name = doc.get("employee", Document.class).get("manager", Document.class).get("name", "John Smith") }.
     *
     * @param keys  the list of keys
     * @param defaultValue what to return if the value is null
     * @param <T>   the type of the class
     * @return the value of the given key, or null if the instance does not contain this key.
     * @throws ClassCastException if the value of the given key is not of type T
     * @since 3.10
     */
    public <T> T getEmbedded(final List<?> keys, final T defaultValue) {
        notNull("keys", keys);
        isTrue("keys", !keys.isEmpty());
        notNull("defaultValue", defaultValue);
        return getEmbeddedValue(keys, null, defaultValue);
    }

    // Gets the embedded value of the given list of keys, casting it to {@code Class<T>} or returning the default value if null.
    // Throws ClassCastException if any of the intermediate embedded values is not a Document.
    @SuppressWarnings("unchecked")
    private <T> T getEmbeddedValue(final List<?> keys, final Class<T> clazz, final T defaultValue) {
        Object value = this;
        Iterator<?> keyIterator = keys.iterator();
        while (keyIterator.hasNext()) {
            Object key = keyIterator.next();
            value = ((Document) value).get(key);
            if (!(value instanceof Document)) {
                if (value == null) {
                    return defaultValue;
                } else if (keyIterator.hasNext()) {
                    throw new ClassCastException(format("At key %s, the value is not a Document (%s)",
                            key, value.getClass().getName()));
                }
            }
        }
        return clazz != null ? clazz.cast(value) : (T) value;
    }

    /**
     * Gets the value of the given key as an Integer.
     *
     * @param key the key
     * @return the value as an integer, which may be null
     * @throws java.lang.ClassCastException if the value is not an integer
     */
    public Integer getInteger(final Object key) {
        return (Integer) get(key);
    }

    /**
     * Gets the value of the given key as a primitive int.
     *
     * @param key          the key
     * @param defaultValue what to return if the value is null
     * @return the value as an integer, which may be null
     * @throws java.lang.ClassCastException if the value is not an integer
     */
    public int getInteger(final Object key, final int defaultValue) {
        return get(key, defaultValue);
    }

    /**
     * Gets the value of the given key as a Long.
     *
     * @param key the key
     * @return the value as a long, which may be null
     * @throws java.lang.ClassCastException if the value is not an long
     */
    public Long getLong(final Object key) {
        return (Long) get(key);
    }

    /**
     * Gets the value of the given key as a Double.
     *
     * @param key the key
     * @return the value as a double, which may be null
     * @throws java.lang.ClassCastException if the value is not an double
     */
    public Double getDouble(final Object key) {
        return (Double) get(key);
    }

    /**
     * Gets the value of the given key as a String.
     *
     * @param key the key
     * @return the value as a String, which may be null
     * @throws java.lang.ClassCastException if the value is not a String
     */
    public String getString(final Object key) {
        return (String) get(key);
    }

    /**
     * Gets the value of the given key as a Boolean.
     *
     * @param key the key
     * @return the value as a Boolean, which may be null
     * @throws java.lang.ClassCastException if the value is not an boolean
     */
    public Boolean getBoolean(final Object key) {
        return (Boolean) get(key);
    }

    /**
     * Gets the value of the given key as a primitive boolean.
     *
     * @param key          the key
     * @param defaultValue what to return if the value is null
     * @return the value as a primitive boolean
     * @throws java.lang.ClassCastException if the value is not a boolean
     */
    public boolean getBoolean(final Object key, final boolean defaultValue) {
        return get(key, defaultValue);
    }

    /**
     * Gets the value of the given key as an ObjectId.
     *
     * @param key the key
     * @return the value as an ObjectId, which may be null
     * @throws java.lang.ClassCastException if the value is not an ObjectId
     */
    public ObjectId getObjectId(final Object key) {
        return (ObjectId) get(key);
    }

    /**
     * Gets the value of the given key as a Date.
     *
     * @param key the key
     * @return the value as a Date, which may be null
     * @throws java.lang.ClassCastException if the value is not a Date
     */
    public Date getDate(final Object key) {
        return (Date) get(key);
    }

    /**
     * Gets the list value of the given key, casting the list elements to the given {@code Class<T>}.  This is useful to avoid having
     * casts in client code, though the effect is the same.
     *
     * @param key   the key
     * @param clazz the non-null class to cast the list value to
     * @param <T>   the type of the class
     * @return the list value of the given key, or null if the instance does not contain this key.
     * @throws ClassCastException if the elements in the list value of the given key is not of type T or the value is not a list
     * @since 3.10
     */
    public <T> List<T> getList(final Object key, final Class<T> clazz) {
        notNull("clazz", clazz);
        return constructValuesList(key, clazz, null);
    }

    /**
     * Gets the list value of the given key, casting the list elements to {@code Class<T>} or returning the default list value if null.
     * This is useful to avoid having casts in client code, though the effect is the same.
     *
     * @param key   the key
     * @param clazz the non-null class to cast the list value to
     * @param defaultValue what to return if the value is null
     * @param <T>   the type of the class
     * @return the list value of the given key, or the default list value if the instance does not contain this key.
     * @throws ClassCastException if the value of the given key is not of type T
     * @since 3.10
     */
    public <T> List<T> getList(final Object key, final Class<T> clazz, final List<T> defaultValue) {
        notNull("defaultValue", defaultValue);
        notNull("clazz", clazz);
        return constructValuesList(key, clazz, defaultValue);
    }

    // Construct the list of values for the specified key, or return the default value if the value is null.
    // A ClassCastException will be thrown if an element in the list is not of type T.
    @SuppressWarnings("unchecked")
    private <T> List<T> constructValuesList(final Object key, final Class<T> clazz, final List<T> defaultValue) {
        List<?> value = get(key, List.class);
        if (value == null) {
            return defaultValue;
        }

        for (Object item : value) {
            if (!clazz.isAssignableFrom(item.getClass())) {
                throw new ClassCastException(format("List element cannot be cast to %s", clazz.getName()));
            }
        }
        return (List<T>) value;
    }

    /**
     * Gets a JSON representation of this document using the {@link org.bson.json.JsonMode#STRICT} output mode, and otherwise the default
     * settings of {@link JsonWriterSettings.Builder} and {@link DocumentCodec}.
     *
     * @return a JSON representation of this document
     * @throws org.bson.codecs.configuration.CodecConfigurationException if the document contains types not in the default registry
     * @see #toJson(JsonWriterSettings)
     * @see JsonWriterSettings
     */
    @SuppressWarnings("deprecation")
    public String toJson() {
        return toJson(new JsonWriterSettings());
    }

    /**
     * Gets a JSON representation of this document
     *
     *
With the default {@link DocumentCodec}.

     *
     * @param writerSettings the json writer settings to use when encoding
     * @return a JSON representation of this document
     * @throws org.bson.codecs.configuration.CodecConfigurationException if the document contains types not in the default registry
     */
    public String toJson(final JsonWriterSettings writerSettings) {
        return toJson(writerSettings, new DocumentCodec());
    }

    /**
     * Gets a JSON representation of this document
     *
     *
With the default {@link JsonWriterSettings}.

     *
     * @param encoder the document codec instance to use to encode the document
     * @return a JSON representation of this document
     * @throws org.bson.codecs.configuration.CodecConfigurationException if the registry does not contain a codec for the document values.
     */
    @SuppressWarnings("deprecation")
    public String toJson(final Encoder<Document> encoder) {
        return toJson(new JsonWriterSettings(), encoder);
    }

    /**
     * Gets a JSON representation of this document
     *
     * @param writerSettings the json writer settings to use when encoding
     * @param encoder the document codec instance to use to encode the document
     * @return a JSON representation of this document
     * @throws org.bson.codecs.configuration.CodecConfigurationException if the registry does not contain a codec for the document values.
     */
    public String toJson(final JsonWriterSettings writerSettings, final Encoder<Document> encoder) {
        JsonWriter writer = new JsonWriter(new StringWriter(), writerSettings);
        encoder.encode(writer, this, EncoderContext.builder().isEncodingCollectibleDocument(true).build());
        return writer.getWriter().toString();
    }

    // Vanilla Map methods delegate to map field

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

    @Override
    public boolean isEmpty() {
        return documentAsMap.isEmpty();
    }

    @Override
    public boolean containsValue(final Object value) {
        return documentAsMap.containsValue(value);
    }

    @Override
    public boolean containsKey(final Object key) {
        return documentAsMap.containsKey(key);
    }

    @Override
    public Object get(final Object key) {
        return documentAsMap.get(key);
    }

    @Override
    public Object put(final String key, final Object value) {
        return documentAsMap.put(key, value);
    }

    @Override
    public Object remove(final Object key) {
        return documentAsMap.remove(key);
    }

    @Override
    public void putAll(final Map<? extends String, ?> map) {
        documentAsMap.putAll(map);
    }

    @Override
    public void clear() {
        documentAsMap.clear();
    }

    @Override
    public Set<String> keySet() {
        return documentAsMap.keySet();
    }

    @Override
    public Collection<Object> values() {
        return documentAsMap.values();
    }

    @Override
    public Set<Map.Entry<String, Object>> entrySet() {
        return documentAsMap.entrySet();
    }

    @Override
    public boolean equals(final Object o) {
        if (this == o) {
            return true;
        }
        if (o == null || getClass() != o.getClass()) {
            return false;
        }

        Document document = (Document) o;

        if (!documentAsMap.equals(document.documentAsMap)) {
            return false;
        }

        return true;
    }

    @Override
    public int hashCode() {
        return documentAsMap.hashCode();
    }

    @Override
    public String toString() {
        return "Document{"
               + documentAsMap
               + '}';
    }
}


/*
 * Copyright 2008-present MongoDB, Inc.
 *
 * 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 com.mongodb;

import com.mongodb.annotations.Immutable;
import com.mongodb.lang.Nullable;

import static com.mongodb.assertions.Assertions.notNull;

/**
 * Options to apply to transactions. The default values for the options depend on context.  For options specified per-transaction, the
 * default values come from the default transaction options.  For the default transaction options themselves, the default values come from
 * the MongoClient on which the session was started.
 *
 * @see com.mongodb.session.ClientSession
 * @see ClientSessionOptions
 * @since 3.8
 * @mongodb.server.release 4.0
 */
@Immutable
public final class TransactionOptions {
    private final ReadConcern readConcern;
    private final WriteConcern writeConcern;
    private final ReadPreference readPreference;

    /**
     * Gets the read concern.
     *
     * @return the read concern
     */
    @Nullable
    public ReadConcern getReadConcern() {
        return readConcern;
    }

    /**
     * Gets the write concern.
     *
     * @return the write concern
     */
    @Nullable
    public WriteConcern getWriteConcern() {
        return writeConcern;
    }

    /**
     * Gets the read preference.
     *
     * @return the write concern
     */
    @Nullable
    public ReadPreference getReadPreference() {
        return readPreference;
    }

    /**
     * Gets an instance of a builder
     *
     * @return a builder instance
     */
    public static Builder builder() {
        return new Builder();
    }

    /**
     * Merge the two provided transaction options, with the first taking precedence over the second.
     *
     * @param options the transaction options, which take precedence for any property that is non-null
     * @param defaultOptions the default transaction options
     * @return the merged transaction options
     */
    public static TransactionOptions merge(final TransactionOptions options, final TransactionOptions defaultOptions) {
        notNull("options", options);
        notNull("defaultOptions", defaultOptions);
        return TransactionOptions.builder()
                .writeConcern(options.getWriteConcern() == null
                        ? defaultOptions.getWriteConcern() : options.getWriteConcern())
                .readConcern(options.getReadConcern() == null
                        ? defaultOptions.getReadConcern() : options.getReadConcern())
                .readPreference(options.getReadPreference() == null
                        ? defaultOptions.getReadPreference() : options.getReadPreference())
                .build();
    }

    @Override
    public boolean equals(final Object o) {
        if (this == o) {
            return true;
        }
        if (o == null || getClass() != o.getClass()) {
            return false;
        }

        TransactionOptions that = (TransactionOptions) o;

        if (readConcern != null ? !readConcern.equals(that.readConcern) : that.readConcern != null) {
            return false;
        }
        if (writeConcern != null ? !writeConcern.equals(that.writeConcern) : that.writeConcern != null) {
            return false;
        }
        if (readPreference != null ? !readPreference.equals(that.readPreference) : that.readPreference != null) {
            return false;
        }

        return true;
    }

    @Override
    public int hashCode() {
        int result = readConcern != null ? readConcern.hashCode() : 0;
        result = 31 * result + (writeConcern != null ? writeConcern.hashCode() : 0);
        result = 31 * result + (readPreference != null ? readPreference.hashCode() : 0);
        return result;
    }

    @Override
    public String toString() {
        return "TransactionOptions{"
                + "readConcern=" + readConcern
                + ", writeConcern=" + writeConcern
                + ", readPreference=" + readPreference
                + '}';
    }

    /**
     * The builder for transaction options
     */
    public static final class Builder {
        private ReadConcern readConcern;
        private WriteConcern writeConcern;
        private ReadPreference readPreference;

        /**
         * Sets the read concern.
         *
         * @param readConcern the read concern
         * @return this
         */
        public Builder readConcern(@Nullable final ReadConcern readConcern) {
            this.readConcern = readConcern;
            return this;
        }

        /**
         * Sets the write concern.
         *
         * @param writeConcern the write concern, which must be acknowledged
         * @return this
         */
        public Builder writeConcern(@Nullable final WriteConcern writeConcern) {
            this.writeConcern = writeConcern;
            return this;
        }

        /**
         * Sets the read preference.
         *
         * @param readPreference the read preference, which currently must be primary. This restriction may be relaxed in future versions.
         * @return this
         */
        public Builder readPreference(@Nullable final ReadPreference readPreference) {
            this.readPreference = readPreference;
            return this;
        }

        /**
         * Build the transaction options instance.
         *
         * @return The {@code TransactionOptions}
         */
        public TransactionOptions build() {
            return new TransactionOptions(this);
        }

        private Builder() {
        }
    }

    private TransactionOptions(final Builder builder) {
        readConcern = builder.readConcern;
        writeConcern = builder.writeConcern;
        readPreference = builder.readPreference;
    }
}


/*
 * Copyright 2008-present MongoDB, Inc.
 *
 * 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 tour;

import com.mongodb.Block;
import com.mongodb.async.SingleResultCallback;
import com.mongodb.async.client.MongoClient;
import com.mongodb.async.client.MongoClients;
import com.mongodb.async.client.MongoCollection;
import com.mongodb.async.client.MongoDatabase;
import com.mongodb.bulk.BulkWriteResult;
import com.mongodb.client.model.BulkWriteOptions;
import com.mongodb.client.model.DeleteOneModel;
import com.mongodb.client.model.InsertOneModel;
import com.mongodb.client.model.ReplaceOneModel;
import com.mongodb.client.model.UpdateOneModel;
import com.mongodb.client.model.WriteModel;
import com.mongodb.client.result.DeleteResult;
import com.mongodb.client.result.UpdateResult;
import org.bson.Document;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;

import static com.mongodb.client.model.Accumulators.sum;
import static com.mongodb.client.model.Aggregates.group;
import static com.mongodb.client.model.Aggregates.match;
import static com.mongodb.client.model.Aggregates.project;
import static com.mongodb.client.model.Filters.and;
import static com.mongodb.client.model.Filters.eq;
import static com.mongodb.client.model.Filters.exists;
import static com.mongodb.client.model.Filters.gt;
import static com.mongodb.client.model.Filters.gte;
import static com.mongodb.client.model.Filters.lt;
import static com.mongodb.client.model.Filters.lte;
import static com.mongodb.client.model.Projections.excludeId;
import static com.mongodb.client.model.Sorts.descending;
import static com.mongodb.client.model.Updates.inc;
import static com.mongodb.client.model.Updates.set;
import static java.util.Arrays.asList;
import static java.util.Collections.singletonList;

/**
 * The QuickTour code example
 */
public class QuickTour {
    /**
     * Run this main method to see the output of this quick example.
     *
     * @param args takes an optional single argument for the connection string
     * @throws InterruptedException if a latch is interrupted
     */
    public static void main(final String[] args) throws InterruptedException {
        MongoClient mongoClient;

        if (args.length == 0) {
            // connect to the local database server
            mongoClient = MongoClients.create();
        } else {
            mongoClient = MongoClients.create(args[0]);
        }

        // get handle to "mydb" database
        MongoDatabase database = mongoClient.getDatabase("mydb");

        // get a handle to the "test" collection
        final MongoCollection<Document> collection = database.getCollection("test");

        // drop all the data in it
        final CountDownLatch dropLatch = new CountDownLatch(1);
        collection.drop(new SingleResultCallback<Void>() {
            @Override
            public void onResult(final Void result, final Throwable t) {
                dropLatch.countDown();
            }
        });
        dropLatch.await();

        // make a document and insert it
        Document doc = new Document("name", "MongoDB")
                .append("type", "database")
                .append("count", 1)
                .append("info", new Document("x", 203).append("y", 102));

        collection.insertOne(doc, new SingleResultCallback<Void>() {
            @Override
            public void onResult(final Void result, final Throwable t) {
                System.out.println("Inserted!");
            }
        });

        // get it (since it's the only one in there since we dropped the rest earlier on)
        collection.find().first(new SingleResultCallback<Document>() {
            @Override
            public void onResult(final Document document, final Throwable t) {
                System.out.println(document.toJson());
            }
        });

        // now, lets add lots of little documents to the collection so we can explore queries and cursors
        List<Document> documents = new ArrayList<Document>();
        for (int i = 0; i < 100; i++) {
            documents.add(new Document("i", i));
        }

        final CountDownLatch countLatch = new CountDownLatch(1);
        collection.insertMany(documents, new SingleResultCallback<Void>() {
            @Override
            public void onResult(final Void result, final Throwable t) {
                collection.countDocuments(new SingleResultCallback<Long>() {
                    @Override
                    public void onResult(final Long count, final Throwable t) {
                        System.out.println("total # of documents after inserting 100 small ones (should be 101) " + count);
                        countLatch.countDown();
                    }
                });
            }
        });
        countLatch.await();

        // find first
        SingleResultCallback<Document> printDocument = new SingleResultCallback<Document>() {
            @Override
            public void onResult(final Document document, final Throwable t) {
                System.out.println(document.toJson());
            }
        };
        collection.find().first(printDocument);

        // lets get all the documents in the collection and print them out
        Block<Document> printDocumentBlock = new Block<Document>() {
            @Override
            public void apply(final Document document) {
                System.out.println(document.toJson());
            }
        };
        SingleResultCallback<Void> callbackWhenFinished = new SingleResultCallback<Void>() {
            @Override
            public void onResult(final Void result, final Throwable t) {
                System.out.println("Operation Finished!");
            }
        };

        collection.find().forEach(printDocumentBlock, callbackWhenFinished);

        // Query Filters
        // now use a query to get 1 document out
        collection.find(eq("i", 71)).first(printDocument);

        // now use a range query to get a larger subset
        collection.find(gt("i", 50)).forEach(printDocumentBlock, callbackWhenFinished);

        // range query with multiple constraints
        collection.find(and(gt("i", 50), lte("i", 100))).forEach(printDocumentBlock, callbackWhenFinished);

        // Sorting
        collection.find(exists("i")).sort(descending("i")).first(printDocument);

        // Projection
        collection.find().projection(excludeId()).first(printDocument);

        // Aggregation
        collection.aggregate(asList(
            match(gt("i", 0)),
            project(Document.parse("{ITimes10: {$multiply: ['$i', 10]}}")))
        ).forEach(printDocumentBlock, callbackWhenFinished);

        collection.aggregate(singletonList(group(null, sum("total", "$i")))).first(printDocument);

        // Update One
        collection.updateOne(eq("i", 10), set("i", 110),
                new SingleResultCallback<UpdateResult>() {
                    @Override
                    public void onResult(final UpdateResult result, final Throwable t) {
                        System.out.println(result.getModifiedCount());
                    }
                });

        // Update Many
        collection.updateMany(lt("i", 100), inc("i", 100),
                new SingleResultCallback<UpdateResult>() {
                    @Override
                    public void onResult(final UpdateResult result, final Throwable t) {
                        System.out.println(result.getModifiedCount());
                    }
                });

        // Delete One
        collection.deleteOne(eq("i", 110), new SingleResultCallback<DeleteResult>() {
            @Override
            public void onResult(final DeleteResult result, final Throwable t) {
                System.out.println(result.getDeletedCount());
            }
        });

        // Delete Many
        collection.deleteMany(gte("i", 100), new SingleResultCallback<DeleteResult>() {
            @Override
            public void onResult(final DeleteResult result, final Throwable t) {
                System.out.println(result.getDeletedCount());
            }
        });

        final CountDownLatch dropLatch2 = new CountDownLatch(1);
        collection.drop(new SingleResultCallback<Void>() {
            @Override
            public void onResult(final Void result, final Throwable t) {
                dropLatch2.countDown();
            }
        });
        dropLatch2.await();

        // ordered bulk writes
        List<WriteModel<Document>> writes = new ArrayList<WriteModel<Document>>();
        writes.add(new InsertOneModel<Document>(new Document("_id", 4)));
        writes.add(new InsertOneModel<Document>(new Document("_id", 5)));
        writes.add(new InsertOneModel<Document>(new Document("_id", 6)));
        writes.add(new UpdateOneModel<Document>(new Document("_id", 1), new Document("$set", new Document("x", 2))));
        writes.add(new DeleteOneModel<Document>(new Document("_id", 2)));
        writes.add(new ReplaceOneModel<Document>(new Document("_id", 3), new Document("_id", 3).append("x", 4)));

        SingleResultCallback<BulkWriteResult> printBatchResult = new SingleResultCallback<BulkWriteResult>() {
            @Override
            public void onResult(final BulkWriteResult result, final Throwable t) {
                System.out.println(result);
            }
        };
        collection.bulkWrite(writes, printBatchResult);

        final CountDownLatch dropLatch3 = new CountDownLatch(1);
        collection.drop(new SingleResultCallback<Void>() {
            @Override
            public void onResult(final Void result, final Throwable t) {
                dropLatch3.countDown();
            }
        });
        dropLatch3.await();

        collection.bulkWrite(writes, new BulkWriteOptions().ordered(false), printBatchResult);
        collection.find().forEach(printDocumentBlock, callbackWhenFinished);

        // Clean up
        final CountDownLatch dropLatch4 = new CountDownLatch(1);
        collection.drop(new SingleResultCallback<Void>() {
            @Override
            public void onResult(final Void result, final Throwable t) {
                dropLatch4.countDown();
            }
        });
        dropLatch4.await();

        // release resources
        mongoClient.close();
    }
}


/*
 * Copyright 2008-present MongoDB, Inc.
 * Copyright 2017 Tom Bentley
 *
 * 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 com.mongodb.internal.authentication;

import java.nio.CharBuffer;
import java.text.Normalizer;

/**
 * Utility class for Sasl string preparation.
 *
 *
This class should not be considered a part of the public API.

 */
public final class SaslPrep {

    /**
     * Return the {@code SASLPrep}-canonicalised version of the given {@code str} for use as a query string.
     * This implements the {@code SASLPrep} algorithm defined in RFC 4013.
     *
     * @param str The string to canonicalise.
     * @return The canonicalised string.
     * @throws IllegalArgumentException if the string contained prohibited codepoints, or broke the requirements for bidirectional
     * character handling.
     * @see RFC 3454, Section 7 for discussion of what a query string is.
     */
    public static String saslPrepQuery(final String str) {
        return saslPrep(str, true);
    }

    /**
     * Return the {@code SASLPrep}-canonicalised version of the given {@code str} for use as a stored string.
     * This implements the {@code SASLPrep} algorithm defined in RFC 4013.
     *
     * @param str The string to canonicalise.
     * @return The canonicalised string.
     * @throws IllegalArgumentException if the string contained prohibited codepoints, or broke the requirements for bidirectional
     * character handling.
     * @see RFC 3454, Section 7 for discussion of what a stored string is.
     */
    public static String saslPrepStored(final String str) {
        return saslPrep(str, false);
    }

    private static String saslPrep(final String str, final boolean allowUnassigned) {
        char[] chars = str.toCharArray();

        // 1. Map

        // non-ASCII space chars mapped to space
        for (int i = 0; i < str.length(); i++) {
            char ch = str.charAt(i);
            if (nonAsciiSpace(ch)) {
                chars[i] = ' ';
            }
        }

        int length = 0;
        for (int i = 0; i < str.length(); i++) {
            char ch = chars[i];
            if (!mappedToNothing(ch)) {
                chars[length++] = ch;
            }
        }

        // 2. Normalize
        String normalized = Normalizer.normalize(CharBuffer.wrap(chars, 0, length), Normalizer.Form.NFKC);

        boolean containsRandALCat = false;
        boolean containsLCat = false;
        boolean initialRandALCat = false;
        for (int i = 0; i < normalized.length();) {
            final int codepoint = normalized.codePointAt(i);
            // 3. Prohibit
            if (prohibited(codepoint)) {
                throw new IllegalArgumentException("Prohibited character at position " + i);
            }

            // 4. Check bidi
            final byte directionality = Character.getDirectionality(codepoint);
            final boolean isRandALcat = directionality == Character.DIRECTIONALITY_RIGHT_TO_LEFT
                    || directionality == Character.DIRECTIONALITY_RIGHT_TO_LEFT_ARABIC;
            containsRandALCat |= isRandALcat;
            containsLCat |= directionality == Character.DIRECTIONALITY_LEFT_TO_RIGHT;

            initialRandALCat |= i == 0 && isRandALcat;
            if (!allowUnassigned && !Character.isDefined(codepoint)) {
                throw new IllegalArgumentException("Character at position " + i + " is unassigned");
            }
            i += Character.charCount(codepoint);

            if (initialRandALCat && i >= normalized.length() && !isRandALcat) {
                throw new IllegalArgumentException("First character is RandALCat, but last character is not");
            }

        }
        if (containsRandALCat && containsLCat) {
            throw new IllegalArgumentException("Contains both RandALCat characters and LCat characters");
        }
        return normalized;
    }

    /**
     * Return true if the given {@code codepoint} is a prohibited character as defined by
     * RFC 4013, Section 2.3.
     */
    static boolean prohibited(final int codepoint) {
        return nonAsciiSpace((char) codepoint)
                || asciiControl((char) codepoint)
                || nonAsciiControl(codepoint)
                || privateUse(codepoint)
                || nonCharacterCodePoint(codepoint)
                || surrogateCodePoint(codepoint)
                || inappropriateForPlainText(codepoint)
                || inappropriateForCanonical(codepoint)
                || changeDisplayProperties(codepoint)
                || tagging(codepoint);
    }

    /**
     * Return true if the given {@code codepoint} is a tagging character as defined by
     * RFC 3454, Appendix C.9.
     */
    private static boolean tagging(final int codepoint) {
        return codepoint == 0xE0001
                || 0xE0020 <= codepoint && codepoint <= 0xE007F;
    }

    /**
     * Return true if the given {@code codepoint} is change display properties or deprecated characters as defined by
     * RFC 3454, Appendix C.8.
     */
    private static boolean changeDisplayProperties(final int codepoint) {
        return codepoint == 0x0340
                || codepoint == 0x0341
                || codepoint == 0x200E
                || codepoint == 0x200F
                || codepoint == 0x202A
                || codepoint == 0x202B
                || codepoint == 0x202C
                || codepoint == 0x202D
                || codepoint == 0x202E
                || codepoint == 0x206A
                || codepoint == 0x206B
                || codepoint == 0x206C
                || codepoint == 0x206D
                || codepoint == 0x206E
                || codepoint == 0x206F;
    }

    /**
     * Return true if the given {@code codepoint} is inappropriate for canonical representation characters as defined by
     * RFC 3454, Appendix C.7.
     */
    private static boolean inappropriateForCanonical(final int codepoint) {
        return 0x2FF0 <= codepoint && codepoint <= 0x2FFB;
    }

    /**
     * Return true if the given {@code codepoint} is inappropriate for plain text characters as defined by
     * RFC 3454, Appendix C.6.
     */
    private static boolean inappropriateForPlainText(final int codepoint) {
        return codepoint == 0xFFF9
                || codepoint == 0xFFFA
                || codepoint == 0xFFFB
                || codepoint == 0xFFFC
                || codepoint == 0xFFFD;
    }

    /**
     * Return true if the given {@code codepoint} is a surrogate code point as defined by
     * RFC 3454, Appendix C.5.
     */
    private static boolean surrogateCodePoint(final int codepoint) {
        return 0xD800 <= codepoint && codepoint <= 0xDFFF;
    }

    /**
     * Return true if the given {@code codepoint} is a non-character code point as defined by
     * RFC 3454, Appendix C.4.
     */
    private static boolean nonCharacterCodePoint(final int codepoint) {
        return 0xFDD0 <= codepoint && codepoint <= 0xFDEF
                || 0xFFFE <= codepoint && codepoint <= 0xFFFF
                || 0x1FFFE <= codepoint && codepoint <= 0x1FFFF
                || 0x2FFFE <= codepoint && codepoint <= 0x2FFFF
                || 0x3FFFE <= codepoint && codepoint <= 0x3FFFF
                || 0x4FFFE <= codepoint && codepoint <= 0x4FFFF
                || 0x5FFFE <= codepoint && codepoint <= 0x5FFFF
                || 0x6FFFE <= codepoint && codepoint <= 0x6FFFF
                || 0x7FFFE <= codepoint && codepoint <= 0x7FFFF
                || 0x8FFFE <= codepoint && codepoint <= 0x8FFFF
                || 0x9FFFE <= codepoint && codepoint <= 0x9FFFF
                || 0xAFFFE <= codepoint && codepoint <= 0xAFFFF
                || 0xBFFFE <= codepoint && codepoint <= 0xBFFFF
                || 0xCFFFE <= codepoint && codepoint <= 0xCFFFF
                || 0xDFFFE <= codepoint && codepoint <= 0xDFFFF
                || 0xEFFFE <= codepoint && codepoint <= 0xEFFFF
                || 0xFFFFE <= codepoint && codepoint <= 0xFFFFF
                || 0x10FFFE <= codepoint && codepoint <= 0x10FFFF;
    }

    /**
     * Return true if the given {@code codepoint} is a private use character as defined by
     * RFC 3454, Appendix C.3.
     */
    private static boolean privateUse(final int codepoint) {
        return 0xE000 <= codepoint && codepoint <= 0xF8FF
                || 0xF000 <= codepoint && codepoint <= 0xFFFFD
                || 0x100000 <= codepoint && codepoint <= 0x10FFFD;
    }

    /**
     * Return true if the given {@code ch} is a non-ASCII control character as defined by
     * RFC 3454, Appendix C.2.2.
     */
    private static boolean nonAsciiControl(final int codepoint) {
        return 0x0080 <= codepoint && codepoint <= 0x009F
                || codepoint == 0x06DD
                || codepoint == 0x070F
                || codepoint == 0x180E
                || codepoint == 0x200C
                || codepoint == 0x200D
                || codepoint == 0x2028
                || codepoint == 0x2029
                || codepoint == 0x2060
                || codepoint == 0x2061
                || codepoint == 0x2062
                || codepoint == 0x2063
                || 0x206A <= codepoint && codepoint <= 0x206F
                || codepoint == 0xFEFF
                || 0xFFF9 <= codepoint && codepoint <= 0xFFFC
                || 0x1D173 <= codepoint && codepoint <= 0x1D17A;
    }

    /**
     * Return true if the given {@code ch} is an ASCII control character as defined by
     * RFC 3454, Appendix C.2.1.
     */
    private static boolean asciiControl(final char ch) {
        return ch <= '\u001F' || ch == '\u007F';
    }

    /**
     * Return true if the given {@code ch} is a non-ASCII space character as defined by
     * RFC 3454, Appendix C.1.2.
     */
    private static boolean nonAsciiSpace(final char ch) {
        return ch == '\u00A0'
                || ch == '\u1680'
                || '\u2000' <= ch && ch <= '\u200B'
                || ch == '\u202F'
                || ch == '\u205F'
                || ch == '\u3000';
    }

    /**
     * Return true if the given {@code ch} is a "commonly mapped to nothing" character as defined by
     * RFC 3454,
     * Appendix B.1.
     */
    private static boolean mappedToNothing(final char ch) {
        return ch == '\u00AD'
                || ch == '\u034F'
                || ch == '\u1806'
                || ch == '\u180B'
                || ch == '\u180C'
                || ch == '\u180D'
                || ch == '\u200B'
                || ch == '\u200C'
                || ch == '\u200D'
                || ch == '\u2060'
                || '\uFE00' <= ch && ch <= '\uFE0F'
                || ch == '\uFEFF';
    }

    private SaslPrep() {
    }
}


/*
 * Copyright 2008-present MongoDB, Inc.
 *
 * 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 com.mongodb.internal.connection;

import com.mongodb.MongoInternalException;
import com.mongodb.MongoInterruptedException;
import com.mongodb.MongoTimeoutException;
import com.mongodb.internal.connection.ConcurrentLinkedDeque.RemovalReportingIterator;

import java.util.Iterator;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;

/**
 * A concurrent pool implementation.
 *
 *
This class should not be considered a part of the public API.

 */
public class ConcurrentPool<T> implements Pool<T> {

    private final int maxSize;
    private final ItemFactory<T> itemFactory;

    private final ConcurrentLinkedDeque<T> available = new ConcurrentLinkedDeque<T>();
    private final Semaphore permits;
    private volatile boolean closed;

    public enum Prune {
        /**
         * Prune this element
         */
        YES,
        /**
         * Don't prone this element
         */
        NO,
        /**
         * Don't prune this element and stop attempting to prune additional elements
         */
        STOP
    }
    /**
     * Factory for creating and closing pooled items.
     *
     * @param <T>
     */
    public interface ItemFactory<T> {
        T create(boolean initialize);

        void close(T t);

        Prune shouldPrune(T t);
    }

    /**
     * Initializes a new pool of objects.
     *
     * @param maxSize     max to hold to at any given time. if < 0 then no limit
     * @param itemFactory factory used to create and close items in the pool
     */
    public ConcurrentPool(final int maxSize, final ItemFactory<T> itemFactory) {
        this.maxSize = maxSize;
        this.itemFactory = itemFactory;
        permits = new Semaphore(maxSize, true);
    }

    /**
     * Return an instance of T to the pool.  This method simply calls {@code release(t, false)}
     *
     * @param t item to return to the pool
     */
    @Override
    public void release(final T t) {
        release(t, false);
    }

    /**
     * call done when you are done with an object from the pool if there is room and the object is ok will get added
     *
     * @param t     item to return to the pool
     * @param prune true if the item should be closed, false if it should be put back in the pool
     */
    @Override
    public void release(final T t, final boolean prune) {
        if (t == null) {
            throw new IllegalArgumentException("Can not return a null item to the pool");
        }
        if (closed) {
            close(t);
            return;
        }

        if (prune) {
            close(t);
        } else {
            available.addLast(t);
        }

        releasePermit();
    }

    /**
     * Gets an object from the pool.  This method will block until a permit is available.
     *
     * @return An object from the pool.
     */
    @Override
    public T get() {
        return get(-1, TimeUnit.MILLISECONDS);
    }

    /**
     * Gets an object from the pool - will block if none are available
     *
     * @param timeout  negative - forever 0        - return immediately no matter what positive ms to wait
     * @param timeUnit the time unit of the timeout
     * @return An object from the pool, or null if can't get one in the given waitTime
     * @throws MongoTimeoutException if the timeout has been exceeded
     */
    @Override
    public T get(final long timeout, final TimeUnit timeUnit) {
        if (closed) {
            throw new IllegalStateException("The pool is closed");
        }

        if (!acquirePermit(timeout, timeUnit)) {
            throw new MongoTimeoutException(String.format("Timeout waiting for a pooled item after %d %s", timeout, timeUnit));
        }

        T t = available.pollLast();
        if (t == null) {
            t = createNewAndReleasePermitIfFailure(false);
        }

        return t;
    }

    public void prune() {
        for (RemovalReportingIterator<T> iter = available.iterator(); iter.hasNext();) {
            T cur = iter.next();
            Prune shouldPrune = itemFactory.shouldPrune(cur);

            if (shouldPrune == Prune.STOP) {
                break;
            }

            if (shouldPrune == Prune.YES) {
                boolean removed = iter.reportingRemove();
                if (removed) {
                    close(cur);
                }
            }
        }
    }

    public void ensureMinSize(final int minSize, final boolean initialize) {
        while (getCount() < minSize) {
            if (!acquirePermit(10, TimeUnit.MILLISECONDS)) {
                break;
            }
            release(createNewAndReleasePermitIfFailure(initialize));
        }
    }

    private T createNewAndReleasePermitIfFailure(final boolean initialize) {
        try {
            T newMember = itemFactory.create(initialize);
            if (newMember == null) {
                throw new MongoInternalException("The factory for the pool created a null item");
            }
            return newMember;
        } catch (RuntimeException e) {
            permits.release();
            throw e;
        }
    }

    protected boolean acquirePermit(final long timeout, final TimeUnit timeUnit) {
        try {
            if (closed) {
                return false;
            } else if (timeout >= 0) {
                return permits.tryAcquire(timeout, timeUnit);
            } else {
                permits.acquire();
                return true;
            }
        } catch (InterruptedException e) {
            throw new MongoInterruptedException("Interrupted acquiring a permit to retrieve an item from the pool ", e);
        }
    }

    protected void releasePermit() {
        permits.release();
    }

    /**
     * Clears the pool of all objects.
     */
    @Override
    public void close() {
        closed = true;
        Iterator<T> iter = available.iterator();
        while (iter.hasNext()) {
            T t = iter.next();
            close(t);
            iter.remove();
        }
    }

    public int getMaxSize() {
        return maxSize;
    }

    public int getInUseCount() {
        return maxSize - permits.availablePermits();
    }

    public int getAvailableCount() {
        return available.size();
    }

    public int getCount() {
        return getInUseCount() + getAvailableCount();
    }

    public String toString() {
        StringBuilder buf = new StringBuilder();
        buf.append("pool: ")
           .append(" maxSize: ").append(maxSize)
           .append(" availableCount ").append(getAvailableCount())
           .append(" inUseCount ").append(getInUseCount());
        return buf.toString();
    }

    // swallow exceptions from ItemFactory.close()
    private void close(final T t) {
        try {
            itemFactory.close(t);
        } catch (RuntimeException e) {
            // ItemFactory.close() really should not throw
        }
    }
}