/* Copyright (C) 2006 Christian Schneider * * This file is part of Nomad. * * Nomad 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 2 of the License, or * (at your option) any later version. * * Nomad 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 Nomad; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ package net.sf.nmedit.jpatch.impl; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.ConcurrentModificationException; import java.util.HashMap; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.NoSuchElementException; import java.util.Queue; import javax.swing.event.EventListenerList; import javax.swing.undo.UndoableEdit; import net.sf.nmedit.jpatch.PConnection; import net.sf.nmedit.jpatch.PConnectionManager; import net.sf.nmedit.jpatch.PConnector; import net.sf.nmedit.jpatch.PModule; import net.sf.nmedit.jpatch.PModuleContainer; import net.sf.nmedit.jpatch.PSignal; import net.sf.nmedit.jpatch.PUndoableEditFactory; import net.sf.nmedit.jpatch.event.PConnectionEvent; import net.sf.nmedit.jpatch.event.PConnectionListener; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; /** * The reference implementation of interface {@link PConnectionManager}. * @author Christian Schneider */ public class PBasicConnectionManager implements PConnectionManager { private PBasicModuleContainer container; private Map<PConnector, Node> nodemap; private int size; private EventListenerList eventListeners = new EventListenerList(); private transient volatile int modCount; private static final boolean DEBUG = true; public PBasicConnectionManager(PBasicModuleContainer container) { this.container = container; nodemap = new HashMap<PConnector, Node>(); } public void postEdit(UndoableEdit edit) { container.postEdit(edit); } public boolean isUndoableEditSupportEnabled() { return container.isUndoableEditSupportEnabled(); } public PUndoableEditFactory getUndoableEditFactory() { return container.getUndoableEditFactory(); } public void addConnectionListener( PConnectionListener l ) { eventListeners.add(PConnectionListener.class, l); } public void removeConnectionListener( PConnectionListener l ) { eventListeners.remove(PConnectionListener.class, l); } protected void fireConnectionAdded(PConnector a, PConnector b) { connectionAddHappened(a, b); PConnectionEvent e = null; Object[] listenerList = eventListeners.getListenerList(); for (int i=listenerList.length-2;i>=0;i-=2) { if (listenerList[i]==PConnectionListener.class) { if (e == null) e = new PConnectionEvent(this, a, b); ((PConnectionListener) listenerList[i+1]).connectionAdded(e); } } } protected void fireConnectionRemoved(PConnector a, PConnector b) { connectionRemoveHappened(a, b); PConnectionEvent e = null; Object[] listenerList = eventListeners.getListenerList(); for (int i=listenerList.length-2;i>=0;i-=2) { if (listenerList[i]==PConnectionListener.class) { if (e == null) e = new PConnectionEvent(this, a, b); ((PConnectionListener) listenerList[i+1]).connectionRemoved(e); } } } protected void connectionRemoveHappened(PConnector a, PConnector b) { if (isUndoableEditSupportEnabled()) { PUndoableEditFactory factory = getUndoableEditFactory(); if (factory != null) postEdit(factory.createDisconnectEdit(a, b)); } } protected void connectionAddHappened(PConnector a, PConnector b) { if (isUndoableEditSupportEnabled()) { PUndoableEditFactory factory = getUndoableEditFactory(); if (factory != null) postEdit(factory.createConnectEdit(a, b)); } } private boolean validTarget(PConnector c) { PModule module = c.getParentComponent(); return module != null && module.getParentComponent() == getModuleContainer(); } private Log getLog() { return LogFactory.getLog(getClass()); } public boolean add(PConnector a, PConnector b) { if (DEBUG) { Log log = getLog(); if (log.isDebugEnabled()) { log.debug("adding connectors a,b="+a+","+b); } } if (a == b) { if (DEBUG) { Log log = getLog(); if (log.isDebugEnabled()) { log.debug("connectors are equal: a,b="+a+","+b); } } return false; // throw new IllegalArgumentException("Cannot connect connector to itself:"+a); } Node na = nodemap.get(a); Node nb = nodemap.get(b); if ((na == null && (!validTarget(a))) ||(nb == null && (!validTarget(b)))) { if (DEBUG) { Log log = getLog(); if (log.isDebugEnabled()) { log.debug("null/valid-target check failed: "+a+","+b); } } return false; } { PConnector checkout1 = a; PConnector checkout2 = b; Node ra = null; Node rb = null; if (na != null) { ra = na.root(); checkout1 = ra.c; } if (nb != null) { rb = nb.root(); checkout2 = rb.c; } if (ra != null && ra == rb) { // already connected if (DEBUG) { Log log = getLog(); if (log.isDebugEnabled()) { log.debug("already connected a,b="+a+","+b); } } return false; } // two outputs ? if (checkout1.isOutput() && checkout2.isOutput()) { if (DEBUG) { Log log = getLog(); if (log.isDebugEnabled()) { log.debug("cannot connect two outputs: a,b="+a+","+b); } } return false; } if (checkout2.isOutput()) { Node swapn = nb; nb = na; na = swapn; PConnector swapc = b; b = a; a = swapc; } } // graph(b) contains no output => graph(a) eventually contains an output boolean updateA = false; boolean updateB = false; if (na == null) { na = new Node(a); nodemap.put(a, na); updateA = true; } if (nb == null) { nb = new Node(b); na.addChild(nb); nodemap.put(b, nb); updateB = true; } else { // nb must be root of graph(nb) nb.becomeRoot(); na.addChild(nb); } modCount++; size++; if (updateA) a.verifyConnectedState(); if (updateB) b.verifyConnectedState(); fireConnectionAdded(a, b); fireUpdateTree(nb); // update sub tree return true; } private void fireUpdateTree(Node root) { // TODO } private void disconnectParentFromChild(Node parent, Node child) { parent.removeChild(child); if (!parent.isConnected()) nodemap.remove(parent.c); if (!child.isConnected()) nodemap.remove(child.c); modCount++; size--; if (!parent.isConnected()) parent.c.verifyConnectedState(); if (!child.isConnected()) child.c.verifyConnectedState(); fireConnectionRemoved(child.c, parent.c); // update disconnected subtree if (child.isConnected() && parent.root().isOutput()) fireUpdateTree(child); } protected boolean remove(Node a, Node b) { if (a.p == b) { disconnectParentFromChild(b, a); return true; } else if (b.p == a) { disconnectParentFromChild(a, b); return true; } else return false; // not connected } public void clear() { if (!isEmpty()) { removeAllConnections(connections()); } } public boolean add(PConnection c) { return add(c.getA(), c.getB()); } public int childCount(PConnector c) { Node n = nodemap.get(c); return n == null ? 0 : n.childCount(); } public Collection<PConnector> children(PConnector c) { Node n = nodemap.get(c); if (n == null || n.childCount() <= 0) return Collections.<PConnector>emptyList(); Collection<PConnector> children = new LinkedList<PConnector>(); n.addChildConnectors(children); return Collections.<PConnector>unmodifiableCollection(children); } public Collection<PConnector> connected() { int s = size(); if (s <= 0) return Collections.<PConnector>emptyList(); List<PConnector> a = new ArrayList<PConnector>(s); for (Node n: nodemap.values()) a.add(n.c); return Collections.<PConnector>unmodifiableCollection(a); } public Collection<PConnector> connected(PConnector c) { Node n = nodemap.get(c); if (n == null || n.childCount() <= 0) return Collections.<PConnector>emptyList(); List<PConnector> a = new LinkedList<PConnector>(); n.addChildConnectors(a); Node p = n.p; if (p != null) a.add(p.c); return Collections.<PConnector>unmodifiableCollection(a); } public Collection<PConnection> connections() { if (isEmpty()) return Collections.<PConnection>emptyList(); Collection <PConnection> r = new ArrayList<PConnection>(size); for (PConnection connection: this) r.add(connection); return Collections.<PConnection>unmodifiableCollection(r); } protected void addConnections(Collection<PConnection> c, Node n) { PConnector p = n.parent(); if (p != null) c.add(new PConnection(n.c, p)); n.addChildParentConnections(c); } public Collection<PConnection> connections(PConnector c) { Node n = nodemap.get(c); if (n == null) return Collections.<PConnection>emptyList(); Collection<PConnection> r = new ArrayList<PConnection>(n.childCount()+1); addConnections(r, n); return Collections.<PConnection>unmodifiableCollection(r); } public Collection<PConnection> graphConnections(PConnector c) { Node n = nodemap.get(c); if (n == null) return Collections.<PConnection>emptyList(); Queue<Node> queue = new LinkedList<Node>(); Collection<PConnection> g = new LinkedList<PConnection>(); n.addChildNodes(queue); while (!queue.isEmpty()) { n = queue.remove(); g.add(new PConnection(n.c, n.parent())); n.addChildNodes(queue); } return Collections.<PConnection>unmodifiableCollection(g); } public Collection<PConnection> connections(PModule m) { Collection<PConnection> r = new LinkedList<PConnection>(); for (int i=m.getConnectorCount()-1;i>=0;i--) { PConnector c = m.getConnector(i); Node n = nodemap.get(c); if (n != null) addConnections(r, n); } return Collections.<PConnection>unmodifiableCollection(r); } public Collection<PConnection> connections(Collection<? extends PModule> ms) { Collection<PConnection> r = new LinkedList<PConnection>(); for (PModule m : ms) { if (m != null) r.addAll(connections(m)); } return Collections.<PConnection>unmodifiableCollection(r); } public boolean isConnected(PModule m) { for (int i=m.getConnectorCount()-1;i>=0;i--) { PConnector c = m.getConnector(i); if (nodemap.get(c)!=null) return true; } return false; } public boolean contains(PConnection c) { return isConnected(c.getA(), c.getB()); } public PModuleContainer getModuleContainer() { return container; } public PSignal getSignalType(PConnector connector) { Node n = nodemap.get(connector); if (n == null) return connector.getDefinedSignals().noSignal(); n = n.root(); return n.isOutput() ? n.c.getDefaultSignalType() : connector.getDefinedSignals().noSignal(); } public Collection<PConnector> graph(PConnector c) { Node n = nodemap.get(c); if (n == null) return Collections.<PConnector>emptyList(); Queue<Node> queue = new LinkedList<Node>(); Collection<PConnector> g = new LinkedList<PConnector>(); queue.offer(n.root()); while (!queue.isEmpty()) { n = queue.remove(); g.add(n.c); n.addChildNodes(queue); } return Collections.<PConnector>unmodifiableCollection(g); } public boolean isConnected(PConnector a, PConnector b) { Node na = nodemap.get(a); if (na == null) return false ; if (na.isParent(b)) return true; Node nb = nodemap.get(b); return nb != null && nb.p == na; } public boolean isConnected(PConnector c) { return nodemap.containsKey(c); } public boolean isEmpty() { return nodemap.isEmpty(); } public PConnector output(PConnector c) { PConnector root = root(c); return root.isOutput() ? root : null; } public PConnector parent(PConnector c) { Node n = nodemap.get(c); return n == null ? null : n.parent(); } public PConnector root(PConnector c) { Node n = nodemap.get(c); return n == null ? c : n.root().c; } public boolean pathExists(PConnector a, PConnector b) { Node na = nodemap.get(a); Node nb = nodemap.get(b); return na != null && nb != null && na.root() == nb.root(); } public boolean remove(PConnection c) { return remove(c.getA(), c.getB()); } public boolean removeAllConnectors(Collection<? extends PConnector> c) { boolean modified = false; for (PConnector connector: c) modified |= removeAllConnections(connections(connector)); return modified; } public boolean removeAllConnections(Collection<? extends PConnection> c) { boolean modified = false; for (PConnection connection: c) modified |= remove(connection); return modified; } public boolean remove(PConnector a, PConnector b) { Node na = nodemap.get(a); Node nb = nodemap.get(b); return na != null && nb != null && remove(na, nb); } public boolean remove(PConnector c) { PConnector parent = c.getParentConnector(); Collection<PConnector> children = c.getChildren(); boolean removed = false; // remove connection to parent if (parent != null) removed |= remove(c, parent); // remove connections to children for (PConnector child: children) removed |= remove(c, child); if (parent != null) { // connect children with parent for (PConnector child: children) add(child, parent); } else { // connect children with each other PConnector previous = null; for (PConnector child: children) { if (previous != null) add(previous, child); previous = child; } } return removed; } public int size() { return size; } public Iterator<PConnection> iterator() { return new Iterator<PConnection>() { Iterator<Node> iter = nodemap.values().iterator(); Node next; int expectedModCount = modCount; void align() { if (next == null) { Node n; while (iter.hasNext()) { n = iter.next(); if (n.p != null) { next = n; break; } } } } public boolean hasNext() { align(); return next != null; } public PConnection next() { if (expectedModCount != modCount) throw new ConcurrentModificationException(); if (!hasNext()) throw new NoSuchElementException(); PConnection c = new PConnection(next.c, next.parent()); next = null; return c; } public void remove() { throw new UnsupportedOperationException(); } }; } protected Node createNode(PConnector c) { return new Node(c); } protected class Node { public PConnector c; public Node p; private LinkedListItem<Node> children; public Node(PConnector c) { this.c = c; } public void addChildParentConnections(Collection<PConnection> collection) { LinkedListItem<Node> pos = children; while (pos != null) { collection.add(new PConnection(c, pos.element.c)); pos = pos.next; } } public void addChildConnectors(Collection<PConnector> collection) { LinkedListItem<Node> pos = children; while (pos != null) { collection.add(pos.element.c); pos = pos.next; } } public void addChildNodes(Collection<Node> collection) { LinkedListItem<Node> pos = children; while (pos != null) { collection.add(pos.element); pos = pos.next; } } public boolean isConnected() { return p!=null || childCount()>0; } public PConnection parentConnection() { return p == null ? null : new PConnection(c, p.c); } public PConnector parent() { return p == null ? null : p.c; } public int childCount() { return children == null ? 0 : LinkedListItem.size(children); } /* public boolean isChild(Object o) { return LinkedListItem. }*/ public int hashCode() { return c.hashCode(); } public boolean equals(Object o) { return o == this || o == c; } public Node root() { Node r = this; while (r.p != null) r = r.p; return r; } public boolean isParent(PConnector c) { return p == null ? false : p.c == c; } public void becomeRoot() { while (p != null) becomeParent(); assert root() == this; } public void becomeParent() { if (p == null) return; Node subtree = p; p.removeChild(this); subtree.becomeRoot(); addChild(subtree); } boolean isOutput() { return c.isOutput(); } private void addChild(Node child) { // children==null-argument supported children = LinkedListItem.<Node>add(children, child); child.p = this; } private void removeChild(Node child) { // children==null-argument supported children = LinkedListItem.<Node>remove(children, child); child.p = null; } } static class LinkedListItem<E> { private LinkedListItem<E> next; private E element; LinkedListItem(LinkedListItem<E> next, E element) { this.element = element; this.next = next; } LinkedListItem<E> next() { return next; } E element() { return element; } static <E> LinkedListItem<E> add(LinkedListItem<E> list, E element) { return new LinkedListItem<E>(list, element); } static <E> LinkedListItem<E> remove(LinkedListItem<E> firstInList, E element) { if (firstInList == null) return null; LinkedListItem<E> pos = firstInList; LinkedListItem<E> prev = null; while (pos != null) { if (eq(pos.element, element)) { if (prev == null) firstInList = pos.next; else prev.next = pos.next; break; } prev = pos; pos = pos.next; } return firstInList; } static int size(LinkedListItem<?> list) { int size = 0; while (list != null) { size++; list = list.next; } return size; } static boolean eq(Object a, Object b) { return a==b/* || a.equals(b)*/; } } }