fixes for netlist

This commit is contained in:
Bane Stojkovic
2025-03-11 23:18:36 +01:00
parent eedc370a6e
commit f966f8873a
5 changed files with 950 additions and 1025 deletions
@@ -1,381 +1,281 @@
/*
*
* DIY Layout Creator (DIYLC). Copyright (c) 2009-2018 held jointly by the individual authors.
*
* This file is part of DIYLC.
*
* DIYLC 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.
*
* DIYLC 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 DIYLC. If not, see
* <http://www.gnu.org/licenses/>.
*/
package org.diylc.netlist;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.diylc.core.IDIYComponent;
public abstract class NetlistAnalyzer {
public NetlistAnalyzer() {}
public List<Summary> summarize(List<Netlist> netlists, Node preferredOutput) throws TreeException {
Map<String, Summary> summaries = new HashMap<String, Summary>();
for (Netlist n : netlists) {
Summary s = summarize(n, preferredOutput);
if (summaries.containsKey(s.getSummary()))
summaries.get(s.getSummary()).append(n);
else
summaries.put(s.getSummary(), s);
}
List<Summary> res = new ArrayList<Summary>(summaries.values());
Collections.sort(res);
return res;
}
protected abstract Summary summarize(Netlist netlist, Node preferredOutput) throws TreeException;
public Tree constructTreeBetween(Netlist netlist, Node nodeA, Node nodeB) {
Tree tree = new Tree(TreeConnectionType.Parallel);
connectNodes(netlist, nodeA, nodeB, tree.getChildren(), new Tree(TreeConnectionType.Series), new HashSet<Node>());
return tree;
}
// protected Tree removeRedundantElements(Tree tree) {
// List<Tree> children = tree.getChildren()
// .stream()
// .map(t -> removeRedundantElements(t))
// .collect(Collectors.toList());
// if (children.size() == 1) {
// Tree first = children.get(0);
// return first;
// }
// }
protected void connectNodes(Netlist netlist, Node nodeA, Node nodeB, List<Tree> concurrentPaths, Tree currentPath,
Set<Node> visited) {
if (nodeA == nodeB) {
concurrentPaths.add(currentPath);
return;
}
Group groupA = findGroup(netlist, nodeA);
if (groupA == null)
return;
if (groupA.getNodes().contains(nodeB)) {
concurrentPaths.add(currentPath);
return;
}
// Only mark the current node as visited to allow proper path exploration
visited.add(nodeA);
List<Tree> newConcurrentPaths = new ArrayList<Tree>();
// Track processed connections to prevent duplicates from bidirectional links
Set<String> processedConnections = new HashSet<String>();
for (Node n : groupA.getNodes()) {
IDIYComponent<?> c = n.getComponent();
for (int i = 0; i < c.getControlPointCount(); i++) {
int targetPoint = i;
int sourcePoint = n.getPointIndex();
// Skip self-connections
if (targetPoint == sourcePoint) continue;
// Check if there's a connection between these points
if (c.getInternalLinkName(targetPoint, sourcePoint) != null) {
// Create a unique identifier for this connection regardless of direction
String connectionId = c.getName() + "_" +
Math.min(sourcePoint, targetPoint) + "_" +
Math.max(sourcePoint, targetPoint);
// Skip if we've already processed this connection (prevents duplicate branches)
if (processedConnections.contains(connectionId)) continue;
processedConnections.add(connectionId);
TreeLeaf l = new TreeLeaf(c, targetPoint, sourcePoint);
Node newNodeA = new Node(c, targetPoint, n.getZIndex());
if (!visited.contains(newNodeA)) {
// visited.add(n);
Tree newCurrentPath = null;
try {
newCurrentPath = (Tree) currentPath.clone();
} catch (CloneNotSupportedException e) {
}
Tree newItem = new Tree(l);
if (!newCurrentPath.getChildren().contains(newItem))
newCurrentPath.getChildren().add(newItem);
// if (!currentPath.contains(l))
// newCurrentPath.add(l);
Set<Node> newVisited = new HashSet<Node>(visited);
connectNodes(netlist, newNodeA, nodeB, newConcurrentPaths, newCurrentPath, newVisited);
}
}
}
}
if (newConcurrentPaths.size() == 0)
return;
if (newConcurrentPaths.size() == 1) {
if (newConcurrentPaths.get(0).getConnectionType() == TreeConnectionType.Series) {
currentPath.getChildren().clear();
currentPath.getChildren().addAll(newConcurrentPaths.get(0).getChildren());
} else {
currentPath.getChildren().add(newConcurrentPaths.get(0));
}
concurrentPaths.add(currentPath);
return;
}
mergePaths(newConcurrentPaths, false);
concurrentPaths.addAll(newConcurrentPaths);
}
/**
* Merges paths that share common elements, taking into account bidirectional connections.
*
* @param paths List of paths to merge
* @param backward Direction for merging (forward/backward)
*/
protected void mergePaths(List<Tree> paths, boolean backward) {
// Skip if there are no paths or only one path
if (paths == null || paths.size() <= 1) {
return;
}
Map<Tree, Integer> uniques = new HashMap<Tree, Integer>();
for (Tree t : paths) {
if (t.getConnectionType() != TreeConnectionType.Series)
continue;
Tree key = t.getChildren().get(backward ? t.getChildren().size() - 1 : 0);
Integer count = uniques.get(key);
uniques.put(key, count == null ? 1 : count + 1);
}
List<Tree> unmerged = new ArrayList<Tree>(paths);
for (Map.Entry<Tree, Integer> e : uniques.entrySet()) {
if (e.getValue() > 1) {
List<Tree> pathsToMerge = new ArrayList<Tree>();
for (Tree t : paths) {
Tree key = t.getChildren().get(backward ? t.getChildren().size() - 1 : 0);
if (key.equals(e.getKey()))
pathsToMerge.add(t);
}
unmerged.removeAll(pathsToMerge);
Tree jointStart = new Tree(new ArrayList<Tree>(), TreeConnectionType.Series);
Tree jointFinish = new Tree(new ArrayList<Tree>(), TreeConnectionType.Series);
Tree unique = e.getKey();
for (int i = 0; i < pathsToMerge.get(0).getChildren().size(); i++) {
boolean canMerge = true;
unique = pathsToMerge.get(0).getChildren().get(backward ? pathsToMerge.get(0).getChildren().size() - i - 1 : i);
for (Tree t : pathsToMerge) {
if (t.getChildren().size() <= i || !t.getChildren().get(backward ? t.getChildren().size() - i - 1 : i).equals(unique)) {
canMerge = false;
break;
}
}
if (canMerge)
(backward ? jointFinish : jointStart).getChildren().add(unique);
else
break;
}
for (int i = 0; i < pathsToMerge.get(0).getChildren().size(); i++) {
boolean canMerge = true;
unique = pathsToMerge.get(0).getChildren().get(backward ? i : pathsToMerge.get(0).getChildren().size() - i - 1);
for (Tree t : pathsToMerge) {
// Fix: Correct index calculation to check the proper element in each path
if (t.getChildren().size() <= i || !t.getChildren().get(backward ? i : t.getChildren().size() - i - 1).equals(unique)) {
canMerge = false;
break;
}
}
if (canMerge)
(backward ? jointStart : jointFinish).getChildren().add(unique);
else
break;
}
if (!jointStart.getChildren().isEmpty() || !jointFinish.getChildren().isEmpty()) {
Tree mergedTree = new Tree(new ArrayList<Tree>(), TreeConnectionType.Series);
Tree parallelSection = new Tree(new ArrayList<Tree>(pathsToMerge), TreeConnectionType.Parallel);
if (!jointStart.getChildren().isEmpty()) {
mergedTree.getChildren().add(jointStart);
for (Tree t : parallelSection.getChildren()) {
t.trimChildrenLeft(jointStart.getChildren().size());
}
}
mergedTree.getChildren().add(parallelSection);
if (!jointFinish.getChildren().isEmpty()) {
mergedTree.getChildren().add(jointFinish);
for (Tree t : parallelSection.getChildren()) {
t.trimChildrenRight(jointFinish.getChildren().size());
}
}
paths.removeAll(pathsToMerge);
paths.add(mergedTree);
}
}
}
// try in the opposite direction for paths we haven't merged
if (!backward && !unmerged.isEmpty()) {
paths.removeAll(unmerged);
mergePaths(unmerged, true);
paths.addAll(unmerged);
}
}
/**
* Creates a unique identifier for a connection between two points
* that is the same regardless of the direction of connection.
* This helps prevent duplicate paths in bidirectional connections.
*
* @param component The component containing the connection
* @param point1 First point index
* @param point2 Second point index
* @return A direction-agnostic connection identifier
*/
protected String createBidirectionalConnectionId(IDIYComponent<?> component, int point1, int point2) {
if (component == null) return null;
// Always use min/max to ensure the same ID regardless of direction
int minPoint = Math.min(point1, point2);
int maxPoint = Math.max(point1, point2);
return component.getName() + "_" + minPoint + "_" + maxPoint;
}
/**
* Compares two tree paths to check if they represent the same physical connection path
* but possibly in reverse directions. This helps identify paths that should be merged
* due to bidirectional connections.
*
* @param path1 First path to compare
* @param path2 Second path to compare
* @return true if the paths represent the same physical connection (possibly in reverse order)
*/
protected boolean arePathsEquivalent(Tree path1, Tree path2) {
if (path1 == null || path2 == null) return false;
if (path1 == path2) return true;
// Different connection types can't be equivalent
if (path1.getConnectionType() != path2.getConnectionType()) return false;
// Must have same number of elements
List<Tree> children1 = path1.getChildren();
List<Tree> children2 = path2.getChildren();
if (children1.size() != children2.size()) return false;
// Simple path with no children
if (children1.isEmpty()) return true;
// Check forward direction (standard comparison)
boolean forwardMatch = true;
for (int i = 0; i < children1.size(); i++) {
if (!children1.get(i).equals(children2.get(i))) {
forwardMatch = false;
break;
}
}
if (forwardMatch) return true;
// Check reverse direction (bidirectional check)
boolean reverseMatch = true;
for (int i = 0; i < children1.size(); i++) {
if (!children1.get(i).equals(children2.get(children2.size() - 1 - i))) {
reverseMatch = false;
break;
}
}
return reverseMatch;
}
protected Group findGroup(Netlist netlist, Node node) {
for (Group g : netlist.getGroups())
if (g.getNodes().contains(node))
return g;
return null;
}
protected List<Node> find(Set<String> typeNames, String nodeName, Netlist netlist) {
List<Node> res = new ArrayList<Node>();
for (Group g : netlist.getGroups()) {
for (Node n : g.getNodes()) {
if (typeNames.contains(n.getComponent().getClass().getCanonicalName())
&& (nodeName == null || n.getDisplayName().equalsIgnoreCase(nodeName))) {
res.add(n);
}
}
}
return res;
}
protected String extractName(IDIYComponent<?> c) {
return c.getName() + " " + (c.getValueForDisplay() == null ? "" : c.getValueForDisplay());
}
public static List<Set<IDIYComponent<?>>> extractComponentGroups(List<Netlist> netlists) {
List<Set<IDIYComponent<?>>> res = new ArrayList<Set<IDIYComponent<?>>>();
for (Netlist n : netlists) {
res.addAll(extractComponentGroups(n));
}
return res;
}
public static List<Set<IDIYComponent<?>>> extractComponentGroups(Netlist netlist) {
List<Set<IDIYComponent<?>>> res = new ArrayList<Set<IDIYComponent<?>>>();
for (Group g : netlist.getGroups()) {
Set<IDIYComponent<?>> components = new HashSet<IDIYComponent<?>>();
for (Node n : g.getNodes())
components.add(n.getComponent());
res.add(components);
}
return res;
}
protected int find(Node node, List<Group> groups) {
for (int i = 0; i < groups.size(); i++)
if (groups.get(i).getNodes().contains(node))
return i;
return -1;
}
protected boolean intersect(List<Group> groups, Node node) {
for (Group g : groups)
if (g.getNodes().contains(node))
return true;
return false;
}
protected Node intersect(List<Group> groups, Group group) {
for (Group g : groups)
for (Node node : group.getNodes())
if (g.getNodes().contains(node))
return node;
return null;
}
}
/*
*
* DIY Layout Creator (DIYLC). Copyright (c) 2009-2018 held jointly by the individual authors.
*
* This file is part of DIYLC.
*
* DIYLC 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.
*
* DIYLC 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 DIYLC. If not, see
* <http://www.gnu.org/licenses/>.
*/
package org.diylc.netlist;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.diylc.core.IDIYComponent;
public abstract class NetlistAnalyzer {
public NetlistAnalyzer() {
}
public List<Summary> summarize(List<Netlist> netlists, Node preferredOutput)
throws TreeException {
Map<String, Summary> summaries = new HashMap<String, Summary>();
for (Netlist n : netlists) {
Summary s = summarize(n, preferredOutput);
if (summaries.containsKey(s.getSummary()))
summaries.get(s.getSummary()).append(n);
else
summaries.put(s.getSummary(), s);
}
List<Summary> res = new ArrayList<Summary>(summaries.values());
Collections.sort(res);
return res;
}
protected abstract Summary summarize(Netlist netlist, Node preferredOutput) throws TreeException;
public Tree constructTreeBetween(Netlist netlist, Node nodeA, Node nodeB) {
Tree tree = new Tree(TreeConnectionType.Parallel);
connectNodes(netlist, nodeA, nodeB, tree.getChildren(), new Tree(TreeConnectionType.Series),
new HashSet<Node>());
return tree;
}
protected void connectNodes(Netlist netlist, Node nodeA, Node nodeB, List<Tree> concurrentPaths,
Tree currentPath, Set<Node> visited) {
if (nodeA == nodeB) {
concurrentPaths.add(currentPath);
return;
}
Group groupA = findGroup(netlist, nodeA);
if (groupA == null)
return;
if (groupA.getNodes().contains(nodeB)) {
concurrentPaths.add(currentPath);
return;
}
Set<Node> pathVisited = new HashSet<>(visited);
pathVisited.add(nodeA);
// First collect all immediate next nodes and their paths
Map<Node, List<Tree>> nextNodes = new HashMap<>();
for (Node n : groupA.getNodes()) {
if (pathVisited.contains(n))
continue;
IDIYComponent<?> c = n.getComponent();
for (int i = 0; i < c.getControlPointCount(); i++) {
int targetPoint = i;
int sourcePoint = n.getPointIndex();
if (targetPoint == sourcePoint)
continue;
if (c.getInternalLinkName(targetPoint, sourcePoint) != null) {
Node newNodeA = new Node(c, targetPoint, n.getZIndex());
if (pathVisited.contains(newNodeA))
continue;
try {
Tree newPath = (Tree) currentPath.clone();
TreeLeaf leaf = new TreeLeaf(c, targetPoint, sourcePoint);
Tree newItem = new Tree(leaf);
if (!newPath.getChildren().contains(newItem)) {
newPath.getChildren().add(newItem);
nextNodes.computeIfAbsent(newNodeA, k -> new ArrayList<>()).add(newPath);
}
} catch (CloneNotSupportedException e) {
// Skip this path if clone fails
}
}
}
}
// Now explore each next node
List<Tree> allPaths = new ArrayList<>();
for (Map.Entry<Node, List<Tree>> entry : nextNodes.entrySet()) {
for (Tree path : entry.getValue()) {
List<Tree> branchPaths = new ArrayList<>();
connectNodes(netlist, entry.getKey(), nodeB, branchPaths, path, pathVisited);
allPaths.addAll(branchPaths);
}
}
if (allPaths.isEmpty())
return;
if (allPaths.size() > 1) {
// Find common prefix among all paths
List<Tree> commonPrefix = findCommonElements(allPaths, true, false);
// Create a new tree for the merged result
Tree mergedTree = new Tree(TreeConnectionType.Series);
// Add common prefix elements
for (Tree prefixElement : commonPrefix) {
mergedTree.getChildren().add(prefixElement);
}
// Extract unique parts
List<Tree> uniqueParts = new ArrayList<>();
for (Tree path : allPaths) {
Tree uniquePart = extractUniquePart(path, commonPrefix.size(), 0);
if (uniquePart != null) {
if (uniquePart.getChildren().size() == 1) {
uniqueParts.add(uniquePart.getChildren().get(0));
} else {
uniqueParts.add(uniquePart);
}
}
}
// Create parallel section for unique parts
if (!uniqueParts.isEmpty()) {
Tree parallelSection = new Tree(TreeConnectionType.Parallel);
parallelSection.getChildren().addAll(uniqueParts);
// Add parallel section to series container
Tree parallelContainer = new Tree(TreeConnectionType.Series);
parallelContainer.getChildren().add(parallelSection);
mergedTree.getChildren().add(parallelContainer);
}
concurrentPaths.add(mergedTree);
} else {
concurrentPaths.add(allPaths.get(0));
}
}
private List<Tree> findCommonElements(List<Tree> paths, boolean fromStart, boolean backward) {
if (paths.isEmpty())
return new ArrayList<>();
List<Tree> common = new ArrayList<>();
Tree template = paths.get(0);
int maxLength = template.getChildren().size();
for (int i = 0; i < maxLength; i++) {
int index = fromStart ?
(backward ? template.getChildren().size() - i - 1 : i) :
(backward ? i : template.getChildren().size() - i - 1);
if (index < 0 || index >= template.getChildren().size())
break;
Tree element = template.getChildren().get(index);
boolean isCommon = true;
for (int j = 1; j < paths.size(); j++) {
Tree current = paths.get(j);
if (current.getChildren().size() <= i || index >= current.getChildren()
.size() || !current.getChildren().get(index).equals(element)) {
isCommon = false;
break;
}
}
if (isCommon) {
common.add(element);
} else {
break;
}
}
return common;
}
private Tree extractUniquePart(Tree path, int prefixSize, int suffixSize) {
if (path.getChildren().size() <= prefixSize + suffixSize)
return null;
Tree uniquePart = new Tree(TreeConnectionType.Series);
for (int i = prefixSize; i < path.getChildren().size() - suffixSize; i++) {
uniquePart.getChildren().add(path.getChildren().get(i));
}
return uniquePart.getChildren().isEmpty() ? null : uniquePart;
}
protected Group findGroup(Netlist netlist, Node node) {
for (Group g : netlist.getGroups())
if (g.getNodes().contains(node))
return g;
return null;
}
protected List<Node> find(Set<String> typeNames, String nodeName, Netlist netlist) {
List<Node> res = new ArrayList<Node>();
for (Group g : netlist.getGroups()) {
for (Node n : g.getNodes()) {
if (typeNames.contains(n.getComponent().getClass()
.getCanonicalName()) && (nodeName == null || n.getDisplayName()
.equalsIgnoreCase(nodeName))) {
res.add(n);
}
}
}
return res;
}
public static List<Set<IDIYComponent<?>>> extractComponentGroups(List<Netlist> netlists) {
List<Set<IDIYComponent<?>>> res = new ArrayList<Set<IDIYComponent<?>>>();
for (Netlist n : netlists) {
res.addAll(extractComponentGroups(n));
}
return res;
}
public static List<Set<IDIYComponent<?>>> extractComponentGroups(Netlist netlist) {
List<Set<IDIYComponent<?>>> res = new ArrayList<Set<IDIYComponent<?>>>();
for (Group g : netlist.getGroups()) {
Set<IDIYComponent<?>> components = new HashSet<IDIYComponent<?>>();
for (Node n : g.getNodes())
components.add(n.getComponent());
res.add(components);
}
return res;
}
protected int find(Node node, List<Group> groups) {
for (int i = 0; i < groups.size(); i++)
if (groups.get(i).getNodes().contains(node))
return i;
return -1;
}
protected boolean intersect(List<Group> groups, Node node) {
for (Group g : groups)
if (g.getNodes().contains(node))
return true;
return false;
}
protected Node intersect(List<Group> groups, Group group) {
for (Group g : groups)
for (Node node : group.getNodes())
if (g.getNodes().contains(node))
return node;
return null;
}
}
@@ -1,332 +1,343 @@
/*
DIY Layout Creator (DIYLC).
Copyright (c) 2009-2018 held jointly by the individual authors.
This file is part of DIYLC.
DIYLC 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.
DIYLC 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 DIYLC. If not, see <http://www.gnu.org/licenses/>.
*/
package org.diylc.netlist;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import org.diylc.core.IDIYComponent;
public class Tree {
private List<Tree> children;
private TreeConnectionType connectionType;
private TreeLeaf leaf;
public Tree(TreeConnectionType connectionType) {
this.children = new ArrayList<Tree>();
this.connectionType = connectionType;
}
public Tree(TreeLeaf leaf) {
this.leaf = leaf;
}
public Tree(List<Tree> children, TreeConnectionType connectionType) {
this.children = children;
this.connectionType = connectionType;
}
public List<Tree> getChildren() {
return children;
}
public void trimChildrenLeft(int count) {
children = children.subList(count, children.size());
}
public void trimChildrenRight(int count) {
children = children.subList(0, children.size() - count);
}
public TreeConnectionType getConnectionType() {
return connectionType;
}
public TreeLeaf getLeaf() {
return leaf;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((children == null) ? 0 : children.hashCode());
result = prime * result + ((connectionType == null) ? 0 : connectionType.hashCode());
result = prime * result + ((leaf == null) ? 0 : leaf.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Tree other = (Tree) obj;
if (children == null) {
if (other.children != null)
return false;
} else if (!children.equals(other.children))
return false;
if (connectionType != other.connectionType)
return false;
if (leaf == null) {
if (other.leaf != null)
return false;
} else if (!leaf.equals(other.leaf))
return false;
return true;
}
private List<Tree> getOrderedChildren() {
if (getConnectionType() == TreeConnectionType.Series)
return children;
List<Tree> ordered = new ArrayList<Tree>(children);
Collections.sort(ordered, new Comparator<Tree>() {
@Override
public int compare(Tree o1, Tree o2) {
return o1.toString().compareToIgnoreCase(o2.toString());
}});
return ordered;
}
@Override
public String toString() {
if (leaf != null)
return leaf.toString();
StringBuilder sb = new StringBuilder("(");
boolean first = true;
for(Tree child : getOrderedChildren()) {
if (!first)
sb.append(" " ).append(connectionType).append(" ");
first = false;
sb.append(child);
}
sb.append(")");
return sb.toString();
}
public String toHTML(int depth) {
if (leaf != null)
return leaf.toHTML();
List<Tree> children = getOrderedChildren();
StringBuilder sb = new StringBuilder();
if (depth > 0 && children.size() > 1)
sb.append("(");
boolean first = true;
for(Tree child : children) {
if (!first) {
sb.append("&nbsp;").append(connectionType.toHTML()).append("&nbsp;");
if (depth == 0)
sb.append("<br>");
}
first = false;
sb.append(child.toHTML(depth + children.size() > 1 ? 1 : 0));
}
if (depth > 0 && children.size() > 1)
sb.append(")");
return sb.toString();
}
public String toAsciiString() {
return String.join("<br>", toAscii());
}
private List<String> toAscii() {
if (leaf != null) {
return Arrays.asList(TreeAsciiUtil.generateString(TreeAsciiUtil.BOXH, 1) + " " + leaf.toHTML() + " " + TreeAsciiUtil.generateString(TreeAsciiUtil.BOXH, 1));
}
List<Tree> children = getOrderedChildren();
List<List<String>> childrenAscii =
children.stream().map(c -> c.toAscii()).collect(Collectors.toList());
if (TreeConnectionType.Series.equals(connectionType)) {
return TreeAsciiUtil.concatenateMultiLineSerial(TreeAsciiUtil.generateString(TreeAsciiUtil.BOXH, 1), childrenAscii);
} else {
return TreeAsciiUtil.concatenateMultiLineParallel(childrenAscii);
}
}
@Override
protected Object clone() throws CloneNotSupportedException {
if (leaf != null)
return new Tree(leaf);
List<Tree> newChildren = new ArrayList<Tree>();
for (Tree t : children)
newChildren.add((Tree) t.clone());
return new Tree(newChildren, connectionType);
}
public Tree filter(Set<String> types) {
if (leaf != null) {
if (types.contains(leaf.getComponent().getClass().getCanonicalName()))
return new Tree(leaf);
return null;
}
List<Tree> newChildren = new ArrayList<Tree>();
for (Tree t : children) {
Tree child = t.filter(types);
if (child != null)
newChildren.add(child);
}
if (newChildren.isEmpty())
return null;
return new Tree(newChildren, connectionType);
}
public void walk(ITreeWalker walker) {
if (leaf != null) {
walker.visit(leaf);
} else {
for (Tree t : children) {
walker.visit(t);
t.walk(walker);
}
}
}
public Set<IDIYComponent<?>> extractComponents(Set<String> types) {
Set<IDIYComponent<?>> res = new HashSet<IDIYComponent<?>>();
if (leaf != null && types.contains(leaf.getComponent().getClass().getCanonicalName())) {
res.add(leaf.getComponent());
} else if (children != null) {
for (Tree t : children) {
Set<IDIYComponent<?>> childRes = t.extractComponents(types);
if (childRes != null)
res.addAll(childRes);
}
}
return res;
}
public Tree locate(TreeLeaf l, boolean forceDirection) {
if (leaf != null && leaf.equals(l, forceDirection))
return this;
if (children != null) {
for (Tree t : children) {
Tree childL = t.locate(l, forceDirection);
if (childL != null) {
// if (t.getLeaf() != null)
// return this;
return childL;
}
}
}
return null;
}
public Tree findCommonParent(Tree t1, Tree t2) {
if (t1 == null)
return t2;
if (t2 == null)
return t1;
if (children == null)
return null;
if (!this.contains(t1) || !this.contains(t2))
return null;
Tree p1 = this;
Tree p2 = this;
for (Tree c : children) {
if (c.contains(t1))
p1 = c;
if (c.contains(t2))
p2 = c;
}
if (p1 != p2 || p1 == this || p2 == this)
return this;
return p1.findCommonParent(t1, t2);
}
public Tree findCommonParent(List<Tree> t) {
if (t.contains(null))
{
while(t.contains(null))
t.remove(null);
return findCommonParent(t);
}
if (t.size() == 0)
return null;
if (t.size() == 1)
return t.get(0);
if (t.size() == 2)
return findCommonParent(t.get(0), t.get(1));
List<Tree> remainder = t.subList(1, t.size());
return findCommonParent(t.get(0), findCommonParent(remainder));
}
public Tree findParent(Tree t) {
if (children == null)
return null;
if (children.contains(t))
return this;
for (Tree c : children)
{
Tree p = c.findParent(t);
if (p != null)
return p;
}
return null;
}
public boolean contains(Tree t) {
if (this == t)
return true;
if (children == null)
return false;
if (children.contains(t))
return true;
for (Tree c : children)
if (c.contains(t))
return true;
return false;
}
public interface ITreeWalker {
void visit(Tree t);
void visit(TreeLeaf l);
}
}
/*
DIY Layout Creator (DIYLC).
Copyright (c) 2009-2018 held jointly by the individual authors.
This file is part of DIYLC.
DIYLC 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.
DIYLC 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 DIYLC. If not, see <http://www.gnu.org/licenses/>.
*/
package org.diylc.netlist;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import org.diylc.core.IDIYComponent;
public class Tree {
private List<Tree> children;
private TreeConnectionType connectionType;
private TreeLeaf leaf;
public Tree(TreeConnectionType connectionType) {
this.children = new ArrayList<Tree>();
this.connectionType = connectionType;
}
public Tree(TreeLeaf leaf) {
this.leaf = leaf;
}
public Tree(List<Tree> children, TreeConnectionType connectionType) {
this.children = children;
this.connectionType = connectionType;
}
public List<Tree> getChildren() {
return children;
}
public void trimChildrenLeft(int count) {
children = children.subList(count, children.size());
}
public void trimChildrenRight(int count) {
children = children.subList(0, children.size() - count);
}
public TreeConnectionType getConnectionType() {
return connectionType;
}
public TreeLeaf getLeaf() {
return leaf;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((children == null) ? 0 : children.hashCode());
result = prime * result + ((connectionType == null) ? 0 : connectionType.hashCode());
result = prime * result + ((leaf == null) ? 0 : leaf.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Tree other = (Tree) obj;
if (children == null) {
if (other.children != null)
return false;
} else if (!children.equals(other.children))
return false;
if (connectionType != other.connectionType)
return false;
if (leaf == null) {
if (other.leaf != null)
return false;
} else if (!leaf.equals(other.leaf))
return false;
return true;
}
private List<Tree> getOrderedChildren() {
if (getConnectionType() == TreeConnectionType.Series)
return children;
List<Tree> ordered = new ArrayList<Tree>(children);
Collections.sort(ordered, new Comparator<Tree>() {
@Override
public int compare(Tree o1, Tree o2) {
return o1.toString().compareToIgnoreCase(o2.toString());
}
});
return ordered;
}
@Override
public String toString() {
if (leaf != null)
return leaf.toString();
// If we only have one child, no need for parentheses
if (children.size() == 1)
return children.get(0).toString();
StringBuilder sb = new StringBuilder("(");
boolean first = true;
for (Tree child : getOrderedChildren()) {
if (!first)
sb.append(" ").append(connectionType).append(" ");
first = false;
// Only include child's parentheses if it has multiple children with a different connection type
String childStr = child.toString();
if (child.children != null && child.children.size() > 1 && child.connectionType != connectionType) {
sb.append(childStr);
} else {
sb.append(childStr.replaceAll("^\\((.*)\\)$", "$1"));
}
}
sb.append(")");
return sb.toString();
}
public String toHTML(int depth) {
if (leaf != null)
return leaf.toHTML();
List<Tree> children = getOrderedChildren();
StringBuilder sb = new StringBuilder();
if (depth > 0 && children.size() > 1)
sb.append("(");
boolean first = true;
for (Tree child : children) {
if (!first) {
sb.append("&nbsp;").append(connectionType.toHTML()).append("&nbsp;");
if (depth == 0)
sb.append("<br>");
}
first = false;
sb.append(child.toHTML(depth + children.size() > 1 ? 1 : 0));
}
if (depth > 0 && children.size() > 1)
sb.append(")");
return sb.toString();
}
public String toAsciiString() {
return String.join("<br>", toAscii());
}
private List<String> toAscii() {
if (leaf != null) {
return Arrays.asList(TreeAsciiUtil.generateString(TreeAsciiUtil.BOXH,
1) + " " + leaf.toHTML() + " " + TreeAsciiUtil.generateString(TreeAsciiUtil.BOXH, 1));
}
List<Tree> children = getOrderedChildren();
List<List<String>> childrenAscii =
children.stream().map(c -> c.toAscii()).collect(Collectors.toList());
if (TreeConnectionType.Series.equals(connectionType)) {
return TreeAsciiUtil.concatenateMultiLineSerial(
TreeAsciiUtil.generateString(TreeAsciiUtil.BOXH, 1), childrenAscii);
} else {
return TreeAsciiUtil.concatenateMultiLineParallel(childrenAscii);
}
}
@Override
protected Object clone() throws CloneNotSupportedException {
if (leaf != null)
return new Tree(leaf);
List<Tree> newChildren = new ArrayList<Tree>();
for (Tree t : children)
newChildren.add((Tree) t.clone());
return new Tree(newChildren, connectionType);
}
public Tree filter(Set<String> types) {
if (leaf != null) {
if (types.contains(leaf.getComponent().getClass().getCanonicalName()))
return new Tree(leaf);
return null;
}
List<Tree> newChildren = new ArrayList<Tree>();
for (Tree t : children) {
Tree child = t.filter(types);
if (child != null)
newChildren.add(child);
}
if (newChildren.isEmpty())
return null;
return new Tree(newChildren, connectionType);
}
public void walk(ITreeWalker walker) {
if (leaf != null) {
walker.visit(leaf);
} else {
for (Tree t : children) {
walker.visit(t);
t.walk(walker);
}
}
}
public Set<IDIYComponent<?>> extractComponents(Set<String> types) {
Set<IDIYComponent<?>> res = new HashSet<IDIYComponent<?>>();
if (leaf != null && types.contains(leaf.getComponent().getClass().getCanonicalName())) {
res.add(leaf.getComponent());
} else if (children != null) {
for (Tree t : children) {
Set<IDIYComponent<?>> childRes = t.extractComponents(types);
if (childRes != null)
res.addAll(childRes);
}
}
return res;
}
public Tree locate(TreeLeaf l, boolean forceDirection) {
if (leaf != null && leaf.equals(l, forceDirection))
return this;
if (children != null) {
for (Tree t : children) {
Tree childL = t.locate(l, forceDirection);
if (childL != null) {
// if (t.getLeaf() != null)
// return this;
return childL;
}
}
}
return null;
}
public Tree findCommonParent(Tree t1, Tree t2) {
if (t1 == null)
return t2;
if (t2 == null)
return t1;
if (children == null)
return null;
if (!this.contains(t1) || !this.contains(t2))
return null;
Tree p1 = this;
Tree p2 = this;
for (Tree c : children) {
if (c.contains(t1))
p1 = c;
if (c.contains(t2))
p2 = c;
}
if (p1 != p2 || p1 == this || p2 == this)
return this;
return p1.findCommonParent(t1, t2);
}
public Tree findCommonParent(List<Tree> t) {
if (t.contains(null)) {
while (t.contains(null))
t.remove(null);
return findCommonParent(t);
}
if (t.size() == 0)
return null;
if (t.size() == 1)
return t.get(0);
if (t.size() == 2)
return findCommonParent(t.get(0), t.get(1));
List<Tree> remainder = t.subList(1, t.size());
return findCommonParent(t.get(0), findCommonParent(remainder));
}
public Tree findParent(Tree t) {
if (children == null)
return null;
if (children.contains(t))
return this;
for (Tree c : children) {
Tree p = c.findParent(t);
if (p != null)
return p;
}
return null;
}
public boolean contains(Tree t) {
if (this == t)
return true;
if (children == null)
return false;
if (children.contains(t))
return true;
for (Tree c : children)
if (c.contains(t))
return true;
return false;
}
public interface ITreeWalker {
void visit(Tree t);
void visit(TreeLeaf l);
}
}
@@ -137,27 +137,9 @@ public class Presenter implements IPlugInPort, IConfigListener {
private static final Logger LOG = Logger.getLogger(Presenter.class);
public static VersionNumber CURRENT_VERSION = new VersionNumber(4, 0, 0);
public static List<Version> RECENT_VERSIONS = null;
// Read the latest version from the local update.xml file
static {
try {
BufferedInputStream in = new BufferedInputStream(Presenter.class.getResourceAsStream("/update.xml"));
XStream xStream = new XStream(new DomDriver());
xStream.addPermission(AnyTypePermission.ANY);
@SuppressWarnings("unchecked")
List<Version> allVersions = (List<Version>) xStream.fromXML(in);
CURRENT_VERSION = allVersions.get(allVersions.size() - 1).getVersionNumber();
LOG.info("Current DIYLC version: " + CURRENT_VERSION);
RECENT_VERSIONS = allVersions.stream()
.sorted(Comparator.comparing(x -> ((Version)x).getVersionNumber()).reversed())
.limit(10)
.collect(Collectors.toList());
in.close();
} catch (Exception e) {
LOG.error("Could not find version number, using default", e);
}
}
public static VersionNumber CURRENT_VERSION = null;
private static List<Version> RECENT_VERSIONS = null;
public static final String DEFAULTS_KEY_PREFIX = "default.";
public static final List<IDIYComponent<?>> EMPTY_SELECTION = Collections.emptyList();
@@ -1091,11 +1073,39 @@ public class Presenter implements IPlugInPort, IConfigListener {
@Override
public VersionNumber getCurrentVersionNumber() {
if (CURRENT_VERSION == null) {
List<Version> recentUpdates = getRecentUpdates();
CURRENT_VERSION = recentUpdates.stream()
.findFirst().map(v -> v.getVersionNumber())
.orElse(new VersionNumber(5, 0, 0));
LOG.info("Current DIYLC version: " + CURRENT_VERSION);
}
return CURRENT_VERSION;
}
@Override
public List<Version> getRecentUpdates() {
return getRecentVersions();
}
private static List<Version> getRecentVersions() {
if (RECENT_VERSIONS == null) {
try {
BufferedInputStream in =
new BufferedInputStream(Presenter.class.getResourceAsStream("/update.xml"));
XStream xStream = new XStream(new DomDriver());
xStream.addPermission(AnyTypePermission.ANY);
@SuppressWarnings("unchecked")
List<Version> allVersions = (List<Version>) xStream.fromXML(in);
RECENT_VERSIONS = allVersions.stream()
.sorted(Comparator.comparing(x -> ((Version) x).getVersionNumber()).reversed())
.limit(10).collect(Collectors.toList());
in.close();
} catch (Exception e) {
LOG.error("Could not load recent versions", e);
RECENT_VERSIONS = new ArrayList<>();
}
}
return RECENT_VERSIONS;
}
@@ -253,6 +253,10 @@
<changeType>IMPROVEMENT</changeType>
<description>Upgraded source code structure to modern standards and introduced Maven</description>
</org.diylc.appframework.update.Change>
<org.diylc.appframework.update.Change>
<changeType>IMPROVEMENT</changeType>
<description>Improved robustness of series/parallel graph construction code used in circuit analysis</description>
</org.diylc.appframework.update.Change>
<org.diylc.appframework.update.Change>
<changeType>BUG_FIX</changeType>
<description>Moving selected components with a mouse doesn't register file modification action and doesn't enable Undo action</description>