/** * This file is protected by Copyright. * Please refer to the COPYRIGHT file distributed with this source distribution. * * This file is part of REDHAWK IDE. * * All rights reserved. This program and the accompanying materials are made available under * the terms of the Eclipse Public License v1.0 which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html. */ package gov.redhawk.ui.port.nxmblocks; import gov.redhawk.sca.util.Debug; import gov.redhawk.ui.port.nxmplot.AbstractNxmPlotWidget; import gov.redhawk.ui.port.nxmplot.INxmBlock; import gov.redhawk.ui.port.nxmplot.PlotActivator; import gov.redhawk.ui.port.nxmplot.preferences.Preference; import java.util.AbstractMap.SimpleImmutableEntry; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import nxm.sys.inc.Commandable; import nxm.sys.lib.Command; import org.eclipse.core.runtime.CoreException; import org.eclipse.jdt.annotation.NonNull; import org.eclipse.jdt.annotation.Nullable; import org.eclipse.jface.preference.IPreferencePage; import org.eclipse.jface.preference.IPreferenceStore; import org.eclipse.jface.util.IPropertyChangeListener; import org.eclipse.jface.util.PropertyChangeEvent; import BULKIO.StreamSRI; /** * This class provides a skeletal implementation of the {@link INxmBlock} interface, * to minimize the effort required to implement this interface. * <br> * C is the NeXtMidas Command that should have been launch. * @noreference This class is provisional/beta and is subject to API changes * @since 4.4 */ public abstract class AbstractNxmBlock< C extends Command > implements INxmBlock, IPropertyChangeListener { /** state flag: for block is disposed. */ protected static final int DISPOSED = 1 << 0; /** state flag: for block is stopped. */ protected static final int STOPPED = 1 << 1; private static final Debug TRACE_LOG = new Debug(PlotActivator.PLUGIN_ID, AbstractNxmBlock.class.getSimpleName()); private static final StreamSRI[] EMPTY_STREAMSRI_ARRAY = new StreamSRI[0]; private final AbstractNxmPlotWidget plotWidget; private final Class< ? extends C> desiredLaunchClass; private int defaultInputIndex = 0; /** state mask of this block */ private int state = 0; // FYI: ConcurrentHashMap does not allow null key or value /** key=streamID value=(cmdline,Command). */ private final ConcurrentHashMap<String, SimpleImmutableEntry<String, C>> streamIDToCmdMap = new ConcurrentHashMap<String, SimpleImmutableEntry<String, C>>(); private final List<StreamSRI> launchedStreamsList = Collections.synchronizedList(new ArrayList<StreamSRI>()); private final ConcurrentHashMap<String, String> outIndexStreamIDToOutNameMap = new ConcurrentHashMap<String, String>(); /** key=input index; value=source block & it's output index. */ private final ConcurrentHashMap<Integer, BlockIndexPair> inputMap = new ConcurrentHashMap<Integer, BlockIndexPair>(); /** key=output index; value=list of (destination block & it's input index). */ private final ConcurrentHashMap<Integer, List<BlockIndexPair>> outputMap = new ConcurrentHashMap<Integer, List<BlockIndexPair>>(); private final IPreferenceStore preferenceStore; protected AbstractNxmBlock(@NonNull Class<C> desiredLaunchCommandClass, @NonNull AbstractNxmPlotWidget plotWidget, IPreferenceStore store) { this.plotWidget = plotWidget; this.desiredLaunchClass = desiredLaunchCommandClass; if (store == null) { store = Preference.createRuntimeStore(); } this.preferenceStore = store; this.preferenceStore.addPropertyChangeListener(this); } @NonNull @Override public AbstractNxmPlotWidget getContext() { return plotWidget; } @Override public IPreferenceStore getPreferences() { return this.preferenceStore; } @Override public IPreferencePage createPreferencePage() { return null; } @Override public void addInput(int myInIndex, INxmBlock srcBlock, int srcBlockOutIndex) { final int maxInIndex = getMaxInputs(); if (maxInIndex == 0) { throw new UnsupportedOperationException(getClass().getName() + " does not have any inputs."); } else if (myInIndex >= maxInIndex) { throw new IllegalArgumentException(getClass().getName() + " has max input of " + maxInIndex + ", given input index of " + myInIndex); } BlockIndexPair srcBlockPair = new BlockIndexPair(srcBlock, srcBlockOutIndex); inputMap.put(myInIndex, srcBlockPair); srcBlock.internalAddOutputMapping(srcBlockOutIndex, this, myInIndex); // TODO: throw IllegalArgumentException for invalid inIndex, and srcBlockOutIndex)? } @Override public void addInput(INxmBlock srcBlock) { addInput(getDefaultInputIndex(), srcBlock, 0); } @Override public void removeInput(int myInIndex) { final int maxInIndex = getMaxInputs(); if (maxInIndex == 0) { throw new UnsupportedOperationException(getClass().getName() + " does not have any inputs."); } else if (myInIndex >= maxInIndex) { throw new IllegalArgumentException(getClass().getName() + " has max input of " + maxInIndex + ", given input index of " + myInIndex); } BlockIndexPair srcBlockPair = inputMap.remove(myInIndex); if (srcBlockPair != null) { INxmBlock block = srcBlockPair.getBlock(); block.internalRemoveOutputMapping(srcBlockPair.getIndex(), this, myInIndex); } } @Override public INxmBlock getInputBlock(int myInIndex) { final int maxInIndex = getMaxInputs(); if (maxInIndex == 0) { throw new UnsupportedOperationException(getClass().getName() + " does not have any inputs."); } else if (myInIndex >= maxInIndex) { throw new IllegalArgumentException(getClass().getName() + " has max input of " + maxInIndex + ", given input index of " + myInIndex); } BlockIndexPair srcBlockPair = inputMap.get(myInIndex); if (srcBlockPair != null) { INxmBlock block = srcBlockPair.getBlock(); return block; } return null; } protected Set<Entry<Integer, BlockIndexPair>> getInputMappings() { return inputMap.entrySet(); } protected Collection<BlockIndexPair> getInputBlocks() { return inputMap.values(); } @Override public void internalAddOutputMapping(int outIndex, INxmBlock destBlock, int destBlockInIndex) { final int maxOutIndex = getMaxOutputs(); if (maxOutIndex == 0) { throw new UnsupportedOperationException(getClass().getName() + " does not have any outputs."); } else if (outIndex >= maxOutIndex) { throw new IllegalArgumentException(getClass().getName() + " has max ouput of " + maxOutIndex + ", given output index of " + outIndex); } BlockIndexPair destBlockPair = new BlockIndexPair(destBlock, destBlockInIndex); List<BlockIndexPair> destBlockList = outputMap.get(outIndex); if (destBlockList == null) { synchronized (outputMap) { // lock outpuMap, since do not have output block list at this output index destBlockList = outputMap.get(outIndex); // double check, if someone else added a new list if (destBlockList == null) { destBlockList = Collections.synchronizedList(new ArrayList<BlockIndexPair>()); outputMap.put(outIndex, destBlockList); } } } destBlockList.add(destBlockPair); } @Override public void internalRemoveOutputMapping(int outIndex, INxmBlock destBlock, int destBlockInIndex) { final int maxOutIndex = getMaxOutputs(); if (maxOutIndex == 0) { throw new UnsupportedOperationException(getClass().getName() + " does not have any outputs."); } else if (outIndex >= maxOutIndex) { throw new IllegalArgumentException(getClass().getName() + " has max ouput of " + maxOutIndex + ", given output index of " + outIndex); } List<BlockIndexPair> destBlockList = outputMap.get(outIndex); if (destBlockList != null) { BlockIndexPair destBlockPair = new BlockIndexPair(destBlock, destBlockInIndex); destBlockList.remove(destBlockPair); } } /** Uses subclass's {@link #formCmdLine()} to get NeXtMidas command line to launch/execute * (in background thread) and start PIPE data flow. * NOTE: subclasses should generally NOT override this implementation. */ @SuppressWarnings("unchecked") // this is checked via Class.isAssignableFrom(..) before casting @Override public void launch(String streamID, StreamSRI sri) { checkLaunchParams(streamID, sri); final AbstractNxmPlotWidget currentPlotContext = getContext(); // subclasses specifies the NeXtMidas command line to execute String cmdLine = formCmdLine(currentPlotContext, streamID); if (AbstractNxmBlock.TRACE_LOG.enabled) { AbstractNxmBlock.TRACE_LOG.message("launch({0}) cmdline: {1} [{2}]", streamID, cmdLine, toString()); } if (cmdLine != null && !cmdLine.trim().isEmpty()) { final Command cmd = currentPlotContext.runGlobalCommand(cmdLine + " /BG"); if (cmd == null) { throw new IllegalStateException("Expected to launch NeXtMidas command, but got null"); } else { if (desiredLaunchClass.isAssignableFrom(cmd.getClass())) { C nxmCommand = (C) cmd; SimpleImmutableEntry<String, C> cmd4Stream = new SimpleImmutableEntry<String, C>(cmdLine, nxmCommand); streamIDToCmdMap.put(streamID, cmd4Stream); launchedStreamsList.add(sri); if (AbstractNxmBlock.TRACE_LOG.enabled) { AbstractNxmBlock.TRACE_LOG.message("launched({0}) Command: {1} [{2}]", streamID, nxmCommand, toString()); } } else { throw new IllegalStateException("Expected to launch " + desiredLaunchClass + " command, but found: " + cmd.getClass()); } } cmd.setMessageHandler(currentPlotContext.getPlotMessageHandler()); currentPlotContext.runGlobalCommand("PIPE RUN"); // start NeXtMidas pipe data flow (if it has not already started) if (AbstractNxmBlock.TRACE_LOG.enabled) { currentPlotContext.runGlobalCommand("STATUS/VERBOSE " + getOutputName(0, streamID)); } } // launch hooked output blocks to consume this streamID for (List<BlockIndexPair> outBlocks : outputMap.values()) { for (BlockIndexPair outBlockIndexPair : outBlocks) { outBlockIndexPair.getBlock().launch(streamID, sri); } } } public void update(String streamID, StreamSRI sri) { for (List<BlockIndexPair> outBlocks : outputMap.values()) { for (BlockIndexPair outBlockIndexPair : outBlocks) { outBlockIndexPair.getBlock().update(streamID, sri); } } } @Override public void shutdown(String streamID) { SimpleImmutableEntry<String, C> cmd4Stream = streamIDToCmdMap.remove(streamID); if (cmd4Stream != null) { C nxmCommand = cmd4Stream.getValue(); if (nxmCommand != null) { nxmCommand.setState("Finish"); // tell command to wrap-up and exit } } // shutdown hooked/follow-on output blocks for this streamID for (List<BlockIndexPair> outBlocks : outputMap.values()) { for (BlockIndexPair outBlockIndexPair : outBlocks) { outBlockIndexPair.getBlock().shutdown(streamID); } } } @Override public StreamSRI[] getLaunchedStreams() { StreamSRI[] retval = launchedStreamsList.toArray(AbstractNxmBlock.EMPTY_STREAMSRI_ARRAY); return retval; } @Override public int getMaxOutputs() { return 1; } @Override public int getNumberInputs() { return inputMap.size(); } @Override public int getNumberOutputs() { return outputMap.size(); } @Override public String getOutputName(int outIndex, String streamID) { String outputName = outIndexStreamIDToOutNameMap.get(outIndex + streamID); return outputName; } /** Adds outIndex/streamID to outputName mapping. returns previous value (if any). * Subclass MUST call this (most likely in it's {@link #formCmdLine(AbstractNxmPlotWidget, String)}) * to setup output name mapping for it's outputs. */ protected String putOutputNameMapping(int outIndex, String streamID, String outputName) { String prevOutputName = outIndexStreamIDToOutNameMap.put(outIndex + streamID, outputName); return prevOutputName; } /** Removes outIndex/streamID to outputName mapping. returns current value (if any). * Subclass SHOULD call this in it's {@link #shutdown(String)} to clean up. */ protected String removeOutputNameMapping(int outIndex, String streamID) { String outputName = outIndexStreamIDToOutNameMap.remove(outIndex + streamID); return outputName; } @Override public void start() throws CoreException { // nothing to startup by default } @Override public void stop() { if (isStopped()) { return; // It is valid to attempt to stop a block more than once, so just return } state |= STOPPED; } @Override public boolean isStopped() { return (state & STOPPED) != 0; } @Override public boolean isDisposed() { return (state & DISPOSED) != 0; } @Override public void dispose() { AbstractNxmBlock.TRACE_LOG.enteringMethod(isDisposed()); if (isDisposed()) { return; // It is valid to attempt to dispose a block more than once, so just return } state |= DISPOSED; stop(); // shutdown all launched Commands // for (SimpleImmutableEntry<String, C> entry : streamIDToCmdMap.values()) { Iterator<Entry<String, SimpleImmutableEntry<String, C>>> iter = streamIDToCmdMap.entrySet().iterator(); while (iter.hasNext()) { Entry<String, SimpleImmutableEntry<String, C>> entry = iter.next(); C nxmCommand = entry.getValue().getValue(); nxmCommand.setState(Commandable.FINISH); // tell Command it should FINISH/EXIT iter.remove(); } this.preferenceStore.removePropertyChangeListener(this); } public List<C> getNxmCommands() { List<C> retVal = new ArrayList<C>(); for (SimpleImmutableEntry<String, C> entry : streamIDToCmdMap.values()) { retVal.add(entry.getValue()); } return retVal; } /** * @return the NeXtMidas Command for the specified streamID (null if none) */ @Nullable public C getNxmCommand(String streamID) { C nxmCommand; SimpleImmutableEntry<String, C> entry = streamIDToCmdMap.get(streamID); if (entry != null) { nxmCommand = entry.getValue(); } else { nxmCommand = null; } return nxmCommand; } @NonNull public List<String> getStreamIDs() { return Collections.unmodifiableList(new ArrayList<String>(streamIDToCmdMap.keySet())); } protected int getDefaultInputIndex() { return defaultInputIndex; } /** only sub-classes can set it's default input index. */ protected void setDefaultInputIndex(int defaultInputIndex) { this.defaultInputIndex = defaultInputIndex; } protected BlockIndexPair getInputBlockInfo(int inIndex) { BlockIndexPair srcBlockPair = inputMap.get(inIndex); return srcBlockPair; } protected boolean hasOutputBlockAt(int outIndex) { List<BlockIndexPair> destBlockList = outputMap.get(outIndex); if (destBlockList != null) { return !destBlockList.isEmpty(); } return false; } protected void checkLaunchParams(@NonNull String streamID, @Nullable StreamSRI sri) { if (getContext() == null) { throw new IllegalStateException("A context (AbstractNxmPlotWidget) must be set before launch() can be called."); } if (sri != null && !streamID.equals(sri.streamID)) { // sri specified, check that sri.streamID matches streamID throw new IllegalArgumentException("streamID [" + streamID + "] MUST match sri.streamID [" + sri.streamID + "] when sri is specified."); } } protected Map<String, SimpleImmutableEntry<String, C>> getStreamIdToCommandMap() { return Collections.unmodifiableMap(streamIDToCmdMap); } // ========================================================================= // METHODS that subclasses should implement // ========================================================================= /** * @param plotWidget * @param streamID * @return NeXtMidas command line to run/execute (null to NOT run any command) */ @Nullable protected abstract String formCmdLine(@NonNull AbstractNxmPlotWidget plotWidget, @NonNull String streamID); @Override public void propertyChange(PropertyChangeEvent event) { // TODO Auto-generated method stub } // ========================================================================= // Begin Inner classes // ========================================================================= protected static class BlockIndexPair { private final INxmBlock block; private final int index; public BlockIndexPair(INxmBlock block, int index) { super(); this.block = block; this.index = index; } public INxmBlock getBlock() { return block; } public int getIndex() { return index; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((block == null) ? 0 : block.hashCode()); // SUPPRESS CHECKSTYLE AvoidInline result = prime * result + index; return result; } @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (obj == null) { return false; } if (getClass() != obj.getClass()) { return false; } BlockIndexPair other = (BlockIndexPair) obj; if (block == null) { if (other.block != null) { return false; } } else if (!block.equals(other.block)) { return false; } if (index != other.index) { return false; } return true; } } // end class BlockIndexPair }