/* * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. * * Copyright 2016 Neil C Smith. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 3 only, as * published by the Free Software Foundation. * * This code 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 * version 3 for more details. * * You should have received a copy of the GNU General Public License version 3 * along with this work; if not, see http://www.gnu.org/licenses/ * * * Please visit http://neilcsmith.net if you need additional information or * have any questions. */ package net.neilcsmith.praxis.live.pxr; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.util.ArrayList; import java.util.Collections; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.logging.Level; import java.util.logging.Logger; import net.neilcsmith.praxis.core.Argument; import net.neilcsmith.praxis.core.ArgumentFormatException; import net.neilcsmith.praxis.core.CallArguments; import net.neilcsmith.praxis.core.ComponentAddress; import net.neilcsmith.praxis.core.ComponentType; import net.neilcsmith.praxis.core.ControlAddress; import net.neilcsmith.praxis.core.InterfaceDefinition; import net.neilcsmith.praxis.core.info.ComponentInfo; import net.neilcsmith.praxis.core.interfaces.ContainerInterface; import net.neilcsmith.praxis.core.types.PArray; import net.neilcsmith.praxis.gui.ControlBinding; import net.neilcsmith.praxis.live.core.api.Callback; import net.neilcsmith.praxis.live.properties.PraxisProperty; import net.neilcsmith.praxis.live.model.Connection; import net.neilcsmith.praxis.live.model.ContainerProxy; import net.neilcsmith.praxis.live.model.ProxyException; import net.neilcsmith.praxis.live.util.ArgumentPropertyAdaptor; import org.openide.nodes.Node; import org.openide.util.Exceptions; /** * * @author Neil C Smith (http://neilcsmith.net) */ public class PXRContainerProxy extends PXRComponentProxy implements ContainerProxy { private final static Logger LOG = Logger.getLogger(PXRContainerProxy.class.getName()); private final Map<String, PXRComponentProxy> children; private final Set<Connection> connections; private final ChildrenProperty childProp; private final ConnectionsProperty conProp; private ArgumentPropertyAdaptor.ReadOnly conAdaptor; boolean ignore; PXRContainerProxy(PXRContainerProxy parent, ComponentType type, ComponentInfo info) { super(parent, type, info); children = new LinkedHashMap<>(); connections = new LinkedHashSet<>(); childProp = new ChildrenProperty(); conProp = new ConnectionsProperty(); } @Override List<? extends PraxisProperty<?>> getProxyProperties() { List<PraxisProperty<?>> proxies = new ArrayList<>(3); proxies.addAll(super.getProxyProperties()); proxies.add(childProp); proxies.add(conProp); return proxies; } @Override public PXRComponentProxy getChild(String id) { return children.get(id); } @Override public String[] getChildIDs() { Set<String> keySet = children.keySet(); return keySet.toArray(new String[keySet.size()]); } @Override public void addChild(final String id, final ComponentType type, final Callback callback) throws ProxyException { ComponentAddress childAddress = ComponentAddress.create(getAddress(), id); PXRHelper.getDefault().createComponentAndGetInfo(childAddress, type, new Callback() { @Override public void onReturn(CallArguments args) { try { ComponentInfo info = ComponentInfo.coerce(args.get(0)); if (isContainer(info)) { children.put(id, new PXRContainerProxy(PXRContainerProxy.this, type, info)); } else { children.put(id, new PXRComponentProxy(PXRContainerProxy.this, type, info)); } if (node != null) { node.refreshChildren(); } firePropertyChange(ContainerInterface.CHILDREN, null, null); if (callback != null) { callback.onReturn(args); } } catch (Exception ex) { Exceptions.printStackTrace(ex); onError(args); } } @Override public void onError(CallArguments args) { if (callback != null) { callback.onError(args); } } }); } private boolean isContainer(ComponentInfo info) { return info.hasInterface(ContainerInterface.class); } @Override public void removeChild(final String id, final Callback callback) throws ProxyException { ComponentAddress childAddress = ComponentAddress.create(getAddress(), id); PXRHelper.getDefault().removeComponent(childAddress, new Callback() { @Override public void onReturn(CallArguments args) { PXRComponentProxy child = children.get(id); if (child != null) { child.dispose(); } children.remove(id); Iterator<Connection> itr = connections.iterator(); boolean conChanged = false; while (itr.hasNext()) { Connection con = itr.next(); if (con.getChild1().equals(id) || con.getChild2().equals(id)) { itr.remove(); conChanged = true; } } if (conChanged) { firePropertyChange(ContainerInterface.CONNECTIONS, null, null); } if (node != null) { node.refreshChildren(); } firePropertyChange(ContainerInterface.CHILDREN, null, null); if (callback != null) { callback.onReturn(args); } } @Override public void onError(CallArguments args) { if (callback != null) { callback.onError(args); } } }); } @Override public void connect(final Connection connection, final Callback callback) throws ProxyException { PXRHelper.getDefault().connect(getAddress(), connection, new Callback() { @Override public void onReturn(CallArguments args) { connections.add(connection); firePropertyChange(ContainerInterface.CONNECTIONS, null, null); if (callback != null) { callback.onReturn(args); } } @Override public void onError(CallArguments args) { if (callback != null) { callback.onError(args); } } }); } @Override public void disconnect(final Connection connection, final Callback callback) throws ProxyException { PXRHelper.getDefault().disconnect(getAddress(), connection, new Callback() { @Override public void onReturn(CallArguments args) { connections.remove(connection); firePropertyChange(ContainerInterface.CONNECTIONS, null, null); if (callback != null) { callback.onReturn(args); } } @Override public void onError(CallArguments args) { if (callback != null) { callback.onError(args); } } }); } @Override public Node getNodeDelegate() { Node n = super.getNodeDelegate(); n.getChildren().getNodes(); return n; } ComponentAddress getAddress(PXRComponentProxy child) { String childID = getChildID(child); if (childID == null) { return null; } else { return ComponentAddress.create(getAddress(), childID); } } String getChildID(PXRComponentProxy child) { Set<Map.Entry<String, PXRComponentProxy>> entries = children.entrySet(); for (Map.Entry<String, PXRComponentProxy> entry : entries) { if (entry.getValue() == child) { return entry.getKey(); } } return null; } @Override public Connection[] getConnections() { return connections.toArray(new Connection[connections.size()]); } @Override protected boolean isProxiedProperty(String id) { return super.isProxiedProperty(id) || ContainerInterface.CHILDREN.equals(id) || ContainerInterface.CONNECTIONS.equals(id); } void revalidate(PXRComponentProxy child) { // String id = getChildID(child); // if (id == null) { // return; // } // // ignore = true; // // // remove all connections temporarily // List<Connection> tmpCons = connections; // connections = Collections.emptyList(); // if (!tmpCons.isEmpty()) { // firePropertyChange(PROP_CONNECTIONS, null, null); // } // // // temporarily remove child // Map<String, PXRComponentProxy> tmpChildren = children; // children = new LinkedHashMap<String, PXRComponentProxy>(tmpChildren); // children.remove(id); // firePropertyChange(PROP_CHILDREN, null, null); // // // re-add child // children.clear(); // children = tmpChildren; // firePropertyChange(PROP_CHILDREN, null, null); // // // re-add and validate connections // connections = tmpCons; // List<String> ports = Arrays.asList(child.getInfo().getPorts()); // Iterator<Connection> itr = connections.iterator(); // while (itr.hasNext()) { // Connection con = itr.next(); // if ((con.getChild1().equals(id) && !ports.contains(con.getPort1())) // || (con.getChild2().equals(id) && !ports.contains(con.getPort2()))) { // itr.remove(); // } // } // firePropertyChange(ContainerProxy.PROP_CONNECTIONS, null, null); // // ignore = false; } @Override void checkSyncing() { super.checkSyncing(); if (conAdaptor == null) { initConAdaptor(); } if (syncing) { conAdaptor.setSyncRate(ControlBinding.SyncRate.Low); } else { conAdaptor.setSyncRate(ControlBinding.SyncRate.None); } } private void initConAdaptor() { conAdaptor = new ArgumentPropertyAdaptor.ReadOnly(this, ContainerInterface.CONNECTIONS, true, ControlBinding.SyncRate.None); conAdaptor.addPropertyChangeListener(new ConnectionsListener()); PXRHelper.getDefault().bind(ControlAddress.create(getAddress(), ContainerInterface.CONNECTIONS), conAdaptor); } @Override void dispose() { for (PXRComponentProxy child : children.values()) { child.dispose(); } if (conAdaptor != null) { PXRHelper.getDefault().unbind(conAdaptor); } super.dispose(); } private class ChildrenProperty extends PraxisProperty<String[]> { private ChildrenProperty() { super(String[].class); setName(ContainerInterface.CHILDREN); } @Override public String[] getValue() { return getChildIDs(); } @Override public boolean canRead() { return true; } } private class ConnectionsProperty extends PraxisProperty<Connection[]> { private ConnectionsProperty() { super(Connection[].class); setName(ContainerInterface.CONNECTIONS); } @Override public Connection[] getValue() { return getConnections(); } @Override public boolean canRead() { return true; } } private class ConnectionsListener implements PropertyChangeListener { @Override public void propertyChange(PropertyChangeEvent evt) { try { Set<Connection> updated = externalToConnections((Argument) evt.getNewValue()); if (connections.equals(updated)) { LOG.fine("Connections change reported but we're up to date."); } else { LOG.fine("Connections change reported - updating."); connections.clear(); connections.addAll(updated); firePropertyChange(ContainerInterface.CONNECTIONS, null, null); } } catch (Exception ex) { LOG.log(Level.WARNING, "Invalid Connection list", ex); } } private Set<Connection> externalToConnections(Argument extCons) throws ArgumentFormatException { if (extCons.isEmpty()) { return Collections.emptySet(); } PArray extArr = PArray.coerce(extCons); Set<Connection> cons = new LinkedHashSet<>(extArr.getSize()); for (Argument arg : extArr) { PArray con = PArray.coerce(arg); if (con.getSize() != 4) { throw new ArgumentFormatException("Connection array has invalid number of parts\n" + extCons); } cons.add(new Connection(con.get(0).toString(), con.get(1).toString(), con.get(2).toString(), con.get(3).toString())); } return cons; } } }