Issue #289: Porting file-handling enhancements from main

This change introduces file-handling enhancements from
apache/uima-uimaj#209 and apache/uima-uimaj#211 to the main-v2
branch, including the fix addressing CVE-2022-32287.

This meant porting the entire org.apache.uima.util.impl.Constants class
and also the unit test with associated org.junit.jupiter libraries.
Given that these are only used in the test scope, it didn't feel too
outrageous to use them just in this test and keep the others how
they've always been in v2.
diff --git a/uimaj-core/pom.xml b/uimaj-core/pom.xml
index 1b7d8ab..df15e98 100644
--- a/uimaj-core/pom.xml
+++ b/uimaj-core/pom.xml
@@ -37,6 +37,7 @@
     <uimaScmProject>${project.artifactId}</uimaScmProject>
     <postNoticeText>${ibmNoticeText}</postNoticeText>
     <maven.surefire.heap>650M</maven.surefire.heap>
+    <junit-version>5.9.1</junit-version>
   </properties>
   
 	<dependencies>
@@ -53,6 +54,18 @@
 			<version>${project.parent.version}</version>
 			<scope>test</scope>
 		</dependency>
+    <dependency>
+      <groupId>org.junit.jupiter</groupId>
+      <artifactId>junit-jupiter-engine</artifactId>
+      <version>${junit-version}</version>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.junit.jupiter</groupId>
+      <artifactId>junit-jupiter-params</artifactId>
+      <version>${junit-version}</version>
+      <scope>test</scope>
+    </dependency>
 		<dependency>
 			<groupId>org.assertj</groupId>
 			<artifactId>assertj-core</artifactId>
diff --git a/uimaj-core/src/main/java/org/apache/uima/pear/util/FileUtil.java b/uimaj-core/src/main/java/org/apache/uima/pear/util/FileUtil.java
index f9dc130..50a8ff9 100644
--- a/uimaj-core/src/main/java/org/apache/uima/pear/util/FileUtil.java
+++ b/uimaj-core/src/main/java/org/apache/uima/pear/util/FileUtil.java
@@ -39,6 +39,9 @@
 import java.net.MalformedURLException;
 import java.net.URL;
 import java.net.URLConnection;
+import java.nio.charset.Charset;
+import java.nio.file.CopyOption;
+import java.nio.file.Path;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Comparator;
@@ -55,11 +58,22 @@
 import java.util.zip.ZipEntry;
 import java.util.zip.ZipOutputStream;
 
+import org.apache.uima.util.impl.Constants;
+
 /**
  * The <code>FileUtil</code> class provides utility methods for working with general files.
  */
-
 public class FileUtil {
+  private static final char DOT = '.';
+  private static final String UTF8_ENCODING = "UTF-8";
+  private static final String ASCII_ENCODING = "ASCII";
+  private static final String XML_EXTENSION = DOT + "xml";
+  private static final String BACKUP_EXTENSION = DOT + "bak";
+  private static final String ZIP_EXTENSION = DOT + "zip";
+  private static final char UNIX_SEPARATOR_CHAR = '/';
+  private static final char WINDOWS_SEPARATOR_CHAR = '\\';
+  private static final String UNIX_SEPARATOR = String.valueOf(UNIX_SEPARATOR_CHAR);
+  private static final String WINDOWS_SEPARATOR = String.valueOf(WINDOWS_SEPARATOR_CHAR);
 
   /**
    * The <code>FileTimeComparator</code> class allows comparing 'last modified' time in 2 given
@@ -73,6 +87,7 @@
      *           if the arguments' types prevent them from being compared by this
      *           <code>Comparator</code>.
      */
+    @Override
     public int compare(File o1, File o2) throws ClassCastException {
       long t1 = o1.lastModified();
       long t2 = o2.lastModified();
@@ -83,9 +98,9 @@
      * @param obj
      *          The reference object with which to compare.
      * @return <code>true</code> only if the specified object is also a
-     *         <code>FileTimeComparator</code>, and it imposes the same ordering as this
-     *         comparator.
+     *         <code>FileTimeComparator</code>, and it imposes the same ordering as this comparator.
      */
+    @Override
     public boolean equals(Object obj) {
       return (obj instanceof FileTimeComparator);
     }
@@ -110,25 +125,28 @@
      *          The given file extension.
      */
     public DirFileFilter(String dirPath, String fileExt) {
-      _dirPath = (dirPath != null) ? dirPath.replace('\\', '/') : null;
-      if (fileExt != null)
+      _dirPath = (dirPath != null) ? dirPath.replace(WINDOWS_SEPARATOR_CHAR, UNIX_SEPARATOR_CHAR)
+              : null;
+      if (fileExt != null) {
         _fileExt = fileExt.startsWith(".") ? fileExt.toLowerCase() : "." + fileExt.toLowerCase();
-      else
+      } else {
         _fileExt = null;
+      }
     }
 
     /**
      * @param file
      *          The given file to be tested.
-     * @return <code>true</code> if the given file should be accepted, <code>false</code>
-     *         otherwise.
+     * @return <code>true</code> if the given file should be accepted, <code>false</code> otherwise.
      */
+    @Override
     public boolean accept(File file) {
       boolean dirAccepted = true;
       boolean extAccepted = true;
       if (_dirPath != null) {
         String parentDir = file.getParent();
-        dirAccepted = parentDir != null && parentDir.replace('\\', '/').startsWith(_dirPath);
+        dirAccepted = parentDir != null && parentDir
+                .replace(WINDOWS_SEPARATOR_CHAR, UNIX_SEPARATOR_CHAR).startsWith(_dirPath);
       }
       if (_fileExt != null) {
         extAccepted = file.getPath().toLowerCase().endsWith(_fileExt);
@@ -139,7 +157,6 @@
 
   /**
    * The <code>NameFileFilter</code> class allows to filter files based on specified file name.
-   * 
    */
   public static class NameFileFilter implements FileFilter {
     // attributes
@@ -152,24 +169,26 @@
      *          The given file name for filtering.
      */
     public NameFileFilter(String fileName) {
-      _fileName = fileName.replace('\\', '/');
+      _fileName = fileName.replace(WINDOWS_SEPARATOR_CHAR, UNIX_SEPARATOR_CHAR);
     }
 
     /**
      * @param file
      *          The given file to be tested.
-     * @return <code>true</code> if the given file should be accepted, <code>false</code>
-     *         otherwise.
+     * @return <code>true</code> if the given file should be accepted, <code>false</code> otherwise.
      */
+    @Override
     public boolean accept(File file) {
-      String filePath = file.getAbsolutePath().replace('\\', '/');
+      String filePath = file.getAbsolutePath().replace(WINDOWS_SEPARATOR_CHAR, UNIX_SEPARATOR_CHAR);
       if (filePath.endsWith(_fileName)) {
         if (filePath.length() > _fileName.length()) {
           char prevChar = filePath.charAt(filePath.length() - _fileName.length() - 1);
-          if (prevChar == ':' || prevChar == '/')
+          if (prevChar == ':' || prevChar == UNIX_SEPARATOR_CHAR) {
             return true;
-        } else
+          }
+        } else {
           return true;
+        }
       }
       return false;
     }
@@ -199,8 +218,8 @@
     }
 
     /**
-     * Create instance of the <code>ExtFileFilter</code> class for a given filename extension. If
-     * a given <code>boolean</code> flag is <code>true</code>, this filename filter is case
+     * Create instance of the <code>ExtFileFilter</code> class for a given filename extension. If a
+     * given <code>boolean</code> flag is <code>true</code>, this filename filter is case
      * insensitive, otherwise it's case sensitive. If the given filename extension does not start
      * from the '.' character, adds this character at the beginning.
      * 
@@ -212,8 +231,9 @@
     public ExtFilenameFilter(String fileExt, boolean ignoreCase) {
       _fileExt = fileExt.startsWith(".") ? fileExt : "." + fileExt;
       _ignoreCase = ignoreCase;
-      if (ignoreCase)
+      if (ignoreCase) {
         _fileExt = _fileExt.toLowerCase();
+      }
     }
 
     /**
@@ -226,6 +246,7 @@
      * @return <code>true</code>, if the given file should be included in the list,
      *         <code>false</code> otherwise.
      */
+    @Override
     public boolean accept(File dir, String name) {
       String fileName = _ignoreCase ? name.toLowerCase() : name;
       return fileName.endsWith(_fileExt);
@@ -234,8 +255,8 @@
 
   /**
    * Deletes all files and subdirectories in a given directory. In case of unsuccessful deletion,
-   * calls the <code>deleteOnExit()</code> method to request that files and subdirs are deleted
-   * when the JVM terminates.
+   * calls the <code>deleteOnExit()</code> method to request that files and subdirs are deleted when
+   * the JVM terminates.
    * 
    * @param directory
    *          The given directory to be cleaned-up.
@@ -251,15 +272,17 @@
         File aFile = allDirFiles[i];
         if (aFile.isDirectory()) {
           counter += cleanUpDirectoryContent(aFile);
-          if (aFile.delete())
+          if (aFile.delete()) {
             counter++;
-          else
+          } else {
             aFile.deleteOnExit();
+          }
         } else if (aFile.isFile()) {
-          if (aFile.delete())
+          if (aFile.delete()) {
             counter++;
-          else
+          } else {
             aFile.deleteOnExit();
+          }
         }
       }
     }
@@ -283,10 +306,11 @@
       for (int i = 0; i < allDirFiles.length; i++) {
         File aFile = allDirFiles[i];
         if (aFile.isFile()) {
-          if (aFile.delete())
+          if (aFile.delete()) {
             counter++;
-          else
+          } else {
             aFile.deleteOnExit();
+          }
         }
       }
     }
@@ -317,16 +341,25 @@
         File file = list.next();
         no++;
         if (no > maxLimit) {
-          if (file.delete())
+          if (file.delete()) {
             counter++;
-          else
+          } else {
             file.deleteOnExit();
+          }
         }
       }
     }
     return counter;
   }
 
+  private static String normalizeToUnix(String aPath) {
+    if (aPath == null) {
+      return null;
+    }
+
+    return aPath.replace(WINDOWS_SEPARATOR_CHAR, UNIX_SEPARATOR_CHAR);
+  }
+
   /**
    * Computes relative path to a given file from a given reference directory, if both the reference
    * directory and the file are in the same logical file system (partition).
@@ -343,25 +376,26 @@
    */
   public static String computeRelativePath(File referenceDir, File file) throws IOException {
     // get canonical path expressions
-    String refPath = referenceDir.getCanonicalPath().replace('\\', '/');
-    String filePath = file.getCanonicalPath().replace('\\', '/');
+    String refPath = normalizeToUnix(referenceDir.getCanonicalPath());
+    String filePath = normalizeToUnix(file.getCanonicalPath());
     // compute relative path from reference dir to file dir-tree
     StringBuffer relBuffer = new StringBuffer();
     while (refPath != null && !filePath.startsWith(refPath)) {
       relBuffer.append("../");
-      refPath = (new File(refPath)).getParent();
-      if (refPath != null)
-        refPath = refPath.replace('\\', '/');
+      refPath = normalizeToUnix((new File(refPath)).getParent());
     }
+
     if (refPath != null) {
       // construct relative path
       String subPath = filePath.substring(refPath.length());
-      if (relBuffer.length() == 0)
+      if (relBuffer.length() == 0) {
         relBuffer.append("./");
-      if (subPath.startsWith("/"))
+      }
+      if (subPath.startsWith("/")) {
         relBuffer.append(subPath.substring(1));
-      else
+      } else {
         relBuffer.append(subPath);
+      }
       return relBuffer.toString();
     }
     // relative path does not exist
@@ -379,37 +413,20 @@
    *         otherwise.
    * @throws IOException
    *           If any I/O exception occurred.
+   * @deprecated use Java 7 for this see {@link java.nio.file.Files#copy(Path, Path, CopyOption...)}
    */
+  @Deprecated
   public static boolean copyFile(File source, File destination) throws IOException {
-    boolean completed = false;
-    BufferedInputStream iStream = null;
-    BufferedOutputStream oStream = null;
-    try {
-      iStream = new BufferedInputStream(new FileInputStream(source));
-      oStream = new BufferedOutputStream(new FileOutputStream(destination));
+    try (BufferedInputStream iStream = new BufferedInputStream(new FileInputStream(source));
+            BufferedOutputStream oStream = new BufferedOutputStream(
+                    new FileOutputStream(destination))) {
       byte[] block = new byte[4096];
       int bCount = 0;
       while ((bCount = iStream.read(block)) > 0) {
         oStream.write(block, 0, bCount);
       }
-      iStream.close();
-      oStream.close();
-      completed = true;
-    } finally {
-      if (iStream != null) {
-        try {
-          iStream.close();
-        } catch (Exception e) {
-        }
-      }
-      if (oStream != null) {
-        try {
-          oStream.close();
-        } catch (Exception e) {
-        }
-      }
     }
-    return completed;
+    return true;
   }
 
   /**
@@ -423,37 +440,21 @@
    *         otherwise.
    * @throws IOException
    *           If any I/O exception occurred.
+   * @deprecated use Java 7 for this see
+   *             {@link java.nio.file.Files#copy(InputStream, Path, CopyOption...)}
    */
+  @Deprecated
   public static boolean copyFile(URL sourceUrl, File destination) throws IOException {
-    boolean completed = false;
-    BufferedInputStream iStream = null;
-    BufferedOutputStream oStream = null;
-    try {
-      iStream = new BufferedInputStream(sourceUrl.openStream());
-      oStream = new BufferedOutputStream(new FileOutputStream(destination));
+    try (BufferedInputStream iStream = new BufferedInputStream(sourceUrl.openStream());
+            BufferedOutputStream oStream = new BufferedOutputStream(
+                    new FileOutputStream(destination))) {
       byte[] block = new byte[4096];
       int bCount = 0;
       while ((bCount = iStream.read(block)) > 0) {
         oStream.write(block, 0, bCount);
       }
-      iStream.close();
-      oStream.close();
-      completed = true;
-    } finally {
-      if (iStream != null) {
-        try {
-          iStream.close();
-        } catch (Exception e) {
-        }
-      }
-      if (oStream != null) {
-        try {
-          oStream.close();
-        } catch (Exception e) {
-        }
-      }
     }
-    return completed;
+    return true;
   }
 
   /**
@@ -461,8 +462,8 @@
    * 
    * @param rootDir
    *          The given root directory.
-   * @return <code>Collection</code> of <code>File</code> objects, representing subdirectories
-   *         in the given root directory and all its subdirectories.
+   * @return <code>Collection</code> of <code>File</code> objects, representing subdirectories in
+   *         the given root directory and all its subdirectories.
    * 
    * @throws java.io.IOException
    *           If any I/O exception occurs.
@@ -472,12 +473,12 @@
   }
 
   /**
-   * Creates list of subdirectories in a given root directory. If a given <code>boolean</code>
-   * flag is <code>true</code>, all the subdirectories of the given root directory are also
-   * scanned, otherwise only subdirectories in the given root directory are included.
+   * Creates list of subdirectories in a given root directory. If a given <code>boolean</code> flag
+   * is <code>true</code>, all the subdirectories of the given root directory are also scanned,
+   * otherwise only subdirectories in the given root directory are included.
    * 
-   * @return <code>Collection</code> of <code>File</code> objects, representing subdirectories
-   *         in the given root directory.
+   * @return <code>Collection</code> of <code>File</code> objects, representing subdirectories in
+   *         the given root directory.
    * @param rootDir
    *          The given root directory.
    * @param includeSubdirs
@@ -488,17 +489,20 @@
    * @exception java.io.IOException
    *              If any I/O exception occurs.
    */
-  public static Collection<File> createDirList(File rootDir, boolean includeSubdirs) throws IOException {
-    ArrayList<File> listOfDirs = new ArrayList<File>();
+  public static Collection<File> createDirList(File rootDir, boolean includeSubdirs)
+          throws IOException {
+    ArrayList<File> listOfDirs = new ArrayList<>();
     File[] allDirFiles = rootDir.listFiles();
-    if (allDirFiles == null)
+    if (allDirFiles == null) {
       throw new FileNotFoundException("invalid directory specified");
+    }
     for (int i = 0; i < allDirFiles.length; i++) {
       File aFile = allDirFiles[i];
       if (aFile.isDirectory()) {
         listOfDirs.add(aFile);
-        if (includeSubdirs)
+        if (includeSubdirs) {
           listOfDirs.addAll(createDirList(aFile, includeSubdirs));
+        }
       }
     }
     return listOfDirs;
@@ -511,15 +515,15 @@
    * 
    * @param archive
    *          The input archive (JAR) file.
-   * @return <code>Collection</code> of <code>File</code> objects, representing directories in
-   *         the given archive file.
+   * @return <code>Collection</code> of <code>File</code> objects, representing directories in the
+   *         given archive file.
    * @throws IOException
    *           If any I/O exception occurs.
    */
   public static Collection<File> createDirList(JarFile archive) throws IOException {
-    ArrayList<File> listOfDirs = new ArrayList<File>();
+    ArrayList<File> listOfDirs = new ArrayList<>();
     // set root_dir_path = archive_file_path (w/o file name extension)
-    int nameEndIndex = archive.getName().lastIndexOf('.');
+    int nameEndIndex = archive.getName().lastIndexOf(DOT);
     String rootDirPath = (nameEndIndex > 0) ? archive.getName().substring(0, nameEndIndex)
             : archive.getName();
     File rootDir = new File(rootDirPath);
@@ -528,14 +532,15 @@
     while (entries.hasMoreElements()) {
       JarEntry entry = entries.nextElement();
       File file = new File(rootDir, entry.getName());
-      if (entry.isDirectory())
+      if (entry.isDirectory()) {
         listOfDirs.add(file);
-      else {
+      } else {
         // make sure the parent dir is added
         File parentDir = file.getParentFile();
         while (!parentDir.equals(rootDir)) {
-          if (!listOfDirs.contains(parentDir))
+          if (!listOfDirs.contains(parentDir)) {
             listOfDirs.add(parentDir);
+          }
           parentDir = parentDir.getParentFile();
         }
       }
@@ -546,8 +551,8 @@
   /**
    * Creates list of files in a given directory, including all its subdirectories.
    * 
-   * @return <code>Collection</code> of <code>File</code> objects in the given directory,
-   *         including all its subdirectories.
+   * @return <code>Collection</code> of <code>File</code> objects in the given directory, including
+   *         all its subdirectories.
    * @param filesDir
    *          The given directory.
    * 
@@ -560,8 +565,8 @@
 
   /**
    * Creates list of files in a given directory. If a given <code>boolean</code> flag is
-   * <code>true</code>, all the sub-directories of the given directory are also scanned,
-   * otherwise only files in the given directory are included.
+   * <code>true</code>, all the sub-directories of the given directory are also scanned, otherwise
+   * only files in the given directory are included.
    * 
    * @return <code>Collection</code> of <code>File</code> objects in the given directory.
    * @param filesDir
@@ -574,17 +579,20 @@
    * @exception java.io.IOException
    *              If any I/O exception occurs.
    */
-  public static Collection<File> createFileList(File filesDir, boolean includeSubdirs) throws IOException {
-    ArrayList<File> listOfFiles = new ArrayList<File>();
+  public static Collection<File> createFileList(File filesDir, boolean includeSubdirs)
+          throws IOException {
+    ArrayList<File> listOfFiles = new ArrayList<>();
     File[] allDirFiles = filesDir.listFiles();
-    if (allDirFiles == null)
+    if (allDirFiles == null) {
       throw new FileNotFoundException("invalid directory specified");
+    }
     for (int i = 0; i < allDirFiles.length; i++) {
       File aFile = allDirFiles[i];
-      if (aFile.isDirectory() && includeSubdirs)
+      if (aFile.isDirectory() && includeSubdirs) {
         listOfFiles.addAll(createFileList(aFile, includeSubdirs));
-      else if (!aFile.isDirectory())
+      } else if (!aFile.isDirectory()) {
         listOfFiles.add(aFile);
+      }
     }
     return listOfFiles;
   }
@@ -595,15 +603,15 @@
    * 
    * @param archive
    *          The input archive (JAR) file.
-   * @return <code>Collection</code> of <code>File</code> objects, representing files in the
-   *         given archive file.
+   * @return <code>Collection</code> of <code>File</code> objects, representing files in the given
+   *         archive file.
    * @throws IOException
    *           If any I/O exception occurs.
    */
   public static Collection<File> createFileList(JarFile archive) throws IOException {
-    ArrayList<File> listOfFiles = new ArrayList<File>();
+    ArrayList<File> listOfFiles = new ArrayList<>();
     // set root_dir_path = archive_file_path (w/o file name extension)
-    int nameEndIndex = archive.getName().lastIndexOf('.');
+    int nameEndIndex = archive.getName().lastIndexOf(DOT);
     String rootDirPath = (nameEndIndex > 0) ? archive.getName().substring(0, nameEndIndex)
             : archive.getName();
     File rootDir = new File(rootDirPath);
@@ -612,8 +620,9 @@
     while (entries.hasMoreElements()) {
       JarEntry entry = entries.nextElement();
       File file = new File(rootDir, entry.getName());
-      if (!entry.isDirectory())
+      if (!entry.isDirectory()) {
         listOfFiles.add(file);
+      }
     }
     return listOfFiles;
   }
@@ -632,24 +641,30 @@
    * @return The <code>File</code> object denoting the newly created file.
    * @throws IOException
    *           If a temporary directory not found or other I/O exception occurred.
+   * @deprecated use Java 7 method for this see
+   *             {@link java.io.File#createTempFile(String, String, File)}
    */
+  @Deprecated
   public static File createTempFile(String prefix, String suffix) throws IOException {
     String tempDirPath = System.getProperty("java.io.tmpdir");
-    if (tempDirPath == null)
+    if (tempDirPath == null) {
       tempDirPath = System.getProperty("user.home");
-    if (tempDirPath == null)
+    }
+    if (tempDirPath == null) {
       throw new IOException("could not find temporary directory");
+    }
     File tempDir = new File(tempDirPath);
-    if (!tempDir.isDirectory())
+    if (!tempDir.isDirectory()) {
       throw new IOException("temporary directory not available");
+    }
     return File.createTempFile(prefix, suffix, tempDir);
   }
 
   /**
    * Deletes a given directory, including all its subdirectories and files. Returns
-   * <code>true</code> if the deletion was successful, otherwise returns <code>false</code>. In
-   * case of unsuccessful deletion, calls <code>deleteOnExit()</code> method to request that files
-   * and subdirs be deleted when the virtual machine terminates.
+   * <code>true</code> if the deletion was successful, otherwise returns <code>false</code>. In case
+   * of unsuccessful deletion, calls <code>deleteOnExit()</code> method to request that files and
+   * subdirs be deleted when the virtual machine terminates.
    * 
    * @param dir
    *          The given directory to be deleted.
@@ -664,9 +679,9 @@
     // first, delete plain files and sub-directories (recursive)
     for (int i = 0; i < fileList.length; i++) {
       File entry = fileList[i];
-      if (entry.isDirectory())
+      if (entry.isDirectory()) {
         done = deleteDirectory(entry);
-      else if (!entry.delete()) {
+      } else if (!entry.delete()) {
         entry.deleteOnExit();
         done = false;
       }
@@ -751,48 +766,48 @@
           throws IOException {
     long totalBytes = 0;
     byte[] block = new byte[4096];
+
+    String prefix = normalizeToUnix(targetDir.getCanonicalPath());
+    if (!prefix.endsWith(UNIX_SEPARATOR)) {
+      prefix = prefix + UNIX_SEPARATOR_CHAR;
+    }
+
     Enumeration<JarEntry> jarList = jarFile.entries();
     while (jarList.hasMoreElements()) {
       JarEntry jarEntry = jarList.nextElement();
-      if (!jarEntry.isDirectory()) {
-        // check that file is accepted
-        if (filter != null && !filter.accept(new File(jarEntry.getName())))
-          continue;
-        // extract file
-        File file = new File(targetDir, jarEntry.getName());
-        // make sure the file directory exists
-        File dir = file.getParentFile();
-        if (!dir.exists() && !dir.mkdirs())
-          throw new IOException("could not create directory " + dir.getAbsolutePath());
-        BufferedInputStream iStream = null;
-        BufferedOutputStream oStream = null;
-        try {
-          iStream = new BufferedInputStream(jarFile.getInputStream(jarEntry));
-          oStream = new BufferedOutputStream(new FileOutputStream(file));
-          int bCount = 0;
-          while ((bCount = iStream.read(block)) > 0) {
-            totalBytes += bCount;
-            oStream.write(block, 0, bCount);
-          }
-          iStream.close();
-          oStream.close();
-        } finally {
-          // close streams
-          if (iStream != null) {
-            try {
-              iStream.close();
-            } catch (Exception e) {
-            }
-          }
-          if (oStream != null) {
-            try {
-              oStream.close();
-            } catch (Exception e) {
-            }
-          }
+      if (jarEntry.isDirectory()) {
+        continue;
+      }
+
+      // check that file is accepted
+      if (filter != null && !filter.accept(new File(jarEntry.getName()))) {
+        continue;
+      }
+
+      // make sure the file directory exists
+      File file = new File(targetDir, jarEntry.getName());
+
+      if (!normalizeToUnix(file.getCanonicalPath()).startsWith(prefix)) {
+        throw new IOException("Can only write within target folder [" + targetDir.getAbsolutePath()
+                + "]. Please validate ZIP contents.");
+      }
+
+      File dir = file.getParentFile();
+      if (!dir.exists() && !dir.mkdirs()) {
+        throw new IOException("Could not create directory [" + dir.getAbsolutePath() + "]");
+      }
+
+      // extract file
+      try (BufferedInputStream iStream = new BufferedInputStream(jarFile.getInputStream(jarEntry));
+              BufferedOutputStream oStream = new BufferedOutputStream(new FileOutputStream(file))) {
+        int bCount = 0;
+        while ((bCount = iStream.read(block)) > 0) {
+          totalBytes += bCount;
+          oStream.write(block, 0, bCount);
         }
       }
     }
+
     return totalBytes;
   }
 
@@ -820,15 +835,16 @@
    */
   public static String getFileNameExtension(String fileName) {
     StringBuffer buffer = new StringBuffer();
-    int begIndex = fileName.lastIndexOf('.');
+    int begIndex = fileName.lastIndexOf(DOT);
     if (begIndex > 0) {
-      buffer.append('.');
+      buffer.append(DOT);
       for (int i = begIndex + 1; i < fileName.length(); i++) {
         char ch = fileName.charAt(i);
-        if (Character.isLetterOrDigit(ch))
+        if (Character.isLetterOrDigit(ch)) {
           buffer.append(ch);
-        else
+        } else {
           break;
+        }
       }
     }
     return buffer.toString();
@@ -840,14 +856,16 @@
    * @param fileLocation
    *          The given file location - local file path or URL.
    * @return The given file size, if the specified file can be accessed, -1 otherwise.
+   * @deprecated use Java 7 method for this see {@link java.nio.file.Files#size(Path)}
    */
+  @Deprecated
   public static long getFileSize(String fileLocation) {
     long fileSize = 0;
     // choose file size method: local FS or HTTP
     File file = new File(fileLocation);
-    if (file.isFile())
+    if (file.isFile()) {
       fileSize = file.length();
-    else {
+    } else {
       try {
         URL fileUrl = new URL(fileLocation);
         URLConnection urlConn = fileUrl.openConnection();
@@ -872,46 +890,50 @@
    * @return The relative path of the given object, located in the given root directory.
    */
   public static String getRelativePath(File rootDir, String absolutePath) {
-    String rootDirPath = rootDir.getAbsolutePath().replace('\\', '/');
-    String objectPath = absolutePath.replace('\\', '/');
-    if (objectPath.startsWith(rootDirPath))
+    String rootDirPath = normalizeToUnix(rootDir.getAbsolutePath());
+    String objectPath = normalizeToUnix(absolutePath);
+    if (objectPath.startsWith(rootDirPath)) {
       objectPath = objectPath.substring(rootDirPath.length());
-    if (objectPath.startsWith("/"))
+    }
+    if (objectPath.startsWith("/")) {
       objectPath = objectPath.substring(1);
+    }
     return objectPath;
   }
 
   /**
    * Makes and attempt to identify possible UTF signature (BOM) in a given sequence of bytes.
-   * Returns the identified UTF signature name or <code>null</code>, if the signature could not
-   * be identified. For more on UTF and its signatures see <a
-   * href="http://www.unicode.org/faq/utf_bom.html" target="_blank"> FAQ - UTF and BOM</a>.
+   * Returns the identified UTF signature name or <code>null</code>, if the signature could not be
+   * identified. For more on UTF and its signatures see
+   * <a href="http://www.unicode.org/faq/utf_bom.html" target="_blank"> FAQ - UTF and BOM</a>.
    * 
    * @param prefix
    *          The given sequence of bytes to analyze.
    * @param length
    *          The length of the given sequence of bytes.
-   * @return The UTF signature name or <code>null</code>, if the signature could not be
-   *         identified.
+   * @return The UTF signature name or <code>null</code>, if the signature could not be identified.
    */
   public static String identifyUtfSignature(int[] prefix, int length) {
     String utfSignature = null;
     if (length == 3) {
       // check for UTF-8 signature
-      if (prefix[0] == 0xEF && prefix[1] == 0xBB && prefix[2] == 0xBF)
-        utfSignature = "UTF-8";
+      if (prefix[0] == 0xEF && prefix[1] == 0xBB && prefix[2] == 0xBF) {
+        utfSignature = UTF8_ENCODING;
+      }
     } else if (length == 2) {
       // check for UTF-16 signature
-      if (prefix[0] == 0xFE && prefix[1] == 0xFF)
+      if (prefix[0] == 0xFE && prefix[1] == 0xFF) {
         utfSignature = "UTF-16BE";
-      else if (prefix[0] == 0xFF && prefix[1] == 0xFE)
+      } else if (prefix[0] == 0xFF && prefix[1] == 0xFE) {
         utfSignature = "UTF-16LE";
+      }
     } else if (length == 4) {
       // check for UTF-32 signature
-      if (prefix[0] == 0x00 && prefix[1] == 0x00 && prefix[2] == 0xFE && prefix[3] == 0xFF)
+      if (prefix[0] == 0x00 && prefix[1] == 0x00 && prefix[2] == 0xFE && prefix[3] == 0xFF) {
         utfSignature = "UTF-32BE";
-      else if (prefix[0] == 0xFF && prefix[1] == 0xFE && prefix[2] == 0x00 && prefix[3] == 0x00)
+      } else if (prefix[0] == 0xFF && prefix[1] == 0xFE && prefix[2] == 0x00 && prefix[3] == 0x00) {
         utfSignature = "UTF-32LE";
+      }
     }
     return utfSignature;
   }
@@ -928,23 +950,9 @@
    *           If an I/O exception occurred.
    */
   public static boolean isAsciiFile(File textFile) throws IOException {
-    boolean isAscii = true;
-    FileInputStream iStream = null;
-    try {
-      iStream = new FileInputStream(textFile);
-      isAscii = isAsciiStream(iStream);
-      iStream.close();
-    } catch (IOException exc) {
-      isAscii = false;
-      throw exc;
-    } finally {
-      if (iStream != null)
-        try {
-          iStream.close();
-        } catch (Exception e) {
-        }
+    try (FileInputStream iStream = new FileInputStream(textFile)) {
+      return isAsciiStream(iStream);
     }
-    return isAscii;
   }
 
   /**
@@ -960,17 +968,12 @@
    */
   public static boolean isAsciiStream(InputStream iStream) throws IOException {
     boolean isAscii = true;
-    try {
-      int nextByte = 0;
-      while ((nextByte = iStream.read()) >= 0) {
-        if (nextByte > 127) {
-          isAscii = false;
-          break;
-        }
+    int nextByte = 0;
+    while ((nextByte = iStream.read()) >= 0) {
+      if (nextByte > 127) {
+        isAscii = false;
+        break;
       }
-    } catch (IOException exc) {
-      isAscii = false;
-      throw exc;
     }
     return isAscii;
   }
@@ -986,18 +989,19 @@
    */
   public static String[] loadListOfStrings(BufferedReader iStream) throws IOException {
     String[] outputArray = null;
-    List<String> outputList = new ArrayList<String>();
+    List<String> outputList = new ArrayList<>();
     String line = null;
     while ((line = iStream.readLine()) != null) {
       String string = line.trim();
-      if (string.length() > 0)
+      if (string.length() > 0) {
         outputList.add(string);
+      }
     }
     if (outputList.size() > 0) {
       outputArray = new String[outputList.size()];
       outputList.toArray(outputArray);
     }
-    return (outputArray != null) ? outputArray : new String[0];
+    return (outputArray != null) ? outputArray : Constants.EMPTY_STRING_ARRAY;
   }
 
   /**
@@ -1009,24 +1013,17 @@
    * @return The array of non-empty strings loaded from the given text file.
    * @throws IOException
    *           If any I/O exception occurred.
+   * @deprecated use Java 7 method for this see
+   *             {@link java.nio.file.Files#readAllLines(Path, Charset)}
    */
+  @Deprecated
   public static String[] loadListOfStrings(File textFile) throws IOException {
-    BufferedReader iStream = null;
-    String[] outputArray = null;
-    try {
-      iStream = new BufferedReader(new InputStreamReader(new FileInputStream(textFile)));
+    String[] outputArray;
+    try (BufferedReader iStream = new BufferedReader(
+            new InputStreamReader(new FileInputStream(textFile)))) {
       outputArray = loadListOfStrings(iStream);
-    } catch (IOException exc) {
-      throw exc;
-    } finally {
-      if (iStream != null) {
-        try {
-          iStream.close();
-        } catch (Exception e) {
-        }
-      }
     }
-    return (outputArray != null) ? outputArray : new String[0];
+    return (outputArray != null) ? outputArray : Constants.EMPTY_STRING_ARRAY;
   }
 
   /**
@@ -1042,22 +1039,12 @@
     URLConnection urlConnection = textFileURL.openConnection();
     // See https://issues.apache.org/jira/browse/UIMA-1746
     urlConnection.setUseCaches(false);
-    BufferedReader iStream = null;
-    String[] outputArray = null;
-    try {
-      iStream = new BufferedReader(new InputStreamReader(urlConnection.getInputStream()));
+    String[] outputArray;
+    try (BufferedReader iStream = new BufferedReader(
+            new InputStreamReader(urlConnection.getInputStream()))) {
       outputArray = loadListOfStrings(iStream);
-    } catch (IOException exc) {
-      throw exc;
-    } finally {
-      if (iStream != null) {
-        try {
-          iStream.close();
-        } catch (Exception e) {
-        }
-      }
     }
-    return (outputArray != null) ? outputArray : new String[0];
+    return (outputArray != null) ? outputArray : Constants.EMPTY_STRING_ARRAY;
   }
 
   /**
@@ -1067,29 +1054,20 @@
    *          The given properties file path in the JAR file.
    * @param jarFile
    *          The given JAR file.
-   * @return <code>Properties</code> object containing loaded properties, or <code>null</code>,
-   *         if the properties file was not found in the given JAR file.
+   * @return <code>Properties</code> object containing loaded properties, or <code>null</code>, if
+   *         the properties file was not found in the given JAR file.
    * @throws IOException
    *           If any I/O exception occurred.
    */
   public static Properties loadPropertiesFromJar(String propFilePath, JarFile jarFile)
           throws IOException {
     Properties properties = null;
-    String name = propFilePath.replace('\\', '/');
+    String name = normalizeToUnix(propFilePath);
     JarEntry jarEntry = jarFile.getJarEntry(name);
     if (jarEntry != null) {
-      InputStream iStream = null;
-      try {
-        iStream = jarFile.getInputStream(jarEntry);
+      try (InputStream iStream = jarFile.getInputStream(jarEntry)) {
         properties = new Properties();
         properties.load(iStream);
-      } finally {
-        if (iStream != null) {
-          try {
-            iStream.close();
-          } catch (Exception e) {
-          }
-        }
       }
     }
     return properties;
@@ -1105,26 +1083,13 @@
    *           If any I/O exception occurs.
    */
   public static String loadTextFile(BufferedReader iStream) throws IOException {
-    StringWriter buffer = null;
-    PrintWriter writer = null;
-    try {
-      buffer = new StringWriter();
-      writer = new PrintWriter(buffer);
+    try (StringWriter buffer = new StringWriter(); PrintWriter writer = new PrintWriter(buffer);) {
       String line = null;
-      while ((line = iStream.readLine()) != null)
+      while ((line = iStream.readLine()) != null) {
         writer.println(line);
-      writer.flush();
-    } catch (IOException exc) {
-      throw exc;
-    } finally {
-      if (writer != null) {
-        try {
-          writer.close();
-        } catch (Exception e) {
-        }
       }
+      return buffer.toString();
     }
-    return buffer.toString();
   }
 
   /**
@@ -1135,24 +1100,15 @@
    *          The given text file.
    * @throws IOException
    *           If any I/O exception occurs.
+   * @deprecated use main file util for this, see
+   *             {@link org.apache.uima.util.FileUtils#file2String(File)} if using the default
+   *             charset is OK
    */
+  @Deprecated
   public static String loadTextFile(File textFile) throws IOException {
-    BufferedReader iStream = null;
-    String content = null;
-    try {
-      iStream = new BufferedReader(new FileReader(textFile));
-      content = loadTextFile(iStream);
-    } catch (IOException exc) {
-      throw exc;
-    } finally {
-      if (iStream != null) {
-        try {
-          iStream.close();
-        } catch (Exception e) {
-        }
-      }
+    try (BufferedReader iStream = new BufferedReader(new FileReader(textFile))) {
+      return loadTextFile(iStream);
     }
-    return content;
   }
 
   /**
@@ -1165,24 +1121,16 @@
    *          The given text file encoding name.
    * @throws IOException
    *           If any I/O exception occurs.
+   * @deprecated use main file util for this, see
+   *             {@link org.apache.uima.util.FileUtils#file2String(File, String)} if using the
+   *             default Charset is OK
    */
+  @Deprecated
   public static String loadTextFile(File textFile, String encoding) throws IOException {
-    BufferedReader iStream = null;
-    String content = null;
-    try {
-      iStream = new BufferedReader(new InputStreamReader(new FileInputStream(textFile), encoding));
-      content = loadTextFile(iStream);
-    } catch (IOException exc) {
-      throw exc;
-    } finally {
-      if (iStream != null) {
-        try {
-          iStream.close();
-        } catch (Exception e) {
-        }
-      }
+    try (BufferedReader iStream = new BufferedReader(
+            new InputStreamReader(new FileInputStream(textFile), encoding))) {
+      return loadTextFile(iStream);
     }
-    return content;
   }
 
   /**
@@ -1197,7 +1145,7 @@
   public static String loadTextFile(URL textFileURL) throws IOException {
     URLConnection urlConnection = textFileURL.openConnection();
     // See https://issues.apache.org/jira/browse/UIMA-1746
-    urlConnection.setUseCaches(false);    
+    urlConnection.setUseCaches(false);
     return loadTextFile(urlConnection);
   }
 
@@ -1211,24 +1159,12 @@
    *           If any I/O exception occurs.
    */
   public static String loadTextFile(URLConnection urlConnection) throws IOException {
-    BufferedReader iStream = null;
-    String content = null;
     // See https://issues.apache.org/jira/browse/UIMA-1746
-    urlConnection.setUseCaches(false);    
-    try {
-      iStream = new BufferedReader(new InputStreamReader(urlConnection.getInputStream()));
-      content = loadTextFile(iStream);
-    } catch (IOException exc) {
-      throw exc;
-    } finally {
-      if (iStream != null) {
-        try {
-          iStream.close();
-        } catch (Exception e) {
-        }
-      }
+    urlConnection.setUseCaches(false);
+    try (BufferedReader iStream = new BufferedReader(
+            new InputStreamReader(urlConnection.getInputStream()))) {
+      return loadTextFile(iStream);
     }
-    return content;
   }
 
   /**
@@ -1238,27 +1174,19 @@
    *          The specified text file path inside the JAR file.
    * @param jarFile
    *          The given JAR file.
-   * @return The content of the text specified file, or <code>null</code>, if the text file was
-   *         not found in the given JAR file.
+   * @return The content of the text specified file, or <code>null</code>, if the text file was not
+   *         found in the given JAR file.
    * @throws IOException
    *           If any I/O exception occurs.
    */
   public static String loadTextFileFromJar(String filePath, JarFile jarFile) throws IOException {
     String content = null;
-    String name = filePath.replace('\\', '/');
+    String name = normalizeToUnix(filePath);
     JarEntry jarEntry = jarFile.getJarEntry(name);
     if (jarEntry != null) {
-      BufferedReader iStream = null;
-      try {
-        iStream = new BufferedReader(new InputStreamReader(jarFile.getInputStream(jarEntry)));
+      try (BufferedReader iStream = new BufferedReader(
+              new InputStreamReader(jarFile.getInputStream(jarEntry)))) {
         content = loadTextFile(iStream);
-      } finally {
-        if (iStream != null) {
-          try {
-            iStream.close();
-          } catch (Exception e) {
-          }
-        }
       }
     }
     return content;
@@ -1274,7 +1202,7 @@
   public static String localPathToFileUrl(String path) {
     // get absolute path
     File file = new File(path);
-    String absPath = file.getAbsolutePath().replace('\\', '/');
+    String absPath = normalizeToUnix(file.getAbsolutePath());
     // construct file URL
     StringBuffer urlBuffer = new StringBuffer("file:///");
     urlBuffer.append(absPath.replace(':', '|'));
@@ -1299,12 +1227,15 @@
    *         otherwise.
    * @throws IOException
    *           If any I/O exception occurred.
+   * @deprecated use Java 7 for this see {@link java.nio.file.Files#move(Path, Path, CopyOption...)}
    */
+  @Deprecated
   public static boolean moveFile(File source, File destinationDir) throws IOException {
     boolean completed = false;
     File destination = new File(destinationDir, source.getName());
-    if (destination.exists())
+    if (destination.exists()) {
       destination.delete();
+    }
     if (copyFile(source, destination)) {
       completed = source.delete();
     }
@@ -1312,9 +1243,9 @@
   }
 
   /**
-   * Replaces all occurrences of a given regular expression with a given string in a given text file.
-   * Supports only 1 file encoding - ASCII - for all general text files. Supports 2 encodings -
-   * UTF-8 (ASCII) and UTF-16 for XML files.
+   * Replaces all occurrences of a given regular expression with a given string in a given text
+   * file. Supports only 1 file encoding - ASCII - for all general text files. Supports 2 encodings
+   * - UTF-8 (ASCII) and UTF-16 for XML files.
    * 
    * @param textFile
    *          The given text file.
@@ -1330,23 +1261,22 @@
           throws IOException {
     int counter = 0;
     // for general text file - supporting ASCII encoding only
-    String encoding = "ASCII";
+    String encoding = ASCII_ENCODING;
     // check file extension
-    int extIndex = textFile.getName().lastIndexOf('.');
+    int extIndex = textFile.getName().lastIndexOf(DOT);
     String fileExt = (extIndex > 0) ? textFile.getName().substring(extIndex) : null;
-    if (".xml".equalsIgnoreCase(fileExt)) {
+    if (XML_EXTENSION.equalsIgnoreCase(fileExt)) {
       // for XML file - supporting UTF-8 (ASCII) and UTF-16 encodings
       String xmlEncoding = XMLUtil.detectXmlFileEncoding(textFile);
       if (xmlEncoding != null) {
         encoding = xmlEncoding;
       } else {
-        encoding = "UTF-8";
+        encoding = UTF8_ENCODING;
       }
     }
     // load text file, using supported encoding
     String fileContent = loadTextFile(textFile, encoding);
-    BufferedReader sReader = null;
-    PrintStream fStream = null;
+
     boolean done = false;
     File backupFile = null;
     // get pattern for given regex
@@ -1355,51 +1285,48 @@
     String replaceWith = StringUtil.toRegExpReplacement(replacement);
     try {
       // save backup copy of input file
-      backupFile = new File(textFile.getAbsolutePath() + ".bak");
-      if (backupFile.exists())
+      backupFile = new File(textFile.getAbsolutePath() + BACKUP_EXTENSION);
+      if (backupFile.exists()) {
         backupFile.delete();
-      if (!textFile.renameTo(backupFile))
-        throw new IOException("can't save backup copy of " + textFile.getAbsolutePath());
-      sReader = new BufferedReader(new StringReader(fileContent));
-      fStream = new PrintStream(new FileOutputStream(textFile), true, encoding);
-      String srcLine = null;
-      while ((srcLine = sReader.readLine()) != null) {
-        // count pattern matches in the source string
-        Matcher matcher = pattern.matcher(srcLine);
-        while (matcher.find())
-          counter++;
-        // replace all pattern matches in the source string
-        String resLine = srcLine.replaceAll(subStringRegex, replaceWith);
-        fStream.println(resLine);
       }
-      fStream.close();
+      if (!textFile.renameTo(backupFile)) {
+        throw new IOException("can't save backup copy of " + textFile.getAbsolutePath());
+      }
+      try (BufferedReader sReader = new BufferedReader(new StringReader(fileContent));
+              PrintStream fStream = new PrintStream(new FileOutputStream(textFile), true,
+                      encoding);) {
+        String srcLine = null;
+        while ((srcLine = sReader.readLine()) != null) {
+          // count pattern matches in the source string
+          Matcher matcher = pattern.matcher(srcLine);
+          while (matcher.find()) {
+            counter++;
+          }
+          // replace all pattern matches in the source string
+          String resLine = srcLine.replaceAll(subStringRegex, replaceWith);
+          fStream.println(resLine);
+        }
+      }
       done = true;
     } catch (IOException exc) {
       throw exc;
     } catch (Throwable err) {
-      if (err instanceof IOException)
+      if (err instanceof IOException) {
         throw new IOException(err.toString() + " in " + textFile.getAbsolutePath());
+      }
       throw new RuntimeException(err.toString() + " in " + textFile.getAbsolutePath());
     } finally {
-      if (sReader != null) {
-        try {
-          sReader.close();
-        } catch (Exception e) {
-        }
-      }
-      if (fStream != null) {
-        try {
-          fStream.close();
-        } catch (Exception e) {
-        }
-      }
       if (done) {
         // remove backup file
-        backupFile.delete();
+        if (backupFile != null) {
+          backupFile.delete();
+        }
       } else {
         // restore input file
         textFile.delete();
-        backupFile.renameTo(textFile);
+        if (backupFile != null) {
+          backupFile.renameTo(textFile);
+        }
       }
     }
     return counter;
@@ -1413,7 +1340,7 @@
    * @return The list of files sorted by the 'last modified' time in the descending order.
    */
   public static SortedSet<File> sortFileListByTime(Collection<File> fileList) {
-    TreeSet<File> set = new TreeSet<File>(new FileTimeComparator());
+    TreeSet<File> set = new TreeSet<>(new FileTimeComparator());
     set.addAll(fileList);
     return set;
   }
@@ -1430,7 +1357,7 @@
    */
   public static File zipDirectory(File dir2zip) throws IOException {
     // construct zipped file path
-    String zipFileName = dir2zip.getName() + ".zip";
+    String zipFileName = dir2zip.getName() + ZIP_EXTENSION;
     File zipFile = new File(dir2zip, zipFileName);
     return zipDirectory(dir2zip, zipFile);
   }
@@ -1447,21 +1374,12 @@
    *           If any I/O exception occurred.
    */
   public static File zipDirectory(File dir2zip, File zippedFile) throws IOException {
-    ZipOutputStream zoStream = null;
-    try {
+    try (ZipOutputStream zoStream = new ZipOutputStream(new FileOutputStream(zippedFile))) {
       // open compressed output stream
-      zoStream = new ZipOutputStream(new FileOutputStream(zippedFile));
       // add output zip file to exclusions
       File[] excludeFiles = new File[1];
       excludeFiles[0] = zippedFile;
       zipDirectory(dir2zip, zoStream, dir2zip, excludeFiles);
-    } finally {
-      if (zoStream != null) {
-        try {
-          zoStream.close();
-        } catch (Exception e) {
-        }
-      }
     }
     return zippedFile;
   }
@@ -1469,8 +1387,8 @@
   /**
    * Zips the contents of a given directory to a given ZIP output stream. Paths of file entries in
    * the ZIP stream are taken relatively to a given reference directory. If the reference directory
-   * is <code>null</code>, the file paths are taken relatively to the given directory to be
-   * zipped. The method allows to specify the list of files (or dirs) that should not be zipped.
+   * is <code>null</code>, the file paths are taken relatively to the given directory to be zipped.
+   * The method allows to specify the list of files (or dirs) that should not be zipped.
    * 
    * @param dir2zip
    *          The given directory to be zipped.
@@ -1488,48 +1406,43 @@
           File referenceDir, File[] excludeFiles) throws IOException {
     byte[] block = new byte[4096];
     int inBytes = 0;
-    FileInputStream iStream = null;
-    try {
-      // get list of all files/dirs in the given directory
-      File[] dirFileList = dir2zip.listFiles();
-      // compress all files and sub-dirs
-      for (int i = 0; i < dirFileList.length; i++) {
-        File entry = dirFileList[i];
-        // check if this entry is not in the list of exclusions
-        boolean isExcluded = false;
-        for (int n = 0; n < excludeFiles.length; n++) {
-          if (entry.equals(excludeFiles[n])) {
-            isExcluded = true;
-            break;
-          }
+
+    // get list of all files/dirs in the given directory
+    File[] dirFileList = dir2zip.listFiles();
+    // compress all files and sub-dirs
+    for (int i = 0; i < dirFileList.length; i++) {
+      File entry = dirFileList[i];
+      // check if this entry is not in the list of exclusions
+      boolean isExcluded = false;
+      for (int n = 0; n < excludeFiles.length; n++) {
+        if (entry.equals(excludeFiles[n])) {
+          isExcluded = true;
+          break;
         }
-        if (isExcluded)
-          continue;
-        // for each file - add ZipEntry and compress the file
-        if (entry.isFile()) {
-          // open input stream
-          iStream = new FileInputStream(entry);
+      }
+      if (isExcluded) {
+        continue;
+      }
+      // for each file - add ZipEntry and compress the file
+      if (entry.isFile()) {
+        // open input stream
+        try (FileInputStream iStream = new FileInputStream(entry)) {
           // put ZipEntry for the file
-          String zipEntryName = (referenceDir != null) ? getRelativePath(referenceDir, entry
-                  .getAbsolutePath()) : getRelativePath(dir2zip, entry.getAbsolutePath());
+          String zipEntryName = (referenceDir != null)
+                  ? getRelativePath(referenceDir, entry.getAbsolutePath())
+                  : getRelativePath(dir2zip, entry.getAbsolutePath());
           ZipEntry zipEntry = new ZipEntry(zipEntryName);
           zoStream.putNextEntry(zipEntry);
           // read input stream and write to output stream
-          while ((inBytes = iStream.read(block)) > 0)
+          while ((inBytes = iStream.read(block)) > 0) {
             zoStream.write(block, 0, inBytes);
-          // close input stream
-          iStream.close();
-        } else if (entry.isDirectory()) // zip sub-dir recursively
-          zipDirectory(entry, zoStream, referenceDir, excludeFiles);
-      }
-    } finally {
-      if (iStream != null) {
-        try {
-          iStream.close();
-        } catch (Exception e) {
+          }
         }
+      } else if (entry.isDirectory()) { // zip sub-dir recursively
+        zipDirectory(entry, zoStream, referenceDir, excludeFiles);
       }
     }
+
     return zoStream;
   }
 
@@ -1546,9 +1459,9 @@
   public static File zipFile(File file2zip) throws IOException {
     // construct zipped file path
     String zipFileName = file2zip.getName();
-    int extIndex = zipFileName.lastIndexOf('.');
-    zipFileName = (extIndex >= 0) ? zipFileName.substring(0, extIndex) + ".zip" : zipFileName
-            + ".zip";
+    int extIndex = zipFileName.lastIndexOf(DOT);
+    zipFileName = (extIndex >= 0) ? zipFileName.substring(0, extIndex) + ZIP_EXTENSION
+            : zipFileName + ZIP_EXTENSION;
     File zipFile = new File(file2zip.getParentFile(), zipFileName);
     return zipFile(file2zip, zipFile);
   }
@@ -1567,32 +1480,15 @@
   public static File zipFile(File file2zip, File zippedFile) throws IOException {
     byte[] block = new byte[4096];
     int inBytes = 0;
-    FileInputStream iStream = null;
-    ZipOutputStream oStream = null;
-    try {
-      // open input stream
-      iStream = new FileInputStream(file2zip);
+    try (FileInputStream iStream = new FileInputStream(file2zip);
+            ZipOutputStream oStream = new ZipOutputStream(new FileOutputStream(zippedFile));) {
       // create ZipEntry, using input file name
       ZipEntry zipEntry = new ZipEntry(file2zip.getName());
-      // open compressed output stream
-      oStream = new ZipOutputStream(new FileOutputStream(zippedFile));
       // add new ZipEntry
       oStream.putNextEntry(zipEntry);
       // read input stream and write to output stream
-      while ((inBytes = iStream.read(block)) > 0)
+      while ((inBytes = iStream.read(block)) > 0) {
         oStream.write(block, 0, inBytes);
-    } finally {
-      if (iStream != null) {
-        try {
-          iStream.close();
-        } catch (Exception e) {
-        }
-      }
-      if (oStream != null) {
-        try {
-          oStream.close();
-        } catch (Exception e) {
-        }
       }
     }
     return zippedFile;
diff --git a/uimaj-core/src/main/java/org/apache/uima/util/impl/Constants.java b/uimaj-core/src/main/java/org/apache/uima/util/impl/Constants.java
new file mode 100644
index 0000000..789e802
--- /dev/null
+++ b/uimaj-core/src/main/java/org/apache/uima/util/impl/Constants.java
@@ -0,0 +1,42 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ * 
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.uima.util.impl;
+
+import java.io.File;
+import java.net.URL;
+
+import org.apache.uima.cas.impl.FeatureImpl;
+import org.apache.uima.jcas.cas.TOP;
+import org.apache.uima.resource.metadata.ConfigurationParameter;
+
+/**
+ * Constants
+ */
+public interface Constants {
+  String[] EMPTY_STRING_ARRAY = new String[0];
+  FeatureImpl[] EMPTY_FEATURE_ARRAY = new FeatureImpl[0];
+  int[] EMPTY_INT_ARRAY = new int[0];
+  Class<?>[] EMPTY_CLASS_ARRAY = new Class[0];
+  char[] EMPTY_CHAR_ARRAY = new char[0];
+  TOP[] EMPTY_TOP_ARRAY = new TOP[0];
+  File[] EMPTY_FILE_ARRAY = new File[0];
+  URL[] EMPTY_URL_ARRAY = new URL[0];
+  ConfigurationParameter[] EMPTY_CONFIG_PARM_ARRAY = new ConfigurationParameter[0];
+  Object[] EMPTY_OBJ_ARRAY = new Object[0];
+}
diff --git a/uimaj-core/src/test/java/org/apache/uima/util/FileUtilsTest.java b/uimaj-core/src/test/java/org/apache/uima/util/FileUtilsTest.java
index 7119bdc..9c1b60a 100644
--- a/uimaj-core/src/test/java/org/apache/uima/util/FileUtilsTest.java
+++ b/uimaj-core/src/test/java/org/apache/uima/util/FileUtilsTest.java
@@ -18,13 +18,24 @@
  */
 package org.apache.uima.util;
 
+import static org.apache.uima.pear.util.FileUtil.extractFilesFromJar;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
+import static org.junit.Assert.assertEquals;
+
 import java.io.File;
+import java.io.FileOutputStream;
 import java.io.IOException;
+import java.nio.file.Path;
+import java.util.jar.JarFile;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipOutputStream;
 
-import junit.framework.TestCase;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.io.TempDir;
 
-
-public class FileUtilsTest extends TestCase {
+public class FileUtilsTest {
+  @Test
   public void testFindRelativePath() throws Exception {
     File target = new File("/this/is/a/file.txt");
     File base = new File("/this/is/a/test");
@@ -41,24 +52,37 @@
     }
   }
 
-  public void testReadWriteTempFile() throws IOException {
-    final String tmpDirPath = System.getProperty("java.io.tmpdir");
-    assertNotNull("java.io.tmpdir system property not available", tmpDirPath);
-    File tmpDir = FileUtils.createTempDir(new File(tmpDirPath), "fileUtilsTest");
-    File tmpFile1 = FileUtils.createTempFile("test", null, tmpDir);
-    File tmpFile2 = FileUtils.createTempFile("test", null, tmpDir);
+  @Test
+  public void testReadWriteTempFile(@TempDir Path aTempDir) throws IOException {
+    File tmpFile2 = aTempDir.resolve("file2.txt").toFile();
     final String text = "This is some text to test file writing.  Add an Umlaut for encoding tests:"
-        + "\n  Greetings from T\u00FCbingen!\n";
+            + "\n  Greetings from T\u00FCbingen!\n";
     final String utf8 = "UTF-8";
 
-   //  UIMA-2050 Does not work on all platform encodings
-   //  Solution: Do not do it!
-   //  FileUtils.saveString2File(text, tmpFile1);
-   //  assertEquals(text, FileUtils.file2String(tmpFile1));
-    
+    // UIMA-2050 Does not work on all platform encodings
+    // Solution: Do not do it!
+    // FileUtils.saveString2File(text, tmpFile1);
+    // assertEquals(text, FileUtils.file2String(tmpFile1));
+
     FileUtils.saveString2File(text, tmpFile2, utf8);
-    assertEquals(text, FileUtils.file2String(tmpFile2, utf8));
-    
-    FileUtils.deleteRecursive(tmpDir);
+    assertThat(FileUtils.file2String(tmpFile2, utf8)).isEqualTo(text);
+  }
+
+  @Test
+  void thatAllFilesGoToTargetFolder(@TempDir Path aTempDir) throws Exception {
+    File zipFile = aTempDir.resolve("test.zip").toFile();
+
+    try (ZipOutputStream oStream = new ZipOutputStream(new FileOutputStream(zipFile))) {
+      ZipEntry zipEntry = new ZipEntry("../whoops.txt");
+      oStream.putNextEntry(zipEntry);
+    }
+
+    File target = aTempDir.resolve("target").toFile();
+
+    try (JarFile jarFile = new JarFile(zipFile)) {
+      assertThatExceptionOfType(IOException.class)
+              .isThrownBy(() -> extractFilesFromJar(jarFile, target))
+              .withMessageContaining("Can only write within target folder");
+    }
   }
 }