diff --git a/src/org/openlcb/Utilities.java b/src/org/openlcb/Utilities.java index 09e9740e..a0937c2e 100644 --- a/src/org/openlcb/Utilities.java +++ b/src/org/openlcb/Utilities.java @@ -9,8 +9,9 @@ *

* NodeID objects are immutable once created. * - * @author Bob Jacobsen Copyright 2009, 2010, 2011 2012 - * @version $Revision$ + * @see org.openlcb.cdi.cmd.Util + * + * @author Bob Jacobsen Copyright 2009, 2010, 2011, 2012, 2023 */ @Immutable @ThreadSafe @@ -218,4 +219,26 @@ static public void HostToNetworkUint48(byte[] arr, int offset, long value) { arr[offset+5] = (byte) ((value) & 0xff); } + /** + * Find the longest starting substring of a List of Strings. + * This is useful for finding e.g. the common prefix of a replication dump + */ + @CheckReturnValue + @NonNull + static public String longestLeadingSubstring(@NonNull java.util.List list) { + if (list.size()==0) return ""; + + String result = list.get(0); + for (int entry = 1; entry < list.size(); entry++) { + for (int index = Math.min(result.length(), list.get(entry).length()); index >= 0; index--) { + if (list.get(entry).startsWith(result.substring(0,index))) { + // found longest match + result = result.substring(0, index); + break; + } + } + } + return result; + } + } diff --git a/src/org/openlcb/Version.java b/src/org/openlcb/Version.java index 9023831d..7a18bf3a 100644 --- a/src/org/openlcb/Version.java +++ b/src/org/openlcb/Version.java @@ -9,9 +9,8 @@ * two places. *

* You have to manually keep this synchronized with the manifest file. - *

+ * * @author Bob Jacobsen Copyright 2011 - 2012 - * @version $Revision: 17977 $ */ public class Version { diff --git a/src/org/openlcb/cdi/CdiRep.java b/src/org/openlcb/cdi/CdiRep.java index cc45775c..5508ca59 100644 --- a/src/org/openlcb/cdi/CdiRep.java +++ b/src/org/openlcb/cdi/CdiRep.java @@ -7,7 +7,6 @@ * object implementing this interface. * * @author Bob Jacobsen Copyright 2011 - * @version $Revision: -1 $ */ public interface CdiRep { diff --git a/src/org/openlcb/cdi/cmd/BackupConfig.java b/src/org/openlcb/cdi/cmd/BackupConfig.java index 8968d96e..c5690195 100644 --- a/src/org/openlcb/cdi/cmd/BackupConfig.java +++ b/src/org/openlcb/cdi/cmd/BackupConfig.java @@ -7,6 +7,7 @@ import java.io.BufferedWriter; import java.io.IOException; +import java.io.Writer; import java.nio.charset.Charset; import java.nio.file.Files; import java.nio.file.Paths; @@ -17,12 +18,12 @@ public class BackupConfig { - static public void writeEntry(BufferedWriter outFile, String key, String value) { + static public void writeEntry(BufferedWriter writer, String key, String value) { try { - outFile.write(Util.escapeString(key)); - outFile.write('='); - outFile.write(Util.escapeString(value)); - outFile.write('\n'); + writer.write(Util.escapeString(key)); + writer.write('='); + writer.write(Util.escapeString(value)); + writer.write('\n'); } catch (IOException e1) { e1.printStackTrace(); System.exit(1); @@ -31,29 +32,42 @@ static public void writeEntry(BufferedWriter outFile, String key, String value) public static void writeConfigToFile(String fileName, ConfigRepresentation repr) throws IOException { - BufferedWriter outFile = null; + + BufferedWriter outFile = Files.newBufferedWriter(Paths.get(fileName), Charset.forName("UTF-8")); + + writeConfigToWriter(outFile, repr); + + outFile.close(); + } - outFile = Files.newBufferedWriter(Paths.get(fileName), Charset.forName("UTF-8")); - final BufferedWriter finalOutFile = outFile; + /** + * @param writer Receives output. Flushed at end, but not closed. + * @param repr Representation containing contents to be written. + */ + public static void writeConfigToWriter(BufferedWriter writer, ConfigRepresentation repr) throws + IOException { + + final BufferedWriter finalWriter = writer; repr.visit(new ConfigRepresentation.Visitor() { @Override public void visitString(ConfigRepresentation.StringEntry e) { - writeEntry(finalOutFile, e.key, e.getValue()); + writeEntry(finalWriter, e.key, e.getValue()); } @Override public void visitInt(ConfigRepresentation.IntegerEntry e) { - writeEntry(finalOutFile, e.key, Long.toString(e.getValue())); + writeEntry(finalWriter, e.key, Long.toString(e.getValue())); } @Override public void visitEvent(ConfigRepresentation.EventEntry e) { - writeEntry(finalOutFile, e.key, Utilities.toHexDotsString(e.getValue + writeEntry(finalWriter, e.key, Utilities.toHexDotsString(e.getValue ().getContents())); } } ); - outFile.close(); + + finalWriter.flush(); } diff --git a/src/org/openlcb/cdi/cmd/RestoreConfig.java b/src/org/openlcb/cdi/cmd/RestoreConfig.java index cc579de3..49cd91b3 100644 --- a/src/org/openlcb/cdi/cmd/RestoreConfig.java +++ b/src/org/openlcb/cdi/cmd/RestoreConfig.java @@ -32,9 +32,13 @@ public static void parseConfigFromFile(@NonNull String filePath, @NonNull Config callback.onError("Failed to open input file: " + e.toString()); return; } + parseConfigFromReader(inFile, callback); + } + + public static void parseConfigFromReader(@NonNull BufferedReader reader, @NonNull ConfigCallback callback) { String line = null; try { - while ((line = inFile.readLine()) != null) { + while ((line = reader.readLine()) != null) { if (line.charAt(0) == '#') continue; int pos = line.indexOf('='); if (pos < 0) { @@ -46,9 +50,9 @@ public static void parseConfigFromFile(@NonNull String filePath, @NonNull Config callback.onConfigEntry(key, value); } - inFile.close(); + reader.close(); } catch (IOException x) { - callback.onError("Error reading input file: " + x.toString()); + callback.onError("Error reading for restore: " + x.toString()); return; } } diff --git a/src/org/openlcb/cdi/cmd/Util.java b/src/org/openlcb/cdi/cmd/Util.java index e79ce016..150d70a3 100644 --- a/src/org/openlcb/cdi/cmd/Util.java +++ b/src/org/openlcb/cdi/cmd/Util.java @@ -11,6 +11,8 @@ /** * Created by bracz on 4/9/16. + * + * @see org.openlcb.Utilities */ public class Util { private final static Logger logger = Logger.getLogger(Util.class.getName()); diff --git a/src/org/openlcb/cdi/jdom/CdiMemConfigReader.java b/src/org/openlcb/cdi/jdom/CdiMemConfigReader.java index 20a9a179..8b5f6ef1 100644 --- a/src/org/openlcb/cdi/jdom/CdiMemConfigReader.java +++ b/src/org/openlcb/cdi/jdom/CdiMemConfigReader.java @@ -15,7 +15,6 @@ * by call back. * * @author Bob Jacobsen Copyright (C) 2012 - * @version $Revision$ */ public class CdiMemConfigReader { private final static Logger logger = getLogger(CdiMemConfigReader.class.getName()); diff --git a/src/org/openlcb/cdi/jdom/JdomCdiReader.java b/src/org/openlcb/cdi/jdom/JdomCdiReader.java index 66a810ab..e2978b7f 100644 --- a/src/org/openlcb/cdi/jdom/JdomCdiReader.java +++ b/src/org/openlcb/cdi/jdom/JdomCdiReader.java @@ -11,7 +11,6 @@ * JDOM-based OpenLCB loader * * @author Bob Jacobsen Copyright 2011 - * @version $Revision: -1 $ */ public class JdomCdiReader { diff --git a/src/org/openlcb/cdi/jdom/JdomCdiRep.java b/src/org/openlcb/cdi/jdom/JdomCdiRep.java index 2a888faf..47ec80cd 100644 --- a/src/org/openlcb/cdi/jdom/JdomCdiRep.java +++ b/src/org/openlcb/cdi/jdom/JdomCdiRep.java @@ -9,7 +9,6 @@ * JDOM for reading the underlying XML. * * @author Bob Jacobsen Copyright 2011 - * @version $Revision: -1 $ */ public class JdomCdiRep implements CdiRep { diff --git a/src/org/openlcb/cdi/swing/CdiPanel.java b/src/org/openlcb/cdi/swing/CdiPanel.java index 3b9f18fd..64c1e29f 100644 --- a/src/org/openlcb/cdi/swing/CdiPanel.java +++ b/src/org/openlcb/cdi/swing/CdiPanel.java @@ -3,6 +3,7 @@ import edu.umd.cs.findbugs.annotations.NonNull; import org.openlcb.EventID; import org.openlcb.NodeID; +import org.openlcb.Utilities; import org.openlcb.cdi.CdiRep; import org.openlcb.cdi.cmd.BackupConfig; import org.openlcb.cdi.cmd.RestoreConfig; @@ -30,14 +31,21 @@ import java.awt.event.ActionListener; import java.awt.event.ComponentAdapter; import java.awt.event.ComponentEvent; +import java.awt.event.InputEvent; import java.awt.event.KeyEvent; import java.awt.event.KeyListener; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.io.File; +import java.io.BufferedReader; import java.io.IOException; +import java.io.OutputStreamWriter; +import java.io.Reader; +import java.io.StringReader; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; @@ -63,6 +71,7 @@ import javax.swing.JFormattedTextField; import javax.swing.JFrame; import javax.swing.JLabel; +import javax.swing.JMenuItem; import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JPopupMenu; @@ -114,7 +123,7 @@ public class CdiPanel extends JPanel { */ static JFileChooser fci = new JFileChooser(); { - fci.setSelectedFile(new File(".txt")); + fci.setSelectedFile(new File(".txt")); } private ConfigRepresentation rep; @@ -314,6 +323,8 @@ public java.awt.Dimension getMaximumSize() { } /** + * Load from a CDI representation with a default {@link GuiItemFactory}. + * * @param rep Representation of the config to be loaded */ public void initComponents(ConfigRepresentation rep) { @@ -385,7 +396,9 @@ private void checkForSave() { } /** - * Triggers a warning at the close of this dialog that the panel file needs to be saved in JMRI. + * Triggers a warning at the close of this dialog that a Sensor has been made. + * This triggers a message the panel file needs to be saved in JMRI. + * * @param uName unused. */ public void madeSensor(String uName) { @@ -393,7 +406,9 @@ public void madeSensor(String uName) { } /** - * Triggers a warning at the close of this dialog that the panel file needs to be saved in JMRI. + * Triggers a warning at the close of this dialog that a Turnout has been made. + * This triggers a message the panel file needs to be saved in JMRI. + * * @param uName unused. */ public void madeTurnout(String uName) { @@ -547,6 +562,11 @@ public void run() { }, 500); } + /** + * Remove the listener to the CDI representation that gets a notification that + * loading of the CDI is complete. + * Paired with {@link #addLoadingListener}. + */ private void removeLoadingListener() { synchronized (rep) { if (loadingListener != null) rep.removePropertyChangeListener(loadingListener); @@ -554,6 +574,11 @@ private void removeLoadingListener() { } } + /** + * Add a listener to the CDI representation to get a notification that + * loading of the CDI is complete. + * Paired with {@link #removeLoadingListener}. + */ private void addLoadingListener() { synchronized(rep) { if (loadingListener != null) return; @@ -576,6 +601,9 @@ public void propertyChange(PropertyChangeEvent event) { } } + /** + * CDI loading is done, update the UI and listeners + */ private void hideLoadingProgress() { if (loadingPanel == null) return; removeLoadingListener(); @@ -591,6 +619,10 @@ private void displayLoadingProgress() { loadingPanel.setVisible(true); } + /** + * Create a separate thread to render the CDI to the UI. + * When done, invokes {@link displayComplete} on the Swing/AWT thread + */ private void displayCdi() { displayLoadingProgress(); loadingText.setText("Creating display..."); @@ -608,6 +640,10 @@ public void run() { }, "openlcb-cdi-render").start(); } + /** + * Rendering thread is complete, show the CDI display in the UI. + * Must be invoked on the Swing/AWT thread + */ private void displayComplete() { synchronized (startupTasks) { renderingInProgress = false; @@ -713,6 +749,10 @@ private void setSaveClean() { }); } + /** + * Visitor class to find a full name for changed elements + * that have not been saved. + */ private class GetEntryNameVisitor extends ConfigRepresentation.Visitor { CdiRep.Item item; int segNum = 1; @@ -966,13 +1006,16 @@ public void visitGroupRep(final ConfigRepresentation.GroupRep e) { final String name = (item.getRepName() != null ? (item.getRepName()) : "Group") + " " + (e.index); //currentPane.setBorder(BorderFactory.createTitledBorder(name)); + + // set the name of this pane, which names the tab currentPane.setName(name); - + // Finds a string field that could be used as a caption. FindDescriptorVisitor vv = new FindDescriptorVisitor(); vv.visitContainer(e); if (vv.foundEntry != null) { + // here a unique descriptor has been found final JPanel tabPanel = currentPane; final ConfigRepresentation.StringEntry source = vv.foundEntry; final JTabbedPane parentTabs = currentTabbedPane; @@ -985,15 +1028,20 @@ public void propertyChange(PropertyChangeEvent event) { String downstreamName = ""; if (source.lastVisibleValue != null && !source.lastVisibleValue .isEmpty()) { + // we have a new name from a description, use it String newName = (name + " (" + source.lastVisibleValue + ")"); tabPanel.setName(newName); if (parentTabs.getTabCount() >= e.index) { - parentTabs.setTitleAt(e.index - 1, newName); + JComponent tabLabel = getTabLabel(parentTabs, e.index-1, newName, e); + parentTabs.setTabComponentAt(e.index - 1, tabLabel); } downstreamName = source.lastVisibleValue; } else { + // use the name created above from the repName if (parentTabs.getTabCount() >= e.index) { - parentTabs.setTitleAt(e.index - 1, name); + // update the name listed in the tab + JComponent tabLabel = getTabLabel(parentTabs, e.index-1, name, e); + parentTabs.setTabComponentAt(e.index - 1, tabLabel); } } new UpdateGroupNameVisitor(e.key, downstreamName).visitContainer(e); @@ -1010,10 +1058,209 @@ public void propertyChange(PropertyChangeEvent event) { factory.handleGroupPaneEnd(currentPane); currentPane.add(Box.createVerticalGlue()); + // add this new pane to the combined tab pane currentTabbedPane.add(currentPane); tabsByKey.put(e.key, currentTabbedPane); + + // set the tab to a label with copy/pasteValue + int index = currentTabbedPane.indexOfComponent(currentPane); + JComponent tabLabel = getTabLabel(currentTabbedPane, index, name, e); + currentTabbedPane.setTabComponentAt(index, tabLabel); + + } + + /** + * Generate the tab label for a group item. + * Including any needed navigation, tooltip, popup menu, etc. + * @param parentTabbedPane The tabbed pane which it to be navigated + * @param name The name to display + * @param rep the configuration data representation + * @return Tab label component + */ + protected JComponent getTabLabel(JTabbedPane parentTabbedPane, int index, String name, ConfigRepresentation.GroupRep rep) { + JLabel tabLabel = new JLabel(name); + + tabLabel.addMouseListener(new MouseAdapter() { + + // for click logic: https://stackoverflow.com/questions/46840814/right-click-jpopupmenu-on-jtabbedpane + @Override + public void mouseClicked(MouseEvent event) { + + // isPopupTrigger doesn't work on all platforms, all versions? + boolean isPopup = (event.getModifiersEx() & InputEvent.CTRL_DOWN_MASK) != 0 || event.isPopupTrigger(); + if ( !isPopup ) { + // user selected a tab + parentTabbedPane.setSelectedIndex(index); + } else { + // user requested the popup menu + // move to tab, in case non-active one clicked + parentTabbedPane.setSelectedIndex(index); + + JPopupMenu popupMenu = new JPopupMenu(); + JMenuItem menuItem = new JMenuItem("Copy"); + popupMenu.add(menuItem); + menuItem.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + performGroupReplCopy(index, rep); + } + }); + menuItem = new JMenuItem("Paste"); + popupMenu.add(menuItem); + menuItem.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + performGroupReplPaste(index, rep); + } + }); + + popupMenu.show(tabLabel, event.getX(), event.getY()); + } + } + + }); + + return tabLabel; } + + /** + * Perform a "copy" operation on a selected group tab + */ + protected void performGroupReplCopy(int index, ConfigRepresentation.GroupRep rep) { + String result = groupReplToString(rep); + + // store to clipboard + StringSelection selection = new StringSelection(result); + Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard(); + clipboard.setContents(selection, selection); + + } + + /** + * Copy an entire group replication to a String + */ + protected String groupReplToString(ConfigRepresentation.GroupRep rep) { + StringBuilder result = new StringBuilder(); + + ConfigRepresentation.Visitor visitor = new ConfigRepresentation.Visitor() { + + @Override + public void visitString(ConfigRepresentation.StringEntry e) { + writeEntry(e.key, e.getValue()); + } + + @Override + public void visitInt(ConfigRepresentation.IntegerEntry e) { + writeEntry(e.key, Long.toString(e.getValue())); + } + + @Override + public void visitEvent(ConfigRepresentation.EventEntry e) { + writeEntry(e.key, org.openlcb.Utilities.toHexDotsString(e.getValue + ().getContents())); + } + + protected void writeEntry(String key, String entry) { + result.append(key); + result.append("="); + // result.append(entry); // use the value currently in CD + result.append(entriesByKey.get(key).getCurrentValue()); // use value currently in UI + result.append("\n"); + } + }; + + visitor.visitGroupRep(rep); + return new String(result); + } + + + /** + * Perform a "paste" operation into a selected group tab + */ + protected void performGroupReplPaste(int index, ConfigRepresentation.GroupRep rep) { + // retrieve from clipboard + Clipboard c = Toolkit.getDefaultToolkit().getSystemClipboard(); + Transferable t = c.getContents( null ); + String newContentString = ""; + if ( t.isDataFlavorSupported(DataFlavor.stringFlavor) ) { + try { + newContentString = (String)t.getTransferData( DataFlavor.stringFlavor ); + } catch (UnsupportedFlavorException | IOException e) { + // this can never happen as we checked before + return; + } + } // this should always have succeeded, but if it doesn't the match below will fail + + // store the values that are going to be replaced + String previousContentString = groupReplToString(rep); + + String[] newContentLines = newContentString.split("\n"); + String[] previousContentLines = previousContentString.split("\n"); + + // compare keys to see if the variables match up + // this version just checks the line count, more could be added here + if (previousContentLines.length != newContentLines.length) { + logger.log(Level.WARNING, "Cannot paste into a mis-matching entry type"); + // provide a system alert to notify user + Toolkit.getDefaultToolkit().beep(); + // end of attempt to paste + return; + } + + // change the repl number in the newContentLines to this index + // First, find the prefix we're going to change + List list = java.util.Arrays.asList(newContentLines); + String prefix = Utilities.longestLeadingSubstring(list); + // That prefix should end with "(1)." + + // replace and rebuild the lines + StringBuilder processedContentSB = new StringBuilder(); + for (int i = 0; i 0) { + String mapvalue = map.getEntry(value); + if (mapvalue != null) value = mapvalue; + } + pp.updateDisplayText(value); + pp.updateColor(); + } + + @Override + public void onError(String error) { + if (!hasError) { + logger.severe("Error(s) encountered during loading configuration backup."); + hasError = true; + } + logger.severe(error); + } + }); + _unsavedRestore = true; + } + @Override public void visitString(ConfigRepresentation.StringEntry e) { currentLeaf = new StringPane(e); @@ -1624,9 +1871,20 @@ boolean isDirty() { protected abstract void updateDisplayText(@NonNull String value); // returns the currently displayed value ("" if none). - protected abstract @NonNull - String getDisplayText(); + protected abstract String getDisplayText(); + + /** + * Get the current value as a String. + * Usually, this is the display text, but in the case of a + * {@link IntPane} with a map it's the integer value of the + * current selection. + * @return Current value for storage as a String. + */ + @NonNull + protected String getCurrentValue() { + return getDisplayText(); + } } private class EventIdPane extends EntryPane { @@ -1949,6 +2207,26 @@ protected String getDisplayText() { : (String) box.getSelectedItem(); return s == null ? "" : s; } + + /** + * Get the current value as a numerical String. + * Usually, this is the display text, but in the case of a + * a map it's the integer value of the + * current selection. + * @return Current value for storage as a String. + */ + @NonNull + protected String getCurrentValue() { + String s; + if (box==null) { + s = (String) textField.getText(); + } else { + String entry = (String) box.getSelectedItem(); + s = map.getKey(entry); + } + return s == null ? "" : s; + } + } private class StringPane extends EntryPane { @@ -2007,33 +2285,98 @@ protected String getDisplayText() { * Default behavior is to do nothing */ public static class GuiItemFactory { + /** + * Allows replacement of the Read button in the interface + * @param button proposed Read button for the interface + * @return the actual Read button for the interface + */ public JButton handleReadButton(JButton button) { return button; } + + /** + * Allows replacement of the Write button in the interface + * @param button proposed Write button for the interface + * @return the actual Write button for the interface + */ public JButton handleWriteButton(JButton button) { return button; } + + /** + * Allows replacement of the More... button on an event ID in the interface + * @param button proposed More... button for the interface + * @return the actual More... button for the interface + */ public JButton handleEventidMoreButton(JButton button) { return button; - } + } + + /** + * Allows replacement of the Produce button in the interface + * @param button proposed Produce button for the interface + * @return the actual Produce button for the interface + */ public JButton handleProduceButton(JButton button) { return button; } + + /** + * Process pressing the Make Sensor button + * @param ev EventId to use + * @param mdesc Description to Use + */ public void makeSensor(String ev, String mdesc) { return; } + + /** + * A new CDI group is being constructed. This makes the place for that + * available so that it can be replaced if need be. + * Paired with {@link handleGroupPaneEnd}. + * + * @param pane The GUI panel that will be populated with the + * representation of the group. In {@link CdiPanel}, + * this is a {@link GroupPane}. + */ public void handleGroupPaneStart(JPanel pane) { return; } + + /** + * Called after a group have been constructedd. + * Paired with {@link handleGroupPaneStart}. + * + * @param pane The GUI panel that has been populated with the + * representation of the group. In {@link CdiPanel}, + * this is a {@link GroupPane}. + */ public void handleGroupPaneEnd(JPanel pane) { return; } + + /** + * Allow separate processing of an Event ID entry field + * @param field The proposed EventID entry field + * @return The EventID entry field to use + */ public JFormattedTextField handleEventIdTextField(JFormattedTextField field) { return field; } + + /** Allow updates of the input field for a text value. + * @param value The proposed input field. + * @return The input field to use. + */ public JTextField handleStringValue(JTextField value) { return value; } + + /** Allow updates of the input field for a text value. + * In {@link CdiPanel}, this is an input field with a length of more than 64. + * @param value The proposed input field. + * @return The input field to use. + */ public JTextArea handleEditorValue(JTextArea value) { return value; } diff --git a/src/org/openlcb/implementations/DatagramMeteringBuffer.java b/src/org/openlcb/implementations/DatagramMeteringBuffer.java index 5e991817..f01220c4 100644 --- a/src/org/openlcb/implementations/DatagramMeteringBuffer.java +++ b/src/org/openlcb/implementations/DatagramMeteringBuffer.java @@ -24,10 +24,8 @@ *

  • Does not parallelize Datagrams to separate nodes *
  • Needs to timeout and resume operation if no reply received * - *

    * * @author Bob Jacobsen Copyright 2012 - * @version $Revision$ */ public class DatagramMeteringBuffer extends MessageDecoder { diff --git a/src/org/openlcb/implementations/DatagramReceiver.java b/src/org/openlcb/implementations/DatagramReceiver.java index 382edad8..c4528e45 100644 --- a/src/org/openlcb/implementations/DatagramReceiver.java +++ b/src/org/openlcb/implementations/DatagramReceiver.java @@ -4,10 +4,8 @@ /** * Example of receiving a OpenLCB datagram. - *

    * * @author Bob Jacobsen Copyright 2009 - * @version $Revision$ */ public class DatagramReceiver extends MessageDecoder { public DatagramReceiver(NodeID here, NodeID far, Connection c) { diff --git a/src/org/openlcb/implementations/DatagramTransmitter.java b/src/org/openlcb/implementations/DatagramTransmitter.java index 5c1b3ea7..11e6fb2c 100644 --- a/src/org/openlcb/implementations/DatagramTransmitter.java +++ b/src/org/openlcb/implementations/DatagramTransmitter.java @@ -4,10 +4,8 @@ /** * Example of sending a OpenLCB datagram. - *

    * * @author Bob Jacobsen Copyright 2009 - * @version $Revision$ */ public class DatagramTransmitter extends MessageDecoder { diff --git a/src/org/openlcb/implementations/StreamReceiver.java b/src/org/openlcb/implementations/StreamReceiver.java index e428195a..66491b93 100644 --- a/src/org/openlcb/implementations/StreamReceiver.java +++ b/src/org/openlcb/implementations/StreamReceiver.java @@ -4,10 +4,8 @@ /** * Example of receiving a OpenLCB stream. - *

    * * @author Bob Jacobsen Copyright 2009 - * @version $Revision$ */ public class StreamReceiver extends MessageDecoder { public StreamReceiver(NodeID here, NodeID far, Connection c) { diff --git a/src/org/openlcb/swing/networktree/NodeTreeRep.java b/src/org/openlcb/swing/networktree/NodeTreeRep.java index 22ed0575..ba991cdd 100644 --- a/src/org/openlcb/swing/networktree/NodeTreeRep.java +++ b/src/org/openlcb/swing/networktree/NodeTreeRep.java @@ -15,10 +15,8 @@ /** * Represent a single node for the tree display - *

    * * @author Bob Jacobsen Copyright (C) 2010, 2012 - * @version $Revision$ */ public class NodeTreeRep extends DefaultMutableTreeNode { /** Comment for serialVersionUID. */ diff --git a/src/org/openlcb/swing/networktree/TreePane.java b/src/org/openlcb/swing/networktree/TreePane.java index 3aadcad8..75acdbe0 100644 --- a/src/org/openlcb/swing/networktree/TreePane.java +++ b/src/org/openlcb/swing/networktree/TreePane.java @@ -45,10 +45,8 @@ /** * Pane for monitoring an entire OpenLCB network as a logical tree - *

    * * @author Bob Jacobsen Copyright (C) 2010, 2012 - * @version $Revision$ */ public class TreePane extends JPanel { /** Comment for serialVersionUID. */ diff --git a/test/org/openlcb/UtilitiesTest.java b/test/org/openlcb/UtilitiesTest.java index 5d899fd2..752fdea7 100644 --- a/test/org/openlcb/UtilitiesTest.java +++ b/test/org/openlcb/UtilitiesTest.java @@ -1,5 +1,7 @@ package org.openlcb; +import java.util.ArrayList; + import org.junit.Assert; import org.junit.Test; @@ -114,4 +116,17 @@ boolean compareArrays(byte[] a, byte[]b) { } return false; } + + @Test + public void testLongestLeadingSubstring() { + ArrayList list = new ArrayList<>(); + list.add("Port I/O.Line(1).Line Description"); + list.add("Port I/O.Line(1).Output Function"); + list.add("Port I/O.Line(1).Receiving the configured Command (C)"); + + String result = Utilities.longestLeadingSubstring(list); + + Assert.assertTrue(result.equals("Port I/O.Line(1).")); + } + }