Merge branch 'bug/JAL-2992panelHeight' into releases/Release_2_10_4_Branch
authorJim Procter <jprocter@issues.jalview.org>
Fri, 25 May 2018 12:58:01 +0000 (13:58 +0100)
committerJim Procter <jprocter@issues.jalview.org>
Fri, 25 May 2018 12:58:01 +0000 (13:58 +0100)
THIRDPARTYLIBS
resources/lang/Messages.properties
resources/lang/Messages_es.properties
src/ext/edu/ucsf/rbvi/strucviz2/StructureManager.java
src/jalview/gui/Desktop.java
src/jalview/gui/SeqCanvas.java
src/jalview/jbgui/GPreferences.java
src/org/stackoverflowusers/file/WindowsShortcut.java [new file with mode: 0644]

index e2baa85..d0d9125 100644 (file)
@@ -9,6 +9,7 @@ ext.edu.ucsf.rbvi.strucviz2 includes sources originally developed by Scooter Mor
  
  jalview.ext.android includes code taken from the Android Open Source Project (https://android.googlesource.com/platform/frameworks/base/+/master/core/java/android/util).
  The Apache 2.0 Licence (http://www.apache.org/licenses/LICENSE-2.0) is acknowledged in the source code.
+ org.stackoverflowusers.file.WindowsShortcuts was downloaded from http://github.com/codebling/WindowsShortcuts via https://stackoverflow.com/questions/309495/windows-shortcut-lnk-parser-in-java 
 
 Licensing information for each library is given below:
 
index a80ac17..4d87b80 100644 (file)
@@ -267,6 +267,7 @@ label.use_rnaview = Use RNAView for secondary structure
 label.autoadd_secstr = Add secondary structure annotation to alignment
 label.autoadd_temp = Add Temperature Factor annotation to alignment
 label.structure_viewer = Default structure viewer
+label.double_click_to_browse = Double-click to browse for file
 label.chimera_path = Path to Chimera program
 label.chimera_path_tip = Jalview will first try any path entered here, else standard installation locations.<br>Double-click to browse for file.
 label.invalid_chimera_path = Chimera path not found or not executable
index 61bf42a..d7c040c 100644 (file)
@@ -1173,6 +1173,7 @@ action.select_by_annotation=Seleccionar/Ocultar Columnas por Anotaci
 action.export_features=Exportar Características
 error.invalid_regex=Expresión regular inválida
 label.autoadd_temp=Añadir anotación factor de temperatura al alineamiento
+label.double_click_to_browse = Haga doble clic para buscar fichero 
 label.chimera_path_tip=Jalview intentará primero las rutas introducidas aquí, Y si no las rutas usuales de instalación
 label.structure_chooser=Selector de Estructuras
 label.structure_chooser_manual_association=Selector de Estructuras - asociación manual
@@ -1219,13 +1220,13 @@ exception.resource_not_be_found=El recurso solicitado no se ha encontrado
 label.aacon_calculations=cálculos AACon
 label.pdb_web-service_error=Error de servicio web PDB
 exception.unable_to_detect_internet_connection=Jalview no puede detectar una conexión a Internet
-label.chimera_path=Ruta de acceso a programa Chimera
+label.chimera_path=Ruta de acceso a Chimera
 warn.delete_all=<html>Borrar todas las secuencias cerrará la ventana del alineamiento.<br>Confirmar o Cancelar.
 label.select_all=Seleccionar Todos
 label.alpha_helix=Hélice Alfa
 label.chimera_help=Ayuda para Chimera
 label.find_tip=Buscar alineamiento, selección o IDs de secuencia para una subsecuencia (sin huecos)
-label.structure_viewer=Visualizador de estructura por defecto
+label.structure_viewer=Visualizador por defecto
 label.embbed_biojson=Incrustar BioJSON al exportar HTML
 label.transparency_tip=Ajustar la transparencia a "ver a través" los colores de las características.
 label.choose_annotations=Escoja anotaciones
index effe556..09a9713 100644 (file)
@@ -97,7 +97,7 @@ public class StructureManager
     this.haveGUI = haveGUI;
     // Create the Chimera interface
     chimeraManager = new ChimeraManager(this);
-    chimSelectionList = new ArrayList<ChimeraStructuralObject>();
+    chimSelectionList = new ArrayList<>();
     pathProps = new Properties();
   }
 
@@ -110,7 +110,7 @@ public class StructureManager
           ModelType type)
   {
     // new models
-    Map<String, List<ChimeraModel>> newModels = new HashMap<String, List<ChimeraModel>>();
+    Map<String, List<ChimeraModel>> newModels = new HashMap<>();
     if (chimObjNames.size() > 0)
     {
       List<String> names = chimObjNames.iterator().next();
@@ -846,7 +846,7 @@ public class StructureManager
     // alDialog.dispose();
     // }
     // System.out.println("launch align dialog");
-    List<ChimeraStructuralObject> chimObjectList = new ArrayList<ChimeraStructuralObject>();
+    List<ChimeraStructuralObject> chimObjectList = new ArrayList<>();
     for (ChimeraModel model : chimeraManager.getChimeraModels())
     {
       if (useChains)
@@ -887,7 +887,7 @@ public class StructureManager
 
   public List<String> getAllChimeraResidueAttributes()
   {
-    List<String> attributes = new ArrayList<String>();
+    List<String> attributes = new ArrayList<>();
     // attributes.addAll(rinManager.getResAttrs());
     attributes.addAll(chimeraManager.getAttrList());
     return attributes;
@@ -898,7 +898,7 @@ public class StructureManager
   // TODO: [Optional] Change priority of Chimera paths
   public static List<String> getChimeraPaths()
   {
-    List<String> pathList = new ArrayList<String>();
+    List<String> pathList = new ArrayList<>();
 
     // if no network is available and the settings have been modified by the
     // user, check for a
@@ -934,8 +934,18 @@ public class StructureManager
     }
     else if (os.startsWith("Windows"))
     {
-      pathList.add("\\Program Files\\Chimera\\bin\\chimera");
-      pathList.add("C:\\Program Files\\Chimera\\bin\\chimera.exe");
+      for (String root : new String[] { "\\Program Files",
+          "C:\\Program Files", "\\Program Files (x86)",
+          "C:\\Program Files (x86)" })
+      {
+        for (String version : new String[] { "1.11", "1.11.1", "1.11.2",
+            "1.12", "1.12.1", "1.12.2", "1.13" })
+        {
+          pathList.add(root + "\\Chimera " + version + "\\bin\\chimera");
+          pathList.add(
+                  root + "\\Chimera " + version + "\\bin\\chimera.exe");
+        }
+      }
     }
     else if (os.startsWith("Mac"))
     {
index 9a696e9..8be4eac 100644 (file)
@@ -32,6 +32,7 @@ import jalview.io.FileFormatException;
 import jalview.io.FileFormatI;
 import jalview.io.FileFormats;
 import jalview.io.FileLoader;
+import jalview.io.FormatAdapter;
 import jalview.io.IdentifyFile;
 import jalview.io.JalviewFileChooser;
 import jalview.io.JalviewFileView;
@@ -116,6 +117,8 @@ import javax.swing.event.InternalFrameEvent;
 import javax.swing.event.MenuEvent;
 import javax.swing.event.MenuListener;
 
+import org.stackoverflowusers.file.WindowsShortcut;
+
 /**
  * Jalview Desktop
  * 
@@ -3294,13 +3297,67 @@ public class Desktop extends jalview.jbgui.GDesktop
     return groovyConsole;
   }
 
+  /**
+   * handles the payload of a drag and drop event.
+   * 
+   * TODO refactor to desktop utilities class
+   * 
+   * @param files
+   *          - Data source strings extracted from the drop event
+   * @param protocols
+   *          - protocol for each data source extracted from the drop event
+   * @param evt
+   *          - the drop event
+   * @param t
+   *          - the payload from the drop event
+   * @throws Exception
+   */
   public static void transferFromDropTarget(List<String> files,
           List<DataSourceType> protocols, DropTargetDropEvent evt,
           Transferable t) throws Exception
   {
 
     DataFlavor uriListFlavor = new DataFlavor(
-            "text/uri-list;class=java.lang.String");
+            "text/uri-list;class=java.lang.String"), urlFlavour = null;
+    try
+    {
+      urlFlavour = new DataFlavor(
+              "application/x-java-url; class=java.net.URL");
+    } catch (ClassNotFoundException cfe)
+    {
+      Cache.log.debug("Couldn't instantiate the URL dataflavor.", cfe);
+    }
+
+    if (urlFlavour != null && t.isDataFlavorSupported(urlFlavour))
+    {
+
+      try
+      {
+      java.net.URL url = (URL) t.getTransferData(urlFlavour);
+        // nb: java 8 osx bug https://bugs.openjdk.java.net/browse/JDK-8156099
+        // means url may be null.
+      if (url != null)
+      {
+        protocols.add(DataSourceType.URL);
+        files.add(url.toString());
+        Cache.log.debug("Drop handled as URL dataflavor "
+                + files.get(files.size() - 1));
+          return;
+        }
+        else
+        {
+          if (Platform.isAMac())
+          {
+            System.err.println(
+                    "Please ignore plist error - occurs due to problem with java 8 on OSX");
+          }
+          ;
+      }
+      } catch (Throwable ex)
+      {
+        Cache.log.debug("URL drop handler failed.", ex);
+      }
+    }
     if (t.isDataFlavorSupported(DataFlavor.javaFileListFlavor))
     {
       // Works on Windows and MacOSX
@@ -3328,63 +3385,112 @@ public class Desktop extends jalview.jbgui.GDesktop
         // fallback to text: workaround - on OSX where there's a JVM bug
         Cache.log.debug("standard URIListFlavor failed. Trying text");
         // try text fallback
-        data = (String) t.getTransferData(
-                new DataFlavor("text/plain;class=java.lang.String"));
-        if (Cache.log.isDebugEnabled())
+        DataFlavor textDf = new DataFlavor(
+                "text/plain;class=java.lang.String");
+        if (t.isDataFlavorSupported(textDf))
         {
-          Cache.log.debug("fallback returned " + data);
+          data = (String) t.getTransferData(textDf);
         }
+
+        Cache.log.debug("Plain text drop content returned "
+                + (data == null ? "Null - failed" : data));
+
       }
-      while (protocols.size() < files.size())
-      {
-        Cache.log.debug("Adding missing FILE protocol for "
-                + files.get(protocols.size()));
-        protocols.add(DataSourceType.FILE);
-      }
-      for (java.util.StringTokenizer st = new java.util.StringTokenizer(
-              data, "\r\n"); st.hasMoreTokens();)
+      if (data != null)
       {
-        added = true;
-        String s = st.nextToken();
-        if (s.startsWith("#"))
+        while (protocols.size() < files.size())
         {
-          // the line is a comment (as per the RFC 2483)
-          continue;
-        }
-        java.net.URI uri = new java.net.URI(s);
-        if (uri.getScheme().toLowerCase().startsWith("http"))
-        {
-          protocols.add(DataSourceType.URL);
-          files.add(uri.toString());
+          Cache.log.debug("Adding missing FILE protocol for "
+                  + files.get(protocols.size()));
+          protocols.add(DataSourceType.FILE);
         }
-        else
+        for (java.util.StringTokenizer st = new java.util.StringTokenizer(
+                data, "\r\n"); st.hasMoreTokens();)
         {
-          // otherwise preserve old behaviour: catch all for file objects
-          java.io.File file = new java.io.File(uri);
-          protocols.add(DataSourceType.FILE);
-          files.add(file.toString());
+          added = true;
+          String s = st.nextToken();
+          if (s.startsWith("#"))
+          {
+            // the line is a comment (as per the RFC 2483)
+            continue;
+          }
+          java.net.URI uri = new java.net.URI(s);
+          if (uri.getScheme().toLowerCase().startsWith("http"))
+          {
+            protocols.add(DataSourceType.URL);
+            files.add(uri.toString());
+          }
+          else
+          {
+            // otherwise preserve old behaviour: catch all for file objects
+            java.io.File file = new java.io.File(uri);
+            protocols.add(DataSourceType.FILE);
+            files.add(file.toString());
+          }
         }
       }
+
       if (Cache.log.isDebugEnabled())
       {
         if (data == null || !added)
         {
-          Cache.log.debug(
-                  "Couldn't resolve drop data. Here are the supported flavors:");
-          for (DataFlavor fl : t.getTransferDataFlavors())
+
+          if (t.getTransferDataFlavors() != null
+                  && t.getTransferDataFlavors().length > 0)
           {
             Cache.log.debug(
-                    "Supported transfer dataflavor: " + fl.toString());
-            Object df = t.getTransferData(fl);
-            if (df != null)
+                    "Couldn't resolve drop data. Here are the supported flavors:");
+            for (DataFlavor fl : t.getTransferDataFlavors())
             {
-              Cache.log.debug("Retrieves: " + df);
-            }
-            else
-            {
-              Cache.log.debug("Retrieved nothing");
+              Cache.log.debug(
+                      "Supported transfer dataflavor: " + fl.toString());
+              Object df = t.getTransferData(fl);
+              if (df != null)
+              {
+                Cache.log.debug("Retrieves: " + df);
+              }
+              else
+              {
+                Cache.log.debug("Retrieved nothing");
+              }
             }
           }
+          else
+          {
+            Cache.log.debug("Couldn't resolve dataflavor for drop: "
+                    + t.toString());
+          }
+        }
+      }
+    }
+    if (Platform.isWindows())
+
+    {
+      Cache.log.debug("Scanning dropped content for Windows Link Files");
+
+      // resolve any .lnk files in the file drop
+      for (int f = 0; f < files.size(); f++)
+      {
+        String source = files.get(f).toLowerCase();
+        if (protocols.get(f).equals(DataSourceType.FILE)
+                && (source.endsWith(".lnk") || source.endsWith(".url")
+                        || source.endsWith(".site")))
+        {
+          try {
+            File lf = new File(files.get(f));
+            // process link file to get a URL
+            Cache.log.debug("Found potential link file: " + lf);
+            WindowsShortcut wscfile = new WindowsShortcut(lf);
+            String fullname = wscfile.getRealFilename();
+            protocols.set(f, FormatAdapter.checkProtocol(fullname));
+            files.set(f, fullname);
+            Cache.log.debug("Parsed real filename " + fullname
+                    + " to extract protocol: " + protocols.get(f));
+          }
+          catch (Exception ex)
+          {
+            Cache.log.error("Couldn't parse "+files.get(f)+" as a link file.",ex);
+          }
         }
       }
     }
index 2d8eb7d..8f315bd 100755 (executable)
@@ -652,6 +652,11 @@ public class SeqCanvas extends JComponent implements ViewportListenerI
     ViewportRanges ranges = av.getRanges();
     ranges.setViewportStartAndWidth(startColumn, wrappedWidthInResidues);
 
+    // we need to call this again to make sure the startColumn +
+    // wrappedWidthInResidues values are used to calculate wrappedVisibleWidths
+    // correctly.
+    calculateWrappedGeometry(canvasWidth, canvasHeight);
+
     /*
      * draw one width at a time (including any scales or annotation shown),
      * until we have run out of either alignment or vertical space available
index 1ca0802..26e0919 100755 (executable)
@@ -524,7 +524,9 @@ public class GPreferences extends JPanel
             MessageManager.getString("label.default_browser_unix"));
     defaultBrowser.setFont(LABEL_FONT);
     defaultBrowser.setText("");
-
+    final String tooltip = JvSwingUtils.wrapTooltip(true,
+            MessageManager.getString("label.double_click_to_browse"));
+    defaultBrowser.setToolTipText(tooltip);
     defaultBrowser.addMouseListener(new MouseAdapter()
     {
       @Override
@@ -1206,14 +1208,14 @@ public class GPreferences extends JPanel
     pathLabel.setFont(new java.awt.Font("SansSerif", 0, 11));
     pathLabel.setHorizontalAlignment(SwingConstants.LEFT);
     pathLabel.setText(MessageManager.getString("label.chimera_path"));
-    final String tooltip = JvSwingUtils.wrapTooltip(true,
-            MessageManager.getString("label.chimera_path_tip"));
-    pathLabel.setToolTipText(tooltip);
     pathLabel.setBounds(new Rectangle(10, ypos, 140, height));
     structureTab.add(pathLabel);
 
     chimeraPath.setFont(LABEL_FONT);
     chimeraPath.setText("");
+    final String tooltip = JvSwingUtils.wrapTooltip(true,
+            MessageManager.getString("label.chimera_path_tip"));
+    chimeraPath.setToolTipText(tooltip);
     chimeraPath.setBounds(new Rectangle(160, ypos, 300, height));
     chimeraPath.addMouseListener(new MouseAdapter()
     {
@@ -1512,6 +1514,9 @@ public class GPreferences extends JPanel
     startupCheckbox.setSelected(true);
     startupFileTextfield.setFont(LABEL_FONT);
     startupFileTextfield.setBounds(new Rectangle(172, 310, 330, 20));
+    final String tooltip = JvSwingUtils.wrapTooltip(true,
+            MessageManager.getString("label.double_click_to_browse"));
+    startupFileTextfield.setToolTipText(tooltip);
     startupFileTextfield.addMouseListener(new MouseAdapter()
     {
       @Override
diff --git a/src/org/stackoverflowusers/file/WindowsShortcut.java b/src/org/stackoverflowusers/file/WindowsShortcut.java
new file mode 100644 (file)
index 0000000..671e002
--- /dev/null
@@ -0,0 +1,215 @@
+package org.stackoverflowusers.file;
+
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.text.ParseException;
+
+/**
+ * Represents a Windows shortcut (typically visible to Java only as a '.lnk' file).
+ *
+ * Retrieved 2011-09-23 from http://stackoverflow.com/questions/309495/windows-shortcut-lnk-parser-in-java/672775#672775
+ * Originally called LnkParser
+ *
+ * Written by: (the stack overflow users, obviously!)
+ *   Apache Commons VFS dependency removed by crysxd (why were we using that!?) https://github.com/crysxd
+ *   Headerified, refactored and commented by Code Bling http://stackoverflow.com/users/675721/code-bling
+ *   Network file support added by Stefan Cordes http://stackoverflow.com/users/81330/stefan-cordes
+ *   Adapted by Sam Brightman http://stackoverflow.com/users/2492/sam-brightman
+ *   Based on information in 'The Windows Shortcut File Format' by Jesse Hager &lt;jessehager@iname.com&gt;
+ *   And somewhat based on code from the book 'Swing Hacks: Tips and Tools for Killer GUIs'
+ *     by Joshua Marinacci and Chris Adamson
+ *     ISBN: 0-596-00907-0
+ *     http://www.oreilly.com/catalog/swinghks/
+ */
+public class WindowsShortcut
+{
+    private boolean isDirectory;
+    private boolean isLocal;
+    private String real_file;
+
+    /**
+     * Provides a quick test to see if this could be a valid link !
+     * If you try to instantiate a new WindowShortcut and the link is not valid,
+     * Exceptions may be thrown and Exceptions are extremely slow to generate,
+     * therefore any code needing to loop through several files should first check this.
+     *
+     * @param file the potential link
+     * @return true if may be a link, false otherwise
+     * @throws IOException if an IOException is thrown while reading from the file
+     */
+    public static boolean isPotentialValidLink(File file) throws IOException {
+        final int minimum_length = 0x64;
+        InputStream fis = new FileInputStream(file);
+        boolean isPotentiallyValid = false;
+        try {
+            isPotentiallyValid = file.isFile()
+                && file.getName().toLowerCase().endsWith(".lnk")
+                && fis.available() >= minimum_length
+                && isMagicPresent(getBytes(fis, 32));
+        } finally {
+            fis.close();
+        }
+        return isPotentiallyValid;
+    }
+
+    public WindowsShortcut(File file) throws IOException, ParseException {
+        InputStream in = new FileInputStream(file);
+        try {
+            parseLink(getBytes(in));
+        } finally {
+            in.close();
+        }
+    }
+
+    /**
+     * @return the name of the filesystem object pointed to by this shortcut
+     */
+    public String getRealFilename() {
+        return real_file;
+    }
+
+    /**
+     * Tests if the shortcut points to a local resource.
+     * @return true if the 'local' bit is set in this shortcut, false otherwise
+     */
+    public boolean isLocal() {
+        return isLocal;
+    }
+
+    /**
+     * Tests if the shortcut points to a directory.
+     * @return true if the 'directory' bit is set in this shortcut, false otherwise
+     */
+    public boolean isDirectory() {
+        return isDirectory;
+    }
+
+    /**
+     * Gets all the bytes from an InputStream
+     * @param in the InputStream from which to read bytes
+     * @return array of all the bytes contained in 'in'
+     * @throws IOException if an IOException is encountered while reading the data from the InputStream
+     */
+    private static byte[] getBytes(InputStream in) throws IOException {
+        return getBytes(in, null);
+    }
+    
+    /**
+     * Gets up to max bytes from an InputStream
+     * @param in the InputStream from which to read bytes
+     * @param max maximum number of bytes to read
+     * @return array of all the bytes contained in 'in'
+     * @throws IOException if an IOException is encountered while reading the data from the InputStream
+     */
+    private static byte[] getBytes(InputStream in, Integer max) throws IOException {
+        // read the entire file into a byte buffer
+        ByteArrayOutputStream bout = new ByteArrayOutputStream();
+        byte[] buff = new byte[256];
+        while (max == null || max > 0) {
+            int n = in.read(buff);
+            if (n == -1) {
+                break;
+            }
+            bout.write(buff, 0, n);
+            if (max != null)
+                max -= n;
+        }
+        in.close();
+        return bout.toByteArray();
+    }
+
+    private static boolean isMagicPresent(byte[] link) {
+        final int magic = 0x0000004C;
+        final int magic_offset = 0x00;
+        return link.length >= 32 && bytesToDword(link, magic_offset) == magic;
+    }
+
+    /**
+     * Gobbles up link data by parsing it and storing info in member fields
+     * @param link all the bytes from the .lnk file
+     */
+    private void parseLink(byte[] link) throws ParseException {
+        try {
+            if (!isMagicPresent(link))
+                throw new ParseException("Invalid shortcut; magic is missing", 0);
+
+            // get the flags byte
+            byte flags = link[0x14];
+
+            // get the file attributes byte
+            final int file_atts_offset = 0x18;
+            byte file_atts = link[file_atts_offset];
+            byte is_dir_mask = (byte)0x10;
+            if ((file_atts & is_dir_mask) > 0) {
+                isDirectory = true;
+            } else {
+                isDirectory = false;
+            }
+
+            // if the shell settings are present, skip them
+            final int shell_offset = 0x4c;
+            final byte has_shell_mask = (byte)0x01;
+            int shell_len = 0;
+            if ((flags & has_shell_mask) > 0) {
+                // the plus 2 accounts for the length marker itself
+                shell_len = bytesToWord(link, shell_offset) + 2;
+            }
+
+            // get to the file settings
+            int file_start = 0x4c + shell_len;
+
+            final int file_location_info_flag_offset_offset = 0x08;
+            int file_location_info_flag = link[file_start + file_location_info_flag_offset_offset];
+            isLocal = (file_location_info_flag & 2) == 0;
+            // get the local volume and local system values
+            //final int localVolumeTable_offset_offset = 0x0C;
+            final int basename_offset_offset = 0x10;
+            final int networkVolumeTable_offset_offset = 0x14;
+            final int finalname_offset_offset = 0x18;
+            int finalname_offset = link[file_start + finalname_offset_offset] + file_start;
+            String finalname = getNullDelimitedString(link, finalname_offset);
+            if (isLocal) {
+                int basename_offset = link[file_start + basename_offset_offset] + file_start;
+                String basename = getNullDelimitedString(link, basename_offset);
+                real_file = basename + finalname;
+            } else {
+                int networkVolumeTable_offset = link[file_start + networkVolumeTable_offset_offset] + file_start;
+                int shareName_offset_offset = 0x08;
+                int shareName_offset = link[networkVolumeTable_offset + shareName_offset_offset]
+                    + networkVolumeTable_offset;
+                String shareName = getNullDelimitedString(link, shareName_offset);
+                real_file = shareName + "\\" + finalname;
+            }
+        } catch (ArrayIndexOutOfBoundsException e) {
+            throw new ParseException("Could not be parsed, probably not a valid WindowsShortcut", 0);
+        }
+    }
+
+    private static String getNullDelimitedString(byte[] bytes, int off) {
+        int len = 0;
+        // count bytes until the null character (0)
+        while (true) {
+            if (bytes[off + len] == 0) {
+                break;
+            }
+            len++;
+        }
+        return new String(bytes, off, len);
+    }
+
+    /*
+     * convert two bytes into a short note, this is little endian because it's
+     * for an Intel only OS.
+     */
+    private static int bytesToWord(byte[] bytes, int off) {
+        return ((bytes[off + 1] & 0xff) << 8) | (bytes[off] & 0xff);
+    }
+
+    private static int bytesToDword(byte[] bytes, int off) {
+        return (bytesToWord(bytes, off + 2) << 16) | bytesToWord(bytes, off);
+    }
+
+}