3
\$\begingroup\$

I have this Java library for building logic circuits and running binary data through them:

AbstractCircuitComponent.java

package net.coderodde.circuits.components;

import java.util.List;

/**
 * Defines the common API for all circuit components.
 * 
 * @author Rodion "rodde" Efremov
 * @version 1.6 (Oct 6, 2017)
 */
public abstract class AbstractCircuitComponent {

    private final String name;

    /**
     * Each component has a single output wire.
     */
    protected AbstractCircuitComponent output;

    public AbstractCircuitComponent(String name) {
        this.name = name;
    }

    /**
     * This method simulates a cycle over this component.
     * 
     * @return the output from this component.
     */
    public abstract boolean doCycle();

    public String getName() {
        return name;
    }

    public AbstractCircuitComponent getOutputComponent() {
        return output;
    }

    public void setOutputComponent(AbstractCircuitComponent output) {
        this.output = output;
    }

    public abstract List<AbstractCircuitComponent> getInputComponents();
    public abstract List<AbstractCircuitComponent> getOutputComponents();
}

AbstractSingleInputPinCircuitComponent.java

package net.coderodde.circuits.components;

/**
 * Defines the API for all components having only one input.
 * 
 * @author Rodion "rodde" Efremov
 * @version 1.6 (Oct 6, 2017)
 */
public abstract class AbstractSingleInputPinCircuitComponent
extends AbstractCircuitComponent{

    protected AbstractCircuitComponent input;

    public AbstractSingleInputPinCircuitComponent(String name) {
        super(name);
    }

    public AbstractCircuitComponent getInputComponent() {
        return input;
    }

    public void setInputComponent(AbstractCircuitComponent input) {
        this.input = input;
    }
}

AbstractDoubleInputPinCircuitComponent.java

package net.coderodde.circuits.components;

/**
 * Defines the API for all the components having two inputs.
 * 
 * @author Rodion "rodde" Efremov
 * @version 1.6 (Oct 6, 2017)
 */
public abstract class AbstractDoubleInputPinCircuitComponent 
extends AbstractCircuitComponent {

    protected AbstractCircuitComponent input1;
    protected AbstractCircuitComponent input2;

    public AbstractDoubleInputPinCircuitComponent(String name) {
        super(name);
    }

    public AbstractCircuitComponent getInputComponent1() {
        return input1;
    }

    public AbstractCircuitComponent getInputComponent2() {
        return input2;
    }

    public void setInputComponent1(AbstractCircuitComponent input1) {
        this.input1 = input1;
    }

    public void setInputComponent2(AbstractCircuitComponent input2) {
        this.input2 = input2;
    }
}

Circuit.java

package net.coderodde.circuits;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.TreeMap;
import net.coderodde.circuits.components.AbstractCircuitComponent;
import net.coderodde.circuits.components.AbstractDoubleInputPinCircuitComponent;
import net.coderodde.circuits.components.AbstractSingleInputPinCircuitComponent;
import net.coderodde.circuits.components.support.AndGate;
import net.coderodde.circuits.components.support.InputGate;
import net.coderodde.circuits.components.support.BranchWire;
import net.coderodde.circuits.components.support.NotGate;
import net.coderodde.circuits.components.support.OrGate;
import net.coderodde.circuits.components.support.OutputGate;

/**
 * This class serves as a circuit component containers implementing a logical
 * board.
 * 
 * @author Rodion "rodde" Efremov
 * @version 1.6 (Sep 20, 2017)
 */
public final class Circuit extends AbstractCircuitComponent {

    /**
     * The minimum allowed number of input pins per circuit.
     */
    private static final int MINIMUM_INPUT_PINS = 1;

    /**
     * The minimum allowed number of output pins per circuit.
     */
    private static final int MINIMUM_OUTPUT_PINS = 1;

    /**
     * The input pin name prefix.
     */
    private static final String INPUT_PIN_NAME_PREFIX = "inputPin";

    /**
     * The output pin name prefix.
     */
    private static final String OUTPUT_PIN_NAME_PREFIX = "outputPin";

    /**
     * The map mapping the name of a component to the actual component.
     */
    private final Map<String, AbstractCircuitComponent> componentMap = 
            new TreeMap<>();

    /**
     * Contains all current gates, both named and unnamed (such as 
     * {@code OffStubGate} and so on).
     */
    private final Set<AbstractCircuitComponent> componentSet = new HashSet<>();

    /**
     * The number of input pins in this circuit.
     */
    private final int numberOfInputPins;

    /**
     * The number of output pins in this circuit.
     */
    private final int numberOfOutputPins;

    /**
     * The list of input pins.
     */
    private final List<InputGate> inputGates;

    /**
     * The list of output pins.
     */
    private final List<OutputGate> outputGates;

    /**
     * Set to {@code true} if this circuit is minimized.
     */
    private boolean locked = false;

    /**
     * Creates a new circuit.
     * 
     * @param name       the name of this circuit.
     * @param inputPins  the number of input pins.
     * @param outputPins the number of output pins.
     */
    public Circuit(String name, int inputPins, int outputPins) {
        super(checkName(name));
        this.numberOfInputPins  = checkInputPinCount(inputPins);
        this.numberOfOutputPins = checkOutputPinCount(outputPins);

        this.inputGates  = new ArrayList<>(this.numberOfInputPins);
        this.outputGates = new ArrayList<>(this.numberOfOutputPins);

        for (int inputPin = 0; inputPin < inputPins; ++inputPin) {
            String inputComponentName = INPUT_PIN_NAME_PREFIX + inputPin;
            InputGate inputComponent = new InputGate(inputComponentName);
            componentMap.put(inputComponentName, inputComponent);
            inputGates.add(inputComponent);
            addComponent(inputComponent);
        }

        for (int outputPin = 0; outputPin < outputPins; ++outputPin) {
            String outputComponentName = OUTPUT_PIN_NAME_PREFIX + outputPin;
            OutputGate outputComponent = new OutputGate(outputComponentName);
            componentMap.put(outputComponentName, outputComponent);
            outputGates.add(outputComponent);
            addComponent(outputComponent);
        }
    }

    /**
     * Copy constructs a new circuit by copying the contents of the input
     * circuit.
     * 
     * @param circuit the circuit to copy.
     * @param name    the name of this new circuit.
     */
    public Circuit(Circuit circuit, String name) {
        this(checkName(name),
             circuit.getNumberOfInputPins(),
             circuit.getNumberOfOutputPins());

        Map<AbstractCircuitComponent, AbstractCircuitComponent> 
                componentMap = new HashMap<>(circuit.componentSet.size());

        for (InputGate mappedInputGate : this.inputGates) {
            AbstractCircuitComponent inputGate = 
                    circuit.componentMap.get(mappedInputGate.getName());

            componentMap.put(inputGate, mappedInputGate);
        }

        for (OutputGate mappedOutputGate : this.outputGates) {
            AbstractCircuitComponent outputGate =
                    circuit.componentMap.get(mappedOutputGate.getName());

            componentMap.put(outputGate, mappedOutputGate);
        }

        for (AbstractCircuitComponent component : circuit.componentSet) {
            if (component instanceof InputGate || component instanceof OutputGate) {
                continue;
            }

            AbstractCircuitComponent newComponent = copyComponent(component);
            componentMap.put(component, newComponent);
        }

        for (AbstractCircuitComponent component : circuit.componentSet) {
            AbstractCircuitComponent mappedComponent = 
                    componentMap.get(component);

            for (AbstractCircuitComponent inputComponent
                    : component.getInputComponents()) {
                AbstractCircuitComponent mappedInputComponent =
                        componentMap.get(inputComponent);

                connectInput(component, 
                             inputComponent,
                             mappedComponent,
                             mappedInputComponent);
            }

            for (AbstractCircuitComponent outputComponent 
                    : component.getOutputComponents()) {
                AbstractCircuitComponent mappedOutputComponent =
                        componentMap.get(outputComponent);

                connectOutput(component,
                              mappedComponent,
                              mappedOutputComponent);
            }
        }
    }

    private void connectInput(AbstractCircuitComponent component,
                              AbstractCircuitComponent inputComponent,
                              AbstractCircuitComponent mappedComponent,
                              AbstractCircuitComponent mappedInputComponent) {
        if (mappedComponent instanceof AbstractSingleInputPinCircuitComponent) {
            ((AbstractSingleInputPinCircuitComponent) mappedComponent)
                    .setInputComponent(mappedInputComponent);
        } else {
            AbstractDoubleInputPinCircuitComponent c1 = 
                    (AbstractDoubleInputPinCircuitComponent) mappedComponent;

            AbstractDoubleInputPinCircuitComponent c2 =
                    (AbstractDoubleInputPinCircuitComponent) component;

            if (inputComponent == c2.getInputComponent1()) {
                c1.setInputComponent1(mappedInputComponent);
            } else {
                c1.setInputComponent2(mappedInputComponent);
            }
        }
    }

    private void connectOutput(AbstractCircuitComponent component,
                               AbstractCircuitComponent mappedComponent,
                               AbstractCircuitComponent mappedOutputComponent) {
        if (component instanceof BranchWire) {
            BranchWire branchWire = (BranchWire) mappedComponent;
            branchWire.connectTo(mappedOutputComponent);
        } else {
            mappedComponent.setOutputComponent(mappedOutputComponent);
        }
    }

    private AbstractCircuitComponent
        copyComponent(AbstractCircuitComponent component) {
        if (component instanceof NotGate) {
            NotGate gate = (NotGate) component;
            return new NotGate(gate.getName());
        }

        if (component instanceof AndGate) {
            AndGate gate = (AndGate) component;
            return new AndGate(gate.getName());
        }

        if (component instanceof OrGate) {
            OrGate gate = (OrGate) component;
            return new OrGate(gate.getName());
        }

        if (component instanceof BranchWire) {
            BranchWire wire = (BranchWire) component;
            return new BranchWire();
        }

        if (component instanceof InputGate) {
            InputGate gate = (InputGate) component;
            return new InputGate(gate.getName());
        }

        if (component instanceof OutputGate) {
            OutputGate gate = (OutputGate) component;
            return new OutputGate(gate.getName());
        }

        throw new IllegalStateException(
                "Unknown gate type: " + component.getClass());
    }

    /**
     * Returns the number of components in this circuit.
     * 
     * @return the number of components.
     */
    public int size() {
        return componentSet.size();
    }

    /**
     * Adds a new <code>NOT</code>-gate to this circuit.
     * 
     * @param notGateName the name of the gate.
     */
    public void addNotGate(String notGateName) {
        checkIsNotLocked();
        checkNewGateName(notGateName);
        NotGate notGate = new NotGate(notGateName);
        componentMap.put(notGateName, notGate);
        componentSet.add(notGate);
    }

    /**
     * Adds a new <code>AND</code>-gate to this circuit.
     * 
     * @param andGateName the name of the gate.
     */
    public void addAndGate(String andGateName) {
        checkIsNotLocked();
        checkNewGateName(andGateName);
        AndGate andGate = new AndGate(andGateName);
        componentMap.put(andGateName, andGate);
        componentSet.add(andGate);
    }

    /**
     * Adds a new <code>OR</code>-gate to this circuit.
     * 
     * @param orGateName the name of the gate.
     */
    public void addOrGate(String orGateName) {
        checkIsNotLocked();
        checkNewGateName(orGateName);
        OrGate orGate = new OrGate(orGateName);
        componentMap.put(orGateName, orGate);
        componentSet.add(orGate);
    }

    /**
     * Adds a subcircuit to this circuit.
     * 
     * @param circuit the subcircuit to add.
     */
    public void addCircuit(Circuit circuit) {
        checkIsNotLocked();
        checkNewGateName(circuit.getName());
        componentMap.put(circuit.getName(), circuit);
        componentSet.add(circuit);
    }

    public int getNumberOfInputPins() {
        return numberOfInputPins;
    }

    public int getNumberOfOutputPins() {
        return numberOfOutputPins;
    }

    /**
     * Performs a single cycle of this circuit.
     * 
     * @return dummy value.
     */
    @Override
    public boolean doCycle() {
        for (OutputGate outputGate : outputGates) {
            outputGate.doCycle();
        }

        return false;
    }

    public boolean[] doCycle(boolean... bits) {
        setInputBits(bits);
        doCycle();
        return getOutputBits();
    }

    /**
     * Sets the states of all the input pins. If the length of {@code bits} is 
     * smaller than the number of input pins in this circuit, the rest of input
     * pins are set to zero. If the length of {@code bits} is greater than the 
     * number of input pins, the overflowing values of {@code bits} are ignored.
     * 
     * @param bits the bit vector.
     */
    public void setInputBits(boolean... bits) {
        Objects.requireNonNull(bits, "The input bit array is null.");
        unsetAllInputPins();

        for (int i = 0; i < Math.min(bits.length, inputGates.size()); ++i) {
            inputGates.get(i).setBit(bits[i]);
        }
    }

    /**
     * Returns a bit vector representing a result of a circuit cycle.
     * 
     * @return a bit vector.
     */
    public boolean[] getOutputBits() {
        boolean[] bits = new boolean[numberOfOutputPins];

        for (int i = 0; i < bits.length; ++i) {
            bits[i] = outputGates.get(i).doCycle();
        }

        return bits;
    }

    /**
     * Attempts to produce a logical circuit with minimal possible number of 
     * gates that is equivalent to this circuit.
     */
    public void lock() {
        if (locked) {
            return;
        }

        locked = true;
        checkAllPinsAreConnected();
        checkIsDagInForwardDirection();
        checkIsDagInBackwardDirection();
    }

    /**
     * Initiates a call for connecting some gates.
     * 
     * @param sourceComponentName the source component name.
     * @return a target component selector.
     */
    public TargetComponentSelector connect(String sourceComponentName) {
        checkIsNotLocked();
        return new TargetComponentSelector(sourceComponentName);
    }

    @Override
    public List<AbstractCircuitComponent> getInputComponents() {
        return new ArrayList<>(inputGates);
    }

    @Override
    public List<AbstractCircuitComponent> getOutputComponents() {
        return new ArrayList<>(outputGates);
    }

    public final class TargetComponentSelector {

        private final AbstractCircuitComponent sourceComponent;

        TargetComponentSelector(String sourceComponentName) {
            Objects.requireNonNull(sourceComponentName,
                                   "The source component name is null.");

            AbstractCircuitComponent sourceComponent;

            if (sourceComponentName.contains(".")) {
                String[] nameComponents = sourceComponentName.split("\\.");

                if (nameComponents.length != 2) {
                    throw new IllegalArgumentException(
                            "More than one dot operators: " + 
                                    sourceComponentName);
                }

                Circuit subcircuit = 
                        (Circuit) componentMap.get(nameComponents[0]);

                if (subcircuit == null) {
                    throw new IllegalArgumentException(
                            "Subcircuit \"" + nameComponents[0] + "\" is " +
                            "not present in this circuit (" + getName() +
                            ").");
                }

                sourceComponent = 
                        subcircuit.componentMap.get(nameComponents[1]);
            } else {
                sourceComponent = componentMap.get(sourceComponentName);
            }

            if (sourceComponent == null) {
                throwComponentNotPresent(sourceComponentName);
            }

            this.sourceComponent = sourceComponent;
        }

        public void toFirstPinOf(String targetComponentName) {
            AbstractCircuitComponent targetComponent =
                    getTargetComponent(targetComponentName);

            checkIsDoubleInputGate(targetComponent);

            if (((AbstractDoubleInputPinCircuitComponent) targetComponent)
                    .getInputComponent1() != null) {
                throw new InputPinOccupiedException(
                        "The 1st input pin of \"" + targetComponentName + "\"" + 
                        " is occupied.");
            }

            if (sourceComponent.getOutputComponent() == null) {
                ((AbstractDoubleInputPinCircuitComponent) targetComponent)
                        .setInputComponent1(sourceComponent);

                sourceComponent.setOutputComponent(targetComponent);
            } else if (
                   sourceComponent.getOutputComponent() instanceof BranchWire) {
                ((AbstractDoubleInputPinCircuitComponent) targetComponent)
                        .setInputComponent1(
                                sourceComponent.getOutputComponent());
                ((BranchWire) sourceComponent.getOutputComponent())
                        .connectTo(targetComponent);
            } else {
                // Replace an existing wire with BranchWire.
                BranchWire branchWire = new BranchWire();

                // Introduce the new BranchWire to the circuit.
                addComponent(branchWire);

                // Load the BranchWire outputs:
                branchWire.connectTo(sourceComponent.getOutputComponent());
                branchWire.connectTo(targetComponent);

                if (sourceComponent.getOutputComponent()
                        instanceof AbstractDoubleInputPinCircuitComponent) {
                    AbstractDoubleInputPinCircuitComponent tmpComponent =
                            (AbstractDoubleInputPinCircuitComponent)
                            sourceComponent.getOutputComponent();

                    if (tmpComponent.getInputComponent2() == sourceComponent) {
                        tmpComponent.setInputComponent1(branchWire);
                    } else {
                        tmpComponent.setInputComponent2(branchWire);
                    }
                } else {
                    AbstractSingleInputPinCircuitComponent tmpComponent = 
                            (AbstractSingleInputPinCircuitComponent)
                            sourceComponent.getOutputComponent();

                    tmpComponent.setInputComponent(branchWire);
                }

                ((AbstractDoubleInputPinCircuitComponent) targetComponent)
                        .setInputComponent1(branchWire);

                sourceComponent.setOutputComponent(branchWire);
                branchWire.setInputComponent(sourceComponent);
            }
        }

        public void toSecondPinOf(String targetComponentName) {
            AbstractCircuitComponent targetComponent = 
                    getTargetComponent(targetComponentName);

            checkIsDoubleInputGate(targetComponent);

            if (((AbstractDoubleInputPinCircuitComponent) targetComponent)
                    .getInputComponent2() != null) {
                throw new InputPinOccupiedException(
                        "The 2nd input pin of \"" + targetComponentName + "\"" + 
                        " is occupied.");
            }

            if (sourceComponent.getOutputComponent() == null) {
                ((AbstractDoubleInputPinCircuitComponent) targetComponent)
                        .setInputComponent2(sourceComponent);

                sourceComponent.setOutputComponent(targetComponent);
            } else if (
                   sourceComponent.getOutputComponent() instanceof BranchWire) {
                ((AbstractDoubleInputPinCircuitComponent) targetComponent)
                        .setInputComponent2(
                                sourceComponent.getOutputComponent());
                ((BranchWire) sourceComponent.getOutputComponent())
                        .connectTo(targetComponent);
            } else {
                // Replace an existing wire with BranchWire.
                BranchWire branchWire = new BranchWire();

                // Introduce the new BranchWire to the circuit.
                addComponent(branchWire);

                // Load the BranchWire outputs:
                branchWire.connectTo(sourceComponent.getOutputComponent());
                branchWire.connectTo(targetComponent);

                if (sourceComponent.getOutputComponent() 
                        instanceof AbstractDoubleInputPinCircuitComponent) {
                    AbstractDoubleInputPinCircuitComponent tmpComponent = 
                            (AbstractDoubleInputPinCircuitComponent)
                            sourceComponent.getOutputComponent();

                    if (tmpComponent.getInputComponent1() == sourceComponent) {
                        tmpComponent.setInputComponent1(branchWire);
                    } else {
                        tmpComponent.setInputComponent2(branchWire);
                    }
                } else {
                    AbstractSingleInputPinCircuitComponent tmpComponent = 
                            (AbstractSingleInputPinCircuitComponent) 
                            sourceComponent.getOutputComponent();

                    tmpComponent.setInputComponent(branchWire);
                }

                ((AbstractDoubleInputPinCircuitComponent) targetComponent)
                        .setInputComponent2(branchWire);

                sourceComponent.setOutputComponent(branchWire);
                branchWire.setInputComponent(sourceComponent);
            }
        }

        public void to(String targetComponentName) {
            AbstractCircuitComponent targetComponent =
                    getTargetComponent(targetComponentName);

            checkIsSingleInputGate(targetComponent);

            if (((AbstractSingleInputPinCircuitComponent) targetComponent)
                    .getInputComponent() != null) {
                throw new InputPinOccupiedException(
                        "The only input pin of \"" + targetComponentName + 
                        "\" is occupied.");
            }

            if (sourceComponent.getOutputComponent() == null) {
                ((AbstractSingleInputPinCircuitComponent) targetComponent)
                        .setInputComponent(sourceComponent);

                sourceComponent.setOutputComponent(targetComponent);
            } else if (
                   sourceComponent.getOutputComponent() instanceof BranchWire) {
                ((AbstractSingleInputPinCircuitComponent) targetComponent)
                        .setInputComponent(
                                sourceComponent.getOutputComponent());
                ((BranchWire) sourceComponent.getOutputComponent())
                        .connectTo(targetComponent);
            } else {
                // Replace an existing wire with BranchWire.
                BranchWire branchWire = new BranchWire();

                // Introduce the BranchWire to the circuit.
                addComponent(branchWire);

                // Load the BranchWire outputs:
                branchWire.connectTo(sourceComponent.getOutputComponent());
                branchWire.connectTo(targetComponent);

                sourceComponent.setOutputComponent(branchWire);
                ((AbstractSingleInputPinCircuitComponent) targetComponent)
                        .setInputComponent(branchWire);

                branchWire.setInputComponent(sourceComponent);
            }
        }

        private AbstractCircuitComponent 
        getTargetComponent(String targetComponentName) {
            Objects.requireNonNull(targetComponentName,
                                   "The target component name is null.");

            AbstractCircuitComponent targetComponent;

            if (targetComponentName.contains(".")) {
                String[] targetComponentNameComponents = 
                        targetComponentName.split("\\.");

                if (targetComponentNameComponents.length != 2) {
                    throw new IllegalArgumentException(
                            "More than one dot operators in \"" +
                                    targetComponentName + "\".");
                }

                Circuit subcircuit = 
                        (Circuit) 
                        componentMap.get(targetComponentNameComponents[0]);

                if (subcircuit == null) {
                    throw new IllegalStateException(
                            "Subcircuit \"" + targetComponentNameComponents[0] +
                            "\" not present in circuit \"" + getName() + "\".");
                }

                targetComponent = 
                        subcircuit.componentMap
                                  .get(targetComponentNameComponents[1]);
            } else {
                targetComponent = componentMap.get(targetComponentName);
            }

            if (targetComponent == null) {
                throw new IllegalStateException(
                        "The component \"" + targetComponentName + "\" is " + 
                        "not present in circuit \"" + getName() + "\".");
            }

            return targetComponent;
        }

        private void throwComponentNotPresent(String componentName) {
            throw new IllegalStateException(
                    "The component \"" + componentName + "\" is " +
                    "not present in the circuit \"" + getName() + "\".");
        }

        private void checkIsSingleInputGate(AbstractCircuitComponent gate) {
            if (!(gate instanceof AbstractSingleInputPinCircuitComponent)) {
                throw new IllegalArgumentException(
                        "A single input pin component is expected here.");
            }
        }

        private void checkIsDoubleInputGate(AbstractCircuitComponent gate) {
            if (!(gate instanceof AbstractDoubleInputPinCircuitComponent)) {
                throw new IllegalArgumentException(
                        "A double input pin component is expected here.");
            }
        }
    }

    Map<String, AbstractCircuitComponent> getComponentMap() {
        return componentMap;
    }

    Set<AbstractCircuitComponent> getComponentSet() {
        return componentSet;
    }

    void addComponent(AbstractCircuitComponent component) {
        componentSet.add(component);
    }

    void removeComponent(AbstractCircuitComponent component) {
        componentSet.remove(component);
    }

    private void checkIsNotLocked() {
        if (locked) {
            throw new IllegalStateException(
                    "The circuit \"" + getName() + "\" is locked.");
        }
    }

    private void unsetAllInputPins() {
        for (InputGate inputGate : inputGates) {
            inputGate.setBit(false);
        }
    }

    private static String checkName(String name) {
        Objects.requireNonNull(name, "The circuit name is null.");

        if (name.isEmpty()) {
            throw new IllegalArgumentException("The circuit name is empty.");
        }

        if (name.startsWith(INPUT_PIN_NAME_PREFIX)) {
            throw new IllegalArgumentException(
                    "The circuit name (" + name + ") has illegal prefix \"" +
                    INPUT_PIN_NAME_PREFIX + "\".");
        }

        if (name.startsWith(OUTPUT_PIN_NAME_PREFIX)) {
            throw new IllegalArgumentException(
                    "The circuit name (" + name + ") has illegal prefix \"" + 
                    OUTPUT_PIN_NAME_PREFIX + "\".");
        }

        return name;
    }

    private static int checkInputPinCount(int inputPins) {
        if (inputPins < MINIMUM_INPUT_PINS) {
            throw new IllegalArgumentException(
            "Too few input pins (" + inputPins + "). At least " + 
                    MINIMUM_INPUT_PINS + " expected.");
        }

        return inputPins;
    }

    private static int checkOutputPinCount(int outputPins) {
        if (outputPins < MINIMUM_INPUT_PINS) {
            throw new IllegalArgumentException(
            "Too few output pins (" + outputPins + "). At least " + 
                    MINIMUM_OUTPUT_PINS + " expected.");
        }

        return outputPins;
    }

    private String checkNewGateName(String gateName) {
        Objects.requireNonNull(gateName, "The new gate name is null.");

        if (gateName.isEmpty()) {
            throw new IllegalArgumentException("The new gate name is empty.");
        }



        if (gateName.startsWith(INPUT_PIN_NAME_PREFIX)) {
            throw new IllegalArgumentException(
                    "The new gate name (" + gateName + ") has illegal prefix " +
                    "\"" + INPUT_PIN_NAME_PREFIX + "\".");
        }

        if (gateName.startsWith(OUTPUT_PIN_NAME_PREFIX)) {
            throw new IllegalArgumentException(
                    "The new gate name (" + gateName + ") has illegal prefix " +
                    "\"" + OUTPUT_PIN_NAME_PREFIX + "\".");
        }

        if (componentMap.containsKey(gateName)) {
            throw new IllegalArgumentException(
                    "The new gate name (" + gateName + ") is already "+
                    "occupied.");
        }

        return gateName;
    }

    private void checkInputGateComplete(InputGate inputGate, String name) {
        if (inputGate.getOutputComponent() == null) {
            throw new IncompleteCircuitException(
                    "The input gate \"" + name + "\" has no " + 
                    "output gate.");
        }    
    }

    private void checkOutputGateComplete(OutputGate outputGate, String name) {
        if (outputGate.getInputComponent() == null) {
            throw new IncompleteCircuitException(
                    "The output gate \"" + name + "\" has no input gate.");
        }
    }

    private void checkNotGateComplete(NotGate notGate, String name) {
        if (notGate.getInputComponent() == null) {
            throw new IncompleteCircuitException(
                    "The not gate \"" + name + "\" has no input gate.");
        }

        if (notGate.getOutputComponent() == null) {
            throw new IncompleteCircuitException(
                    "The not gate \"" + name + "\" has no output gate.");
        }
    }

    private void checkOrGateComplete(OrGate gate, String name) {
        if (gate.getInputComponent1() == null) {
            throw new IncompleteCircuitException(
                    "The OrGate \"" + name + "\" has no 1st input gate.");
        }

        if (gate.getInputComponent2() == null) {
            throw new IncompleteCircuitException(
                    "The OrGate \"" + name + "\" has no 2nd input gate.");
        }

        if (gate.getOutputComponent() == null) {
            throw new IncompleteCircuitException(
                    "The OrGate \"" + name + "\" has no output gate.");
        }
    }

    private void checkAndGateComplete(AndGate gate, String name) {
        if (gate.getInputComponent1() == null) {
            throw new IncompleteCircuitException(
                    "The AndGate \"" + name + "\" has no 1st input gate.");
        }

        if (gate.getInputComponent2() == null) {
            throw new IncompleteCircuitException(
                    "The AndGate \"" + name + "\" has no 2nd input gate.");
        }

        if (gate.getOutputComponent() == null) {
            throw new IncompleteCircuitException(
                    "The AndGate \"" + name + "\" has no output gate.");
        }
    }

    private void checkAllPinsAreConnected() {
        for (Map.Entry<String, AbstractCircuitComponent> e : 
                componentMap.entrySet()) {
            if (e.getValue() instanceof InputGate) {
                checkInputGateComplete((InputGate) e.getValue(), e.getKey());
            } else if (e.getValue() instanceof OutputGate) {
                checkOutputGateComplete((OutputGate) e.getValue(), e.getKey());
            } else if (e.getValue() instanceof NotGate) {
                checkNotGateComplete((NotGate) e.getValue(), e.getKey());
            } else if (e.getValue() instanceof OrGate) {
                checkOrGateComplete((OrGate) e.getValue(), e.getKey());
            } else if (e.getValue() instanceof AndGate) {
                checkAndGateComplete((AndGate) e.getValue(), e.getKey());
            } else {
                throw new IllegalStateException(
                        "Unknown component type: " + e.getValue());
            }
        }
    }

    private enum NodeColor {
        WHITE,
        GRAY,
        BLACK
    }

    private void checkIsDagInForwardDirection() {
       Map<AbstractCircuitComponent, NodeColor> colors = new HashMap<>();

       for (AbstractCircuitComponent component : componentSet) {
           colors.put(component, NodeColor.WHITE);
       }

       for (AbstractCircuitComponent component : inputGates) {
           if (colors.get(component).equals(NodeColor.WHITE)) {
               dfsForwardVisit(component, colors);
           }
       }
    }

    private void checkIsDagInBackwardDirection() {
        Map<AbstractCircuitComponent, NodeColor> colors = new HashMap<>();

        for (AbstractCircuitComponent component : componentSet) {
            colors.put(component, NodeColor.WHITE);
        }

        for (AbstractCircuitComponent component : outputGates) {
            if (colors.get(component).equals(NodeColor.WHITE)) {
                dfsBackwardVisit(component, colors);
            }
        }
    }

    private void dfsForwardVisit(
            AbstractCircuitComponent component,
            Map<AbstractCircuitComponent, NodeColor> colors) {
        colors.put(component, NodeColor.GRAY);

        for (AbstractCircuitComponent child : component.getOutputComponents()) {
            if (colors.get(child).equals(NodeColor.GRAY)) {
                throw new ForwardCycleException(
                        "Forward cycle detected in circuit \"" + getName() +
                        "\".");
            }

            if (colors.get(child).equals(NodeColor.WHITE)) {
                dfsForwardVisit(child, colors);
            }
        }

        colors.put(component, NodeColor.BLACK);
    }

    private void dfsBackwardVisit(
            AbstractCircuitComponent component,
            Map<AbstractCircuitComponent, NodeColor> colors) {
        colors.put(component, NodeColor.GRAY);

        for (AbstractCircuitComponent parent : component.getInputComponents()) {
            if (colors.get(parent).equals(NodeColor.GRAY)) {
                throw new BackwardCycleException(
                        "Backward cycle detected in circuit \"" + getName() +
                        "\".");
            }

            if (colors.get(parent).equals(NodeColor.WHITE)) {
                dfsBackwardVisit(parent, colors);
            }
        }

        colors.put(component, NodeColor.BLACK);
    }
}

AndGate.java

package net.coderodde.circuits.components.support;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import net.coderodde.circuits.components.AbstractCircuitComponent;
import net.coderodde.circuits.components.AbstractDoubleInputPinCircuitComponent;

/**
 * This logical gate implements the {@code and} operation, i.e., it outputs
 * {@code true} only if both of the inputs are {@code true}.
 * 
 * @author Rodion "rodde" Efremov
 * @version 1.6 (Sep 20, 2017)
 */
public final class AndGate extends AbstractDoubleInputPinCircuitComponent {

    public AndGate(String name) {
        super(name);
    }

    @Override
    public boolean doCycle() {
        return input1.doCycle() && input2.doCycle();
    }

    @Override
    public List<AbstractCircuitComponent> getInputComponents() {
        return new ArrayList<>(Arrays.asList(input1, input2));
    }

    @Override
    public List<AbstractCircuitComponent> getOutputComponents() {
        if (output instanceof BranchWire) {
            return new ArrayList<>(((BranchWire) output).getOutputs());
        } else {
            return Arrays.asList(output);
        }
    }
}

OrGate.java

package net.coderodde.circuits.components.support;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import net.coderodde.circuits.components.AbstractCircuitComponent;
import net.coderodde.circuits.components.AbstractDoubleInputPinCircuitComponent;

/**
 * This logical gate implements the {@code or} operation, i.e., it outputs
 * {@code true} if either or both of the inputs are {@code true}.
 * 
 * @author Rodion "rodde" Efremov
 * @version 1.6 (Sep 20, 2017)
 */
public final class OrGate extends AbstractDoubleInputPinCircuitComponent {

    public OrGate(String name) {
        super(name);
    }

    @Override
    public boolean doCycle() {
        return input1.doCycle() || input2.doCycle();
    }

    @Override
    public List<AbstractCircuitComponent> getInputComponents() {
        return Arrays.asList(input1, input2);
    }

    @Override
    public List<AbstractCircuitComponent> getOutputComponents() {
        if (output instanceof BranchWire) {
            return new ArrayList<>(((BranchWire) output).getOutputs());
        }

        return Arrays.asList(output);
    }
}

NotGate.java

package net.coderodde.circuits.components.support;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import net.coderodde.circuits.components.AbstractCircuitComponent;
import net.coderodde.circuits.components.AbstractSingleInputPinCircuitComponent;

/**
 * This logical gate inverses the input signal, i.e., it outputs {@code true} 
 * when the input is {@code false}, and vice versa.
 * 
 * @author Rodion "rodde" Efremov
 * @version 1.6 (Sep 20, 2017)
 */
public final class NotGate extends AbstractSingleInputPinCircuitComponent {

    public NotGate(String name) {
        super(name);
    }

    @Override
    public boolean doCycle() {
        return !input.doCycle();
    }

    @Override
    public List<AbstractCircuitComponent> getInputComponents() {
        return Arrays.asList(input);
    }

    @Override
    public List<AbstractCircuitComponent> getOutputComponents() {
        if (output instanceof BranchWire) {
            return new ArrayList<>(((BranchWire) output).getOutputs());
        }

        return Arrays.asList(output);
    }
}

BranchWire.java

package net.coderodde.circuits.components.support;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import net.coderodde.circuits.components.AbstractCircuitComponent;
import net.coderodde.circuits.components.AbstractSingleInputPinCircuitComponent;

/**
 * This class allows sharing an output of a component over more than one input 
 * components.
 * 
 * @author Rodion "rodde" Efremov
 * @version 1.6 (Oct 6, 2017)
 */
public final class BranchWire extends AbstractSingleInputPinCircuitComponent {

    private final Set<AbstractCircuitComponent> outputs = new HashSet<>();

    public BranchWire() {
        super(null);
    }

    @Override
    public boolean doCycle() {
        return input.doCycle();
    }

    public void connectTo(AbstractCircuitComponent circuitComponent) {
        outputs.add(circuitComponent);
    }

    public void removeFrom(AbstractCircuitComponent circuitComponent) {
        outputs.remove(circuitComponent);
    }

    public Set<AbstractCircuitComponent> getOutputs() {
        return Collections.unmodifiableSet(outputs);
    }

    @Override
    public List<AbstractCircuitComponent> getInputComponents() {
        return Arrays.asList(input);
    }

    @Override
    public List<AbstractCircuitComponent> getOutputComponents() {
        return new ArrayList<>(outputs);
    }
}

InputGate.java

package net.coderodde.circuits.components.support;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import net.coderodde.circuits.components.AbstractCircuitComponent;
import net.coderodde.circuits.components.AbstractSingleInputPinCircuitComponent;

/**
 * This class implements an input pin of a circuit.
 * 
 * @author Rodion "rodde" Efremov
 * @version 1.6 (Oct 6, 2017)
 */
public final class InputGate extends AbstractSingleInputPinCircuitComponent {

    public static final boolean DEFAULT_BIT = false;

    private boolean bit;

    public InputGate(String name, boolean bit) {
        super(name);
        setBit(bit);
    }

    public InputGate(String name) {
        this(name, DEFAULT_BIT);
    }

    public boolean getBit() {
        return bit;
    }

    public void setBit(boolean bit) {
        this.bit = bit;
    }

    @Override
    public boolean doCycle() {
        if (getInputComponent() != null) {
            return getInputComponent().doCycle();
        }

        return bit;
    }

    @Override
    public List<AbstractCircuitComponent> getInputComponents() {
        if (input == null) {
            return Collections.<AbstractCircuitComponent>emptyList();
        }

        return Arrays.asList(input);
    }

    @Override
    public List<AbstractCircuitComponent> getOutputComponents() {
        if (output instanceof BranchWire) {
            return new ArrayList<>(((BranchWire) output).getOutputs());
        }

        return Arrays.asList(output);
    }
}

OutputGate.java

package net.coderodde.circuits.components.support;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import net.coderodde.circuits.components.AbstractCircuitComponent;
import net.coderodde.circuits.components.AbstractSingleInputPinCircuitComponent;

/**
 * This class implements the output pins that remember one bit each.
 * 
 * @author Rodion "rodde" Efremov
 * @version 1.6 (Oct 6, 2017)
 */
public final class OutputGate extends AbstractSingleInputPinCircuitComponent {

    public OutputGate(String name) {
        super(name);
    }

    @Override
    public boolean doCycle() {
        return input.doCycle();
    }

    @Override
    public List<AbstractCircuitComponent> getInputComponents() {
        return Arrays.asList(input);
    }

    @Override
    public List<AbstractCircuitComponent> getOutputComponents() {
        if (output == null) {
            return Collections.<AbstractCircuitComponent>emptyList();
        }

        if (output instanceof BranchWire) {
            return new ArrayList<>(((BranchWire) output).getOutputs());
        }

        return Arrays.asList(output);
    }
}

Demo.java

package net.coderodde.circuits;

import static net.coderodde.circuits.Utils.toBinaryString;

public final class Demo {

    public static void main(String[] args) {
        System.out.println("xor:");

        Circuit xor = new Circuit("xor", 2, 1);

        xor.addAndGate("and1");
        xor.addAndGate("and2");
        xor.addNotGate("not1");
        xor.addNotGate("not2");
        xor.addOrGate("or");

        xor.connect("inputPin0").to("not1");
        xor.connect("not1").toFirstPinOf("and1");
        xor.connect("inputPin1").toSecondPinOf("and1");

        xor.connect("inputPin1").to("not2");
        xor.connect("not2").toSecondPinOf("and2");
        xor.connect("inputPin0").toFirstPinOf("and2");

        xor.connect("and1").toFirstPinOf("or");
        xor.connect("and2").toSecondPinOf("or");
        xor.connect("or").to("outputPin0");

        for (boolean bit1 : new boolean[]{ false, true }) {
            for (boolean bit2 : new boolean[]{ false, true }) {
                System.out.println(toBinaryString(bit1, bit2) + " " + 
                        xor.doCycle(bit1, bit2)[0]);
            }
        }

        //// Constructing the addition circuit.
        System.out.println("\n2-bit by 2-bit addition:");
        Circuit additionCircuit = new Circuit("additionCircuit", 4, 3);

        Circuit xor1 = new Circuit(xor, "xor1");
        Circuit xor2 = new Circuit(xor, "xor2");
        Circuit xor3 = new Circuit(xor, "xor3");

        additionCircuit.addCircuit(xor1);
        additionCircuit.addCircuit(xor2);
        additionCircuit.addCircuit(xor3);

        additionCircuit.addAndGate("and1");
        additionCircuit.addAndGate("and2");
        additionCircuit.addAndGate("and3");
        additionCircuit.addAndGate("and4");

        additionCircuit.addOrGate("or1");
        additionCircuit.addOrGate("or2");

        // Output bit 1:
        additionCircuit.connect("inputPin0").to("xor1.inputPin0");
        additionCircuit.connect("inputPin2").to("xor1.inputPin1");
        additionCircuit.connect("xor1.outputPin0").to("outputPin2");

        // Carry bit:
        additionCircuit.connect("inputPin0").toFirstPinOf("and1");
        additionCircuit.connect("inputPin2").toSecondPinOf("and1");

        // Output bit 2:
        additionCircuit.connect("and1").to("xor2.inputPin0");
        additionCircuit.connect("inputPin1").to("xor2.inputPin1");
        additionCircuit.connect("inputPin3").to("xor3.inputPin1");
        additionCircuit.connect("xor2.outputPin0").to("xor3.inputPin0");
        additionCircuit.connect("xor3.outputPin0").to("outputPin1");

        // Output bit 3:
        additionCircuit.connect("inputPin1").toFirstPinOf("and2");
        additionCircuit.connect("inputPin3").toSecondPinOf("and2");
        additionCircuit.connect("and1").toFirstPinOf("and3");
        additionCircuit.connect("inputPin1").toSecondPinOf("and3");
        additionCircuit.connect("and1").toFirstPinOf("and4");
        additionCircuit.connect("inputPin3").toSecondPinOf("and4");

        additionCircuit.connect("and2").toFirstPinOf("or1");
        additionCircuit.connect("and3").toSecondPinOf("or1");
        additionCircuit.connect("or1").toFirstPinOf("or2");
        additionCircuit.connect("and4").toSecondPinOf("or2");
        additionCircuit.connect("or2").to("outputPin0");

        for (boolean bit1 : new boolean[] { false, true }) {
            for (boolean bit0 : new boolean[] { false, true }) {
                for (boolean bit3 : new boolean[] { false, true }) {
                    for (boolean bit2 : new boolean[] { false, true }) {
                        System.out.print(Utils.toBinaryString(bit1, bit0));
                        System.out.print(" + ");
                        System.out.print(Utils.toBinaryString(bit3, bit2));
                        System.out.print(" = ");
                        System.out.println(
                                Utils.toBinaryString(
                                        additionCircuit.doCycle(bit0, 
                                                                bit1, 
                                                                bit2, 
                                                                bit3)));
                    }
                }
            }
        }
    }
}

CircuitTest.java

package net.coderodde.circuits;

import java.util.Arrays;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import org.junit.Test;

public class CircuitTest {

    @Test(expected = ForwardCycleException.class)
    public void testFindsForwardCycle() {
        Circuit circuit = new Circuit("myCircuit", 1, 1);
        circuit.addAndGate("and1");
        circuit.connect("inputPin0").toFirstPinOf("and1");
        circuit.connect("and1").to("outputPin0");
        circuit.connect("and1").toSecondPinOf("and1");
        circuit.lock();
    }

    @Test(expected = BackwardCycleException.class)
    public void testFindBackwardCycle() {
        Circuit circuit = new Circuit("c", 1, 1);
        circuit.addOrGate("or");
        circuit.addAndGate("and");
        circuit.connect("inputPin0").toFirstPinOf("or");
        circuit.connect("and").toSecondPinOf("or");
        circuit.connect("or").to("outputPin0");
        circuit.connect("and").toFirstPinOf("and");
        circuit.connect("and").toSecondPinOf("and");
        circuit.lock();
    }

    @Test(expected = InputPinOccupiedException.class)
    public void testCannotConnectToOccupiedInput() {
        Circuit circuit = new Circuit("myCircuit", 1, 1);
        circuit.addNotGate("not");
        circuit.connect("inputPin0").to("outputPin0");
        circuit.connect("inputPin0").to("not");
        circuit.connect("not").to("outputPin0");
        circuit.lock();
    }

    @Test
    public void test1() {
        Circuit circuit = new Circuit("myCircuit", 4, 1);

        circuit.addAndGate("and1");
        circuit.addAndGate("and2");
        circuit.addOrGate("or");

        circuit.connect("inputPin0").toFirstPinOf("and1");
        circuit.connect("inputPin1").toSecondPinOf("and1");
        circuit.connect("inputPin2").toFirstPinOf("and2");
        circuit.connect("inputPin3").toSecondPinOf("and2");
        circuit.connect("and1").toFirstPinOf("or");
        circuit.connect("and2").toSecondPinOf("or");
        circuit.connect("or").to("outputPin0");

        for (boolean bit0 : new boolean[]{ false, true }) {
            for (boolean bit1 : new boolean[]{ false, true }) {
                for (boolean bit2 : new boolean[]{ false, true }) {
                    for (boolean bit3 : new boolean[]{ false, true }) {
                        circuit.setInputBits(bit0, bit1, bit2, bit3);
                        boolean expected = (bit0 && bit1) || (bit2 && bit3);
                        circuit.doCycle();
                        boolean[] result = circuit.getOutputBits();
                        assertEquals(1, result.length);
                        assertEquals(expected, result[0]);
                    }
                }
            }
        }
    }

    @Test
    public void test2() {
        Circuit circuit = new Circuit("c", 2, 1);

        circuit.addAndGate("and1");
        circuit.addAndGate("and2");
        circuit.addNotGate("not1");
        circuit.addNotGate("not2");
        circuit.addOrGate("or");

        circuit.connect("inputPin0").toFirstPinOf("and1");
        circuit.connect("inputPin1").to("not1");
        circuit.connect("not1").toSecondPinOf("and1");

        circuit.connect("inputPin0").to("not2");
        circuit.connect("not2").toFirstPinOf("and2");
        circuit.connect("inputPin1").toSecondPinOf("and2");

        circuit.connect("and1").toFirstPinOf("or");
        circuit.connect("and2").toSecondPinOf("or");
        circuit.connect("or").to("outputPin0");

        for (boolean bit0 : new boolean[] { false, true }) {
            for (boolean bit1 : new boolean[] { false, true }) {
                boolean expected = (bit0 && !bit1) || (!bit0 && bit1);
                circuit.setInputBits(bit0, bit1);
                circuit.doCycle();
                assertEquals(expected, circuit.getOutputBits()[0]);
            }
        }
    }

    @Test
    public void testSubcircuit1() {
        Circuit subcircuit = new Circuit("mySubcircuit", 2, 1);

        subcircuit.addAndGate("and");
        subcircuit.addNotGate("not1");
        subcircuit.addNotGate("not2");
        subcircuit.connect("inputPin0").to("not1");
        subcircuit.connect("inputPin1").to("not2");
        subcircuit.connect("not1").toFirstPinOf("and");
        subcircuit.connect("not2").toSecondPinOf("and");
        subcircuit.connect("and").to("outputPin0");

        Circuit circuit = new Circuit("myCircuit", 2, 1);
        circuit.addCircuit(subcircuit);
        circuit.addNotGate("not");

        circuit.connect("inputPin0").to("mySubcircuit.inputPin0");
        circuit.connect("inputPin1").to("mySubcircuit.inputPin1");
        circuit.connect("mySubcircuit.outputPin0").to("not");
        circuit.connect("not").to("outputPin0");    

        for (boolean bit0 : new boolean[] { false, true }) {
            for (boolean bit1 : new boolean[] { false, true }) {
                circuit.setInputBits(bit0, bit1);
                circuit.doCycle();
                boolean expected = !(!bit0 && !bit1);
                assertEquals(expected, circuit.getOutputBits()[0]);
            }
        }
    }

    @Test
    public void testCopyConstructor() {
        Circuit c = new Circuit("c", 2, 2);

        c.addAndGate("and");
        c.addOrGate("or");

        c.connect("inputPin0").toFirstPinOf("and");
        c.connect("inputPin1").toSecondPinOf("and");
        c.connect("inputPin0").toFirstPinOf("or");
        c.connect("inputPin1").toSecondPinOf("or");
        c.connect("and").to("outputPin0");
        c.connect("or").to("outputPin1");

        Circuit c2 = new Circuit(c, "c2");

        for (boolean b1 : new boolean[]{ false, true }) {
            for (boolean b2 : new boolean[]{ false, true }) {
                boolean[] expected = new boolean[] { b1 && b2, b1 || b2 };
                assertTrue(Arrays.equals(expected, c.doCycle(b1, b2)));
                assertTrue(Arrays.equals(expected, c2.doCycle(b1, b2)));
            }
        }
    }
}

The demo output is

xor:
00 false
01 true
10 true
11 false

2-bit by 2-bit addition:
00 + 00 = 000
00 + 01 = 001
00 + 10 = 010
00 + 11 = 011
01 + 00 = 001
01 + 01 = 010
01 + 10 = 011
01 + 11 = 100
10 + 00 = 010
10 + 01 = 011
10 + 10 = 100
10 + 11 = 101
11 + 00 = 011
11 + 01 = 100
11 + 10 = 101
11 + 11 = 110

(The entire project lives here.)

Critique request

I would like to hear some comments regarding code organization and API design.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.