<commit_before>// ReplicaSetStatus.java

/**
 *      Copyright (C) 2008 10gen 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.util.JSON;
import org.bson.util.annotations.Immutable;
import org.bson.util.annotations.ThreadSafe;

import java.net.UnknownHostException;
import java.util.List;
import java.util.ArrayList;
import java.util.Set;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Collections;
import java.util.Map;
import java.util.HashMap;
import java.util.Random;
import java.util.Date;
import java.io.IOException;
import java.util.concurrent.atomic.AtomicReference;
import java.util.logging.Level;
import java.util.logging.Logger;

// TODO:
//  pull config to get
//  priority
//  slave delay

/**
 * Keeps replica set status.  Maintains a background thread to ping all members of the set to keep the status current.
 */
@ThreadSafe
public class ReplicaSetStatus {

	static final Logger _rootLogger = Logger.getLogger( "com.mongodb.ReplicaSetStatus" );

    ReplicaSetStatus( Mongo mongo, List<ServerAddress> initial ){
        _mongoOptions = _mongoOptionsDefaults.copy();
        _mongoOptions.socketFactory = mongo._options.socketFactory;
        _mongo = mongo;
        _updater = new Updater(initial);
    }

    void start() {
        _updater.start();
    }

    public String getName() {
        return _setName.get();
    }

    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder();
        sb.append("{replSetName: ").append(_setName.get());
        sb.append(", nextResolveTime: ").append(new Date(_updater.getNextResolveTime()).toString());
        sb.append(", members: ").append(_replicaSetHolder);
        sb.append(", updaterIntervalMS: ").append(updaterIntervalMS);
        sb.append(", updaterIntervalNoMasterMS: ").append(updaterIntervalNoMasterMS);
        sb.append(", slaveAcceptableLatencyMS: ").append(slaveAcceptableLatencyMS);
        sb.append(", inetAddrCacheMS: ").append(inetAddrCacheMS);
        sb.append(", latencySmoothFactor: ").append(latencySmoothFactor);
        sb.append("}");

        return sb.toString();
    }

    void _checkClosed(){
        if ( _closed )
            throw new IllegalStateException( "ReplicaSetStatus closed" );
    }

    /**
     * @return master or null if don't have one
     * @throws MongoException
     */
    public ServerAddress getMaster(){
        Node n = getMasterNode();
        if ( n == null )
            return null;
        return n.getServerAddress();
    }

    Node getMasterNode(){
        _checkClosed();
        return _replicaSetHolder.get().getMaster();
    }

	/**
	 * @param srv
	 *            the server to compare
	 * @return indication if the ServerAddress is the current Master/Primary
	 * @throws MongoException
	 */
	public boolean isMaster(ServerAddress srv) {
		if (srv == null)
			return false;

		return srv.equals(getMaster());
	}

    /**
     * @param tags tags map
     * @return a good secondary by tag value or null if can't find one
     */
    ServerAddress getASecondary( DBObject tags ) {
        // store the reference in local, so that it doesn't change out from under us while looping
        List<Tag> tagList = new ArrayList<Tag>();
        for ( String key : tags.keySet() ) {
            tagList.add(new Tag(key, tags.get(key).toString()));
        }
        Node node =  _replicaSetHolder.get().getASecondary(tagList);
        if (node != null) {
            return node.getServerAddress();
        }
        return null;
    }

    /**
     * @return a good secondary or null if can't find one
     */
    ServerAddress getASecondary() {
        Node node = _replicaSetHolder.get().getASecondary();
        if (node == null) {
            return null;
        }
        return node._addr;
    }

    boolean hasServerUp() {
        for (Node node : _replicaSetHolder.get().getAll()) {
            if (node.isOk()) {
                return true;
            }
        }
        return false;
    }

    // Simple abstraction over a volatile ReplicaSet reference that starts as null.  The get method blocks until members
    // is not null. The set method notifies all, thus waking up all getters.
    @ThreadSafe
    static class ReplicaSetHolder {
       private volatile ReplicaSet members;

       // blocks until replica set is set.
       synchronized ReplicaSet get() {
           while (members == null) {
               try {
                   wait();
               }
               catch (InterruptedException e) {
                   throw new MongoException("Interrupted while waiting for next update to replica set status", e);
               }
           }
           return members;
       }

       // set the replica set to a non-null value and notifies all threads waiting.
       synchronized void set(ReplicaSet members) {
           if (members == null) {
               throw new IllegalArgumentException("members can not be null");
           }
           this.members = members;
           notifyAll();
       }

       // blocks until the replica set is set again
       synchronized void waitForNextUpdate() {
           try {
               wait();
           }
           catch (InterruptedException e) {
              throw new MongoException("Interrupted while waiting for next update to replica set status", e);
           }
       }

        public synchronized void close() {
            this.members = null;
            notifyAll();
        }

        public String toString() {
            ReplicaSet cur = this.members;
            if (cur != null) {
                return cur.toString();
            }
            return "none";
        }
    }

    // Immutable snapshot state of a replica set. Since the nodes don't change state, this class pre-computes the list
    // of good secondaries so that choosing a random good secondary is dead simple
    @Immutable
    static class ReplicaSet {
        final List<Node> all;
        final Random random;
        final List<Node> goodSecondaries;
        final Map<Tag, List<Node>> goodSecondariesByTagMap;
        final Node master;

        public ReplicaSet(List<Node> nodeList, Random random, int acceptableLatencyMS) {
            this.random = random;
            this.all = Collections.unmodifiableList(new ArrayList<Node>(nodeList));
            this.goodSecondaries =
                    Collections.unmodifiableList(calculateGoodSecondaries(all, calculateBestPingTime(all), acceptableLatencyMS));
            Set<Tag> uniqueTags = new HashSet<Tag>();
            for (Node curNode : all) {
                for (Tag curTag : curNode._tags) {
                    uniqueTags.add(curTag);
                }
            }
            Map<Tag, List<Node>> goodSecondariesByTagMap = new HashMap<Tag, List<Node>>();
            for (Tag curTag : uniqueTags) {
                List<Node> taggedMembers = getMembersByTag(all, curTag);
                goodSecondariesByTagMap.put(curTag,
                        Collections.unmodifiableList(calculateGoodSecondaries(taggedMembers,
                                calculateBestPingTime(taggedMembers), acceptableLatencyMS)));
            }
            this.goodSecondariesByTagMap = Collections.unmodifiableMap(goodSecondariesByTagMap);
            master = findMaster();

        }

        public List<Node> getAll() {
            return all;
        }

        public boolean hasMaster() {
            return getMaster() != null;
        }

        public Node getMaster() {
            return master;
        }

        public int getMaxBsonObjectSize() {
            if (hasMaster()) {
                return getMaster().getMaxBsonObjectSize();
            } else {
                return Bytes.MAX_OBJECT_SIZE;
            }
        }

        public Node getASecondary() {
            if (goodSecondaries.isEmpty()) {
                return null;
            }
            return goodSecondaries.get(random.nextInt(goodSecondaries.size()));
        }

        public Node getASecondary(List<Tag> tags) {
            for (Tag tag : tags) {
                List<Node> goodSecondariesByTag = goodSecondariesByTagMap.get(tag);
                if (goodSecondariesByTag != null) {
                    Node node = goodSecondariesByTag.get(random.nextInt(goodSecondariesByTag.size()));
                    if (node != null) {
                        return node;
                    }
                }
            }
            return null;
        }

        @Override
        public String toString() {
            StringBuilder sb = new StringBuilder();
            sb.append("[ ");
            for (Node node : getAll())
                sb.append(node.toJSON()).append(",");
            sb.setLength(sb.length() - 1); //remove last comma
            sb.append(" ]");
            return sb.toString();
        }

        public Node findMaster() {
            for (Node node : all) {
                if (node.master())
                    return node;
            }
            return null;
        }

        static float calculateBestPingTime(List<Node> members) {
            float bestPingTime = Float.MAX_VALUE;
            for (Node cur : members) {
                if (!cur.secondary()) {
                    continue;
                }
                if (cur._pingTime < bestPingTime) {
                    bestPingTime = cur._pingTime;
                }
            }
            return bestPingTime;
        }

        static List<Node> calculateGoodSecondaries(List<Node> members, float bestPingTime, int acceptableLatencyMS) {
            List<Node> goodSecondaries = new ArrayList<Node>(members.size());
            for (Node cur : members) {
                if (!cur.secondary()) {
                    continue;
                }
                if (cur._pingTime - acceptableLatencyMS <= bestPingTime ) {
                    goodSecondaries.add(cur);
                }
            }
            return goodSecondaries;
        }

        static List<Node> getMembersByTag(List<Node> members, Tag tag) {
            List<Node> membersByTag = new ArrayList<Node>();

            for (Node cur : members) {
                if (cur._tags.contains(tag)) {
                    membersByTag.add(cur);
                }
            }

            return membersByTag;
        }
    }

    // Represents the state of a node in the replica set.  Instances of this class are immutable.
    @Immutable
    static class Node {
        Node(ServerAddress addr, Set<String> names, float pingTime, boolean ok, boolean isMaster, boolean isSecondary,
             LinkedHashMap<String, String> tags, int maxBsonObjectSize) {
            this._addr = addr;
            this._names = Collections.unmodifiableSet(new HashSet<String>(names));
            this._pingTime = pingTime;
            this._ok = ok;
            this._isMaster = isMaster;
            this._isSecondary = isSecondary;
            this._tags = Collections.unmodifiableSet(getTagsFromMap(tags));
            this._maxBsonObjectSize = maxBsonObjectSize;
        }

        private static Set<Tag> getTagsFromMap(LinkedHashMap<String,String> tagMap) {
            Set<Tag> tagSet = new HashSet<Tag>();
            for (Map.Entry<String, String> curEntry : tagMap.entrySet()) {
                tagSet.add(new Tag(curEntry.getKey(), curEntry.getValue()));
            }
            return tagSet;
        }

        public boolean isOk() {
            return _ok;
        }

        public boolean master(){
            return _ok && _isMaster;
        }

        public int getMaxBsonObjectSize() {
            return _maxBsonObjectSize;
        }

        public boolean secondary(){
            return _ok && _isSecondary;
        }

        public ServerAddress getServerAddress() {
            return _addr;
        }

        public Set<String> getNames() {
            return _names;
        }

        public Set<Tag> getTags() {
            return _tags;
        }

        public String toJSON(){
            StringBuilder buf = new StringBuilder();
            buf.append( "{ address:'" ).append( _addr ).append( "', " );
            buf.append( "ok:" ).append( _ok ).append( ", " );
            buf.append( "ping:" ).append( _pingTime ).append( ", " );
            buf.append( "isMaster:" ).append( _isMaster ).append( ", " );
            buf.append( "isSecondary:" ).append( _isSecondary ).append( ", " );
            buf.append( "maxBsonObjectSize:" ).append( _maxBsonObjectSize ).append( ", " );
            if(_tags != null && _tags.size() > 0)
                buf.append( "tags:" ).append( JSON.serialize(_tags )  );
            buf.append("}");

            return buf.toString();
        }

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

            Node node = (Node) o;

            if (_isMaster != node._isMaster) return false;
            if (_maxBsonObjectSize != node._maxBsonObjectSize) return false;
            if (_isSecondary != node._isSecondary) return false;
            if (_ok != node._ok) return false;
            if (Float.compare(node._pingTime, _pingTime) != 0) return false;
            if (!_addr.equals(node._addr)) return false;
            if (!_names.equals(node._names)) return false;
            if (!_tags.equals(node._tags)) return false;

            return true;
        }

        @Override
        public int hashCode() {
            int result = _addr.hashCode();
            result = 31 * result + (_pingTime != +0.0f ? Float.floatToIntBits(_pingTime) : 0);
            result = 31 * result + _names.hashCode();
            result = 31 * result + _tags.hashCode();
            result = 31 * result + (_ok ? 1 : 0);
            result = 31 * result + (_isMaster ? 1 : 0);
            result = 31 * result + (_isSecondary ? 1 : 0);
            result = 31 * result + _maxBsonObjectSize;
            return result;
        }

        private final ServerAddress _addr;
        private final float _pingTime;
        private final Set<String> _names;
        private final Set<Tag> _tags;
        private final boolean _ok;
        private final boolean _isMaster;
        private final boolean _isSecondary;
        private final int _maxBsonObjectSize;
    }

    // Simple class to hold a single tag, both key and value
    @Immutable
    static final class Tag {
        final String key;
        final String value;

        Tag(String key, String value) {
            this.key = key;
            this.value = value;
        }

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

            Tag tag = (Tag) o;

            if (key != null ? !key.equals(tag.key) : tag.key != null) return false;
            if (value != null ? !value.equals(tag.value) : tag.value != null) return false;

            return true;
        }

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

    // Represents the state of a node in the replica set.  Instances of this class are mutable.
    static class UpdatableNode {

        UpdatableNode(ServerAddress addr,
                      List<UpdatableNode> all,
                      AtomicReference<Logger> logger,
                      Mongo mongo,
                      MongoOptions mongoOptions,
                      AtomicReference<String> setName,
                      AtomicReference<String> lastPrimarySignal)
        {
            _addr = addr;
            _all = all;
            _mongoOptions = mongoOptions;
            _port = new DBPort( addr , null , _mongoOptions );
            _names.add( addr.toString() );
            _logger = logger;
            _mongo = mongo;

            _setName = setName;
            _lastPrimarySignal = lastPrimarySignal;
        }

        private void updateAddr() {
            try {
                if (_addr.updateInetAddress()) {
                    // address changed, need to use new ports
                    _port = new DBPort(_addr, null, _mongoOptions);
                    _mongo.getConnector().updatePortPool(_addr);
                    _logger.get().log(Level.INFO, "Address of host " + _addr.toString() + " changed to " + _addr.getSocketAddress().toString());
                }
            } catch (UnknownHostException ex) {
                _logger.get().log(Level.WARNING, null, ex);
            }
        }

        synchronized void update(Set<UpdatableNode> seenNodes){
            try {
                long start = System.nanoTime();
                CommandResult res = _port.runCommand( _mongo.getDB("admin") , _isMasterCmd );
                long end = System.nanoTime();
                float newPingMS = (end - start) / 1000000F;
                if (!successfullyContacted)
                    _pingTimeMS = newPingMS;
                else
                    _pingTimeMS = _pingTimeMS + ((newPingMS - _pingTimeMS) / latencySmoothFactor);

                _rootLogger.log( Level.FINE , "Latency to " + _addr + " actual=" + newPingMS + " smoothed=" + _pingTimeMS);

                successfullyContacted = true;

                if ( res == null ){
                    throw new MongoInternalException("Invalid null value returned from isMaster");
                }

                if (!_ok) {
                    _logger.get().log( Level.INFO , "Server seen up: " + _addr );
                }
                _ok = true;
                _isMaster = res.getBoolean( "ismaster" , false );
                _isSecondary = res.getBoolean( "secondary" , false );
                _lastPrimarySignal.set( res.getString( "primary" ) );

                if ( res.containsField( "hosts" ) ){
                    for ( Object x : (List)res.get("hosts") ){
                        String host = x.toString();
                        UpdatableNode node = _addIfNotHere(host);
                        if (node != null && seenNodes != null)
                            seenNodes.add(node);
                    }
                }

                if ( res.containsField( "passives" ) ){
                    for ( Object x : (List)res.get("passives") ){
                        String host = x.toString();
                        UpdatableNode node = _addIfNotHere(host);
                        if (node != null && seenNodes != null)
                            seenNodes.add(node);
                    }
                }

                // Tags were added in 2.0 but may not be present
                if (res.containsField( "tags" )) {
                    DBObject tags = (DBObject) res.get( "tags" );
                    for ( String key : tags.keySet() ) {
                        _tags.put( key, tags.get( key ).toString() );
                    }
                }

                // max size was added in 1.8
                if (res.containsField("maxBsonObjectSize")) {
                    _maxBsonObjectSize = (Integer) res.get("maxBsonObjectSize");
                } else {
                    _maxBsonObjectSize = Bytes.MAX_OBJECT_SIZE;
                }

                if (res.containsField("setName")) {
	                String setName = res.get( "setName" ).toString();
	                if ( _setName.get() == null ){
	                    _setName.set(setName);
	                    _logger.set( Logger.getLogger( _rootLogger.getName() + "." + setName));
	                }
	                else if ( !_setName.get().equals( setName ) ){
	                    _logger.get().log( Level.SEVERE , "mismatch set name old: " + _setName.get() + " new: " + setName );
                    }
                }

            } catch (final Exception e) {

                if (!_logger.get().isLoggable(Level.WARNING)) {
                    _ok = false;
                    return;
                }

                final StringBuilder logError = (new StringBuilder("Server seen down: ")).append(_addr);
<commit_msg>removed check for enabled log level. infrequent enough to not cause performance problems.<commit_after>// ReplicaSetStatus.java

/**
 *      Copyright (C) 2008 10gen 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.util.JSON;
import org.bson.util.annotations.Immutable;
import org.bson.util.annotations.ThreadSafe;

import java.net.UnknownHostException;
import java.util.List;
import java.util.ArrayList;
import java.util.Set;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Collections;
import java.util.Map;
import java.util.HashMap;
import java.util.Random;
import java.util.Date;
import java.io.IOException;
import java.util.concurrent.atomic.AtomicReference;
import java.util.logging.Level;
import java.util.logging.Logger;

// TODO:
//  pull config to get
//  priority
//  slave delay

/**
 * Keeps replica set status.  Maintains a background thread to ping all members of the set to keep the status current.
 */
@ThreadSafe
public class ReplicaSetStatus {

	static final Logger _rootLogger = Logger.getLogger( "com.mongodb.ReplicaSetStatus" );

    ReplicaSetStatus( Mongo mongo, List<ServerAddress> initial ){
        _mongoOptions = _mongoOptionsDefaults.copy();
        _mongoOptions.socketFactory = mongo._options.socketFactory;
        _mongo = mongo;
        _updater = new Updater(initial);
    }

    void start() {
        _updater.start();
    }

    public String getName() {
        return _setName.get();
    }

    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder();
        sb.append("{replSetName: ").append(_setName.get());
        sb.append(", nextResolveTime: ").append(new Date(_updater.getNextResolveTime()).toString());
        sb.append(", members: ").append(_replicaSetHolder);
        sb.append(", updaterIntervalMS: ").append(updaterIntervalMS);
        sb.append(", updaterIntervalNoMasterMS: ").append(updaterIntervalNoMasterMS);
        sb.append(", slaveAcceptableLatencyMS: ").append(slaveAcceptableLatencyMS);
        sb.append(", inetAddrCacheMS: ").append(inetAddrCacheMS);
        sb.append(", latencySmoothFactor: ").append(latencySmoothFactor);
        sb.append("}");

        return sb.toString();
    }

    void _checkClosed(){
        if ( _closed )
            throw new IllegalStateException( "ReplicaSetStatus closed" );
    }

    /**
     * @return master or null if don't have one
     * @throws MongoException
     */
    public ServerAddress getMaster(){
        Node n = getMasterNode();
        if ( n == null )
            return null;
        return n.getServerAddress();
    }

    Node getMasterNode(){
        _checkClosed();
        return _replicaSetHolder.get().getMaster();
    }

	/**
	 * @param srv
	 *            the server to compare
	 * @return indication if the ServerAddress is the current Master/Primary
	 * @throws MongoException
	 */
	public boolean isMaster(ServerAddress srv) {
		if (srv == null)
			return false;

		return srv.equals(getMaster());
	}

    /**
     * @param tags tags map
     * @return a good secondary by tag value or null if can't find one
     */
    ServerAddress getASecondary( DBObject tags ) {
        // store the reference in local, so that it doesn't change out from under us while looping
        List<Tag> tagList = new ArrayList<Tag>();
        for ( String key : tags.keySet() ) {
            tagList.add(new Tag(key, tags.get(key).toString()));
        }
        Node node =  _replicaSetHolder.get().getASecondary(tagList);
        if (node != null) {
            return node.getServerAddress();
        }
        return null;
    }

    /**
     * @return a good secondary or null if can't find one
     */
    ServerAddress getASecondary() {
        Node node = _replicaSetHolder.get().getASecondary();
        if (node == null) {
            return null;
        }
        return node._addr;
    }

    boolean hasServerUp() {
        for (Node node : _replicaSetHolder.get().getAll()) {
            if (node.isOk()) {
                return true;
            }
        }
        return false;
    }

    // Simple abstraction over a volatile ReplicaSet reference that starts as null.  The get method blocks until members
    // is not null. The set method notifies all, thus waking up all getters.
    @ThreadSafe
    static class ReplicaSetHolder {
       private volatile ReplicaSet members;

       // blocks until replica set is set.
       synchronized ReplicaSet get() {
           while (members == null) {
               try {
                   wait();
               }
               catch (InterruptedException e) {
                   throw new MongoException("Interrupted while waiting for next update to replica set status", e);
               }
           }
           return members;
       }

       // set the replica set to a non-null value and notifies all threads waiting.
       synchronized void set(ReplicaSet members) {
           if (members == null) {
               throw new IllegalArgumentException("members can not be null");
           }
           this.members = members;
           notifyAll();
       }

       // blocks until the replica set is set again
       synchronized void waitForNextUpdate() {
           try {
               wait();
           }
           catch (InterruptedException e) {
              throw new MongoException("Interrupted while waiting for next update to replica set status", e);
           }
       }

        public synchronized void close() {
            this.members = null;
            notifyAll();
        }

        public String toString() {
            ReplicaSet cur = this.members;
            if (cur != null) {
                return cur.toString();
            }
            return "none";
        }
    }

    // Immutable snapshot state of a replica set. Since the nodes don't change state, this class pre-computes the list
    // of good secondaries so that choosing a random good secondary is dead simple
    @Immutable
    static class ReplicaSet {
        final List<Node> all;
        final Random random;
        final List<Node> goodSecondaries;
        final Map<Tag, List<Node>> goodSecondariesByTagMap;
        final Node master;

        public ReplicaSet(List<Node> nodeList, Random random, int acceptableLatencyMS) {
            this.random = random;
            this.all = Collections.unmodifiableList(new ArrayList<Node>(nodeList));
            this.goodSecondaries =
                    Collections.unmodifiableList(calculateGoodSecondaries(all, calculateBestPingTime(all), acceptableLatencyMS));
            Set<Tag> uniqueTags = new HashSet<Tag>();
            for (Node curNode : all) {
                for (Tag curTag : curNode._tags) {
                    uniqueTags.add(curTag);
                }
            }
            Map<Tag, List<Node>> goodSecondariesByTagMap = new HashMap<Tag, List<Node>>();
            for (Tag curTag : uniqueTags) {
                List<Node> taggedMembers = getMembersByTag(all, curTag);
                goodSecondariesByTagMap.put(curTag,
                        Collections.unmodifiableList(calculateGoodSecondaries(taggedMembers,
                                calculateBestPingTime(taggedMembers), acceptableLatencyMS)));
            }
            this.goodSecondariesByTagMap = Collections.unmodifiableMap(goodSecondariesByTagMap);
            master = findMaster();

        }

        public List<Node> getAll() {
            return all;
        }

        public boolean hasMaster() {
            return getMaster() != null;
        }

        public Node getMaster() {
            return master;
        }

        public int getMaxBsonObjectSize() {
            if (hasMaster()) {
                return getMaster().getMaxBsonObjectSize();
            } else {
                return Bytes.MAX_OBJECT_SIZE;
            }
        }

        public Node getASecondary() {
            if (goodSecondaries.isEmpty()) {
                return null;
            }
            return goodSecondaries.get(random.nextInt(goodSecondaries.size()));
        }

        public Node getASecondary(List<Tag> tags) {
            for (Tag tag : tags) {
                List<Node> goodSecondariesByTag = goodSecondariesByTagMap.get(tag);
                if (goodSecondariesByTag != null) {
                    Node node = goodSecondariesByTag.get(random.nextInt(goodSecondariesByTag.size()));
                    if (node != null) {
                        return node;
                    }
                }
            }
            return null;
        }

        @Override
        public String toString() {
            StringBuilder sb = new StringBuilder();
            sb.append("[ ");
            for (Node node : getAll())
                sb.append(node.toJSON()).append(",");
            sb.setLength(sb.length() - 1); //remove last comma
            sb.append(" ]");
            return sb.toString();
        }

        public Node findMaster() {
            for (Node node : all) {
                if (node.master())
                    return node;
            }
            return null;
        }

        static float calculateBestPingTime(List<Node> members) {
            float bestPingTime = Float.MAX_VALUE;
            for (Node cur : members) {
                if (!cur.secondary()) {
                    continue;
                }
                if (cur._pingTime < bestPingTime) {
                    bestPingTime = cur._pingTime;
                }
            }
            return bestPingTime;
        }

        static List<Node> calculateGoodSecondaries(List<Node> members, float bestPingTime, int acceptableLatencyMS) {
            List<Node> goodSecondaries = new ArrayList<Node>(members.size());
            for (Node cur : members) {
                if (!cur.secondary()) {
                    continue;
                }
                if (cur._pingTime - acceptableLatencyMS <= bestPingTime ) {
                    goodSecondaries.add(cur);
                }
            }
            return goodSecondaries;
        }

        static List<Node> getMembersByTag(List<Node> members, Tag tag) {
            List<Node> membersByTag = new ArrayList<Node>();

            for (Node cur : members) {
                if (cur._tags.contains(tag)) {
                    membersByTag.add(cur);
                }
            }

            return membersByTag;
        }
    }

    // Represents the state of a node in the replica set.  Instances of this class are immutable.
    @Immutable
    static class Node {
        Node(ServerAddress addr, Set<String> names, float pingTime, boolean ok, boolean isMaster, boolean isSecondary,
             LinkedHashMap<String, String> tags, int maxBsonObjectSize) {
            this._addr = addr;
            this._names = Collections.unmodifiableSet(new HashSet<String>(names));
            this._pingTime = pingTime;
            this._ok = ok;
            this._isMaster = isMaster;
            this._isSecondary = isSecondary;
            this._tags = Collections.unmodifiableSet(getTagsFromMap(tags));
            this._maxBsonObjectSize = maxBsonObjectSize;
        }

        private static Set<Tag> getTagsFromMap(LinkedHashMap<String,String> tagMap) {
            Set<Tag> tagSet = new HashSet<Tag>();
            for (Map.Entry<String, String> curEntry : tagMap.entrySet()) {
                tagSet.add(new Tag(curEntry.getKey(), curEntry.getValue()));
            }
            return tagSet;
        }

        public boolean isOk() {
            return _ok;
        }

        public boolean master(){
            return _ok && _isMaster;
        }

        public int getMaxBsonObjectSize() {
            return _maxBsonObjectSize;
        }

        public boolean secondary(){
            return _ok && _isSecondary;
        }

        public ServerAddress getServerAddress() {
            return _addr;
        }

        public Set<String> getNames() {
            return _names;
        }

        public Set<Tag> getTags() {
            return _tags;
        }

        public String toJSON(){
            StringBuilder buf = new StringBuilder();
            buf.append( "{ address:'" ).append( _addr ).append( "', " );
            buf.append( "ok:" ).append( _ok ).append( ", " );
            buf.append( "ping:" ).append( _pingTime ).append( ", " );
            buf.append( "isMaster:" ).append( _isMaster ).append( ", " );
            buf.append( "isSecondary:" ).append( _isSecondary ).append( ", " );
            buf.append( "maxBsonObjectSize:" ).append( _maxBsonObjectSize ).append( ", " );
            if(_tags != null && _tags.size() > 0)
                buf.append( "tags:" ).append( JSON.serialize(_tags )  );
            buf.append("}");

            return buf.toString();
        }

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

            Node node = (Node) o;

            if (_isMaster != node._isMaster) return false;
            if (_maxBsonObjectSize != node._maxBsonObjectSize) return false;
            if (_isSecondary != node._isSecondary) return false;
            if (_ok != node._ok) return false;
            if (Float.compare(node._pingTime, _pingTime) != 0) return false;
            if (!_addr.equals(node._addr)) return false;
            if (!_names.equals(node._names)) return false;
            if (!_tags.equals(node._tags)) return false;

            return true;
        }

        @Override
        public int hashCode() {
            int result = _addr.hashCode();
            result = 31 * result + (_pingTime != +0.0f ? Float.floatToIntBits(_pingTime) : 0);
            result = 31 * result + _names.hashCode();
            result = 31 * result + _tags.hashCode();
            result = 31 * result + (_ok ? 1 : 0);
            result = 31 * result + (_isMaster ? 1 : 0);
            result = 31 * result + (_isSecondary ? 1 : 0);
            result = 31 * result + _maxBsonObjectSize;
            return result;
        }

        private final ServerAddress _addr;
        private final float _pingTime;
        private final Set<String> _names;
        private final Set<Tag> _tags;
        private final boolean _ok;
        private final boolean _isMaster;
        private final boolean _isSecondary;
        private final int _maxBsonObjectSize;
    }

    // Simple class to hold a single tag, both key and value
    @Immutable
    static final class Tag {
        final String key;
        final String value;

        Tag(String key, String value) {
            this.key = key;
            this.value = value;
        }

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

            Tag tag = (Tag) o;

            if (key != null ? !key.equals(tag.key) : tag.key != null) return false;
            if (value != null ? !value.equals(tag.value) : tag.value != null) return false;

            return true;
        }

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

    // Represents the state of a node in the replica set.  Instances of this class are mutable.
    static class UpdatableNode {

        UpdatableNode(ServerAddress addr,
                      List<UpdatableNode> all,
                      AtomicReference<Logger> logger,
                      Mongo mongo,
                      MongoOptions mongoOptions,
                      AtomicReference<String> setName,
                      AtomicReference<String> lastPrimarySignal)
        {
            _addr = addr;
            _all = all;
            _mongoOptions = mongoOptions;
            _port = new DBPort( addr , null , _mongoOptions );
            _names.add( addr.toString() );
            _logger = logger;
            _mongo = mongo;

            _setName = setName;
            _lastPrimarySignal = lastPrimarySignal;
        }

        private void updateAddr() {
            try {
                if (_addr.updateInetAddress()) {
                    // address changed, need to use new ports
                    _port = new DBPort(_addr, null, _mongoOptions);
                    _mongo.getConnector().updatePortPool(_addr);
                    _logger.get().log(Level.INFO, "Address of host " + _addr.toString() + " changed to " + _addr.getSocketAddress().toString());
                }
            } catch (UnknownHostException ex) {
                _logger.get().log(Level.WARNING, null, ex);
            }
        }

        synchronized void update(Set<UpdatableNode> seenNodes){
            try {
                long start = System.nanoTime();
                CommandResult res = _port.runCommand( _mongo.getDB("admin") , _isMasterCmd );
                long end = System.nanoTime();
                float newPingMS = (end - start) / 1000000F;
                if (!successfullyContacted)
                    _pingTimeMS = newPingMS;
                else
                    _pingTimeMS = _pingTimeMS + ((newPingMS - _pingTimeMS) / latencySmoothFactor);

                _rootLogger.log( Level.FINE , "Latency to " + _addr + " actual=" + newPingMS + " smoothed=" + _pingTimeMS);

                successfullyContacted = true;

                if ( res == null ){
                    throw new MongoInternalException("Invalid null value returned from isMaster");
                }

                if (!_ok) {
                    _logger.get().log( Level.INFO , "Server seen up: " + _addr );
                }
                _ok = true;
                _isMaster = res.getBoolean( "ismaster" , false );
                _isSecondary = res.getBoolean( "secondary" , false );
                _lastPrimarySignal.set( res.getString( "primary" ) );

                if ( res.containsField( "hosts" ) ){
                    for ( Object x : (List)res.get("hosts") ){
                        String host = x.toString();
                        UpdatableNode node = _addIfNotHere(host);
                        if (node != null && seenNodes != null)
                            seenNodes.add(node);
                    }
                }

                if ( res.containsField( "passives" ) ){
                    for ( Object x : (List)res.get("passives") ){
                        String host = x.toString();
                        UpdatableNode node = _addIfNotHere(host);
                        if (node != null && seenNodes != null)
                            seenNodes.add(node);
                    }
                }

                // Tags were added in 2.0 but may not be present
                if (res.containsField( "tags" )) {
                    DBObject tags = (DBObject) res.get( "tags" );
                    for ( String key : tags.keySet() ) {
                        _tags.put( key, tags.get( key ).toString() );
                    }
                }

                // max size was added in 1.8
                if (res.containsField("maxBsonObjectSize")) {
                    _maxBsonObjectSize = (Integer) res.get("maxBsonObjectSize");
                } else {
                    _maxBsonObjectSize = Bytes.MAX_OBJECT_SIZE;
                }

                if (res.containsField("setName")) {
	                String setName = res.get( "setName" ).toString();
	                if ( _setName.get() == null ){
	                    _setName.set(setName);
	                    _logger.set( Logger.getLogger( _rootLogger.getName() + "." + setName));
	                }
	                else if ( !_setName.get().equals( setName ) ){
	                    _logger.get().log( Level.SEVERE , "mismatch set name old: " + _setName.get() + " new: " + setName );
                    }
                }

            } catch (final Exception e) {

                final StringBuilder logError = (new StringBuilder("Server seen down: ")).append(_addr);

{'fraction_non_alphanumeric': 0.10113198458574181, 'fraction_numerical': 0.0024566473988439307, 'mean_word_length': 1.678255821453912, 'pattern_counts': {'":': 0, '<': 97, '<?xml version=': 0, '>': 95, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4658251', 'n_tokens_mistral': 11158, 'n_tokens_neox': 10688, 'n_words': 2925}