/*
* funCKit - functional Circuit Kit
* Copyright (C) 2013 Lukas Elsner <open@mindrunner.de>
* Copyright (C) 2013 Peter Dahlberg <catdog2@tuxzone.org>
* Copyright (C) 2013 Julian Stier <mail@julian-stier.de>
* Copyright (C) 2013 Sebastian Vetter <mail@b4sti.eu>
* Copyright (C) 2013 Thomas Poxrucker <poxrucker_t@web.de>
* Copyright (C) 2013 Alexander Treml <alex.treml@directbox.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package de.sep2011.funckit.validator;
import de.sep2011.funckit.model.graphmodel.AccessPoint;
import de.sep2011.funckit.model.graphmodel.Brick;
import de.sep2011.funckit.model.graphmodel.Circuit;
import de.sep2011.funckit.model.graphmodel.Component;
import de.sep2011.funckit.model.graphmodel.ComponentType;
import de.sep2011.funckit.model.graphmodel.Element;
import de.sep2011.funckit.model.graphmodel.Output;
import de.sep2011.funckit.model.graphmodel.Wire;
import de.sep2011.funckit.model.simulationmodel.SimulationBrick;
import de.sep2011.funckit.util.internationalization.Language;
import java.util.Deque;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.Map;
import java.util.Set;
/**
* Check if a {@link Circuit} has loops. Can be used to detect if a circuit is a
* combinatorial circuit.
*/
public class LoopCheck implements Check {
/**
* Elements detected as flaws in the definition of this {@link Check}.
*/
private final Set<Element> flawElements = new LinkedHashSet<Element>();
private final Map<ComponentType, Boolean> typeCheckResult = new HashMap<ComponentType, Boolean>();
/**
* Visited {@link SimulationBrick}s in the depth-search.
*/
private final Set<SimulationBrick> visited = new LinkedHashSet<SimulationBrick>();
/**
* Visited {@link SimulationBrick}s in the depth-search.
*/
private final Set<SimulationBrick> finished = new LinkedHashSet<SimulationBrick>();
/**
* The current path of the depth search.
*/
private final Deque<Object> currentPath = new LinkedList<Object>();
private final boolean checkForZeroDelay;
/**
* Create a new {@link LoopCheck}
*/
public LoopCheck() {
checkForZeroDelay = false;
}
/**
* Construct a new {@link LoopCheck}.
*
* @param checkForZeroDelay
* true: only check for loops with zero delay, false: check for
* all loops
*/
LoopCheck(boolean checkForZeroDelay) {
this.checkForZeroDelay = checkForZeroDelay;
}
/**
* {@inheritDoc} In this case look if the {@link Circuit} has loops with a
* by performing a depth-search on the flat {@link Circuit}.
*/
@Override
public Result perform(Circuit c) {
flawElements.clear();
typeCheckResult.clear();
visited.clear();
finished.clear();
checkCircuit(c);
// construct Result object
String message = "check." + this.getClass().getSimpleName();
if (flawElements.isEmpty()) {
message += ".passedMessage";
} else {
message += ".failedMessage";
}
return new Result(flawElements.isEmpty(), Language.tr(message),
flawElements, this);
}
/**
* Performs a depth-search on the {@link Circuit} and looks for loops with 0
* delay.
*
* @param c
* {@link Circuit} to check.
* @return
*/
private boolean checkCircuit(Circuit c) {
boolean hasFlaw = false;
currentPath.clear();
// check all elements
for (Element e : c.getElements()) {
if (e instanceof Brick) {
Brick b = (Brick) e;
if (doDepthSearch(new SimulationBrick(b,
new LinkedList<Component>()))) {
hasFlaw = true;
}
}
// check component's type additionally
if (e instanceof Component) {
Component comp = (Component) e;
ComponentType type = comp.getType();
if (!typeCheckResult.containsKey(type)) {
typeCheckResult.put(type, checkCircuit(type.getCircuit()));
}
boolean typeHasFlaw = typeCheckResult.get(type);
if (typeHasFlaw) {
hasFlaw = true;
flawElements.add(comp);
}
}
}
return hasFlaw;
}
/**
* Performs the actual depth-search.
*
* @param simBrick
* {@link SimulationBrick} to search on.
* @return
*/
private boolean doDepthSearch(SimulationBrick simBrick) {
boolean flawFound = false;
// ignore already finished bricks (and those with delay)
if (finished.contains(simBrick)
|| (simBrick.getBrick().hasDelay() && checkForZeroDelay)) {
return false;
}
if (visited.contains(simBrick)) {
// loop detected
flawElements.add(simBrick.getBrick());
for (Object o : currentPath) {
if (o instanceof SimulationBrick) {
flawElements.add(((SimulationBrick) o).getBrick());
} else {
assert o instanceof Wire;
flawElements.add((Element)o);
}
if (simBrick.equals(o)) {
break;
}
}
flawFound = true;
} else {
visited.add(simBrick);
currentPath.push(simBrick);
// search deeper on all outputs
for (Output o : simBrick.getBrick().getOutputs()) {
if (traverseOutput(o, simBrick.getStack())
| traverseUpwards(o, simBrick.getStack())) {
flawFound = true;
}
}
finished.add(simBrick);
currentPath.pop();
}
return flawFound;
}
/**
* Traverses the given {@link Output} by following its {@link Wire}s and
* going deeper in Components of necessary.
*
* @param o
* {@link Output} to traverse
* @param stack
* The path to the current {@link Component} in which we are at
* this step in the Check.
* @return
*/
private boolean traverseOutput(Output o, Deque<Component> stack) {
boolean flawFound = false;
for (Wire w : o.getWires()) {
currentPath.push(w);
AccessPoint otherPoint = w.getOther(o);
Brick otherBrick = otherPoint.getBrick();
Deque<Component> stack2 = new LinkedList<Component>(stack);
SimulationBrick otherSimBrick = new SimulationBrick(otherBrick,
stack2);
// go deeper in components if necessary
int pathSize = currentPath.size();
otherSimBrick = getNonComponent(otherPoint, otherSimBrick);
// keep on searching
if (doDepthSearch(otherSimBrick)) {
flawFound = true;
}
for (int i = 0; i < currentPath.size() - pathSize; i++) {
currentPath.pop();
}
currentPath.pop();
}
return flawFound;
}
/**
* Traverse the given {@link Output} by going upwards and traverse the
* connected wires there if this {@link Output} is part of a
* {@link ComponentType}.
*
* @param o
* {@link Output} to traverse
* @param stack
* The path to the current {@link Component} in which we are at
* this step in the Check.
* @return
*/
private boolean traverseUpwards(Output o, Deque<Component> stack) {
boolean flawFound = false;
// traverse to the outer component outputs if appropriate
if (!stack.isEmpty()) {
Component component = stack.peek();
Output outer = component.getOuterOutput(o);
if (outer != null && !(component.hasDelay() && checkForZeroDelay)) { // stop when component
// has delay in delay mode
Deque<Component> stack2 = new LinkedList<Component>(stack);
stack2.pop();
// traverse through all connected wires
if (traverseOutput(outer, stack2)) {
flawFound = true;
}
// traverse further upwards if necessary
if (traverseUpwards(outer, stack2)) {
flawFound = true;
}
}
}
return flawFound;
}
/**
* Goes deeper in the {@link Component} if the given {@link SimulationBrick}
* has a {@link Component}.
*
* @param a
* {@link AccessPoint} to go deeper with.
* @param simBrick
* {@link SimulationBrick} to go deeper in.
* @return {@link SimulationBrick} which has a non {@link Component}.
*/
private SimulationBrick getNonComponent(AccessPoint a,
SimulationBrick simBrick) {
if (simBrick.getBrick() instanceof Component) {
Component component = (Component) simBrick.getBrick();
currentPath.push(simBrick);
AccessPoint inner = component.getInner(a);
Deque<Component> stack2 = new LinkedList<Component>(
simBrick.getStack());
stack2.push(component);
SimulationBrick innerBrick = new SimulationBrick(inner.getBrick(),
stack2);
return getNonComponent(inner, innerBrick);
}
return simBrick;
}
@Override
public String getName() {
return Language.tr("Check." + this.getClass().getSimpleName());
}
}