3
\$\begingroup\$

I am somewhat new to programming. I've written a program that takes user input accepting voltages and resistors, and calculating total resistance/current/voltage in the circuit. I am looking for ways to improve my coding, whether it be improving its time/space complexity or making better comments to make it more maintainable and scalable (as I have plans to expand it to accept more circuit component types) or implementing more efficient algorithms. You can find the files on GitHub.

There are some additional files in the GitHub (pictures/README) that also help to explain the program.

Circuit.java

package circuit;
import java.util.ArrayList;

/**
 * The circuit to which components can be added to. 
 * 
 * An object of this class exists in the program as a singleton object. 
 * 
 * It holds the ArrayList of components in the circuit.
 * 
 * 
 * @author Michael Sinclair.
 * @version 2.302
 * @since 27 January 2019.
*/

public class Circuit {

/*Creates only one instance of a circuit.*/
private static Circuit instance = null;
protected static Circuit getInstance() {
    if (instance == null) {
    instance = new Circuit();
    }
return instance;
}

/**Instance variable list to contain components.*/
private ArrayList<Component> components;
/**Private constructor ensures only one instance of circuit can be created.*/
private Circuit() {
    this.components = new ArrayList<>();
}

/*Methods.*/

/** get method to get list of components
 * @param none
 * @return ArrayList<Component> components*/
protected ArrayList<Component> getComponents(){
    return this.components;
}

/**Add component to circuit
 * @param Component c.*/
protected void addComponent(Component c){
    this.components.add(c);
}

/**Return information of all components in circuit
     * @return String.*/
@Override
public String toString(){
    String str="";
    /*For each object in array.*/
    for(Object obj : components){
        /*If it is a resistor.*/
        if (obj.getClass() == Resistor.class){
            /*Downcast to original object type to use class toString() method.*/
            str+= ("Resistor: "+(Resistor)obj).toString()+"\n";
        }

        /*Another form of testing object class, if it is a voltage.*/
        if (obj instanceof Voltage){
            /*Downcast to original object type to use class toString() method.*/
            str+= ("Voltage:  "+(Voltage)obj).toString()+"\n";
        }
    }
    /*Remove last \n produced from the above iteration.*/
    str = str.substring(0,str.length()-1);
    return str;
    }
}

CircuitAnalysis.java

package circuit;
import java.util.ArrayList;
import java.util.Collections;

/** Assistant class to calculate circuit characteristics.
 * 
 * Input requires reference ground node and the components in the circuit.
 * 
 * I decided to compartmentalize this part of the program in a separate class to simplify the coding and maintenance, but when considering resource management it would likely just be methods within UserMain.
 * 
 * The resistor reduction algorithm used by this class is to first reduce resistors that are in parallel between the same two nodes to a single equivalent resistor between those nodes, then to reduce any serial resistors 
 * to a single equivalent resistor between the two outer-nodes which will then create more parallel resistors between the same two nodes, and so on.
 * 
 * 
 * @author Michael Sinclair.
 * @version 2.304
 * @since 29 January 2019.
*/

public class CircuitAnalysis {
/* instance variables */
private ArrayList<Node> nodeList;
private ArrayList<Component> components;
private int ground;
/* to calculate characteristics */
private double totalV;
private double totalR;
private int voltageSources;
/* to rewind resistor Id count for user to continue after calculations */
private int countResistors;

/** Only constructor for this class
 * 
 * @param int ground
 * @param ArrayList<Component> comps
 * @param ArrayList<Node> nodes
 */
protected CircuitAnalysis(int ground, ArrayList<Component> comps, ArrayList<Node> nodes) {
    /* initialize variables */
    this.ground = ground;
    this.totalR = 0.0;
    this.totalV = 0.0;
    this.voltageSources = 0;
    countResistors = 0;
    /* copy the ArrayLists so that the User can continue operations after calculation, and to enable node specific calculations based on original list */
    this.components = new ArrayList<>(comps);
    /* have to create new Node objects to avoid altering the input list - basically dereferencing the node objects from the input and creating clone objects of them with t he same id */
    this.nodeList = new ArrayList<>();
    for(Node node:nodes) {
        nodeList.add(new Node(node.getId()));
    }
    /* now point copied components to the copied nodeList, and attach the copied components to the copied nodes */
    for (Component comp:this.components) {
        for(Node node:this.nodeList) {
            /* if the component points to this iteration's node */
            if(comp.getNode1().getId()==node.getId()) {
                /* point it to the new copy object in this class nodeList */
                comp.setNode1(node);
                /* and connect it to the attached copy node */
                node.connect(comp);
            }
            /* same for second node */
            if(comp.getNode2().getId()==node.getId()) {
                comp.setNode2(node);
                node.connect(comp);
            }
        }
    }

    /* sort the resistor nodes of the copies, maintain the ordering for the original list that the user input for their resistors */
    /* sort the ArrayList of components by node 1 and node 2 (smaller of both first) - note that by construction, voltages will always have ordered nodes */
    for (int j = 0; j<components.size();j++) {
        for (int i = 0; i<components.size()-1;i++) {
            if (components.get(i).compare(components.get(i+1))>0) {
                /* if component nodes are disordered, swap them */
                Collections.swap(components, i, i+1);
            }
        }
    }
}


/* methods */

/** No parameters or returns, automates circuit measurements by calling analyzeVoltage(),analyzeResistance(),printCharactersitics() and reduces resistor count for resistors created for circuit calculations */
protected void analyzeCircuit() {
    /* find total voltage and count voltage sources */
    this.analyzeVoltage();
    /* find total resistance of the circuit */
    this.analyzeResistance();
    System.out.println("");
    /* print out calculated circuit characteristics */
    this.printCharacteristics();
    /* rewind resistor count for user to continue altering circuit */
    /* if for some (unknown) reason, the user did not enter any resistors do not alter the resistor count */
    if(countResistors == 0) {
        return;
    }
    /* for each resistor added, lower the global resistor id number to sync the number back with the user's circuit */
    for(int i=0;i<countResistors;i++) {
        Resistor.resnum--;
    }
}



/**No parameters or returns, finds total voltage in the circuit - note that this program can currently only handle directly serial voltage (connected in series to each other) */
protected void analyzeVoltage() {
    /* for each component */
    for (int i = 0; i<components.size();i++) {
        /* if it is a voltage */
        if (components.get(i).getClass() == Voltage.class) {
            /* get the voltage */
            this.totalV+=((Voltage)(components.get(i))).getV();
            /* count voltage sources */
            this.voltageSources++;
        }
    }
}



/**No parameters or returns, finds total resistance in the circuit */
protected void analyzeResistance() {
    /* while more than 1 resistor exists */
    while(components.size()>this.voltageSources+1) {
        /* reduce parallel resistors across the same nodes to one resistor */
        this.analyzeParallelSameNode();
        /* reduce serial resistors individually in the circuit */
        this.analyzeSeriesIndividually();
    }

    /* now that there is only one resistor in the circuit iterate through the circuit */
    for (int i = 0; i<components.size();i++) {
        /* if it is a resistor */
        if (components.get(i) instanceof Resistor) {
            /* get the resistance - note this only executes once */
            this.totalR+=((Resistor)components.get(i)).getR();
        }
    }
}



/**No parameters or returns, reduces same-node parallel resistors to a single equivalent resistor */
protected void analyzeParallelSameNode() {
    ArrayList<Component> temp = new ArrayList<>();
    ArrayList<Component> toRemove = new ArrayList<>();
    ArrayList<Component> toConnect = new ArrayList<>();
    /* for each node */
    /* TODO explore possibility that only one for loop is needed for the nodeList - and simply compare second node */
    for (int n = 0; n<nodeList.size();n++) {
        /* find components connected to each other node */
        for (int m = 0; m<nodeList.size();m++) {
            /* components cannot have the same node on both sides & don't want to do the same two nodes twice */
                if (n!=m && n<m) {
                    /* for each component */
                    for (int k = 0;k<components.size();k++) {
                        if(components.get(k).getNode1().getId() == n && components.get(k).getNode2().getId() == m) { 
                            /* if it is a resistor */
                            if (components.get(k).getClass() == Resistor.class) {
                                /* if it is between the two nodes */
                                if(components.get(k).getNode1().getId() == n && components.get(k).getNode2().getId() == m) {
                                    /* add it to temporary list */
                                    temp.add(components.get(k));
                                }
                            }
                        }
                    }
                    /* if a parallel connection was found between node n and m*/
                    if(temp.size()>1) {
                        /* create equivalent parallel resistor */
                        Resistor equivalent = new Resistor(this.parallelResistors(temp),this.findNode(n),this.findNode(m));
                        /* for rewinding resistor id */
                        this.countResistors++;
                        /* queue it for connection */
                        toConnect.add(equivalent);
                        /* queue resistors that need to be removed */
                        for(Component remove:temp) {
                            toRemove.add(remove);
                        }
                    }
                    /* clear the list for future calculations */
                    temp.clear();
                }
            }
        }
    /* remove resistors to be replaced by single equivalent resistor */
    /* if there are items to be removed */
    if(toRemove.size()>0) {
        /* for each component to be removed */
        for (Component remove:toRemove) {
            /* for each component */
            for(int i = 0; i <components.size();i++) {
                /* if the component is a resistor and it is in the list of resistors to be removed */
                if(components.get(i).getId()==remove.getId()&&components.get(i) instanceof Resistor) {
                    /* remove it from components */
                    remove.getNode1().disconnect(remove);
                    remove.getNode2().disconnect(remove);
                    components.remove(i);
                    /* need to consider that components has shrunk by 1 */
                    i--;
                }
            }
        }
    }
    /* attach equivalent resistors */
    for(Component comp:toConnect) {
        components.add(comp);
        comp.getNode1().connect(comp);
        comp.getNode2().connect(comp);
    }
}



/* No parameters or returns, reduces any two serially connected resistors individually */
protected void analyzeSeriesIndividually() {
    ArrayList<Component> toAdd = new ArrayList<>();
    ArrayList<Component> toRemove = new ArrayList<>();
    Node firstNode = null;
    Node secondNode = null;
    /* can only perform this operation a single time before calling it again - resulted in errors created floating resistors that could not be reduced further otherwise */
    boolean doOnce = false;
    /* for each node */
    for(Node node:nodeList) {
        /* if there are 2 attachments that are both resistors */
        if (node.getAttachments().size()==2 && node.getAttachments().get(0) instanceof Resistor && node.getAttachments().get(1) instanceof Resistor && !doOnce) {
            /* find first and second node by Id - one must have a first node prior to the current node being tested and one must have a node after */
            if(node.getAttachments().get(0).getNode1().getId()<node.getAttachments().get(1).getNode1().getId()) {
                firstNode = node.getAttachments().get(0).getNode1();
                secondNode = node.getAttachments().get(1).getNode2();
            }
            else {
                firstNode = node.getAttachments().get(1).getNode1();
                secondNode = node.getAttachments().get(0).getNode2();
            }
            /* if not already queued for removal */
            if(!toRemove.contains(node.getAttachments().get(0))) {
                if(!toRemove.contains(node.getAttachments().get(1))) {
                    toRemove.add(node.getAttachments().get(0));
                    toRemove.add(node.getAttachments().get(1));
                    toAdd.add(new Resistor(((Resistor)node.getAttachments().get(0)).getR()+((Resistor)node.getAttachments().get(1)).getR(),firstNode,secondNode));
                    /* for rewinding resistor id */
                    this.countResistors++;
                }
            }
            /* prevent program from combining more than two serial resistors at the same time */
            doOnce = true;
        }
    }
    /* combine serial resistors individually - first remove them from the circuit */
    for(Component remove:toRemove) {
        remove.getNode1().disconnect(remove);
        remove.getNode2().disconnect(remove);
        components.remove(remove);
    }
    /* then add the equivalent resistors */
    for(Component addR:toAdd) {
        addR.getNode1().connect(addR);
        addR.getNode2().connect(addR);
        components.add(addR);
    }
}



/** Find ArrayList index - note ArrayList has built in remove with object parameter but I wanted to use this instead as I was encountering problems with the built in method 
 * @param ArrayList<Component> findList
 * @param Component find
 * @return int */
protected int findIndex(ArrayList<Component> findList, Component find) {
    int i;
    /* iterate through ArrayList until object is found */
    for (i = 0;i<findList.size();i++) {
        if(findList.contains(find)) {
            break;
        }
    }
    return i;
}



/** Determine if resistor already queued for removal, returns true to enable above loop if component is not already queued for removal 
 * @param Component resistor
 * @param ArrayList<Component> toRemove
 * @return boolean*/
protected boolean queuedRemoval(Component resistor, ArrayList<Component> toRemove){
    /* for each component queued for removal */
    for(Component component:toRemove) {
        /* if the Id matches any resistor Id in the removal list, and for good measure check that it is a resistor */
        if(component.getId()==resistor.getId() && component.getClass()==Resistor.class) {
            /* return false to disable the above loop */
            return false;
        }
    }
    /* else return true */
    return true;
}



/** Find node based on id 
 * @param int id
 * @return Node*/
protected Node findNode(int id) {
    /* value to store index */
    int i  = 0;
    /* for each node */
    for(Node node:nodeList) {
        /* if it does not equal the desired node */
        if(node.getId()!=id) {
            /* increase the index */
            i++;
        }
        /* if it does */
        else {
            /* stop searching */
            break;
        }
    }
    return nodeList.get(i);
}



/** Calculate parallel resistance from a list of resistors
 * @param ArrayList<Component> resistors
 * @return double*/
protected double parallelResistors(ArrayList<Component> resistors) {
    double parallelR = 0.0;
    if(resistors.size()==0) {
        throw new IllegalArgumentException("Must input at least one resistor.");
    }
    for (Component res:resistors) {
        /* quick check to make sure only resistances get added to the total */
        if(res.getClass()==Resistor.class) {
            parallelR+=1/(((Resistor)res).getR());
        }
    }
    return 1/parallelR;
}



/**No parameters or returns, Print circuit Characteristics */
protected void printCharacteristics() {
    System.out.println("Ground voltage is located at Node "+this.ground+".");
    System.out.println("Total voltage in circuit is: "+this.totalV+ " Volts.");
    System.out.println("Total resistance in circuit is: "+this.totalR+" Ohms.");
    System.out.println("Total current is: "+this.totalV/this.totalR+" Amps.");
}

/* get methods for testing private instance variables */

/** get nodeList, no parameters
 * @return ArrayList<Node>
 */
public ArrayList<Node> getNodeList(){
    return this.nodeList;
}

/** gets the list of components, no parameters
 * 
 * @return ArrayList<Component>
 */
public ArrayList<Component> getComponents(){
    return this.components;
}

/** get voltage, no parameters
 * @return double
 */
public double getV() {
    return this.totalV;
}

/** gets the resistance of the circuit, no parameters
 * 
 * @return double
 */
public double getR() {
    return this.totalR;
}

/** gets the ground node id
 * 
 * @return int
 */
public int getG() {
    return this.ground;
}




/** METHOD NO LONGER IN USE - changed algorithm for solving circuit problem - storing it in case it is useful in future */
/* Reduce multiple serial resistors to a single equivalent resistor */
protected void analyzeSerialResistances() {
    ArrayList<Component> temp = new ArrayList<>();
    ArrayList<Component> toRemove = new ArrayList<>();
    int nodalCase = 0;
    /* for each node */
    for(Node node1:nodeList) {
        /* compare to other nodes */
        for(Node node2:nodeList) {
            /* if not the same node and looking forwards */
            if(node1.getId()<node2.getId()) {
                /* if both nodes only have 2 attachments */
                if(node1.getAttachments().size()==2 && node2.getAttachments().size()==2) {
                    /* iterate through the attachments that are resistors */
                    for(int i = 0; i<2; i++) {
                        for(int j = 0; j<2; j++) {
                            /* if the components are not already queued for removal */
                            if(this.queuedRemoval(node1.getAttachments().get(i),toRemove) && this.queuedRemoval(node2.getAttachments().get((j+1)%2),toRemove)) {
                                /* if a common resistor is found between the nodes and both nodes only have 2 attachments, then the common resistor must be in series with the second nodes attached item */
                                if (node1.getAttachments().get(i).getId()==node2.getAttachments().get(j).getId() && node1.getAttachments().get(i) instanceof Resistor) {
                                    /* if the second node's other attached item is also a resistor */
                                    if(node2.getAttachments().get((j+1)%2) instanceof Resistor) {
                                        /* queue them for equivalence calculation */
                                        temp.add(node1.getAttachments().get(i));
                                        temp.add(node2.getAttachments().get((j+1)%2));
                                        /* find the common node */
                                        if(temp.get(0).getNode1().getId() == temp.get(1).getNode1().getId()) {
                                            /* queue equivalent resistor nodes to be the non-common nodes */
                                            nodalCase = 1;
                                        }
                                        if(temp.get(0).getNode1().getId() == temp.get(1).getNode2().getId()) {
                                            /* queue equivalent resistor nodes to be the non-common nodes */
                                            nodalCase = 2;
                                        }
                                        if(temp.get(0).getNode2().getId() == temp.get(1).getNode1().getId()) {
                                            /* queue equivalent resistor nodes to be the non-common nodes */
                                            nodalCase = 3;
                                        }
                                        /* note chose to not use just plain else to verify the last condition is true, even though it is the only possible combination of common nodes left */
                                        if(temp.get(0).getNode2().getId() == temp.get(1).getNode2().getId()) {
                                            nodalCase = 4;
                                        }
                                    }
                                }
                            }
                        }
                    }
                    /* if series resistors were found */
                    if(temp.size()==2) {
                        /* queue resistors for removal */
                        toRemove.add(temp.get(0));
                        toRemove.add(temp.get(1));
                        Resistor equivalent = null;
                        /* queue equivalent resistor to be added */
                        if(nodalCase == 1) {
                            /* first nodal case - shared 1st/1st node so connect equivalent resistor between both 2nd nodes */
                            equivalent = new Resistor(((Resistor)temp.get(0)).getR()+((Resistor)temp.get(1)).getR(),temp.get(0).getNode2(),temp.get(1).getNode2());
                        }
                        if(nodalCase == 2) {
                            /* second nodal case - shared 1st/2nd node so connect equivalent resistor between 2nd/1st nodes */
                            equivalent = new Resistor(((Resistor)temp.get(0)).getR()+((Resistor)temp.get(1)).getR(),temp.get(0).getNode2(),temp.get(1).getNode1());
                        }
                        if(nodalCase == 3) {
                            /* third nodal case - shared 2nd/1st node so connect equivalent resistor between 1st/2nd nodes */
                            equivalent = new Resistor(((Resistor)temp.get(0)).getR()+((Resistor)temp.get(1)).getR(),temp.get(0).getNode1(),temp.get(1).getNode2());
                        }
                        /* chose not to use simple else for reason stated above */
                        if(nodalCase == 4) {
                            /* last nodal case - shared 2nd/2nd node so connect equivalent resistor between both 1st nodes */
                            equivalent = new Resistor(((Resistor)temp.get(0)).getR()+((Resistor)temp.get(1)).getR(),temp.get(0).getNode1(),temp.get(1).getNode1());
                        }
                        components.add(equivalent);
                        equivalent.getNode1().connect(equivalent);
                        equivalent.getNode2().connect(equivalent);
                        temp.clear();
                    }
                }
            }
        }
    }
    /* remove resistors to be replaced by single equivalent resistor */
    /* if there are items to be removed */
    if(toRemove.size()>0) {
        /* for each component to be removed */
        for (Component remove:toRemove) {
            /* for each component */
            for(int i = 0; i <components.size();i++) {
                /* if the component is a resistor and it is in the list of resistors to be removed */
                if(components.get(i).getId()==remove.getId()&&components.get(i) instanceof Resistor) {
                    /* remove it from components */
                    remove.getNode1().disconnect(remove);
                    remove.getNode2().disconnect(remove);
                    components.remove(i);
                    /* need to consider that components has shrunk by 1 */
                    i--;
                }
            }
        }
    }
}

}

Component.java

package circuit;

/**
 * An abstract class to quantify common elements to components within the circuit. 
 * 
 * Mainly handles component Id features.
 * 
 * 
 * @author Michael Sinclair.
 * @version 2.302
 * @since 27 January 2019.
*/

public abstract class Component {
/*Instance variables.*/
protected Node nodal1;
protected Node nodal2;
protected int id;

/** Superclass constructor 
 * @param Node node1
 * @param Node node2
*/
protected Component(Node node1, Node node2){
    this.nodal1 = node1;
    this.nodal2 = node2;
}

/*Methods */

/*get/set methods*/

/** get first node, no parameters
 * 
 * @return Node nodal1
 */
protected Node getNode1() {
    return this.nodal1;
}

/** get second node, no parameters
 * 
 * @return Node nodal2
 */
protected Node getNode2() {
    return this.nodal2;
}

/** set first node, no return
 * 
 * @param Node node1
 */
protected void setNode1(Node node1) {
    this.nodal1 = node1;
}

/** set second node, no return
 * 
 * @param Node node2
 */
protected void setNode2(Node node2) {
    this.nodal2 = node2;
}

/** get component id, no parameters
 * 
 * @return int id
 */
protected int getId() {
    return this.id;
}

/** set component id, no return
 * 
 * @param int i
 */
protected void setId(int i) {
    this.id = i;
}

/** method for testing if connected through only 1 node for use in nodal analysis , returns true if components are connected through the first node and not the second
 * 
 * @param Component other
 * @return boolean
 * */
protected boolean testNode(Component other) {
    if (this.nodal1 == other.nodal1) {
        if (this.nodal2 != other.nodal1) {
            return true;
        }
    }
    return false;
}

/**Return list of nodes connected to voltage source, no parameters
 * @return Node[] list.
 * */
protected Node[] getNodes(){
    return new Node[] {this.nodal1 , this.nodal2};
}

/** define equals method, returns true if Ids are the same otherwise false
 * @param Component other
 * @return boolean
 * */
protected boolean equals(Component other) {
    if (this.getId() == other.getId()) {
        return true;
    }
    else return false;
}

/** define compare method for sorting
* if the first node Id is smaller than the other first node, method returns a negative number and vice versa
* if the first node Id is the same, and the second node is smaller than the other second node, method returns a negative number and vice versa
* if both nodes are equal, method returns 0 
* @param Component other
* @return int
* */
protected int compare(Component other) {
    if (this.getNode1().getId() == other.getNode1().getId()) {
        return this.getNode2().getId()-other.getNode2().getId();
    }
    else {
        return this.getNode1().getId()-other.getNode1().getId();
    }
}


/** make components override toString() 
 * @return String 
 */
@Override
public abstract String toString();

/** desire a toString that displays different information 
 * @return String
 * */
public abstract String toStringFinal();
}

Node.java

package circuit;
import java.util.ArrayList;

/**
* A node class, to connect circuit components.
 * 
 * Contains an id that describes the node, as well as the voltage at the node (to be added), the current leaving the node (to be added) and an array list of components attached to the node.
 * 
 * 
 * @author Michael Sinclair.
 * @version 2.302
 * @since 27 January 2019.
*/

public class Node {
/*Instance variables*/
private int id;
private double voltageAt;
private ArrayList<Component> attachments;
private double currentLeaving;

/**Assign an id to this node.
 * @param nodal_id.*/
protected Node(int nodalId) {
    this.id = nodalId;
    this.voltageAt = 0.0;
    this.attachments = new ArrayList<>();
    this.currentLeaving = 0.0;
}

/*Methods*/

/**get id, no parameters
 * @return int id
 * */
protected int getId(){
    return this.id;
}

/** set voltage at Node, no return
 * @param double volts
 * */
protected void setVoltage(double volts) {
    this.voltageAt = volts;
}


/** get voltage at Node, no parameters
 * @return double voltageAt
 * */
protected double getVoltage() {
    return this.voltageAt;
}

/** set current leaving Node, no return
 * @param double current
 * */
protected void setCurrent(double current) {
    this.currentLeaving = current;
}

/** get current leaving Node, no parameters
 * @return double currentLeaving
 * */
protected double getCurrent() {
    return this.currentLeaving;
}

/** connection a component to this node, methods for tracking component connections, no return
 * @param Component component
 * */
protected void connect(Component component) {
    this.attachments.add(component);
}

/** disconnect a component from this node, methods for tracking component connections, no return
 * 
 * @param Component component
 */
protected void disconnect(Component component) {
    this.attachments.remove(component);
}

/** get the list of attachments that are attached to this node, no parameters
 * 
 * @return ArrayList<Component> attachments
 */
protected ArrayList<Component> getAttachments(){
    return this.attachments;
}   

/**Display node id, overrides toString(), no parameters
 * @return String.*/
@Override
public String toString() {
    return ""+this.id;
}

/** method for displaying specific information about this node, no parameters
 * 
 * @return String
 */
public String toStringSpecific() {
    return "Node"+this.id+" Current Leaving: "+this.currentLeaving+" Amps and Voltage at node:" + this.voltageAt+" Volts.";
}

/** method for displaying the attachments connected to this node, mainly used for debugging, no parameters, displays a string version of the list of attachments
 * 
 * @return String
 */
public String toStringAttachments() {
    String str = "Node"+this.id;
    for(int i=0;i<attachments.size();i++) {
        str+=" "+attachments.get(i).toString();
    }
    return str;
}
}

NodeChecker.java

package circuit;
import java.util.ArrayList;

/** A helper class to simplify UserMain code.
 * 
 * Evaluates whether nodes exist already as a component is added, and if not creates them within UserMain's nodeList ArrayList.
 * 
 * Decided to compartmentalize this portion of the program to remove large duplicate code blocks and simplify UserMain.
 * 
 * Would likely normally just make this a method within UserMain if resources and time were of concern in this program. Chose to make it a separate class to make program easier to view, for now.
 * 
 * 
 * @author Michael Sinclair.
 * @version 2.302
 * @since 27 January 2019.
*/

public class NodeChecker {

private Node node1;
private Node node2;
private ArrayList<Node> nodeList;

/** constructor for building this class
 * 
 * @param Node nod1
 * @param Node nod2
 * @param ArrayList<Node> nodeList
 */
public NodeChecker(int nod1, int nod2, ArrayList<Node> nodeList){
    this.nodeList = nodeList;
    /*Check that component does not have the same nodes on both ends. Note would be part of input validation*/
    if (nod1 == nod2){
        throw new IllegalArgumentException("Nodes must be different for a single component.");
    }

    this.node1 = new Node(nod1);
    this.node2 = new Node(nod2);

    /*If these are the first two nodes, add them.*/
    if (nodeList.isEmpty()){
        nodeList.add(node1);
        nodeList.add(node2);
    }

    int flag1 = 0;
    int flag2 = 0;
    /*If nodes do not exist, create them.*/
    for (Node node : nodeList){
        if (node.getId() == node1.getId()){
            /*If found set flag and break.*/
            flag1 = 1;
            break;
        } 
    }
    for (Node node : nodeList){
        if (node.getId() == node2.getId()){
            /*If found set flag and break.*/
            flag2 = 1;
            break;
        }
    }

    /*If not found.*/
    if (flag1 == 0){
        nodeList.add(node1);
    }

    if (flag2 == 0){
        nodeList.add(node2);
    }
}

/** get first node to check, no parameters
 * 
 * @return Node node1
 */
protected Node getCheckedNode1() {
    return this.node1;
}

/** get second node to check, no parameters
 * 
 * @return Node node2
 */
protected Node getCheckedNode2() {
    return this.node2;
}

/** method to find index for node 1 or node 2, depending on whether it is called with i = 1 or i = 2 (only two values that will do anything in this method as a component can only have 2 nodes)
 * @param int i
 * @return index1 or index2
 * */
protected int findIndex(int i) {
    if (i == 1) {
        int index1 = 0;
        for (Node node : nodeList){
            if (node.getId() == node1.getId()){
                break;
            }
            index1++;
        }
        return index1;
    }
    else {
        int index2 = 0;
        for (Node node : nodeList){
            if (node.getId() == node2.getId()){
                break;
            }
            index2++;
        }
        return index2;
    }
}
}

Resistor.java

package circuit;
/**
 * A resistor class with resistance that is connected to two different nodes.
 * 
 * It contains a resistance value, a current through the resistor (to be added) which will correspond to voltage drop across the resistor.
 * 
 * It also contains an inherited Id as well as two connected nodes from the Component class.
 * 
 * 
 * 
 * @author Michael Sinclair.
 * @version 2.303
 * @since 29 January 2019.
 */
public class Resistor extends Component{
/*Instance variables.*/
private double resistance;
protected static int resnum = 1;
/* functionality will be added later */
private double current_through;

/**Constructor that checks that resistor is non-zero and non-negative, sets resistance and attaches nodes
 * @param res.
 * @param nod1
 * @param nod2*/
protected Resistor(double res, Node nod1, Node nod2) {
    super(nod1,nod2);
    double threshold = 0.00001;
    /*Ensure resistor is greater than 0, and not negative.*/
    if (res <= threshold){
        throw new IllegalArgumentException("Resistance must be greater than 0.");
    }
    /*Ensure the specified nodes exist for the resistor.*/
    if (nod1 == null || nod2 == null){
        throw new IllegalArgumentException("Nodes must both exist before attaching resistor.");
    }
    /*Create the resistor variables.*/
    this.resistance = res;
    this.setId(Resistor.resnum);
    Resistor.resnum++;
    this.current_through=0.0;
}

/*Methods.*/

/** get the resistance, no parameters
 * 
 * @return double resistance
 */
protected double getR() {
    return this.resistance;
}

/** functionality will be added later, sets current through this resistor, no return
 * @param double i_c
 * */
protected void set_current(double i_c){
    this.current_through = i_c;
}

/** functionality will be added later, gets current through this resistor, no parameters
 * 
 * @return double current_through
 */
protected double get_current(){
    return this.current_through;
}

/**Return the information of the resistor
 * @return String.*/
@Override
public String toString(){
    return "R"+this.getId()+" "+this.getNodes()[0]+" "+this.getNodes()[1]+" "+this.resistance+" Ohms";
}

/** method to get specific information about a resistor, a toString() that does not display the resistor Id, used when not wanting to display the id, no parameters
 * @return String
 * */
public String toStringFinal() {
    return "Req "+this.getNodes()[0]+" "+this.getNodes()[1]+" "+this.resistance+" Ohms";
}
}

UserMain.java

package circuit;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Scanner;

/**
 * Main function that creates a circuit, and takes input from user to add resistors or voltage sources to the circuit, and display components within the circuit.
 * 
 * Plan to add functionality that will check that the user has input a complete circuit (complete path from node 0 to node 0 through the circuit)
 * 
 * Plan to add functionality that would allow inductors and capacitors to the circuit, likely using rectangular form complex calculations, and possibly phasors.
 * 
 * Plan to add functionality that will allow voltage sources to be placed anywhere within the circuit.
 * 
 * Plan to add functionality that will calculate voltages at each node and current leaving each node.
 * 
 * Plan to add functionality to process Y-Delta transformations for resistors that can't be serial or parallel calculated.
 * 
 * @author Michael Sinclair.
 * @version 2.303
 * @since 29 January 2019.
*/

public class UserMain {

/*Instance variables.*/

/* Need to take input from user */
protected static Scanner user;

/* Need dynamic node list to ensure only one node exists per node id */
protected static ArrayList<Node> nodeList;

/** Constructor to initialize instance variables, no parameters */
protected UserMain(){
    UserMain.user = new Scanner(System.in);
    UserMain.nodeList = new ArrayList<>();    
}
/**Main method that interacts with user
 * @param args.
 * */
public static void main(String[] args){

    /* Create objects in main */
    Circuit cir = Circuit.getInstance();
    @SuppressWarnings("unused")
    UserMain instance = new UserMain();

    /*Instruct user on  how to use program.*/
    System.out.println("Welcome to the circuit builder program.");
    System.out.println("Input 'add' to add components into the circuit.");
    System.out.println("Input 'edit' to remove components from the circuit.");
    System.out.println("Input 'display' to display components currently in the circuit.");
    System.out.println("Input 'calculate' to determine total resistance and current in circuit.");
    System.out.println("Input 'end' to end the program.");
    System.out.println("");
    System.out.println("Input resistors (R) and voltage sources (V) into the circuit by the following syntax:");
    System.out.println("R/V X Y Z");
    System.out.println("R indicates a resistor and V indicates a voltage source.");
    System.out.println("X is an integer indicating the first node attached to component.");
    System.out.println("Y is an integer indicating the second node attached to component.");
    System.out.println("Z a double indicating the resistance in Ohms or Voltage in volts."); 
    System.out.println("For example: R 1 2 10 will add a resistor connected to nodes 1 and 2 with a resistance of 10 Ohms.");
    System.out.println("");
    System.out.println("Rules:");
    System.out.println("Voltage/Resistor values must be non-zero and Resistor values must also be non-negative. Voltage polarity is directed to increasing node Id.");
    System.out.println("Calculation function will assume that nodes are ordered and sequential from 0 to N-1 where N is the total number of nodes.");
    System.out.println("Voltage sources cannot be placed in parallel with eachother.");
    System.out.println("");
    System.out.println("V2.303 Notes:");
    System.out.println("Resistors must be connected serially or in parallel. This program does not currently support connections that are neither.");
    System.out.println("Currently the program only supports purely directly serial voltage sources, one of which must be between nodes 0 and 1.");
    System.out.println("Voltages may not be connected in parallel with resistors.");
    System.out.println("Currently it is the user's responsibility to enter a complete circuit.");
    System.out.println("");

    /* Request user input with input verification */
    String input = null;
    while(true) {
        try {
            /* test inputs */
            input = UserMain.user.nextLine();
            if(input.equals("add")) {
                break;
            }
            if(input.equals("edit")) {
                break;
            }
            if(input.equals("display")) {
                break;
            }
            if(input.equals("calculate")) {
                break;
            }
            if(input.equals("end")) {
                break;
            }
            /* if not a viable input, allow user to retry */
            throw new IllegalArgumentException("Enter a valid input.");
        } catch (Exception e) {
            /* instruct user on error and to retry */
            System.out.println(e);
            System.out.println("Retry:");
        }
    }

    /* While the program is not requested to end */
     while (!"end".equals(input)){

         /* if adding a component */
         if ("add".equals(input)) {

             /* request details with input verification */
            System.out.println("Add a resistor or a voltage.");
            input = UserMain.user.nextLine();
            while(true){
                    try {
                    String[] testCase = input.split(" ");
                    /* if not the proper number of data entities in order to test further down */
                    if(testCase.length!=4) {
                        /* throw exception to keep user within loop */
                        throw new IllegalArgumentException("Input must be R/V X Y Z.");
                    }
                    /* otherwise allow program to proceed */
                    break;
                } catch (IllegalArgumentException e) {
                    /* instruct user on error and to retry */
                    System.out.println(e);
                    System.out.println("Try again:");
                    input = UserMain.user.nextLine();
                }
            }

            /* If resistor is being added */
            if ((input.charAt(0) == 'r'||input.charAt(0) == 'R')&&input.charAt(1)==' '){
                int firstNode = 0;
                int secondNode=0;
                double rVal=0.0;

                /* Split input into various fields with input validation */
                while(true) {
                    try {
                        String[] inputSplit = input.split(" ");
                        /* if not the proper number of data entities */
                        if(inputSplit.length!=4) {
                            /* throw exception to keep user within loop */
                            throw new IllegalArgumentException("Input must be R X Y Z.");
                        }
                        /* store the data */
                        String testLetter = inputSplit[0];
                        firstNode = Integer.parseInt(inputSplit[1]);
                        secondNode = Integer.parseInt(inputSplit[2]);
                        rVal = Double.parseDouble(inputSplit[3]);
                        /* if not a resistor entered */
                        if (!testLetter.equals("r")) {
                            if(!testLetter.equals("R")) {
                                /* throw exception to keep user within loop */
                                throw new IllegalArgumentException("You must enter a resistor.");
                            }
                        }
                        /* no negative resistances - testing against a double so do not test against exactly 0 due to imprecision in floating point numbers */
                        if(rVal < 0.00001){
                            throw new IllegalArgumentException("You enterred a resistance of "+rVal+". Resistances must be positive and non-zero.");
                        }
                        /* component must be connected to two different nodes */
                        if(firstNode == secondNode) {
                            throw new IllegalArgumentException("Components must be connected to two different nodes.");
                        }
                        /* only reached if no exceptions are thrown */
                        break;
                    /* note could just catch all exceptions since the retry message is the same, but that is bad practice */
                    } catch (NumberFormatException e) {
                        /* instruct user on error and to retry */
                        System.out.println(e);
                        System.out.println("Invalid input. Resistor syntax is R X Y Z. Input a resistor:");
                        input = UserMain.user.nextLine();
                    } catch(IllegalArgumentException e) {
                        /* instruct user on error and to retry */
                        System.out.println(e);
                        System.out.println("Invalid input. Resistor syntax is R X Y Z. Input a resistor:");
                        input = UserMain.user.nextLine();
                    } catch (ArrayIndexOutOfBoundsException e) {
                        /* instruct user on error and to retry */
                        System.out.println(e);
                        System.out.println("Invalid input. Resistor syntax is R X Y Z. Input a resistor:");
                        input = UserMain.user.nextLine();
                    }
                }

                /* create nodes if they do not already exist*/
                NodeChecker evaluate = new NodeChecker(firstNode,secondNode,nodeList);
                @SuppressWarnings("unused")
                Node node1 = evaluate.getCheckedNode1();
                @SuppressWarnings("unused")
                Node node2 = evaluate.getCheckedNode2();

                /*Find list index now that the node is definitely in the array.*/
                int index1 = evaluate.findIndex(1);
                int index2 = evaluate.findIndex(2);

                /*Create add resistor to circuit.*/
                Resistor res = new Resistor(rVal,nodeList.get(index1),nodeList.get(index2));
                cir.addComponent(res);
                /* track connections through nodes */
                nodeList.get(index1).connect(res);
                nodeList.get(index2).connect(res);

                System.out.println("Added Resistor: "+res.toString());

            }

            /* If voltage source is being added */
            else if ((input.charAt(0) == 'v'||input.charAt(0) == 'V')&&input.charAt(1)==' '){
                int firstNode = 0;
                int secondNode=0;
                double vVal=0.0;

                /* Split input into various fields with input validation */
                while(true) {
                    try {
                        String[] inputSplit = input.split(" ");
                        /* if not the proper number of data entities */
                        if(inputSplit.length!=4) {
                            /* throw exception to keep user within loop */
                            throw new IllegalArgumentException("Input must be R X Y Z.");
                        }
                        /* store the data */
                        String testLetter = inputSplit[0];
                        firstNode = Integer.parseInt(inputSplit[1]);
                        secondNode = Integer.parseInt(inputSplit[2]);
                        vVal = Double.parseDouble(inputSplit[3]);
                        /* if not a voltage entered */
                        if (!testLetter.equals("v")) {
                            if(!testLetter.equals("V")) {
                                /* throw exception to keep user within loop */
                                throw new IllegalArgumentException("You must enter a voltage.");
                            }
                        }
                        /* component must be connected to two different nodes */
                        if(firstNode == secondNode) {
                            throw new IllegalArgumentException("Components must be connected to two different nodes.");
                        }
                        /* only reached if no exceptions are thrown */
                        break;
                    /* note could just catch all exceptions since the retry message is the same, but that is bad practice */
                    } catch (NumberFormatException e) {
                        /* instruct user on error and to retry */
                        System.out.println(e);
                        System.out.println("Invalid input. Voltage syntax is V X Y Z. Input a resistor:");
                        input = UserMain.user.nextLine();
                    } catch(IllegalArgumentException e) {
                        /* instruct user on error and to retry */
                        System.out.println(e);
                        System.out.println("Invalid input. Voltage syntax is V X Y Z. Input a resistor:");
                        input = UserMain.user.nextLine();
                    } catch (ArrayIndexOutOfBoundsException e) {
                        /* instruct user on error and to retry */
                        System.out.println(e);
                        System.out.println("Invalid input. Voltage syntax is V X Y Z. Input a resistor:");
                        input = UserMain.user.nextLine();
                    }
                }

                /* create nodes if they do not already exist*/
                NodeChecker evaluate = new NodeChecker(firstNode,secondNode,nodeList);
                @SuppressWarnings("unused")
                Node node1 = evaluate.getCheckedNode1();
                @SuppressWarnings("unused")
                Node node2 = evaluate.getCheckedNode2();

                /*Find list index now that the node is definitely in the array.*/
                int index1 = evaluate.findIndex(1);
                int index2 = evaluate.findIndex(2);

                /*Create and add voltage source to circuit.*/
                Voltage vol = new Voltage(vVal,nodeList.get(index1),nodeList.get(index2));
                cir.addComponent(vol);
                /* track connections through nodes */
                nodeList.get(index1).connect(vol);
                nodeList.get(index2).connect(vol);

                System.out.println("Voltage added: "+vol.toString());

            }
            /* catch other bad inputs */
            else {
                System.out.println("Invalid input. Enter a voltage source or resistor with the following syntax R/V X Y Z. Try again:");
                input = UserMain.user.nextLine();
            }
         }

        /* option to remove components */
        else if ("edit".equals(input)){
            System.out.println("Which component would you like to remove? Enter only the unique identifier with no spaces (Ex. R1 or V2):");
            /* store values */
            input = UserMain.user.nextLine();
            /* store input */
            char[] question = null;
            /* initialize Letter with a dummy value */
            char Letter = '\0';
            String Number = "";
            /* test user input */
            while(true) {
                try {
                    /* store each character separately */
                    question = input.toCharArray();
                    /* if the first character entered is not in fact a character */
                    if(!Character.isLetter(question[0])) {
                        /* instruct user on error and to retry */
                        throw new IllegalArgumentException("Select a resistor with 'R' or a voltage with 'V'.");
                    }
                    Letter = question[0];
                    /* find the Id of the requested value */
                    for (int j = 1; j<question.length;j++){
                        Number += question[j];
                    }
                    /* if not an integer, this will throw a NumberFormatException */
                    Integer.parseInt(Number);
                    /* if a voltage or resistor are not selected */
                    if(Letter!='r') {
                        if(Letter!='R') {
                            if(Letter!='v') {
                                if(Letter!='V') {
                                    throw new IllegalArgumentException("Select a resistor with 'R' or a voltage with 'V'.");
                                }
                            }
                        }
                    }
                    /* if the Number string does not contain at least one character */
                    if(Number.length()<1) {
                        throw new IllegalArgumentException("Must enter the unique Id of the component you wish to remove.");
                    }
                    /* if no exceptions are thrown */
                    break;
                } catch(IllegalArgumentException e) {
                    /* instruct user on error and to retry */
                    System.out.println(e);
                    System.out.println("Invalid input. Enter only the Letter (R or V) and the number of the component you wish to remove. Try again:");
                    /* clear the Number string or else previous values will still be held within the string */
                    Number = "";
                    input = UserMain.user.nextLine();
                } catch (ArrayIndexOutOfBoundsException e) {
                    /* instruct user on error and to retry */
                    System.out.println(e);
                    System.out.println("Invalid input. Voltage syntax is V X Y Z. Input a resistor:");
                    /* clear the Number string or else previous values will still be held within the string */
                    Number = "";
                    input = UserMain.user.nextLine();
                }
            }

            /* if resistor requested */
            if (Letter == 'r' || Letter == 'R') {
                boolean flag = false;
                Resistor Check=null;
                /*check if it is in the list */
                for (int i = 0; i <cir.getComponents().size();i++){
                    /* if that component is a resistor */
                    if(cir.getComponents().get(i) instanceof Resistor){
                        Check = (Resistor)cir.getComponents().get(i);
                        if (Check.getId() == Integer.parseInt(Number)){
                            /* if it is a resistor and in the list, remove it */
                            cir.getComponents().get(i).getNode1().disconnect(cir.getComponents().get(i));
                            cir.getComponents().get(i).getNode2().disconnect(cir.getComponents().get(i));
                            cir.getComponents().remove(i);
                            System.out.println("Removed component.");
                            /* stop searching */
                            flag = true;
                            break;
                        }
                    }
                }
                if (!flag) {
                    /* if it was not found*/
                    System.out.println("Resistor not found.");
                    }
           }

            /* if voltage requested */
            else if (Letter == 'v' || Letter == 'V') {
                boolean flag = false;
                Voltage Check=null;
                /*check if it is in the list */
                for (int i = 0; i <cir.getComponents().size();i++){
                    /* if that component is a voltage */
                    if(cir.getComponents().get(i) instanceof Voltage){
                        Check = (Voltage)cir.getComponents().get(i);
                        if (Check.getId() == Integer.parseInt(Number)){
                            /* if it is a voltage and in the list, remove it */
                            cir.getComponents().get(i).getNode1().disconnect(cir.getComponents().get(i));
                            cir.getComponents().get(i).getNode2().disconnect(cir.getComponents().get(i));
                            cir.getComponents().remove(i);
                            System.out.println("Removed component.");
                            flag = true;
                            break;
                        }
                    }
                }
                /* if it was not found*/
                if (!flag) {
                    System.out.println("Voltage not found.");
                    }
           }
           /* if bad input */
           else System.out.println("Input component not recognized.");
        }

        /*If 'display' is input - print out the circuit components.*/
        else if ("display".equals(input)){
            /* if there are components */
            if(cir.getComponents().size()>0) {
                System.out.println("");
                System.out.println("Components in circuit are:");
                System.out.println(cir.toString());
                System.out.println("");
            }
            /* otherwise - needed to avoid trying to print an empty array */
            else {
                System.out.println("No Components have been added yet.");
            }
        }

        /* calculate Total Current/Voltage */
        else if ("calculate".equals(input)) {
            /* if there are components in the circuit */
            if(cir.getComponents().size()!=0) {
                /* get ground voltage */
                System.out.println("");
                System.out.println("Where is the ground voltage? Enter the unique node ID number only.");
                input = UserMain.user.nextLine();
                /* input verification - ground functionality to be added later */
                int ground;
                while(true) {
                    try {
                        ground = Integer.parseInt(input);
                        break;
                    } catch (NumberFormatException e) {
                        System.out.println("Invalid input. Enter only the node ID (an integer value):");
                        input = UserMain.user.nextLine();
                    }
                }

                System.out.println("");
                System.out.println("Calculating:");


                /*Display ordered components */
                System.out.println("");
                System.out.println("Components in circuit are:");
                System.out.println(cir.toString());
                System.out.println("");

                /* perform the circuit analysis */
                CircuitAnalysis Calculate = new CircuitAnalysis(ground, cir.getComponents(), nodeList);
                Calculate.analyzeCircuit();
                /* clear the old calculate object */
                Calculate = null;
                /* instruct user to continue altering circuit */
                System.out.println("");
                System.out.println("You may continue to operate on the circuit. Enter a new input command.");

            }
            /* if no components in the circuit - needed to avoid trying to operate on an empty circuit (empty array) */
            else {
                System.out.println("Must have components in circuit before calculating.");
            }
        }

        /* loop back for invalid inputs */
        else{
            System.out.println("Invalid input. Enter a valid command as specified in the instructions.");
        }
    /*Request next instruction.*/
    input = UserMain.user.nextLine();
    }

/* Below shows that if two components are connected to the same node, 
 * they are in fact connected to exactly the same node (object) and not 
 * just nodes with the same id. In other words, nodes 
 * only exist as single objects.*/

/*System.out.println("Printing node list to show no duplicate nodes exist.");
for (Node node : nodeList){
    System.out.println(node.toString());
}*/

/*Program end.*/     
System.out.println("All Done");
}
}

Voltage.java

package circuit;
/**
 * A voltage source class that supplies voltage to the circuit and that is connected to two different nodes.
 * 
 * It contains a voltage value.
 * 
 * It also contains an inherited Id as well as two connected nodes from the Component class.
 *  
 * @author Michael Sinclair.
 * @version 2.302
 * @since 27 January 2019.
*/

public class Voltage extends Component{
/*Instance variables.*/
private double voltage;
protected static int vnum = 1;

/**Constructor checks that voltage is non-zero, sets voltage and attaches two nodes with consistent polarity
 * @param double v.
 * @param Node nod1
 * @param Node nod2*/
protected Voltage(double v, Node nod1, Node nod2) {
    super(nod1,nod2);
    double threshold = 0.00001;
    /*Check that voltage is non-zero.*/
    if (Math.abs(v) <= threshold){ 
        throw new IllegalArgumentException("Voltage must be greater than 0.");
    }
    /*Check that both nodes exist.*/
    if (nod1 == null || nod2 == null){
        throw new IllegalArgumentException("Nodes must both exist before attaching voltage source.");
    }
    this.voltage = v;
    this.setId(Voltage.vnum);
    Voltage.vnum++;

    /*Need a consistent directionality in the circuit, defined as in the direction of increasing node numbers.*/
    /*For example V 2 1 1.0 is equivalent to V 1 2 -1.0.*/
    if (this.nodal1.getId()>this.nodal2.getId()){   
        Node temp = this.getNode1();          
        this.nodal1 = this.nodal2;         
        this.nodal2 = temp;                 
        this.voltage = -this.voltage;
    }
}

/*Methods.*/

/** method to get voltage of voltage source, no parameters
 * 
 * @return
 */
protected double getV() {
    return this.voltage;
}

/** method to set voltage of voltage source, no return
 * 
 * @param double v
 */
protected void setV(double v) {
    this.voltage = v;
}

/**Print information about voltage source, overrides toString()
 * @return String.*/
@Override
public String toString(){
    return "V"+this.getId()+" "+this.getNodes()[0]+" "+this.getNodes()[1]+" "+this.voltage+" Volts DC";
}

/** required override for resistor subclass, but not needed for voltage sources, so simply mimics toString() */
public String toStringFinal() {
    return "V"+this.getId()+" "+this.getNodes()[0]+" "+this.getNodes()[1]+" "+this.voltage+" Volts DC";
}
}
\$\endgroup\$
2
\$\begingroup\$

General

this.member

Statements like this.components = new ArrayList<>(); do not need the this. prefix on the components variable. You only need to use a this. prefix if you've shadowed a member variable with an method argument using the same name. You should remove this. from everywhere it is not necessary.

Javadoc

The text "No parameters" and "No return" is superfluous. The information is available from the method signature.

Circuit

getInstance()

The Circuit singleton is a bad idea. Ask yourself, "can I ever have two?" and you should realize that it is not that much of a stretch to have two circuits. You could compare two circuits, look for a difference between circuits, and so on. And if you use JUnit for testing, it wants to create brand new instances to run each test on; with a singleton, you've painted yourself into a corner.

toString()

Building String objects from many pieces is an expensive operation. StringBuilder removes much of the overhead, by using a large mutable buffer to accumulate partial results in. Use like:

StringBuilder sb = new StringBuilder();

for (...) {
    sb.append(...);
}

return sb.toString();

for(Object obj : components){ is throwing away useful type information. You know components is a ArrayList<Component>, so you should use Component as the type of loop variable:

for (Component component: components) {

The if (obj.getClass() == Resistor.class) { ... } if (obj instanceof Voltage) { ... } is code smell. Doing it once, for one particular class type may be necessary, from time to time, but when you are doing multiple tests in a row, it is time to rethink the design.

Starting with the most minor aspect, if obj was a Resistor, could it possible also be a Voltage? No? Then you should use else if for subsequent tests.

I'm not sure what your goal here was:

/*Downcast to original object type to use class toString() method.*/
str+= ("Resistor: "+(Resistor)obj).toString()+"\n";

First, you do not have to downcast to call .toString(). Second, "Resistor: "+(Resistor)obj automatically invokes .toString() on (Resistor)obj in order to do the concatenation. Third, since Resistor is overriding String.toObject(), the cast to Resistor is unnecessary. Fourth, the result is a string, so (...).toString() is asking a String object to return itself. This line could simply be:

str += "Resistor: " + obj + "\n"; 

But will you be adding other component types, such as a current sources, capacitor, and/or inductors? Having to track down and find everywhere you explicitly check the type of class will be time consuming and error prone. This is just for printing out the type of the object. Why not just ask the object for its type?

StringBuilder sb = new StringBuilder();

for (Component component: components) {
    sb.append(component.getType());      // new abstract function
    sb.append(": ");
    sb.append(component.toString());
    sb.append("\n");
}

if (sb.getLength() > 0)
    sb.setLength(sb.getLength()-1);      // Remove trailing "\n"

return sb.toString();

Component

nodal1 and nodal2 are protected, yet you have getNode1() and getNode2() methods which are also protected. Anything which can call getNode1() can already access nodal1. Perhaps you meant for them to be private, if you want to force the callers to use the getNodeX() accessor methods.

Component has exactly 2 node nodes. This excludes more interesting electrical components like transistors, which have 3, and transformers which can have more.

Perhaps create a public abstract class BranchComponent extends Component which is used for electrical components having exactly 2 nodes. Or make Component general enough to handle an arbitrary number of nodes.

getNodes()

This returns Node[], where as all of your other structures use the Collection objects. Consider returning a List<Node> instead:

protected List<Node> getNodes() {
    return List.of(nodal1, nodal2);
}

setId()/getId()/equals()

Again, id is protected, and the functions are protected, so any code that wants to access id could simply access id instead of going through the accessor functions. Perhaps you meant for id to be private.

Two components are equal if their id values are equal? This is very dangerous, given that the id numbers are duplicated for Voltage and Resistor components. Consider declaring them equal only if their respective classes are equal as well!

Consider overriding Object.equals(Object other) instead of overloading the method signature, which could lead to hard-to-debug behaviour. component.equals(other_component) and component.equals((Object) other_component) are currently handled by different functions.

@Override
public boolean equals(Object other) {
    if (other instanceof Component) {
        Component c = (Component) other;
        return getId() == c.getId()  &&  getClass() == c.getClass();
    }
    return false;
}

compare()

If you are defining a comparison method, you may as well implement the Comparable<Component> interface, and use the official compareTo() method name. This will allow you to use standard library utilities for sorting, etc.

Returning the difference of to int values for a positive/zero/negative comparison result is an anti pattern. It is possible for the subtraction to overflow and return the wrong result. You should use Integer.compare(int, int) to be safe.

class Component implements Comparable<Component> {
    ...

    @Override
    public int compareTo(Component other) {
        ...
    }
}

Since your comparison function depends upon the ordering of Node objects, you should make them comparable too. Then your compareTo method could be written:

int result = getNode1().compareTo(other.getNode1());
if (result == 0) {
    result = getNode2().compareTo(other.getNode2());
}
return result;

toStringFinal()

Method name doesn't make it clear what this function is intended to do. What is final about the returned string?

Node

Node has an private id, which is initialized by the constructor. There is no setId(). Perhaps id should be final. For that matter, attachments could be final as well, since you never reassign the container.

toString()

Integer.toString(id) is preferable over ""+this.id. Without optimizations, the latter would call Integer.toString() to create the string representing the value, and concatenate that with the first empty string, to create a third string. The former directly returns the string.

toStringAttachments()

A StringBuilder would be appropriate here.

compareTo()

Have the class Node implements Comparable<Node>, and implement a public boolean compareTo(Node other) method, for use by Component.compareTo().

NodeChecker

I'm sorry. This class is just a mess of half thought-out design. As evidenced by its usage:

NodeChecker evaluate = new NodeChecker(firstNode,secondNode,nodeList);
@SuppressWarnings("unused")
Node node1 = evaluate.getCheckedNode1();
@SuppressWarnings("unused")
Node node2 = evaluate.getCheckedNode2();

/*Find list index now that the node is definitely in the array.*/
int index1 = evaluate.findIndex(1);
int index2 = evaluate.findIndex(2);

/*Create add resistor to circuit.*/
Resistor res = new Resistor(rVal,nodeList.get(index1),nodeList.get(index2));
nodeList.get(index1).connect(res);
nodeList.get(index2).connect(res);

So the values returned by getCheckedNode1() and getCheckedNode2() are not used. Why are they being retrieved at all? Then findIndex() is used to locate the position in the array, followed by nodeList.get() twice for each node index? Why not use the values returned by getCheckedNode_()? We need to look no further that the NodeChecker() constructor:

this.node1 = new Node(nod1);

int flag1 = 0;
/*If nodes do not exist, create them.*/
for (Node node : nodeList){
    if (node.getId() == node1.getId()){
        /*If found set flag and break.*/
        flag1 = 1;
        break;
    } 
}

/*If not found.*/
if (flag1 == 0){
    nodeList.add(node1);
}

plus duplicate code for node2 using flag2.

Summarizing. You unconditionally create a new Node() for the node id, then search for a node in the nodeList who's id is the same as the new node's id (fetching that id each time through the loop!). If found, you use an int variable (instead of a boolean) as a flag to remember that it was found, but do nothing with the found node. If not found, you add the unconditionally created node to the nodeList. Whether or not the node was found in the list or not, the newly created node is remembered for getCheckedNode(), but it can't be used because it may have just been created without adding it to the nodeList. And you do the same thing for the other node id.

Rip out the entire NodeChecker class.

To class Node, add the following method:

public static Node findOrCreate(int node_id, List<Node> nodeList) {
    for(Node node : nodeList) {
        if (node.getId() == node_id)
            return node;
    }

    Node new_node = new Node(node_id);
    nodeList.add(new_node);
    return new_node;
}

And here is code equivalent to the code at the start of this section, to demonstrate its usage:

Node node1 = Node.findOrCreate(firstNode, nodeList);
Node node2 = Node.findOrCreate(secondNode, nodeList);

/*Create add resistor to circuit.*/
Resistor res = new Resistor(rVal, node1, node2);
node1.connect(res);
node2.connect(res);

The check in NodeChecker for node1 == node2 has been eliminated, but you are already checking that in UserMain:

if(firstNode == secondNode) {
    throw new IllegalArgumentException("Components must be connected to two different nodes.");
}

Resistor

double threshold = 0.00001; should be removed from the constructor, and changed into a class constant:

public final static double MIN_RESISTANCE = 0.00001;

resistance is initialized in the constructor, and never changed. It should be declared final.

set_current()/get_current()

You've broke with convention and used an underscore. The Java Bean Standard for getters and setters is no underscore and capitalize the first letter after get/set: setCurrent() and getCurrent().

toString()/toStringFinal()

You are accessing this.getNodes()[0] and this.getNodes()[1]. Each call to getNodes() is constructing and returning a new Node[] array. You extract 1 element from the array, discard the array, and then immediately request a new copy of the array. For efficiency, you should just call getNodes() once, and access the returned value twice for the desired nodes.

Node[] nodes = getNodes();
return return "R" + getId() + " " + nodes[0] + " " + nodes[1] + " " + resistance + " Ohms";

Or perhaps:

return String.format("R%d %s %s %f Ohms", getId(), nodes[0], nodes[1], resistance);

Voltage

Similar comments to Resistor. Change threshold to public static final double MIN_VOLTAGE. Make the toString() methods more efficient.

UserMain

Why are you creating a UserMain object to initialize 2 static variables?

Exception handling is a huge sledge hammer to use for validation of 1 of 5 words. Instead, use:

private final static List<String> COMMANDS = List.of("add", "edit", "display", "calculate", "end");

...

while (true) {
    input = UserMain.user.nextLine();
    if (COMMANDS.contains(input))
        break;
    System.out.println("Invalid input.  Must be one of: " + String.join(" ", COMMANDS);
    System.out.println("Retry:");
}

When you do need it to catch exceptions, if your exception handling is identical for all exceptions, use a compound exception handler:

} catch (NumberFormatException | IllegalArgumentException | ArrayIndexOutOfBoundsException e) {
    /* instruct user on error and to retry */
    System.out.println(e);
    System.out.println("Invalid input. Resistor syntax is R X Y Z. Input a resistor:");
    input = UserMain.user.nextLine();
}

Structure

As mentioned above, Circuit should allow multiple instances.

Circuit could have as a member Map<Integer,Node> which would give you \$O(1)\$ lookup of a Node from a node number.

Instead of numbering nodes and resistors, you could name them instead. Then you could have both:

Map<String, Node> nodes = new HashMap<>();
Map<String, Component> components = new HashMap<>();

And have a node named "mid", and branch named "load".

\$\endgroup\$
  • 2
    \$\begingroup\$ I agree with almost all the points mentioned in this answer. One thing you should be careful with is the use of Comparable. This interface should only ever be used if there is a single obvious meaning of what the word "greater" means for a type. For numbers, this is the case. For components or nodes it is not obvious, therefore these classes should not implement Comparable but use a separate Comparator instead. \$\endgroup\$ – Roland Illig Feb 5 at 1:04
  • 1
    \$\begingroup\$ @RolandIllig That is a fair point. I’ll rework the answer when I get to reviewing CircuitAnalysis, which is where the sorting is actually done. \$\endgroup\$ – AJNeufeld Feb 5 at 1:23
  • \$\begingroup\$ @AJNeufeld Amazing, thank you so much. I will get to work on all the points you have mentioned. This is so helpful, and has given me quite a bit to study/learn/improve on. I really appreciate the time you spent going through all this. Once code is updated, what is normal protocol for StackOverflow? Should I edit my original post to update the code blocks? I am in midterm season right now, but hope to get to updating it this weekend sometime. \$\endgroup\$ – Michael Sinclair Feb 8 at 13:26
  • 1
    \$\begingroup\$ Do not edit your question. See what should I do when someone answers my question for details about writing new follow up questions. \$\endgroup\$ – AJNeufeld Feb 8 at 14:14
  • 1
    \$\begingroup\$ “When should someone ... shadow a variable?” Short answer: never. Slightly longer answer: only when the variable is used just once, but only if you can’t think of a clear way around it. As for stylistic uses of this, such as if (this.member == that.member) {...}, I doubt anyone on Code Review would complain. Re: effect on code metrics: that would depend entirely on the metrics system you choose to evaluate your code with. Or do you mean performance metrics, in which case the answer is “no”. \$\endgroup\$ – AJNeufeld Feb 12 at 15:03

Your Answer

By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.

Not the answer you're looking for? Browse other questions tagged or ask your own question.