package com.github.siyamed.shapeimageview.path.parser;

import org.xmlpull.v1.XmlPullParser;

@SuppressWarnings("FinalStaticMethod")
class ParseUtil {

    static final String escape (String s) {
        return s
                .replaceAll("\"", """)
                .replaceAll("'", "&apos")
                .replaceAll("<", "<")
                .replaceAll(">", ">")
                .replaceAll("&", "&");
    }

    static final String getStringAttr(String name, XmlPullParser attributes) {
        int n = attributes.getAttributeCount();
        for (int i = 0; i < n; i++) {
            if (attributes.getAttributeName(i).equals(name)) {
                return attributes.getAttributeValue(i);
            }
        }
        return null;
    }

    /*
     * Some SVG unit conversions.  This is approximate
     */
    static final Float convertUnits(String name, XmlPullParser atts, float dpi, float width, float height) {
        String value = getStringAttr(name, atts);
        if (value == null) {
            return null;
        } else if (value.endsWith("px")) {
            return Float.parseFloat(value.substring(0, value.length() - 2));
        } else if (value.endsWith("pt")) {
            return Float.valueOf(value.substring(0, value.length() - 2)) * dpi / 72;
        } else if (value.endsWith("pc")) {
            return Float.valueOf(value.substring(0, value.length() - 2)) * dpi / 6;
        } else if (value.endsWith("cm")) {
            return Float.valueOf(value.substring(0, value.length() - 2)) * dpi / 2.54f;
        } else if (value.endsWith("mm")) {
            return Float.valueOf(value.substring(0, value.length() - 2)) * dpi / 254;
        } else if (value.endsWith("in")) {
            return Float.valueOf(value.substring(0, value.length() - 2)) * dpi;
        } else if (value.endsWith("%")) {
            Float result = Float.valueOf(value.substring(0, value.length() - 1));
            float mult;
            if (name.contains("x") || name.equals("width") ) {
                mult = width / 100f;
            } else if (name.contains("y") || name.equals("height")) {
                mult = height / 100f;
            } else {
                mult = (height + width) / 2f;
            }
            return result * mult;
        } else {
            return Float.valueOf(value);
        }
    }
}


package com.github.siyamed.shapeimageview.path.parser;

import android.graphics.Path;
import android.graphics.RectF;
import android.util.Log;

class PathParser {

    private static final String TAG = SvgToPath.TAG;

    /*
     * This is where the hard-to-parse paths are handled.
     * Uppercase rules are absolute positions, lowercase are relative.
     * Types of path rules:
     *

     *

     *
M/m - (x y)+ - Move to (without drawing)
     *
Z/z - (no params) - Close path (back to starting point)
     *
L/l - (x y)+ - Line to
     *
H/h - x+ - Horizontal ine to
     *
V/v - y+ - Vertical line to
     *
C/c - (x1 y1 x2 y2 x y)+ - Cubic bezier to
     *
S/s - (x2 y2 x y)+ - Smooth cubic bezier to (shorthand that assumes the x2, y2 from previous C/S is the x1, y1 of this bezier)
     *
Q/q - (x1 y1 x y)+ - Quadratic bezier to
     *
T/t - (x y)+ - Smooth quadratic bezier to (assumes previous control point is "reflection" of last one w.r.t. to current point)
     *

     *

     * Numbers are separate by whitespace, comma or nothing at all (!) if they are self-delimiting, (ie. begin with a - sign)
     */
    public static Path doPath(String s) {
        int n = s.length();
        ParserHelper ph = new ParserHelper(s);
        ph.skipWhitespace();
        Path p = new Path();
        float lastX = 0;
        float lastY = 0;
        float lastX1 = 0;
        float lastY1 = 0;
        float contourInitialX = 0;
        float contourInitialY = 0;
        RectF r = new RectF();
        char cmd = 'x';
        while (ph.pos < n) {
            char next = s.charAt(ph.pos);
            if (!Character.isDigit(next) && !(next == '.') && !(next == '-')) {
                cmd = next;
                ph.advance();
            } else if (cmd == 'M') { // implied command
                cmd = 'L';
            } else if (cmd == 'm') { // implied command
                cmd = 'l';
            } else { // implied command
                //ignore
            }
            p.computeBounds(r, true);
            // Log.d(TAG, "  " + cmd + " " + r);
            // Util.debug("* Commands remaining: '" + path + "'.");
            boolean wasCurve = false;
            switch (cmd) {
                case 'M':
                case 'm': {
                    float x = ph.nextFloat();
                    float y = ph.nextFloat();
                    if (cmd == 'm') {
                        p.rMoveTo(x, y);
                        lastX += x;
                        lastY += y;
                    } else {
                        p.moveTo(x, y);
                        lastX = x;
                        lastY = y;
                    }
                    contourInitialX = lastX;
                    contourInitialY = lastY;
                    break;
                }
                case 'Z':
                case 'z': {
                    /// p.lineTo(contourInitialX, contourInitialY);
                    p.close();
                    lastX = contourInitialX;
                    lastY = contourInitialY;
                    break;
                }
                case 'L':
                case 'l': {
                    float x = ph.nextFloat();
                    float y = ph.nextFloat();
                    if (cmd == 'l') {
                        p.rLineTo(x, y);
                        lastX += x;
                        lastY += y;
                    } else {
                        p.lineTo(x, y);
                        lastX = x;
                        lastY = y;
                    }
                    break;
                }
                case 'H':
                case 'h': {
                    float x = ph.nextFloat();
                    if (cmd == 'h') {
                        p.rLineTo(x, 0);
                        lastX += x;
                    } else {
                        p.lineTo(x, lastY);
                        lastX = x;
                    }
                    break;
                }
                case 'V':
                case 'v': {
                    float y = ph.nextFloat();
                    if (cmd == 'v') {
                        p.rLineTo(0, y);
                        lastY += y;
                    } else {
                        p.lineTo(lastX, y);
                        lastY = y;
                    }
                    break;
                }
                case 'C':
                case 'c': {
                    wasCurve = true;
                    float x1 = ph.nextFloat();
                    float y1 = ph.nextFloat();
                    float x2 = ph.nextFloat();
                    float y2 = ph.nextFloat();
                    float x = ph.nextFloat();
                    float y = ph.nextFloat();
                    if (cmd == 'c') {
                        x1 += lastX;
                        x2 += lastX;
                        x += lastX;
                        y1 += lastY;
                        y2 += lastY;
                        y += lastY;
                    }
                    p.cubicTo(x1, y1, x2, y2, x, y);
                    lastX1 = x2;
                    lastY1 = y2;
                    lastX = x;
                    lastY = y;
                    break;
                }
                case 'S':
                case 's': {
                    wasCurve = true;
                    float x2 = ph.nextFloat();
                    float y2 = ph.nextFloat();
                    float x = ph.nextFloat();
                    float y = ph.nextFloat();
                    if (cmd == 's') {
                        x2 += lastX;
                        x += lastX;
                        y2 += lastY;
                        y += lastY;
                    }
                    float x1 = 2 * lastX - lastX1;
                    float y1 = 2 * lastY - lastY1;
                    p.cubicTo(x1, y1, x2, y2, x, y);
                    lastX1 = x2;
                    lastY1 = y2;
                    lastX = x;
                    lastY = y;
                    break;
                }
                case 'A':
                case 'a': {
                    float rx = ph.nextFloat();
                    float ry = ph.nextFloat();
                    float theta = ph.nextFloat();
                    int largeArc = (int) ph.nextFloat();
                    int sweepArc = (int) ph.nextFloat();
                    float x = ph.nextFloat();
                    float y = ph.nextFloat();
                    if (cmd == 'a') {
                        x += lastX;
                        y += lastY;
                    }
                    drawArc(p, lastX, lastY, x, y, rx, ry, theta, largeArc == 1, sweepArc == 1);
                    lastX = x;
                    lastY = y;
                    break;
                }
                case 'T':
                case 't': {
                    wasCurve = true;
                    float x = ph.nextFloat();
                    float y = ph.nextFloat();
                    if (cmd == 't') {
                        x += lastX;
                        y += lastY;
                    }
                    float x1 = 2 * lastX - lastX1;
                    float y1 = 2 * lastY - lastY1;
                    p.cubicTo( lastX, lastY, x1, y1, x, y );
                    lastX = x;
                    lastY = y;
                    lastX1 = x1;
                    lastY1 = y1;
                    break;
                }
                case 'Q':
                case 'q': {
                    wasCurve = true;
                    float x1 = ph.nextFloat();
                    float y1 = ph.nextFloat();
                    float x = ph.nextFloat();
                    float y = ph.nextFloat();
                    if (cmd == 'q') {
                        x += lastX;
                        y += lastY;
                        x1 += lastX;
                        y1 += lastY;
                    }
                    p.cubicTo( lastX, lastY, x1, y1, x, y );
                    lastX1 = x1;
                    lastY1 = y1;
                    lastX = x;
                    lastY = y;
                    break;
                }
                default:
                    Log.w(TAG, "Invalid path command: " + cmd);
                    ph.advance();
            }
            if (!wasCurve) {
                lastX1 = lastX;
                lastY1 = lastY;
            }
            ph.skipWhitespace();
        }
        return p;
    }

    /*
     * Elliptical arc implementation based on the SVG specification notes
     * Adapted from the Batik library (Apache-2 license) by SAU
     */
    private static void drawArc(Path path, double x0, double y0, double x, double y, double rx,
                                double ry, double angle, boolean largeArcFlag, boolean sweepFlag) {
        double dx2 = (x0 - x) / 2.0;
        double dy2 = (y0 - y) / 2.0;
        angle = Math.toRadians(angle % 360.0);
        double cosAngle = Math.cos(angle);
        double sinAngle = Math.sin(angle);

        double x1 = (cosAngle * dx2 + sinAngle * dy2);
        double y1 = (-sinAngle * dx2 + cosAngle * dy2);
        rx = Math.abs(rx);
        ry = Math.abs(ry);

        double Prx = rx * rx;
        double Pry = ry * ry;
        double Px1 = x1 * x1;
        double Py1 = y1 * y1;

        // check that radii are large enough
        double radiiCheck = Px1 / Prx + Py1 / Pry;
        if (radiiCheck > 1) {
            rx = Math.sqrt(radiiCheck) * rx;
            ry = Math.sqrt(radiiCheck) * ry;
            Prx = rx * rx;
            Pry = ry * ry;
        }

        // Step 2 : Compute (cx1, cy1)
        double sign = (largeArcFlag == sweepFlag) ? -1 : 1;
        double sq = ((Prx * Pry) - (Prx * Py1) - (Pry * Px1))
                / ((Prx * Py1) + (Pry * Px1));
        sq = (sq < 0) ? 0 : sq;
        double coef = (sign * Math.sqrt(sq));
        double cx1 = coef * ((rx * y1) / ry);
        double cy1 = coef * -((ry * x1) / rx);

        double sx2 = (x0 + x) / 2.0;
        double sy2 = (y0 + y) / 2.0;
        double cx = sx2 + (cosAngle * cx1 - sinAngle * cy1);
        double cy = sy2 + (sinAngle * cx1 + cosAngle * cy1);

        // Step 4 : Compute the angleStart (angle1) and the angleExtent (dangle)
        double ux = (x1 - cx1) / rx;
        double uy = (y1 - cy1) / ry;
        double vx = (-x1 - cx1) / rx;
        double vy = (-y1 - cy1) / ry;
        double p, n;

        // Compute the angle start
        n = Math.sqrt((ux * ux) + (uy * uy));
        p = ux; // (1 * ux) + (0 * uy)
        sign = (uy < 0) ? -1.0 : 1.0;
        double angleStart = Math.toDegrees(sign * Math.acos(p / n));

        // Compute the angle extent
        n = Math.sqrt((ux * ux + uy * uy) * (vx * vx + vy * vy));
        p = ux * vx + uy * vy;
        sign = (ux * vy - uy * vx < 0) ? -1.0 : 1.0;
        double angleExtent = Math.toDegrees(sign * Math.acos(p / n));
        if (!sweepFlag && angleExtent > 0) {
            angleExtent -= 360f;
        } else if (sweepFlag && angleExtent < 0) {
            angleExtent += 360f;
        }
        angleExtent %= 360f;
        angleStart %= 360f;

        RectF oval = new RectF((float) (cx - rx), (float) (cy - ry), (float) (cx + rx), (float) (cy + ry));
        path.addArc(oval, (float) angleStart, (float) angleExtent);
    }

}


package com.github.siyamed.shapeimageview.path.parser;

import android.graphics.Matrix;
import android.util.Log;

class TransformParser {
    private static final String TAG = SvgToPath.class.getSimpleName();

    // Process a list of transforms
    // foo(n,n,n...) bar(n,n,n..._ ...)
    // delims are whitespace or ,'s
    static Matrix parseTransform(String s) {
        //Log.d(TAG, s);
        Matrix matrix = new Matrix();
        while (true) {
            parseTransformItem(s, matrix);
            int rparen = s.indexOf(")");
            if (rparen > 0 && s.length() > rparen + 1) {
                s = s.substring(rparen + 1).replaceFirst("[\\s,]*", "");
            } else {
                break;
            }
        }
        return matrix;
    }

    private static void parseTransformItem(String s, Matrix matrix) {
        if (s.startsWith("matrix(")) {
            NumberParse np = NumberParse.parseNumbers(s.substring("matrix(".length()));
            if (np.numbers.size() == 6) {
                Matrix mat = new Matrix();
                mat.setValues(new float[] {
                        // Row 1
                        np.numbers.get(0),
                        np.numbers.get(2),
                        np.numbers.get(4),
                        // Row 2
                        np.numbers.get(1),
                        np.numbers.get(3),
                        np.numbers.get(5),
                        // Row 3
                        0,
                        0,
                        1,
                });
                matrix.preConcat(mat);
            }
        } else if (s.startsWith("translate(")) {
            NumberParse np = NumberParse.parseNumbers(s.substring("translate(".length()));
            if (np.numbers.size() > 0) {
                float tx = np.numbers.get(0);
                float ty = 0;
                if (np.numbers.size() > 1) {
                    ty = np.numbers.get(1);
                }
                matrix.preTranslate(tx, ty);
            }
        } else if (s.startsWith("scale(")) {
            NumberParse np = NumberParse.parseNumbers(s.substring("scale(".length()));
            if (np.numbers.size() > 0) {
                float sx = np.numbers.get(0);
                float sy = sx;
                if (np.numbers.size() > 1) {
                    sy = np.numbers.get(1);
                }
                matrix.preScale(sx, sy);
            }
        } else if (s.startsWith("skewX(")) {
            NumberParse np = NumberParse.parseNumbers(s.substring("skewX(".length()));
            if (np.numbers.size() > 0) {
                float angle = np.numbers.get(0);
                matrix.preSkew((float) Math.tan(angle), 0);
            }
        } else if (s.startsWith("skewY(")) {
            NumberParse np = NumberParse.parseNumbers(s.substring("skewY(".length()));
            if (np.numbers.size() > 0) {
                float angle = np.numbers.get(0);
                matrix.preSkew(0, (float) Math.tan(angle));
            }
        } else if (s.startsWith("rotate(")) {
            NumberParse np = NumberParse.parseNumbers(s.substring("rotate(".length()));
            if (np.numbers.size() > 0) {
                float angle = np.numbers.get(0);
                float cx = 0;
                float cy = 0;
                if (np.numbers.size() > 2) {
                    cx = np.numbers.get(1);
                    cy = np.numbers.get(2);
                }
                matrix.preTranslate(cx, cy);
                matrix.preRotate(angle);
                matrix.preTranslate(-cx, -cy);
            }
        } else {
            Log.w(TAG, "Invalid transform (" + s + ")");
        }
    }
}


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

import java.util.List;

/**
 * A {@code BSONCallback} for creation of {@code LazyBSONObject} and {@code LazyBSONList} instances.
 */
public class LazyBSONCallback extends EmptyBSONCallback {
    private Object root;

    @Override
    public void reset() {
        this.root = null;
    }

    @Override
    public Object get() {
        return getRoot();
    }

    @Override
    public void gotBinary(final String name, final byte type, final byte[] data) {
        setRoot(createObject(data, 0));
    }

    /**
     * Create a {@code LazyBSONObject} instance from the given bytes starting from the given offset.
     *
     * @param bytes the raw BSON bytes
     * @param offset the offset into the bytes
     * @return the LazyBSONObject
     */
    public Object createObject(final byte[] bytes, final int offset) {
        return new LazyBSONObject(bytes, offset, this);
    }

    /**
     * Create a {@code LazyBSONList} from the given bytes starting from the given offset.
     *
     * @param bytes the raw BSON bytes
     * @param offset the offset into the bytes
     * @return the LazyBSONList
     */
    @SuppressWarnings("rawtypes")
    public List createArray(final byte[] bytes, final int offset) {
        return new LazyBSONList(bytes, offset, this);
    }

    /**
     * This is a factory method pattern to create appropriate objects for BSON type DBPointer(0x0c).
     *
     * @param ns the namespace of the reference
     * @param id the identifier of the reference
     * @return object to be used as reference representation
     */
    public Object createDBRef(final String ns, final ObjectId id) {
        return new BasicBSONObject("$ns", ns).append("$id", id);
    }

    private Object getRoot() {
        return root;
    }

    private void setRoot(final Object root) {
        this.root = root;
    }
}


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

import org.bson.BsonArray;
import org.bson.BsonBinary;
import org.bson.BsonBoolean;
import org.bson.BsonDateTime;
import org.bson.BsonDbPointer;
import org.bson.BsonDecimal128;
import org.bson.BsonDocument;
import org.bson.BsonDocumentWrapper;
import org.bson.BsonDouble;
import org.bson.BsonInt32;
import org.bson.BsonInt64;
import org.bson.BsonJavaScript;
import org.bson.BsonJavaScriptWithScope;
import org.bson.BsonMaxKey;
import org.bson.BsonMinKey;
import org.bson.BsonNull;
import org.bson.BsonObjectId;
import org.bson.BsonRegularExpression;
import org.bson.BsonString;
import org.bson.BsonSymbol;
import org.bson.BsonTimestamp;
import org.bson.BsonType;
import org.bson.BsonUndefined;
import org.bson.BsonValue;
import org.bson.RawBsonDocument;
import org.bson.codecs.configuration.CodecProvider;
import org.bson.codecs.configuration.CodecRegistry;

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

/**
 * A {@code CodecProvider} for all subclass of BsonValue.
 *
 * @since 3.0
 */
public class BsonValueCodecProvider implements CodecProvider {
    private static final BsonTypeClassMap DEFAULT_BSON_TYPE_CLASS_MAP;

    private final Map<Class<?>, Codec<?>> codecs = new HashMap<Class<?>, Codec<?>>();

    /**
     * Construct a new instance with the default codec for each BSON type.
     */
    public BsonValueCodecProvider() {
        addCodecs();
    }

    /**
     * Get the {@code BsonValue} subclass associated with the given {@code BsonType}.
     * @param bsonType the BsonType
     * @return the class associated with the given type
     */
    @SuppressWarnings("unchecked")
    public static Class<? extends BsonValue> getClassForBsonType(final BsonType bsonType) {
        return (Class<? extends BsonValue>) DEFAULT_BSON_TYPE_CLASS_MAP.get(bsonType);
    }

    /**
     * Gets the BsonTypeClassMap used by this provider.
     *
     * @return the non-null BsonTypeClassMap
     * @since 3.3
     */
    public static BsonTypeClassMap getBsonTypeClassMap() {
        return DEFAULT_BSON_TYPE_CLASS_MAP;
    }

    @Override
    @SuppressWarnings("unchecked")
    public <T> Codec<T> get(final Class<T> clazz, final CodecRegistry registry) {
        if (codecs.containsKey(clazz)) {
            return (Codec<T>) codecs.get(clazz);
        }

        if (clazz == BsonJavaScriptWithScope.class) {
            return (Codec<T>) new BsonJavaScriptWithScopeCodec(registry.get(BsonDocument.class));
        }

        if (clazz == BsonValue.class) {
            return (Codec<T>) new BsonValueCodec(registry);
        }

        if (clazz == BsonDocumentWrapper.class) {
            return (Codec<T>) new BsonDocumentWrapperCodec(registry.get(BsonDocument.class));
        }

        if (clazz == RawBsonDocument.class) {
            return (Codec<T>) new RawBsonDocumentCodec();
        }

        if (BsonDocument.class.isAssignableFrom(clazz)) {
            return (Codec<T>) new BsonDocumentCodec(registry);
        }

        if (BsonArray.class.isAssignableFrom(clazz)) {
            return (Codec<T>) new BsonArrayCodec(registry);
        }

        return null;
    }

    private void addCodecs() {
        addCodec(new BsonNullCodec());
        addCodec(new BsonBinaryCodec());
        addCodec(new BsonBooleanCodec());
        addCodec(new BsonDateTimeCodec());
        addCodec(new BsonDBPointerCodec());
        addCodec(new BsonDoubleCodec());
        addCodec(new BsonInt32Codec());
        addCodec(new BsonInt64Codec());
        addCodec(new BsonDecimal128Codec());
        addCodec(new BsonMinKeyCodec());
        addCodec(new BsonMaxKeyCodec());
        addCodec(new BsonJavaScriptCodec());
        addCodec(new BsonObjectIdCodec());
        addCodec(new BsonRegularExpressionCodec());
        addCodec(new BsonStringCodec());
        addCodec(new BsonSymbolCodec());
        addCodec(new BsonTimestampCodec());
        addCodec(new BsonUndefinedCodec());
    }

    private <T extends BsonValue> void addCodec(final Codec<T> codec) {
        codecs.put(codec.getEncoderClass(), codec);
    }

    static {
        Map<BsonType, Class<?>> map = new HashMap<BsonType, Class<?>>();

        map.put(BsonType.NULL, BsonNull.class);
        map.put(BsonType.ARRAY, BsonArray.class);
        map.put(BsonType.BINARY, BsonBinary.class);
        map.put(BsonType.BOOLEAN, BsonBoolean.class);
        map.put(BsonType.DATE_TIME, BsonDateTime.class);
        map.put(BsonType.DB_POINTER, BsonDbPointer.class);
        map.put(BsonType.DOCUMENT, BsonDocument.class);
        map.put(BsonType.DOUBLE, BsonDouble.class);
        map.put(BsonType.INT32, BsonInt32.class);
        map.put(BsonType.INT64, BsonInt64.class);
        map.put(BsonType.DECIMAL128, BsonDecimal128.class);
        map.put(BsonType.MAX_KEY, BsonMaxKey.class);
        map.put(BsonType.MIN_KEY, BsonMinKey.class);
        map.put(BsonType.JAVASCRIPT, BsonJavaScript.class);
        map.put(BsonType.JAVASCRIPT_WITH_SCOPE, BsonJavaScriptWithScope.class);
        map.put(BsonType.OBJECT_ID, BsonObjectId.class);
        map.put(BsonType.REGULAR_EXPRESSION, BsonRegularExpression.class);
        map.put(BsonType.STRING, BsonString.class);
        map.put(BsonType.SYMBOL, BsonSymbol.class);
        map.put(BsonType.TIMESTAMP, BsonTimestamp.class);
        map.put(BsonType.UNDEFINED, BsonUndefined.class);

        DEFAULT_BSON_TYPE_CLASS_MAP = new BsonTypeClassMap(map);
    }
}


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

import com.mongodb.BasicDBObject;
import com.mongodb.DB;
import com.mongodb.DBCollection;
import com.mongodb.DBCursor;
import com.mongodb.DBObject;
import com.mongodb.MongoException;
import org.bson.types.ObjectId;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

/**
 *
 Implementation of GridFS - a specification for storing and retrieving files that exceed the BSON-document size limit of 16MB.

 *
 *
Instead of storing a file in a single document, GridFS divides a file into parts, or chunks, and stores each of those chunks as a
 * separate document. By default GridFS limits chunk size to 255k. GridFS uses two collections to store files. One collection stores the
 * file chunks, and the other stores file metadata.

 *
 *
When you query a GridFS store for a file, the driver or client will reassemble the chunks as needed. You can perform range queries on
 * files stored through GridFS. You also can access information from arbitrary sections of files, which allows you to "skip" into the middle
 * of a video or audio file.

 *
 *
GridFS is useful not only for storing files that exceed 16MB but also for storing any files for which you want access without having
 * to load the entire file into memory. For more information on the indications of GridFS, see MongoDB official documentation.

 *
 * @mongodb.driver.manual core/gridfs/ GridFS
 */
@SuppressWarnings("rawtypes")
public class GridFS {

    /**
     * File's chunk size
     */
    public static final int DEFAULT_CHUNKSIZE = 255 * 1024;
    /**
     * File's max chunk size
     *
     * @deprecated You can calculate max chunkSize with a similar formula {@link com.mongodb.MongoClient#getMaxBsonObjectSize()} - 500*1000.
     * Please ensure that you left enough space for metadata (500kb is enough).
     */
    @Deprecated
    public static final long MAX_CHUNKSIZE = (long) (3.5 * 1000 * 1000);
    /**
     * Bucket to use for the collection namespaces
     */
    public static final String DEFAULT_BUCKET = "fs";

    private final DB database;
    private final String bucketName;

    private final DBCollection filesCollection;
    private final DBCollection chunksCollection;

    /**
     * Creates a GridFS instance for the default bucket "fs" in the given database. Set the preferred WriteConcern on the give DB with
     * DB.setWriteConcern
     *
     * @param db database to work with
     * @throws com.mongodb.MongoException if there's a failure
     * @see com.mongodb.WriteConcern
     */
    public GridFS(final DB db) {
        this(db, DEFAULT_BUCKET);
    }

    /**
     * Creates a GridFS instance for the specified bucket in the given database.  Set the preferred WriteConcern on the give DB with
     * DB.setWriteConcern
     *
     * @param db     database to work with
     * @param bucket bucket to use in the given database
     * @throws com.mongodb.MongoException if there's a failure
     * @see com.mongodb.WriteConcern
     */
    public GridFS(final DB db, final String bucket) {
        this.database = db;
        this.bucketName = bucket;

        this.filesCollection = database.getCollection(bucketName + ".files");
        this.chunksCollection = database.getCollection(bucketName + ".chunks");

        // ensure standard indexes as long as collections are small
        try {
            if (filesCollection.count() < 1000) {
                filesCollection.createIndex(new BasicDBObject("filename", 1).append("uploadDate", 1));
            }
            if (chunksCollection.count() < 1000) {
                chunksCollection.createIndex(new BasicDBObject("files_id", 1).append("n", 1),
                                             new BasicDBObject("unique", true));
            }
        } catch (MongoException e) {
            //TODO: Logging
        }

        filesCollection.setObjectClass(GridFSDBFile.class);
    }

    /**
     * Gets the list of files stored in this gridfs, sorted by filename.
     *
     * @return cursor of file objects
     */
    public DBCursor getFileList() {
        return filesCollection.find().sort(new BasicDBObject("filename", 1));
    }

    /**
     * Gets a filtered list of files stored in this gridfs, sorted by filename.
     *
     * @param query filter to apply
     * @return cursor of file objects
     */
    public DBCursor getFileList(final DBObject query) {
        return filesCollection.find(query).sort(new BasicDBObject("filename", 1));
    }

    /**
     * Gets a sorted, filtered list of files stored in this gridfs.
     *
     * @param query filter to apply
     * @param sort  sorting to apply
     * @return cursor of file objects
     */
    public DBCursor getFileList(final DBObject query, final DBObject sort) {
        return filesCollection.find(query).sort(sort);
    }

    /**
     * Finds one file matching the given objectId. Equivalent to findOne(objectId).
     *
     * @param objectId the objectId of the file stored on a server
     * @return a gridfs file
     * @throws com.mongodb.MongoException if the operation fails
     */
    public GridFSDBFile find(final ObjectId objectId) {
        return findOne(objectId);
    }

    /**
     * Finds one file matching the given objectId.
     *
     * @param objectId the objectId of the file stored on a server
     * @return a gridfs file
     * @throws com.mongodb.MongoException if the operation fails
     */
    public GridFSDBFile findOne(final ObjectId objectId) {
        return findOne(new BasicDBObject("_id", objectId));
    }

    /**
     * Finds one file matching the given filename.
     *
     * @param filename the name of the file stored on a server
     * @return the gridfs db file
     * @throws com.mongodb.MongoException if the operation fails
     */
    public GridFSDBFile findOne(final String filename) {
        return findOne(new BasicDBObject("filename", filename));
    }

    /**
     * Finds one file matching the given query.
     *
     * @param query filter to apply
     * @return a gridfs file
     * @throws com.mongodb.MongoException if the operation fails
     */
    public GridFSDBFile findOne(final DBObject query) {
        return injectGridFSInstance(filesCollection.findOne(query));
    }

    /**
     * Finds a list of files matching the given filename.
     *
     * @param filename the filename to look for
     * @return list of gridfs files
     * @throws com.mongodb.MongoException if the operation fails
     */
    public List<GridFSDBFile> find(final String filename) {
        return find(new BasicDBObject("filename", filename));
    }

    /**
     * Finds a list of files matching the given filename.
     *
     * @param filename the filename to look for
     * @param sort     the fields to sort with
     * @return list of gridfs files
     * @throws com.mongodb.MongoException if the operation fails
     */
    public List<GridFSDBFile> find(final String filename, final DBObject sort) {
        return find(new BasicDBObject("filename", filename), sort);
    }

    /**
     * Finds a list of files matching the given query.
     *
     * @param query the filter to apply
     * @return list of gridfs files
     * @throws com.mongodb.MongoException if the operation fails
     */
    public List<GridFSDBFile> find(final DBObject query) {
        return find(query, null);
    }

    /**
     * Finds a list of files matching the given query.
     *
     * @param query the filter to apply
     * @param sort  the fields to sort with
     * @return list of gridfs files
     * @throws com.mongodb.MongoException if the operation fails
     */
    public List<GridFSDBFile> find(final DBObject query, final DBObject sort) {
        List<GridFSDBFile> files = new ArrayList<GridFSDBFile>();

        DBCursor cursor = filesCollection.find(query);
        if (sort != null) {
            cursor.sort(sort);
        }

        try {
            while (cursor.hasNext()) {
                files.add(injectGridFSInstance(cursor.next()));
            }
        } finally {
            cursor.close();
        }
        return Collections.unmodifiableList(files);
    }

    private GridFSDBFile injectGridFSInstance(final Object o) {
        if (o == null) {
            return null;
        }

        if (!(o instanceof GridFSDBFile)) {
            throw new IllegalArgumentException("somehow didn't get a GridFSDBFile");
        }

        GridFSDBFile f = (GridFSDBFile) o;
        f.fs = this;
        return f;
    }

    /**
     * Removes the file matching the given id.
     *
     * @param id the id of the file to be removed
     * @throws com.mongodb.MongoException if the operation fails
     */
    public void remove(final ObjectId id) {
        if (id == null) {
            throw new IllegalArgumentException("file id can not be null");
        }

        filesCollection.remove(new BasicDBObject("_id", id));
        chunksCollection.remove(new BasicDBObject("files_id", id));
    }

    /**
     * Removes all files matching the given filename.
     *
     * @param filename the name of the file to be removed
     * @throws com.mongodb.MongoException if the operation fails
     */
    public void remove(final String filename) {
        if (filename == null) {
            throw new IllegalArgumentException("filename can not be null");
        }

        remove(new BasicDBObject("filename", filename));
    }

    /**
     * Removes all files matching the given query.
     *
     * @param query filter to apply
     * @throws com.mongodb.MongoException if the operation fails
     */
    public void remove(final DBObject query) {
        if (query == null) {
            throw new IllegalArgumentException("query can not be null");
        }

        for (final GridFSDBFile f : find(query)) {
            f.remove();
        }
    }

    /**
     * Creates a file entry. After calling this method, you have to call {@link com.mongodb.gridfs.GridFSInputFile#save()}.
     *
     * @param data the file's data
     * @return a gridfs input file
     */
    public GridFSInputFile createFile(final byte[] data) {
        return createFile(new ByteArrayInputStream(data), true);
    }

    /**
     * Creates a file entry. After calling this method, you have to call {@link com.mongodb.gridfs.GridFSInputFile#save()}.
     *
     * @param file the file object
     * @return a GridFS input file
     * @throws IOException if there are problems reading {@code file}
     */
    public GridFSInputFile createFile(final File file) throws IOException {
        return createFile(new FileInputStream(file), file.getName(), true);
    }

    /**
     * Creates a file entry. After calling this method, you have to call {@link com.mongodb.gridfs.GridFSInputFile#save()}.
     *
     * @param in an inputstream containing the file's data
     * @return a gridfs input file
     */
    public GridFSInputFile createFile(final InputStream in) {
        return createFile(in, null);
    }

    /**
     * Creates a file entry. After calling this method, you have to call {@link com.mongodb.gridfs.GridFSInputFile#save()}.
     *
     * @param in                   an inputstream containing the file's data
     * @param closeStreamOnPersist indicate the passed in input stream should be closed once the data chunk persisted
     * @return a gridfs input file
     */
    public GridFSInputFile createFile(final InputStream in, final boolean closeStreamOnPersist) {
        return createFile(in, null, closeStreamOnPersist);
    }

    /**
     * Creates a file entry. After calling this method, you have to call {@link com.mongodb.gridfs.GridFSInputFile#save()}.
     *
     * @param in       an inputstream containing the file's data
     * @param filename the file name as stored in the db
     * @return a gridfs input file
     */
    public GridFSInputFile createFile(final InputStream in, final String filename) {
        return new GridFSInputFile(this, in, filename);
    }

    /**
     * Creates a file entry. After calling this method, you have to call {@link com.mongodb.gridfs.GridFSInputFile#save()}.
     *
     * @param in                   an inputstream containing the file's data
     * @param filename             the file name as stored in the db
     * @param closeStreamOnPersist indicate the passed in input stream should be closed once the data chunk persisted
     * @return a gridfs input file
     */
    public GridFSInputFile createFile(final InputStream in, final String filename, final boolean closeStreamOnPersist) {
        return new GridFSInputFile(this, in, filename, closeStreamOnPersist);
    }

    /**
     * Creates a file entry.
     *
     * @param filename the file name as stored in the db
     * @return a gridfs input file
     * @see GridFS#createFile()
     */
    public GridFSInputFile createFile(final String filename) {
        return new GridFSInputFile(this, filename);
    }

    /**
     * This method creates an empty {@link GridFSInputFile} instance. On this instance an {@link java.io.OutputStream} can be obtained using
     * the {@link GridFSInputFile#getOutputStream()} method. You can still call {@link GridFSInputFile#setContentType(String)} and {@link
     * GridFSInputFile#setFilename(String)}. The file will be completely written and closed after calling the {@link
     * java.io.OutputStream#close()} method on the output stream.
     *
     * @return GridFS file handle instance.
     */
    public GridFSInputFile createFile() {
        return new GridFSInputFile(this);
    }

    /**
     * Gets the bucket name used in the collection's namespace. Default value is 'fs'.
     *
     * @return the name of the file bucket
     */
    public String getBucketName() {
        return bucketName;
    }

    /**
     * Gets the database used.
     *
     * @return the database
     */
    public DB getDB() {
        return database;
    }

    /**
     * Gets the {@link DBCollection} in which the file's metadata is stored.
     *
     * @return the collection
     */
    protected DBCollection getFilesCollection() {
        return filesCollection;
    }

    /**
     * Gets the {@link DBCollection} in which the binary chunks are stored.
     *
     * @return the collection
     */
    protected DBCollection getChunksCollection() {
        return chunksCollection;
    }

}


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

import com.mongodb.ConnectionString;
import com.mongodb.MongoClientSettings;
import com.mongodb.MongoDriverInformation;
import com.mongodb.lang.Nullable;
import com.mongodb.client.internal.MongoClientImpl;

/**
 * A factory for {@link MongoClient} instances.  Use of this class is now the recommended way to connect to MongoDB via the Java driver.
 *
 * @see MongoClient
 * @since 3.7
 */
public final class MongoClients {

    /**
     * Creates a new client with the default connection string "mongodb://localhost".
     *
     * @return the client
     */
    public static MongoClient create() {
        return create(new ConnectionString("mongodb://localhost"));
    }

    /**
     * Create a new client with the given client settings.
     *
     * @param settings the settings
     * @return the client
     */
    public static MongoClient create(final MongoClientSettings settings) {
        return create(settings, null);
    }

    /**
     * Create a new client with the given connection string as if by a call to {@link #create(ConnectionString)}.
     *
     * @param connectionString the connection
     * @return the client
     * @see #create(ConnectionString)
     */
    public static MongoClient create(final String connectionString) {
        return create(new ConnectionString(connectionString));
    }

    /**
     * Create a new client with the given connection string.
     *

     * For each of the settings classed configurable via {@link MongoClientSettings}, the connection string is applied by calling the
     * {@code applyConnectionString} method on an instance of setting's builder class, building the setting, and adding it to an instance of
     * {@link com.mongodb.MongoClientSettings.Builder}.
     *

     *

     * The connection string's stream type is then applied by setting the
     * {@link com.mongodb.connection.StreamFactory} to an instance of NettyStreamFactory,
     *

     *
     * @param connectionString the settings
     * @return the client
     * @throws IllegalArgumentException if the connection string's stream type is not one of "netty" or "nio2"
     *
     * @see com.mongodb.MongoClientSettings.Builder#applyConnectionString(ConnectionString)
     */
    public static MongoClient create(final ConnectionString connectionString) {
        return create(connectionString, null);
    }

    /**
     * Create a new client with the given connection string.
     *
     *
Note: Intended for driver and library authors to associate extra driver metadata with the connections.

     *
     * @param connectionString       the settings
     * @param mongoDriverInformation any driver information to associate with the MongoClient
     * @return the client
     * @throws IllegalArgumentException if the connection string's stream type is not one of "netty" or "nio2"
     * @see MongoClients#create(ConnectionString)
     */
    public static MongoClient create(final ConnectionString connectionString,
                                     @Nullable final MongoDriverInformation mongoDriverInformation) {
        return create(MongoClientSettings.builder().applyConnectionString(connectionString).build(), mongoDriverInformation);
    }

    /**
     * Creates a new client with the given client settings.
     *
     *
Note: Intended for driver and library authors to associate extra driver metadata with the connections.

     *
     * @param settings               the settings
     * @param mongoDriverInformation any driver information to associate with the MongoClient
     * @return the client
     */
    public static MongoClient create(final MongoClientSettings settings, @Nullable final MongoDriverInformation mongoDriverInformation) {
        MongoDriverInformation.Builder builder = mongoDriverInformation == null ? MongoDriverInformation.builder()
                : MongoDriverInformation.builder(mongoDriverInformation);
        return new MongoClientImpl(settings, builder.driverName("sync").build());
    }

    private MongoClients() {
    }
}


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

// BasicBSONList.java

package org.bson.types;

import org.bson.BSONObject;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;

/**
 *
Utility class to allow array {@code DBObject}s to be created.
 Note: MongoDB will also create arrays from {@code java.util
 * .List}s.

 *

 * BSONObject obj = new BasicBSONList();
 * obj.put( "0", value1 );
 * obj.put( "4", value2 );
 * obj.put( 2, value3 );
 *

 *
This simulates the array [ value1, null, value3, null, value2 ] by creating the {@code DBObject} {@code { "0" : value1, "1" : null,
 * "2" : value3, "3" : null, "4" : value2 }}.

 *
 *
BasicBSONList only supports numeric keys.  Passing strings that cannot be converted to ints
 * will cause an IllegalArgumentException.

 *

 * BasicBSONList list = new BasicBSONList();
 * list.put("1", "bar"); // ok
 * list.put("1E1", "bar"); // throws exception
 *

 */
@SuppressWarnings("rawtypes")
public class BasicBSONList extends ArrayList<Object> implements BSONObject {

    private static final long serialVersionUID = -4415279469780082174L;

    /**
     * Puts a value at an index. For interface compatibility.  Must be passed a String that is parsable to an int.
     *
     * @param key the index at which to insert the value
     * @param v   the value to insert
     * @return the value
     * @throws IllegalArgumentException if {@code key} cannot be parsed into an {@code int}
     */
    @Override
    public Object put(final String key, final Object v) {
        return put(_getInt(key), v);
    }

    /**
     * Puts a value at an index. This will fill any unset indexes less than {@code index} with {@code null}.
     *
     * @param key the index at which to insert the value
     * @param value   the value to insert
     * @return the value
     */
    public Object put(final int key, final Object value) {
        while (key >= size()) {
            add(null);
        }
        set(key, value);
        return value;
    }

    @SuppressWarnings("unchecked")
    @Override
    public void putAll(final Map m) {
        for (final Map.Entry entry : (Set<Map.Entry>) m.entrySet()) {
            put(entry.getKey().toString(), entry.getValue());
        }
    }

    @Override
    public void putAll(final BSONObject o) {
        for (final String k : o.keySet()) {
            put(k, o.get(k));
        }
    }

    /**
     * Gets a value at an index. For interface compatibility.  Must be passed a String that is parsable to an int.
     *
     * @param key the index
     * @return the value, if found, or null
     * @throws IllegalArgumentException if {@code key} cannot be parsed into an {@code int}
     */
    public Object get(final String key) {
        int i = _getInt(key);
        if (i < 0) {
            return null;
        }
        if (i >= size()) {
            return null;
        }
        return get(i);
    }

    @Override
    public Object removeField(final String key) {
        int i = _getInt(key);
        if (i < 0) {
            return null;
        }
        if (i >= size()) {
            return null;
        }
        return remove(i);
    }

    @Override
    @Deprecated
    public boolean containsKey(final String key) {
        return containsField(key);
    }

    @Override
    public boolean containsField(final String key) {
        int i = _getInt(key, false);
        if (i < 0) {
            return false;
        }
        return i >= 0 && i < size();
    }

    @Override
    public Set<String> keySet() {
        return new StringRangeSet(size());
    }

    @Override
    @SuppressWarnings("unchecked")
    public Map toMap() {
        Map m = new HashMap();
        Iterator i = this.keySet().iterator();
        while (i.hasNext()) {
            Object s = i.next();
            m.put(s, this.get(String.valueOf(s)));
        }
        return m;
    }

    int _getInt(final String s) {
        return _getInt(s, true);
    }

    int _getInt(final String s, final boolean err) {
        try {
            return Integer.parseInt(s);
        } catch (Exception e) {
            if (err) {
                throw new IllegalArgumentException("BasicBSONList can only work with numeric keys, not: [" + s + "]");
            }
            return -1;
        }
    }
}


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

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import static java.lang.String.format;
import static org.bson.assertions.Assertions.notNull;
import static org.bson.codecs.pojo.PropertyReflectionUtils.isGetter;

final class TypeData<T> implements TypeWithTypeParameters<T> {
    private final Class<T> type;
    private final List<TypeData<?>> typeParameters;

    /**
     * Creates a new builder for ClassTypeData
     *
     * @param type the class for the type
     * @param <T> the type
     * @return the builder
     */
    public static <T> Builder<T> builder(final Class<T> type) {
        return new Builder<T>(notNull("type", type));
    }

    public static TypeData<?> newInstance(final Method method) {
        if (isGetter(method)) {
            return newInstance(method.getGenericReturnType(), method.getReturnType());
        } else {
            return newInstance(method.getGenericParameterTypes()[0], method.getParameterTypes()[0]);
        }
    }

    public static TypeData<?> newInstance(final Field field) {
        return newInstance(field.getGenericType(), field.getType());
    }

    public static <T> TypeData<T> newInstance(final Type genericType, final Class<T> clazz) {
        TypeData.Builder<T> builder = TypeData.builder(clazz);
        if (genericType instanceof ParameterizedType) {
            ParameterizedType pType = (ParameterizedType) genericType;
            for (Type argType : pType.getActualTypeArguments()) {
                getNestedTypeData(builder, argType);
            }
        }
        return builder.build();
    }

    @SuppressWarnings({"unchecked", "rawtypes"})
    private static <T> void getNestedTypeData(final TypeData.Builder<T> builder, final Type type) {
        if (type instanceof ParameterizedType) {
            ParameterizedType pType = (ParameterizedType) type;
            TypeData.Builder paramBuilder = TypeData.builder((Class) pType.getRawType());
            for (Type argType : pType.getActualTypeArguments()) {
                getNestedTypeData(paramBuilder, argType);
            }
            builder.addTypeParameter(paramBuilder.build());
        } else if (type instanceof TypeVariable) {
            builder.addTypeParameter(TypeData.builder(Object.class).build());
        } else if (type instanceof Class) {
            builder.addTypeParameter(TypeData.builder((Class) type).build());
        }
    }

    /**
     * @return the class this {@code ClassTypeData} represents
     */
    @Override
    public Class<T> getType() {
        return type;
    }

    /**
     * @return the type parameters for the class
     */
    @Override
    public List<TypeData<?>> getTypeParameters() {
        return typeParameters;
    }

    /**
     * A builder for TypeData
     *
     * @param <T> the main type
     */
    public static final class Builder<T> {
        private final Class<T> type;
        private final List<TypeData<?>> typeParameters = new ArrayList<TypeData<?>>();

        private Builder(final Class<T> type) {
            this.type = type;
        }

        /**
         * Adds a type parameter
         *
         * @param typeParameter the type parameter
         * @param <S> the type of the type parameter
         * @return this
         */
        public <S> Builder<T> addTypeParameter(final TypeData<S> typeParameter) {
            typeParameters.add(notNull("typeParameter", typeParameter));
            return this;
        }

        /**
         * Adds multiple type parameters
         *
         * @param typeParameters the type parameters
         * @return this
         */
        public Builder<T> addTypeParameters(final List<TypeData<?>> typeParameters) {
            notNull("typeParameters", typeParameters);
            for (TypeData<?> typeParameter : typeParameters) {
                addTypeParameter(typeParameter);
            }
            return this;
        }

        /**
         * @return the class type data
         */
        public TypeData<T> build() {
            return new TypeData<T>(type, Collections.unmodifiableList(typeParameters));
        }
    }

    @Override
    public String toString() {
        String typeParams = typeParameters.isEmpty() ? ""
                : ", typeParameters=[" + nestedTypeParameters(typeParameters) + "]";
        return "TypeData{"
                + "type=" + type.getSimpleName()
                + typeParams
                + "}";
    }

    private static String nestedTypeParameters(final List<TypeData<?>> typeParameters) {
        StringBuilder builder = new StringBuilder();
        int count = 0;
        int last = typeParameters.size();
        for (TypeData<?> typeParameter : typeParameters) {
            count++;
            builder.append(typeParameter.getType().getSimpleName());
            if (!typeParameter.getTypeParameters().isEmpty()) {
                builder.append(format("<%s>", nestedTypeParameters(typeParameter.getTypeParameters())));
            }
            if (count < last) {
                builder.append(", ");
            }
        }
        return builder.toString();
    }

    @Override
    public boolean equals(final Object o) {
        if (this == o) {
            return true;
        }
        if (!(o instanceof TypeData)) {
            return false;
        }

        TypeData<?> that = (TypeData<?>) o;

        if (!getType().equals(that.getType())) {
            return false;
        }
        if (!getTypeParameters().equals(that.getTypeParameters())) {
            return false;
        }

        return true;
    }

    @Override
    public int hashCode() {
        int result = getType().hashCode();
        result = 31 * result + getTypeParameters().hashCode();
        return result;
    }

    private TypeData(final Class<T> type, final List<TypeData<?>> typeParameters) {
        this.type = boxType(type);
        this.typeParameters = typeParameters;
    }

    boolean isAssignableFrom(final Class<?> cls) {
        return type.isAssignableFrom(boxType(cls));
    }

    @SuppressWarnings("unchecked")
    private <S> Class<S> boxType(final Class<S> clazz) {
        if (clazz.isPrimitive()) {
            return (Class<S>) PRIMITIVE_CLASS_MAP.get(clazz);
        } else {
            return clazz;
        }
    }

    private static final Map<Class<?>, Class<?>> PRIMITIVE_CLASS_MAP;
    static {
        Map<Class<?>, Class<?>> map = new HashMap<Class<?>, Class<?>>();
        map.put(boolean.class, Boolean.class);
        map.put(byte.class, Byte.class);
        map.put(char.class, Character.class);
        map.put(double.class, Double.class);
        map.put(float.class, Float.class);
        map.put(int.class, Integer.class);
        map.put(long.class, Long.class);
        map.put(short.class, Short.class);
        PRIMITIVE_CLASS_MAP = map;
    }
}


/*
 * 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.client.MongoClient;
import com.mongodb.client.MongoClients;
import com.mongodb.client.MongoCollection;
import com.mongodb.client.MongoDatabase;
import com.mongodb.client.model.CreateCollectionOptions;
import com.mongodb.client.model.Indexes;
import com.mongodb.client.model.TextSearchOptions;
import org.bson.Document;
import org.bson.conversions.Bson;

import static com.mongodb.client.model.Filters.text;

/**
 * The QuickTourAdmin code example
 */
public class QuickTourAdmin {
    /**
     * Run this main method to see the output of this quick example.
     *
     * @param args takes an optional single argument for the connection string
     */
    public static void main(final String[] args) {
        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");

        database.drop();

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

        // drop all the data in it
        collection.drop();

        // getting a list of databases
        for (String name: mongoClient.listDatabaseNames()) {
            System.out.println(name);
        }

        // drop a database
        mongoClient.getDatabase("databaseToBeDropped").drop();

        // create a collection
        database.createCollection("cappedCollection", new CreateCollectionOptions().capped(true).sizeInBytes(0x100000));

        for (String name : database.listCollectionNames()) {
            System.out.println(name);
        }

        // drop a collection:
        collection.drop();

        // create an ascending index on the "i" field
        collection.createIndex(Indexes.ascending("i"));

        // list the indexes on the collection
        for (final Document index : collection.listIndexes()) {
            System.out.println(index.toJson());
        }

        // create a text index on the "content" field
        collection.createIndex(Indexes.text("content"));

        collection.insertOne(new Document("_id", 0).append("content", "textual content"));
        collection.insertOne(new Document("_id", 1).append("content", "additional content"));
        collection.insertOne(new Document("_id", 2).append("content", "irrelevant content"));

        // Find using the text index
        long matchCount = collection.countDocuments(text("textual content -irrelevant"));
        System.out.println("Text search matches: " + matchCount);

        // Find using the $language operator
        Bson textSearch = text("textual content -irrelevant", new TextSearchOptions().language("english"));
        matchCount = collection.countDocuments(textSearch);
        System.out.println("Text search matches (english): " + matchCount);

        // Find the highest scoring match
        Document projection = new Document("score", new Document("$meta", "textScore"));
        Document myDoc = collection.find(textSearch).projection(projection).first();
        System.out.println("Highest scoring document: " + myDoc.toJson());

        // Run a command
        Document buildInfo = database.runCommand(new Document("buildInfo", 1));
        System.out.println(buildInfo);

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


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

final class UuidCodecHelper {
    // reverse elements in the subarray data[start:start+length]
    public static void reverseByteArray(final byte[] data, final int start, final int length) {
        for (int left = start, right = start + length - 1; left < right; left++, right--) {
            // swap the values at the left and right indices
            byte temp = data[left];
            data[left]  = data[right];
            data[right] = temp;
        }
    }

    private UuidCodecHelper() {
    }
}


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

import com.mongodb.MongoCommandException;
import com.mongodb.MongoNamespace;
import com.mongodb.ReadPreference;
import com.mongodb.async.AsyncBatchCursor;
import com.mongodb.async.SingleResultCallback;
import com.mongodb.binding.AsyncConnectionSource;
import com.mongodb.binding.AsyncReadBinding;
import com.mongodb.binding.ConnectionSource;
import com.mongodb.binding.ReadBinding;
import com.mongodb.connection.AsyncConnection;
import com.mongodb.connection.Connection;
import com.mongodb.connection.ConnectionDescription;
import com.mongodb.connection.QueryResult;
import com.mongodb.connection.ServerDescription;
import com.mongodb.operation.CommandOperationHelper.CommandReadTransformer;
import com.mongodb.operation.CommandOperationHelper.CommandReadTransformerAsync;
import com.mongodb.operation.OperationHelper.CallableWithSource;
import org.bson.BsonDocument;
import org.bson.BsonInt64;
import org.bson.BsonString;
import org.bson.codecs.Codec;
import org.bson.codecs.Decoder;

import java.util.concurrent.TimeUnit;

import static com.mongodb.ReadPreference.primary;
import static com.mongodb.assertions.Assertions.notNull;
import static com.mongodb.connection.ServerType.SHARD_ROUTER;
import static com.mongodb.internal.async.ErrorHandlingResultCallback.errorHandlingCallback;
import static com.mongodb.operation.CommandOperationHelper.CommandCreator;
import static com.mongodb.operation.CommandOperationHelper.executeCommandWithConnection;
import static com.mongodb.operation.CommandOperationHelper.executeCommandAsyncWithConnection;
import static com.mongodb.operation.CommandOperationHelper.isNamespaceError;
import static com.mongodb.operation.CommandOperationHelper.rethrowIfNotNamespaceError;
import static com.mongodb.operation.CursorHelper.getCursorDocumentFromBatchSize;
import static com.mongodb.operation.OperationHelper.AsyncCallableWithConnectionAndSource;
import static com.mongodb.operation.OperationHelper.LOGGER;
import static com.mongodb.operation.OperationHelper.createEmptyAsyncBatchCursor;
import static com.mongodb.operation.OperationHelper.createEmptyBatchCursor;
import static com.mongodb.operation.OperationHelper.cursorDocumentToAsyncBatchCursor;
import static com.mongodb.operation.OperationHelper.cursorDocumentToBatchCursor;
import static com.mongodb.operation.OperationHelper.releasingCallback;
import static com.mongodb.internal.operation.ServerVersionHelper.serverIsAtLeastVersionThreeDotZero;
import static com.mongodb.operation.OperationHelper.withConnection;
import static com.mongodb.operation.OperationHelper.withConnectionSource;

/**
 * An operation that lists the indexes that have been created on a collection.  For flexibility, the type of each document returned is
 * generic.
 *
 * @param <T> the operations result type.
 * @since 3.0
 * @mongodb.driver.manual reference/command/listIndexes/ List indexes
 */
@Deprecated
public class ListIndexesOperation<T> implements AsyncReadOperation<AsyncBatchCursor<T>>, ReadOperation<BatchCursor<T>> {
    private final MongoNamespace namespace;
    private final Decoder<T> decoder;
    private boolean retryReads;
    private int batchSize;
    private long maxTimeMS;

    /**
     * Construct a new instance.
     *
     * @param namespace the database and collection namespace for the operation.
     * @param decoder   the decoder for the result documents.
     */
    public ListIndexesOperation(final MongoNamespace namespace, final Decoder<T> decoder) {
        this.namespace = notNull("namespace", namespace);
        this.decoder = notNull("decoder", decoder);
    }

    /**
     * Gets the number of documents to return per batch.
     *
     * @return the batch size
     * @mongodb.server.release 3.0
     * @mongodb.driver.manual reference/method/cursor.batchSize/#cursor.batchSize Batch Size
     */
    public Integer getBatchSize() {
        return batchSize;
    }

    /**
     * Sets the number of documents to return per batch.
     *
     * @param batchSize the batch size
     * @return this
     * @mongodb.server.release 3.0
     * @mongodb.driver.manual reference/method/cursor.batchSize/#cursor.batchSize Batch Size
     */
    public ListIndexesOperation<T> batchSize(final int batchSize) {
        this.batchSize = batchSize;
        return this;
    }

    /**
     * Gets the maximum execution time on the server for this operation.  The default is 0, which places no limit on the execution time.
     *
     * @param timeUnit the time unit to return the result in
     * @return the maximum execution time in the given time unit
     * @mongodb.driver.manual reference/operator/meta/maxTimeMS/ Max Time
     */
    public long getMaxTime(final TimeUnit timeUnit) {
        notNull("timeUnit", timeUnit);
        return timeUnit.convert(maxTimeMS, TimeUnit.MILLISECONDS);
    }

    /**
     * Sets the maximum execution time on the server for this operation.
     *
     * @param maxTime  the max time
     * @param timeUnit the time unit, which may not be null
     * @return this
     * @mongodb.driver.manual reference/operator/meta/maxTimeMS/ Max Time
     */
    public ListIndexesOperation<T> maxTime(final long maxTime, final TimeUnit timeUnit) {
        notNull("timeUnit", timeUnit);
        this.maxTimeMS = TimeUnit.MILLISECONDS.convert(maxTime, timeUnit);
        return this;
    }

    /**
     * Enables retryable reads if a read fails due to a network error.
     *
     * @param retryReads true if reads should be retried
     * @return this
     * @since 3.11
     */
    public ListIndexesOperation<T> retryReads(final boolean retryReads) {
        this.retryReads = retryReads;
        return this;
    }

    /**
     * Gets the value for retryable reads. The default is true.
     *
     * @return the retryable reads value
     * @since 3.11
     */
    public boolean getRetryReads() {
        return retryReads;
    }

    @Override
    public BatchCursor<T> execute(final ReadBinding binding) {
        return withConnectionSource(binding, new CallableWithSource<BatchCursor<T>>() {
            @Override
            public BatchCursor<T> call(final ConnectionSource source) {
                Connection connection = source.getConnection();
                if (serverIsAtLeastVersionThreeDotZero(connection.getDescription())) {
                    try {
                        return executeCommandWithConnection(binding, source, namespace.getDatabaseName(), getCommandCreator(),
                                createCommandDecoder(), transformer(), retryReads, connection);
                    } catch (MongoCommandException e) {
                        return rethrowIfNotNamespaceError(e, createEmptyBatchCursor(namespace, decoder,
                                                                                source.getServerDescription().getAddress(), batchSize));
                    }
                } else {
                    try {
                        return new QueryBatchCursor<T>(connection.query(getIndexNamespace(),
                                asQueryDocument(connection.getDescription(), binding.getReadPreference()), null, 0, 0, batchSize,
                                binding.getReadPreference().isSlaveOk(), false, false, false, false, false, decoder), 0, batchSize, decoder,
                                source);
                    } finally {
                        connection.release();
                    }
                }
            }
        });
    }

    @Override
    public void executeAsync(final AsyncReadBinding binding, final SingleResultCallback<AsyncBatchCursor<T>> callback) {
        withConnection(binding, new AsyncCallableWithConnectionAndSource() {
            @Override
            public void call(final AsyncConnectionSource source, final AsyncConnection connection, final Throwable t) {
                final SingleResultCallback<AsyncBatchCursor<T>> errHandlingCallback = errorHandlingCallback(callback, LOGGER);
                if (t != null) {
                    errHandlingCallback.onResult(null, t);
                } else {
                    if (serverIsAtLeastVersionThreeDotZero(connection.getDescription())) {
                        executeCommandAsyncWithConnection(binding, source, namespace.getDatabaseName(), getCommandCreator(),
                                createCommandDecoder(), asyncTransformer(), retryReads, connection,
                                new SingleResultCallback<AsyncBatchCursor<T>>() {
                                    @Override
                                    public void onResult(final AsyncBatchCursor<T> result,
                                                         final Throwable t) {
                                        if (t != null && !isNamespaceError(t)) {
                                            errHandlingCallback.onResult(null, t);
                                        } else {
                                            errHandlingCallback.onResult(result != null ? result : emptyAsyncCursor(source), null);
                                        }
                                    }
                                });
                    } else {
                        final SingleResultCallback<AsyncBatchCursor<T>> wrappedCallback = releasingCallback(errHandlingCallback,
                                source, connection);
                        connection.queryAsync(getIndexNamespace(),
                                asQueryDocument(connection.getDescription(), binding.getReadPreference()), null, 0, 0, batchSize,
                                binding.getReadPreference().isSlaveOk(), false, false, false, false, false, decoder,
                                new SingleResultCallback<QueryResult<T>>() {
                                    @Override
                                    public void onResult(final QueryResult<T> result, final Throwable t) {
                                        if (t != null) {
                                            wrappedCallback.onResult(null, t);
                                        } else {
                                            wrappedCallback.onResult(new AsyncQueryBatchCursor<T>(result, 0, batchSize, 0, decoder, source,
                                                                                connection),
                                                    null);
                                        }
                                    }
                                });
                    }
                }
            }
        });
    }

    private AsyncBatchCursor<T> emptyAsyncCursor(final AsyncConnectionSource source) {
        return createEmptyAsyncBatchCursor(namespace, source.getServerDescription().getAddress());
    }

    private BsonDocument asQueryDocument(final ConnectionDescription connectionDescription, final ReadPreference readPreference) {
        BsonDocument document = new BsonDocument("$query", new BsonDocument("ns", new BsonString(namespace.getFullName())));
        if (maxTimeMS > 0) {
            document.put("$maxTimeMS", new BsonInt64(maxTimeMS));
        }
        if (connectionDescription.getServerType() == SHARD_ROUTER && !readPreference.equals(primary())) {
            document.put("$readPreference", readPreference.toDocument());
        }
        return document;
    }

    private MongoNamespace getIndexNamespace() {
        return new MongoNamespace(namespace.getDatabaseName(), "system.indexes");
    }

    private CommandCreator getCommandCreator() {
        return new CommandCreator() {
            @Override
            public BsonDocument create(final ServerDescription serverDescription, final ConnectionDescription connectionDescription) {
                return getCommand();
            }
        };
    }

    private BsonDocument getCommand() {
        BsonDocument command = new BsonDocument("listIndexes", new BsonString(namespace.getCollectionName()))
                .append("cursor", getCursorDocumentFromBatchSize(batchSize == 0 ? null : batchSize));
        if (maxTimeMS > 0) {
            command.put("maxTimeMS", new BsonInt64(maxTimeMS));
        }
        return command;
    }

    private CommandReadTransformer<BsonDocument, BatchCursor<T>> transformer() {
        return new CommandReadTransformer<BsonDocument, BatchCursor<T>>() {
            @Override
            public BatchCursor<T> apply(final BsonDocument result, final ConnectionSource source, final Connection connection) {
                return cursorDocumentToBatchCursor(result.getDocument("cursor"), decoder, source, batchSize);
            }
        };
    }

    private CommandReadTransformerAsync<BsonDocument, AsyncBatchCursor<T>> asyncTransformer() {
        return new CommandReadTransformerAsync<BsonDocument, AsyncBatchCursor<T>>() {
            @Override
            public AsyncBatchCursor<T> apply(final BsonDocument result, final AsyncConnectionSource source,
                                             final AsyncConnection connection) {
                return cursorDocumentToAsyncBatchCursor(result.getDocument("cursor"), decoder, source, connection, batchSize);
            }
        };
    }

    private Codec<BsonDocument> createCommandDecoder() {
        return CommandResultDocumentCodec.create(decoder, "firstBatch");
    }
}


/*
 * 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 java.util.Collections;
import java.util.HashMap;
import java.util.Map;

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

/**
 * Metadata describing a compressor to use for sending and receiving messages to a MongoDB server.
 *
 * @since 3.6
 * @mongodb.server.release 3.4
 */
public final class MongoCompressor {

    /**
     * The property key for defining the compression level.
     */
    public static final String LEVEL = "LEVEL";

    private final String name;
    private final Map<String, Object> properties;

    /**
     * Create an instance for snappy compression.
     *
     * @return A compressor based on the snappy compression algorithm
     * @mongodb.server.release 3.4
     */
    public static MongoCompressor createSnappyCompressor() {
        return new MongoCompressor("snappy");
    }

    /**
     * Create an instance for zlib compression.
     *
     * @return A compressor based on the zlib compression algorithm
     * @mongodb.server.release 3.6
     */
    public static MongoCompressor createZlibCompressor() {
        return new MongoCompressor("zlib");
    }

    /**
     * Create an instance for zstd compression.
     *
     * @return A compressor based on the zstd compression algorithm
     * @mongodb.server.release 4.2
     */
    public static MongoCompressor createZstdCompressor() {
        return new MongoCompressor("zstd");
    }

    /**
     * Gets the name of the compressor.
     *
     * @return the non-null compressor name
     */
    public String getName() {
        return name;
    }

    /**
     * Gets the property with the given key.
     *
     * @param key the key
     * @param defaultValue the default value
     * @param <T> the property value type
     * @return the property value, or the default value if the property is not defined
     */
    @SuppressWarnings("unchecked")
    @Nullable
    public <T> T getProperty(final String key, final T defaultValue) {
        notNull("key", key);

        T value = (T) properties.get(key.toLowerCase());
        return (value == null && !properties.containsKey(key)) ? defaultValue : value;
    }

    /**
     * Gets the property with the given key.
     *
     * @param key the key
     * @param defaultValue the default value
     * @param <T> the property value type
     * @return the property value, or the default value if the property is not defined
     * @throws IllegalArgumentException if the value and default value are null
     * @since 3.7
     */
    public <T> T getPropertyNonNull(final String key, final T defaultValue) {
        T value = getProperty(key, defaultValue);
        if (value == null) {
            throw new IllegalArgumentException();
        }
        return value;
    }

    /**
     * Creates a new compressor from this compressor with the given property added to it.
     *
     * @param key the property key
     * @param value the property value
     * @param <T> the property value type
     * @return the new compressor
     */
    public <T> MongoCompressor withProperty(final String key, final T value) {
        return new MongoCompressor(this, key, value);
    }

    private MongoCompressor(final String name) {
        this.name = name;
        properties = Collections.emptyMap();
    }

    private <T> MongoCompressor(final MongoCompressor from, final String propertyKey, final T propertyValue) {
        notNull("propertyKey", propertyKey);

        this.name = from.name;
        this.properties = new HashMap<String, Object>(from.properties);
        this.properties.put(propertyKey.toLowerCase(), propertyValue);
    }

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

        MongoCompressor that = (MongoCompressor) o;

        if (!name.equals(that.name)) {
            return false;
        }
        return properties.equals(that.properties);
    }

    @Override
    public int hashCode() {
        int result = name.hashCode();
        result = 31 * result + properties.hashCode();
        return result;
    }

    @Override
    public String toString() {
        return "MongoCompressor{"
                       + "name='" + name + '\''
                       + ", properties=" + properties
                       + '}';
    }
}


/*
 * 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.connection.Cluster;
import com.mongodb.connection.ClusterDescription;
import com.mongodb.connection.ServerDescription;
import com.mongodb.event.ClusterListener;
import com.mongodb.lang.Nullable;

import java.util.List;

/**
 * Keeps replica set status.
 *
 * @deprecated Prefer {@link MongoClientOptions.Builder#addClusterListener(ClusterListener)}
 */
@Deprecated
public class ReplicaSetStatus {

    private final Cluster cluster;

    ReplicaSetStatus(final Cluster cluster) {
        this.cluster = cluster;
    }

    /**
     * Get the name of the replica set.
     *
     * @return the name of the replica set.
     */
    @SuppressWarnings("deprecation")
    @Nullable
    public String getName() {
        List<ServerDescription> any = getClusterDescription().getAnyPrimaryOrSecondary();
        return any.isEmpty() ? null : any.get(0).getSetName();
    }

    /**
     * Gets the ServerAddress of the master server in this replica set.
     *
     * @return master or null if don't have one
     * @throws MongoException if there's a failure
     */
    @SuppressWarnings("deprecation")
    @Nullable
    public ServerAddress getMaster() {
        List<ServerDescription> primaries = getClusterDescription().getPrimaries();
        return primaries.isEmpty() ? null : primaries.get(0).getAddress();
    }

    /**
     * Checks to see if a given server is the primary server in this replica set.
     *
     * @param serverAddress the server to compare
     * @return true if the given ServerAddress is the current Master/Primary
     */
    public boolean isMaster(final ServerAddress serverAddress) {
        ServerAddress masterServerAddress = getMaster();
        return masterServerAddress != null && masterServerAddress.equals(serverAddress);
    }

    /**
     * Gets the maximum size for a BSON object supported by the current master server. Note that this value may change over time depending
     * on which server is master.
     *
     * @return the maximum size, or 0 if not obtained from servers yet.
     * @throws MongoException if there's a failure
     */
    @SuppressWarnings("deprecation")
    public int getMaxBsonObjectSize() {
        List<ServerDescription> primaries = getClusterDescription().getPrimaries();
        return primaries.isEmpty() ? ServerDescription.getDefaultMaxDocumentSize() : primaries.get(0).getMaxDocumentSize();
    }

    private ClusterDescription getClusterDescription() {
        return cluster.getDescription();
    }

    @Override
    public String toString() {
        return "ReplicaSetStatus{"
               + "name=" + getName()
               + ", cluster=" + getClusterDescription()
               + '}';
    }
}