public interface CommandLog {
    long size();
    default long lastIndex() {
        return size() - 1;
    }
    int readTerm(long index);
    void readTo(long index, LogKey target);
    void readTo(long index, CommandKey target);
    void readTo(long index, LogEntry target);

    void append(int term, Command command);
    void truncateIncluding(long index);

    default int lastTerm() {
        return readTerm(lastIndex());
    }

    default void lastKeyTo(final LogKey target) {
        readTo(lastIndex(), target);
    }

    default int lastKeyCompareTo(final LogKey logKey) {
        final int termCompare = Integer.compare(lastTerm(), logKey.term());
        return termCompare == 0 ? Long.compare(lastIndex(), logKey.index()) : termCompare;
    }

    boolean contains(CommandKey commandKey);
    default LogContainment contains(final LogKey logKey) {
        return LogContainment.containmentFor(logKey, this);
    }

} public interface StateMachine {
    void onMessage(Command message);
} public class ConfigBuilder {

    private static final ToIntFunction<ServerConfig> SERVER_CONFIG_ID_EXTRACTOR = (c) -> c.id();
    private static final ToIntFunction<SourceConfig> SOURCE_CONFIG_ID_EXTRACTOR = (c) -> c.id();

    private long minTimeoutMillis = 150;
    private long maxTimeoutMillis = 300;
    private long heartbeatMillis = 20;
    private String ipcMulticastChannel = null;
    private ThreadingMode threadingMode = ThreadingMode.SHARED;
    private final List<ServerConfig> serverConfigs = new ArrayList<>();
    private final List<SourceConfig> sourceConfigs = new ArrayList<>();

    public ConfigBuilder minElectionTimeoutMillis(final long minTimeoutMillis) {
        this.minTimeoutMillis = minTimeoutMillis;
        return this;
    }

    public ConfigBuilder maxElectionTimeoutMillis(final long maxTimeoutMillis) {
        this.maxTimeoutMillis = maxTimeoutMillis;
        return this;
    }

    public ConfigBuilder heartbeatMillis(final long heartbeatMillis) {
        this.heartbeatMillis = heartbeatMillis;
        return this;
    }

    public ConfigBuilder ipcMulticastChannel(final String ipcMulticastChannel) {
        this.ipcMulticastChannel = ipcMulticastChannel;
        return this;
    }

    public ConfigBuilder threadingMode(final ThreadingMode threadingMode) {
        this.threadingMode = Objects.requireNonNull(threadingMode, "threadingMode cannot be null");
        return this;
    }

    public ConfigBuilder addServer(final String channel) {
        return addServerConfig(nextId(serverConfigs, SERVER_CONFIG_ID_EXTRACTOR), channel);
    }

    public ConfigBuilder addServer(final int id, final String channel) {
        return addServerConfig(checkId("server", serverConfigs, SERVER_CONFIG_ID_EXTRACTOR, id), channel);
    }

    private ConfigBuilder addServerConfig(final int id, final String channel) {
        serverConfigs.add(new DefaultServerConfig(id, channel));
        return this;
    }

    public ConfigBuilder addSource(final String channel) {
        return addSourceConfig(nextId(sourceConfigs, SOURCE_CONFIG_ID_EXTRACTOR), channel);
    }

    public ConfigBuilder addSource(final int id, final String channel) {
        return addSourceConfig(checkId("source", sourceConfigs, SOURCE_CONFIG_ID_EXTRACTOR, id), channel);
    }

    private ConfigBuilder addSourceConfig(final int id, final String channel) {
        serverConfigs.add(new DefaultSourceConfig(id, channel));
        return this;
    }

    public ConsensusConfig build() {
        return new DefaultConsensusConfig(
                minTimeoutMillis, maxTimeoutMillis, heartbeatMillis,
                Optional.ofNullable(ipcMulticastChannel), threadingMode,
                new ArrayList<>(serverConfigs), new ArrayList<>(sourceConfigs));
    }

    private static <E> int nextId(final List<E> list, final ToIntFunction<E> idExtracter) {
        int next = 0;
        for (int i = 0; i < list.size(); i++) {
            next = Math.max(next, idExtracter.applyAsInt(list.get(i)) + 1);
        }
        return next;
    }

    private static <E> int checkId(final String name, final List<E> list, final ToIntFunction<E> idExtracter, final int id) {
        for (int i = 0; i < list.size(); i++) {
            if (id == idExtracter.applyAsInt(list.get(i))) {
                throw new IllegalArgumentException("Duplicate " + name + " ID: " + id);
            }
        }
        return id;
    }

} public interface ConsensusConfig {
    long minElectionTimeoutMillis();
    long maxElectionTimeoutMillis();
    long heartbeatTimeoutMillis();
    Optional<String> ipcMulticastChannel();
    int serverCount();
    ServerConfig serverConfig(int index);
    int sourceCount();
    SourceConfig sourceConfig(int index);
    ThreadingMode threadingMode();

    default ServerConfig serverConfigByIdOrNull(int id) {
        for (int i = 0; i < serverCount(); i++) {
            final ServerConfig config = serverConfig(i);
            if (config.id() == id) {
                return config;
            }
        }
        return null;
    }

    default SourceConfig sourceConfigByIdOrNull(int id) {
        for (int i = 0; i < sourceCount(); i++) {
            final SourceConfig config = sourceConfig(i);
            if (config.id() == id) {
                return config;
            }
        }
        return null;
    }
} public enum ThreadingMode {
    /**
     * 1 thread: source polling and server/state machine logic is performed in a single
     * thread.
     */
    SHARED,
    /**
     * 2 threads:
     * <br>
     * (i) server/state machine logic is performed in main thread
     * (ii) source polling is performed in polling thread
     */
    SOURCES_SHARED,
    /**
     * 1 + n threads:
     * <br>
     * (i) server/state machine logic is performed in main thread
     * (ii) source polling is performed in separate threads, one per source
     */
    DEDICATED;
} public class AllocatingDirectFactory implements DirectFactory {

    private MutableDirectBuffer newBuffer(final int initialCapacity) {
        return new ExpandableArrayBuffer(initialCapacity);
    }

    @Override
    public AppendRequest appendRequest() {
        final DirectAppendRequest directAppendRequest = new DirectAppendRequest();
        directAppendRequest.wrap(newBuffer(DirectAppendRequest.EMPTY_LOG_BYTE_LENGTH), 0);
        return directAppendRequest;
    }

    @Override
    public AppendResponse appendResponse() {
        final DirectAppendResponse directAppendResponse = new DirectAppendResponse();
        directAppendResponse.wrap(newBuffer(DirectAppendResponse.BYTE_LENGTH), 0);
        return directAppendResponse;
    }

    @Override
    public VoteRequest voteRequest() {
        final DirectVoteRequest directVoteRequest = new DirectVoteRequest();
        directVoteRequest.wrap(newBuffer(DirectVoteRequest.BYTE_LENGTH), 0);
        return directVoteRequest;
    }

    @Override
    public VoteResponse voteResponse() {
        final DirectVoteResponse directVoteResponse = new DirectVoteResponse();
        directVoteResponse.wrap(newBuffer(DirectVoteResponse.BYTE_LENGTH), 0);
        return directVoteResponse;
    }

    @Override
    public TimeoutNow timeoutNow() {
        final DirectTimeoutNow directTimeoutNow = new DirectTimeoutNow();
        directTimeoutNow.wrap(newBuffer(DirectTimeoutNow.BYTE_LENGTH), 0);
        return null;
    }

    @Override
    public CommandKey commandKey() {
        final DirectCommandKey directCommandKey = new DirectCommandKey();
        directCommandKey.wrap(newBuffer(DirectCommandKey.BYTE_LENGTH), 0);
        return directCommandKey;
    }

    @Override
    public Command command() {
        final DirectCommand directCommand = new DirectCommand();
        directCommand.wrap(newBuffer(DirectCommand.EMPTY_COMMAND_BYTE_LENGTH), 0);
        return directCommand;
    }

    @Override
    public LogEntry logEntry() {
        final DirectLogEntry directCommandLogEntry = new DirectLogEntry();
        directCommandLogEntry.wrap(newBuffer(DirectLogEntry.EMPTY_COMMAND_BYTE_LENGTH), 0);
        return directCommandLogEntry;
    }
} public interface DirectFactory {

    AppendRequest appendRequest();

    AppendResponse appendResponse();

    VoteRequest voteRequest();

    VoteResponse voteResponse();

    TimeoutNow timeoutNow();

    CommandKey commandKey();

    Command command();

    LogEntry logEntry();
} public interface Message extends DirectPayload, Event {

    MessageType type();

    default void sendTo(final Sender<? super Message> sender, final ResendStrategy resendStrategy) {
        final long res = sender.offer(this);
        if (res < 0) {
            resendStrategy.onRejectedOffer(sender, this, res);
        }
    }


} public interface PersistentState {

    int NOT_VOTED_YET = -1;

    int currentTerm();

    int votedFor();

    int clearVotedForAndSetCurrentTerm(int term);

    int clearVotedForAndIncCurrentTerm();

    void votedFor(final int candidateId);

    CommandLog commandLog();
} public final class VolatileState {

    private final TrackedFollowerState[] trackedFollowerStates;

    @Deprecated //REMOVE this is now storted as State in HoverRaftMachine
    private Role role = Role.FOLLOWER;
    private long commitIndex = -1;
    private long lastApplied = -1;

    public VolatileState(final int serverId, final ConsensusConfig consensusConfig) {
        this.trackedFollowerStates = initFollowerStates(serverId, consensusConfig);
    }

    public long commitIndex() {
        return commitIndex;
    }

    public VolatileState commitIndex(final long commitIndex) {
        this.commitIndex = commitIndex;
        return this;
    }

    public long lastApplied() {
        return lastApplied;
    }

    public VolatileState lastApplied(final long lastApplied) {
        this.lastApplied = lastApplied;
        return this;
    }

    public int followerCount() {
        return trackedFollowerStates.length;
    }

    public TrackedFollowerState followerState(int index) {
        return trackedFollowerStates[index];
    }

    public void resetFollowersState(final long nextIndex) {
        for (final TrackedFollowerState fs : trackedFollowerStates) {
            fs.nextIndex(nextIndex)
                    .resetMatchIndex();
        }
    }

    public TrackedFollowerState followerStateById(int id) {
        for (final TrackedFollowerState fs : trackedFollowerStates) {
            if (fs.serverId() == id) {
                return fs;
            }
        }
        throw new IllegalArgumentException("no follower state found for id " + id);
    }

    private static TrackedFollowerState[] initFollowerStates(final int serverId, final ConsensusConfig consensusConfig) {
        final TrackedFollowerState[] states = new TrackedFollowerState[consensusConfig.serverCount() - 1];
        int index = 0;
        for (int i = 0; i < consensusConfig.serverCount(); i++) {
            final int id = consensusConfig.serverConfig(i).id();
            if (id != serverId) {
                states[index] = new TrackedFollowerState(id);
                index++;
            }
        }
        return states;
    }
} public interface Connections<M extends Message> {
    Receiver<Command> sourceReceiver(int sourceId);
    Receiver<M> serverReceiver(int serverId);
    Sender<M> serverSender(int serverId);
    Sender<M> serverMulticastSender();
}
import org.tools4j.hoverraft.message.Message;
import org.tools4j.hoverraft.state.PersistentState;
import org.tools4j.hoverraft.state.VolatileState;
import org.tools4j.hoverraft.transport.Connections;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import org.tools4j.hoverraft.command.CommandLog;
import org.tools4j.hoverraft.command.machine.StateMachine;
import org.tools4j.hoverraft.config.ConfigBuilder;
import org.tools4j.hoverraft.config.ConsensusConfig;
import org.tools4j.hoverraft.config.ThreadingMode;
import org.tools4j.hoverraft.direct.AllocatingDirectFactory;
import org.tools4j.hoverraft.direct.DirectFactory;
/**
 * The MIT License (MIT)
 *
 * Copyright (c) 2016-2017 hover-raft (tools4j), Marco Terzer
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */
package org.tools4j.hoverraft.server;



public class Mockery {

    public static final int SERVER_ID = 1;
    public static final long MIN_ELECTION_TIMEOUT_MILLIS = 50;
    public static final long MAX_ELECTION_TIMEOUT_MILLIS = 100;

    public static ServerContext simple(final int servers) {
        return simple(servers, 0, 0);
    }

    public static ServerContext simple(final int servers, final int sources, final int connections) {
        return server(servers, sources, connections, new AllocatingDirectFactory());
    }

    private static ServerContext server(final int servers, final int sources, final int connections,
                                        final DirectFactory directFactory) {
        final ConsensusConfig consensusConfig = consensusConfig(servers, sources);
        return new Server(SERVER_ID,
                consensusConfig(servers, sources), persistentState(), volatileState(consensusConfig),
                stateMachine(), connections(servers, sources), directFactory);
    }

    public static ConsensusConfig consensusConfig(final int servers, final int sources) {
        final ConfigBuilder configBuilder = new ConfigBuilder()
                .minElectionTimeoutMillis(MIN_ELECTION_TIMEOUT_MILLIS)
                .maxElectionTimeoutMillis(MAX_ELECTION_TIMEOUT_MILLIS)
                .threadingMode(ThreadingMode.SHARED);
        for (int i = 1; i <= servers; i++) {
            configBuilder.addServer(i, "server-" + i);
        }
        for (int i = 1; i <= sources; i++) {
            configBuilder.addSource(i, "source-" + i);
        }
        return configBuilder.build();
    }

    public static StateMachine stateMachine() {
        return mock(StateMachine.class);
    }

    public static CommandLog commandLog() {
        return mock(CommandLog.class);
    }

    public static <M extends Message> Connections<M> connections(final int servers, final int sources) {
        final Connections<M> connects = mock(Connections.class);
        //when
        return connects;
    }

    public static PersistentState persistentState() {
        final PersistentState persistentState = mock(PersistentState.class);
        final CommandLog commandLog = mock(CommandLog.class);

        when(persistentState.currentTerm()).thenReturn(1);
        when(persistentState.commandLog()).thenReturn(commandLog);

        when(persistentState.votedFor()).thenReturn(PersistentState.NOT_VOTED_YET);
        return persistentState;
    }
    public static VolatileState volatileState(final ConsensusConfig consensusConfig) {