diff --git a/apps/i2psnark/java/src/org/klomp/snark/SnarkManager.java b/apps/i2psnark/java/src/org/klomp/snark/SnarkManager.java
index e33d0b25aa357eb3aba0de68bb2b26a5cfffb223..e1e2e03726268efca0d3a3bf1793b3b2b176ba1c 100644
--- a/apps/i2psnark/java/src/org/klomp/snark/SnarkManager.java
+++ b/apps/i2psnark/java/src/org/klomp/snark/SnarkManager.java
@@ -34,6 +34,7 @@ import net.i2p.data.Base64;
 import net.i2p.data.DataHelper;
 import net.i2p.update.*;
 import net.i2p.util.ConcurrentHashSet;
+import net.i2p.util.FileSuffixFilter;
 import net.i2p.util.FileUtil;
 import net.i2p.util.I2PAppThread;
 import net.i2p.util.Log;
@@ -2577,14 +2578,14 @@ public class SnarkManager implements CompleteListener, ClientApp {
      */
     private boolean monitorTorrents(File dir) {
         boolean rv = true;
-        String fileNames[] = dir.list(TorrentFilenameFilter.instance());
+        File files[] = dir.listFiles(new FileSuffixFilter(".torrent"));
         List<String> foundNames = new ArrayList<String>(0);
-        if (fileNames != null) {
-            for (int i = 0; i < fileNames.length; i++) {
+        if (files != null) {
+            for (int i = 0; i < files.length; i++) {
                 try {
-                    foundNames.add(new File(dir, fileNames[i]).getCanonicalPath());
+                    foundNames.add(files[i].getCanonicalPath());
                 } catch (IOException ioe) {
-                    _log.error("Error resolving '" + fileNames[i] + "' in '" + dir, ioe);
+                    _log.error("Error resolving '" + files[i] + "' in '" + dir, ioe);
                 }
             }
         }
@@ -2731,14 +2732,6 @@ public class SnarkManager implements CompleteListener, ClientApp {
         saveConfig();
     }
 
-    private static class TorrentFilenameFilter implements FilenameFilter {
-        private static final TorrentFilenameFilter _filter = new TorrentFilenameFilter();
-        public static TorrentFilenameFilter instance() { return _filter; }
-        public boolean accept(File dir, String name) {
-            return (name != null) && (name.endsWith(".torrent"));
-        }
-    }
-
     /**
      *  If not connected, thread it, otherwise inline
      *  @throws RuntimeException via Snark.fatal()
diff --git a/apps/routerconsole/java/src/net/i2p/router/news/PersistNews.java b/apps/routerconsole/java/src/net/i2p/router/news/PersistNews.java
index 164b04422cf87d95c7a10b4fd89aa106e4eb6193..132769451d0e46cfd51aa0da196bc9d76f844617 100644
--- a/apps/routerconsole/java/src/net/i2p/router/news/PersistNews.java
+++ b/apps/routerconsole/java/src/net/i2p/router/news/PersistNews.java
@@ -19,6 +19,7 @@ import net.i2p.data.Base64;
 import net.i2p.data.DataHelper;
 import net.i2p.data.Hash;
 import net.i2p.util.Log;
+import net.i2p.util.FileSuffixFilter;
 import net.i2p.util.SecureDirectory;
 import net.i2p.util.SecureFileOutputStream;
 
@@ -98,13 +99,11 @@ class PersistNews {
         Log log = ctx.logManager().getLog(PersistNews.class);
         File dir = new File(ctx.getConfigDir(), DIR);
         List<NewsEntry> rv = new ArrayList<NewsEntry>();
-        File[] files = dir.listFiles();
+        File[] files = dir.listFiles(new FileSuffixFilter(PFX, SFX));
         if (files == null)
             return rv;
         for (File file : files) {
             String name = file.getName();
-            if (!name.startsWith(PFX) || !name.endsWith(SFX))
-                continue;
             XMLParser parser = new XMLParser(ctx);
             InputStream in = null;
             Node node;
diff --git a/apps/routerconsole/java/src/net/i2p/router/web/PluginStarter.java b/apps/routerconsole/java/src/net/i2p/router/web/PluginStarter.java
index 3fa2adece17c6bcd97ea2cdc797a0c59579e6b38..d4a7553f412369285c6452fe51c4aa48f9c5102e 100644
--- a/apps/routerconsole/java/src/net/i2p/router/web/PluginStarter.java
+++ b/apps/routerconsole/java/src/net/i2p/router/web/PluginStarter.java
@@ -30,6 +30,7 @@ import net.i2p.router.startup.LoadClientAppsJob;
 import net.i2p.router.update.ConsoleUpdateManager;
 import static net.i2p.update.UpdateType.*;
 import net.i2p.util.ConcurrentHashSet;
+import net.i2p.util.FileSuffixFilter;
 import net.i2p.util.FileUtil;
 import net.i2p.util.I2PAppThread;
 import net.i2p.util.Log;
@@ -415,13 +416,14 @@ public class PluginStarter implements Runnable {
             File consoleDir = new File(pluginDir, "console");
             Properties wprops = RouterConsoleRunner.webAppProperties(consoleDir.getAbsolutePath());
             File webappDir = new File(consoleDir, "webapps");
-            String fileNames[] = webappDir.list(RouterConsoleRunner.WarFilenameFilter.instance());
-            if (fileNames != null) {
+            File files[] = webappDir.listFiles(RouterConsoleRunner.WAR_FILTER);
+            if (files != null) {
                 if(!pluginWars.containsKey(appName))
                     pluginWars.put(appName, new ConcurrentHashSet<String>());
-                for (int i = 0; i < fileNames.length; i++) {
+                for (int i = 0; i < files.length; i++) {
                     try {
-                        String warName = fileNames[i].substring(0, fileNames[i].lastIndexOf(".war"));
+                        String warName = files[i].getName();
+                        warName = warName.substring(0, warName.lastIndexOf(".war"));
                         //log.error("Found webapp: " + warName);
                         // check for duplicates in $I2P
                         if (Arrays.asList(STANDARD_WEBAPPS).contains(warName)) {
@@ -432,12 +434,12 @@ public class PluginStarter implements Runnable {
                         if (! "false".equals(enabled)) {
                             if (log.shouldLog(Log.INFO))
                                 log.info("Starting webapp: " + warName);
-                            String path = new File(webappDir, fileNames[i]).getCanonicalPath();
+                            String path = files[i].getCanonicalPath();
                             WebAppStarter.startWebApp(ctx, server, warName, path);
                             pluginWars.get(appName).add(warName);
                         }
                     } catch (IOException ioe) {
-                        log.error("Error resolving '" + fileNames[i] + "' in '" + webappDir, ioe);
+                        log.error("Error resolving '" + files[i] + "' in '" + webappDir, ioe);
                     }
                 }
                 // Check for iconfile in plugin.properties
@@ -460,23 +462,21 @@ public class PluginStarter implements Runnable {
         // later in the classpath.
         File localeDir = new File(pluginDir, "console/locale");
         if (localeDir.exists() && localeDir.isDirectory()) {
-            File[] files = localeDir.listFiles();
+            File[] files = localeDir.listFiles(new FileSuffixFilter(".jar"));
             if (files != null) {
                 boolean added = false;
                 for (int i = 0; i < files.length; i++) {
                     File f = files[i];
-                    if (f.getName().endsWith(".jar")) {
-                        try {
-                            addPath(f.toURI().toURL());
-                            log.info("INFO: Adding translation plugin to classpath: " + f);
-                            added = true;
-                        } catch (ClassCastException e) {
-                            log.logAlways(Log.WARN, "Java version: " + System.getProperty("java.version") +
-                                                    " does not support adding classpath element: " + f +
-                                                    " for plugin " + appName);
-                        } catch (RuntimeException e) {
-                            log.error("Plugin " + appName + " bad classpath element: " + f, e);
-                        }
+                    try {
+                        addPath(f.toURI().toURL());
+                        log.info("INFO: Adding translation plugin to classpath: " + f);
+                        added = true;
+                    } catch (ClassCastException e) {
+                        log.logAlways(Log.WARN, "Java version: " + System.getProperty("java.version") +
+                                                " does not support adding classpath element: " + f +
+                                                " for plugin " + appName);
+                    } catch (RuntimeException e) {
+                        log.error("Plugin " + appName + " bad classpath element: " + f, e);
                     }
                 }
                 if (added)
diff --git a/apps/routerconsole/java/src/net/i2p/router/web/RouterConsoleRunner.java b/apps/routerconsole/java/src/net/i2p/router/web/RouterConsoleRunner.java
index ce1d2e7d38d03ecbc64b5a053335ff52511c3d20..abd021a91022948244bce202695f8e75a7295aba 100644
--- a/apps/routerconsole/java/src/net/i2p/router/web/RouterConsoleRunner.java
+++ b/apps/routerconsole/java/src/net/i2p/router/web/RouterConsoleRunner.java
@@ -2,7 +2,7 @@ package net.i2p.router.web;
 
 import java.awt.GraphicsEnvironment;
 import java.io.File;
-import java.io.FilenameFilter;
+import java.io.FileFilter;
 import java.io.IOException;
 import java.io.Serializable;
 import java.io.UnsupportedEncodingException;
@@ -35,6 +35,7 @@ import net.i2p.router.app.RouterApp;
 import net.i2p.router.news.NewsManager;
 import net.i2p.router.update.ConsoleUpdateManager;
 import net.i2p.util.Addresses;
+import net.i2p.util.FileSuffixFilter;
 import net.i2p.util.FileUtil;
 import net.i2p.util.I2PAppThread;
 import net.i2p.util.PortMapper;
@@ -140,6 +141,8 @@ public class RouterConsoleRunner implements RouterApp {
     private static final String THREAD_NAME = "RouterConsole Jetty";
     public static final String PROP_DTG_ENABLED = "desktopgui.enabled";
     static final String PROP_ALLOWED_HOSTS = "routerconsole.allowedHosts";
+    /** @since 0.9.34 */
+    static final FileFilter WAR_FILTER = new WarFilenameFilter();
 
     /**
      *  <pre>
@@ -763,10 +766,11 @@ public class RouterConsoleRunner implements RouterApp {
         List<String> notStarted = new ArrayList<String>();
         if (_server.isRunning()) {
             File dir = new File(_webAppsDir);
-            String fileNames[] = dir.list(WarFilenameFilter.instance());
-            if (fileNames != null) {
-                for (int i = 0; i < fileNames.length; i++) {
-                    String appName = fileNames[i].substring(0, fileNames[i].lastIndexOf(".war"));
+            File files[] = dir.listFiles(WAR_FILTER);
+            if (files != null) {
+                for (int i = 0; i < files.length; i++) {
+                    String appName = files[i].getName();
+                    appName = appName.substring(0, appName.lastIndexOf(".war"));
                     String enabled = props.getProperty(PREFIX + appName + ENABLED);
                     if (appName.equals("addressbook")) {
                         // addressbook.war is now empty, thread is started by SusiDNS
@@ -776,7 +780,7 @@ public class RouterConsoleRunner implements RouterApp {
                         }
                     } else if (! "false".equals(enabled)) {
                         try {
-                            String path = new File(dir, fileNames[i]).getCanonicalPath();
+                            String path = files[i].getCanonicalPath();
                             WebAppStarter.startWebApp(_context, chColl, appName, path);
                             if (enabled == null) {
                                 // do this so configclients.jsp knows about all apps from reading the config
@@ -1115,11 +1119,13 @@ public class RouterConsoleRunner implements RouterApp {
 
     }
 
-    static class WarFilenameFilter implements FilenameFilter {
-        private static final WarFilenameFilter _filter = new WarFilenameFilter();
-        public static WarFilenameFilter instance() { return _filter; }
-        public boolean accept(File dir, String name) {
-            return (name != null) && (name.endsWith(".war") && !name.equals(ROUTERCONSOLE + ".war"));
+    private static class WarFilenameFilter extends FileSuffixFilter {
+        private static final String RCWAR = ROUTERCONSOLE + ".war";
+
+        public WarFilenameFilter() { super(".war"); }
+
+        public boolean accept(File file) {
+            return super.accept(file) && !file.getName().equals(RCWAR);
         }
     }
 
diff --git a/apps/routerconsole/java/src/net/i2p/router/web/WebAppConfiguration.java b/apps/routerconsole/java/src/net/i2p/router/web/WebAppConfiguration.java
index f79533d5ef568e2a743ae11c56f184f01a20dd13..ec671bb7d589064fefa01bd979bd697c1227ff15 100644
--- a/apps/routerconsole/java/src/net/i2p/router/web/WebAppConfiguration.java
+++ b/apps/routerconsole/java/src/net/i2p/router/web/WebAppConfiguration.java
@@ -14,6 +14,7 @@ import java.util.Set;
 import java.util.StringTokenizer;
 
 import net.i2p.I2PAppContext;
+import net.i2p.util.FileSuffixFilter;
 
 import org.apache.tomcat.SimpleInstanceManager;
 import org.eclipse.jetty.webapp.Configuration;
@@ -180,11 +181,11 @@ public class WebAppConfiguration implements Configuration {
             // Java 9 - assume everything in lib/ is in the classpath
             // except addressbook.jar
             File libDir = new File(ctx.getBaseDir(), "lib");
-            File[] files = libDir.listFiles();
+            File[] files = libDir.listFiles(new FileSuffixFilter(".jar"));
             if (files != null) {
                 for (int i = 0; i < files.length; i++) {
                     String name = files[i].getName();
-                    if (name.endsWith(".jar") && !name.equals("addressbook.jar"))
+                    if (!name.equals("addressbook.jar"))
                         rv.add(files[i].toURI());
                 }
             }
diff --git a/apps/routerconsole/java/src/net/i2p/router/web/helpers/CertHelper.java b/apps/routerconsole/java/src/net/i2p/router/web/helpers/CertHelper.java
index 2c80f385ef2f7c1a67b6aeba8d5a01d4205c32e4..1c0386034c0bc6e711a3525dee88c6e273c7ea9d 100644
--- a/apps/routerconsole/java/src/net/i2p/router/web/helpers/CertHelper.java
+++ b/apps/routerconsole/java/src/net/i2p/router/web/helpers/CertHelper.java
@@ -5,6 +5,7 @@ import java.io.IOException;
 
 import net.i2p.data.DataHelper;
 import net.i2p.util.FileUtil;
+import net.i2p.util.FileSuffixFilter;
 import net.i2p.router.crypto.FamilyKeyCrypto;
 import net.i2p.router.web.HelperBase;
 
@@ -37,17 +38,11 @@ public class CertHelper extends HelperBase {
             // i2ptunnel clients
             File tunnelDir = new File(_context.getConfigDir(), I2PTUNNEL_DIR);
             boolean hasTunnels = false;
-            File[] tunnels = tunnelDir.listFiles();
+            File[] tunnels = tunnelDir.listFiles(new FileSuffixFilter("i2ptunnel-", ".local.crt"));
             if (tunnels != null) {
                 for (int i = 0; i < tunnels.length; i++) {
                     File f = tunnels[i];
-                    if (!f.isFile())
-                        continue;
                     String name = f.getName();
-                    if (!name.endsWith(".local.crt"))
-                        continue;
-                    if (!name.startsWith("i2ptunnel-"))
-                        continue;
                     String b32 = name.substring(10, name.length() - 10);
                     output(_t("I2PTunnel") + ' ' + b32, f);
                     hasTunnels = true;
@@ -59,17 +54,10 @@ public class CertHelper extends HelperBase {
             // SAM
             tunnelDir = new File(dir, SAM_DIR);
             hasTunnels = false;
-            tunnels = tunnelDir.listFiles();
+            tunnels = tunnelDir.listFiles(new FileSuffixFilter("sam-", ".local.crt"));
             if (tunnels != null) {
                 for (int i = 0; i < tunnels.length; i++) {
                     File f = tunnels[i];
-                    if (!f.isFile())
-                        continue;
-                    String name = f.getName();
-                    if (!name.endsWith(".local.crt"))
-                        continue;
-                    if (!name.startsWith("sam-"))
-                        continue;
                     output("SAM", f);
                     hasTunnels = true;
                 }
diff --git a/apps/routerconsole/java/src/net/i2p/router/web/helpers/FileDumpHelper.java b/apps/routerconsole/java/src/net/i2p/router/web/helpers/FileDumpHelper.java
index be44d4c2fb0a7d8e3d9ad27183cc2489d0868285..433e4e637ec68706854f2655dac948f50896a812 100644
--- a/apps/routerconsole/java/src/net/i2p/router/web/helpers/FileDumpHelper.java
+++ b/apps/routerconsole/java/src/net/i2p/router/web/helpers/FileDumpHelper.java
@@ -20,6 +20,7 @@ import net.i2p.crypto.SHA256Generator;
 import net.i2p.data.DataHelper;
 import net.i2p.router.web.HelperBase;
 import net.i2p.router.web.PluginStarter;
+import net.i2p.util.FileSuffixFilter;
 import net.i2p.util.FileUtil;
 import net.i2p.util.SystemVersion;
 
@@ -97,13 +98,12 @@ public class FileDumpHelper extends HelperBase {
     }
 
     private static void dumpDir(StringBuilder buf, File dir, String suffix) {
-        File[] files = dir.listFiles();
+        File[] files = dir.listFiles(new FileSuffixFilter(suffix));
         if (files == null)
             return;
         Arrays.sort(files);
         for (int i = 0; i < files.length; i++) {
-            if (files[i].getName().endsWith(suffix))
-                dumpFile(buf, files[i]);
+            dumpFile(buf, files[i]);
         }
     }
 
diff --git a/apps/susimail/src/src/i2p/susi/webmail/PersistentMailCache.java b/apps/susimail/src/src/i2p/susi/webmail/PersistentMailCache.java
index 0f9b3d863b8a46193c8025bcf7ecf94bfaccc856..0df1dd49d5d9286f3ddf7d2ea6b96bee9c2eba23 100644
--- a/apps/susimail/src/src/i2p/susi/webmail/PersistentMailCache.java
+++ b/apps/susimail/src/src/i2p/susi/webmail/PersistentMailCache.java
@@ -31,6 +31,7 @@ import java.util.zip.GZIPOutputStream;
 import net.i2p.I2PAppContext;
 import net.i2p.data.Base64;
 import net.i2p.data.DataHelper;
+import net.i2p.util.FileSuffixFilter;
 import net.i2p.util.I2PAppThread;
 import net.i2p.util.PasswordManager;
 import net.i2p.util.SecureDirectory;
@@ -383,15 +384,11 @@ class PersistentMailCache {
 	private void importMail() {
 		File importDir = new File(_cacheDir.getParentFile(), DIR_IMPORT);
 		if (importDir.exists() && importDir.isDirectory()) {
-			File[] files = importDir.listFiles();
+			File[] files = importDir.listFiles(new FileSuffixFilter(".eml"));
 			if (files == null)
 				return;
 			for (int i = 0; i < files.length; i++) {
 				File f = files[i];
-				if (!f.isFile())
-					continue;
-				if (!f.getName().toLowerCase(Locale.US).endsWith(".eml"))
-					continue;
 				// Read in the headers to get the X-UIDL that Thunderbird stuck in there
 				String uidl = Long.toString(_context.random().nextLong());
 				InputStream in = null;
diff --git a/core/java/src/net/i2p/crypto/CertUtil.java b/core/java/src/net/i2p/crypto/CertUtil.java
index be7d100c938b6c8b02cc5c34edc4f30eb9edbaf1..c443624a17cf8515220f3d7d903659121c5745e5 100644
--- a/core/java/src/net/i2p/crypto/CertUtil.java
+++ b/core/java/src/net/i2p/crypto/CertUtil.java
@@ -40,6 +40,7 @@ import net.i2p.I2PAppContext;
 import net.i2p.data.Base64;
 import net.i2p.data.DataHelper;
 import net.i2p.util.Log;
+import net.i2p.util.FileSuffixFilter;
 import net.i2p.util.SecureFileOutputStream;
 import net.i2p.util.SystemVersion;
 
@@ -486,21 +487,17 @@ public final class CertUtil {
      */
     private static void loadCRLs(Set<X509CRL> crls, File dir) {
         if (dir.exists() && dir.isDirectory()) {
-            File[] files = dir.listFiles();
+            File[] files = dir.listFiles(new FileSuffixFilter(".crl"));
             if (files != null) {
                 for (int i = 0; i < files.length; i++) {
                     File f = files[i];
-                    if (!f.isFile())
-                        continue;
-                    if (f.getName().endsWith(".crl")) {
-                        try {
-                            X509CRL crl = loadCRL(f);
-                            crls.add(crl);
-                        } catch (IOException ioe) {
-                            error("Cannot load CRL from " + f, ioe);
-                        } catch (GeneralSecurityException crle) {
-                            error("Cannot load CRL from " + f, crle);
-                        }
+                    try {
+                        X509CRL crl = loadCRL(f);
+                        crls.add(crl);
+                    } catch (IOException ioe) {
+                        error("Cannot load CRL from " + f, ioe);
+                    } catch (GeneralSecurityException crle) {
+                        error("Cannot load CRL from " + f, crle);
                     }
                 }
             }
diff --git a/core/java/src/net/i2p/util/FileSuffixFilter.java b/core/java/src/net/i2p/util/FileSuffixFilter.java
new file mode 100644
index 0000000000000000000000000000000000000000..38de43060a109116bc361e004ca73b13d9c69597
--- /dev/null
+++ b/core/java/src/net/i2p/util/FileSuffixFilter.java
@@ -0,0 +1,42 @@
+package net.i2p.util;
+    
+import java.io.File;
+import java.io.FileFilter;
+import java.util.Locale;
+
+/**
+ * A FileFilter that accepts regular files
+ * with a suffix and optionally a prefix, case-insensitive.
+ *
+ * @since 0.9.34
+ */
+public class FileSuffixFilter implements FileFilter {
+
+    private final String begin, end;
+
+    /**
+     *  A filter that accepts regular files that
+     *  end with suffix, case-insensitive.
+     */
+    public FileSuffixFilter(String suffix) {
+        begin = null;
+        end = suffix.toLowerCase(Locale.US);
+    }
+
+    /**
+     *  A filter that accepts regular files that
+     *  start with prefix and
+     *  end with suffix, case-insensitive.
+     */
+    public FileSuffixFilter(String prefix, String suffix) {
+        begin = prefix.toLowerCase(Locale.US);
+        end = suffix.toLowerCase(Locale.US);
+    }
+
+    public boolean accept(File file) {
+        String name = file.getName().toLowerCase(Locale.US);
+        return name.endsWith(end) &&
+               (begin == null || name.startsWith(begin)) &&
+               file.isFile();
+    }
+}
diff --git a/installer/tools/java/src/net/i2p/router/networkdb/kademlia/BundleRouterInfos.java b/installer/tools/java/src/net/i2p/router/networkdb/kademlia/BundleRouterInfos.java
index 0dadc097f690c14d67e04f04b9e16ddd4e58464a..f6df2055dc5dee412ae41153227939b039d1fa5e 100644
--- a/installer/tools/java/src/net/i2p/router/networkdb/kademlia/BundleRouterInfos.java
+++ b/installer/tools/java/src/net/i2p/router/networkdb/kademlia/BundleRouterInfos.java
@@ -123,7 +123,7 @@ public class BundleRouterInfos {
         List<File> toRead = new ArrayList<File>(2048);
         for (int j = 0; j < Base64.ALPHABET_I2P.length(); j++) {
             File subdir = new File(dbDir, PersistentDataStore.DIR_PREFIX + Base64.ALPHABET_I2P.charAt(j));
-            File[] files = subdir.listFiles(PersistentDataStore.RouterInfoFilter.getInstance());
+            File[] files = subdir.listFiles(PersistentDataStore.RI_FILTER);
             if (files == null)
                 continue;
             routerCount += files.length;
diff --git a/router/java/src/net/i2p/router/networkdb/kademlia/PersistentDataStore.java b/router/java/src/net/i2p/router/networkdb/kademlia/PersistentDataStore.java
index 515ba8b189036aa4b5abb72cf47b274994f99c70..bef2688bbe5ac5241f77dc40691fe1a4eea65d7f 100644
--- a/router/java/src/net/i2p/router/networkdb/kademlia/PersistentDataStore.java
+++ b/router/java/src/net/i2p/router/networkdb/kademlia/PersistentDataStore.java
@@ -11,8 +11,8 @@ package net.i2p.router.networkdb.kademlia;
 import java.io.BufferedInputStream;
 import java.io.BufferedOutputStream;
 import java.io.File;
+import java.io.FileFilter;
 import java.io.FileInputStream;
-import java.io.FilenameFilter;
 import java.io.Flushable;
 import java.io.IOException;
 import java.io.InputStream;
@@ -34,6 +34,7 @@ import net.i2p.data.router.RouterInfo;
 import net.i2p.router.JobImpl;
 import net.i2p.router.Router;
 import net.i2p.router.RouterContext;
+import net.i2p.util.FileSuffixFilter;
 import net.i2p.util.FileUtil;
 import net.i2p.util.I2PThread;
 import net.i2p.util.Log;
@@ -60,7 +61,7 @@ public class PersistentDataStore extends TransientDataStore {
     private static final String PROP_FLAT = "router.networkDatabase.flat";
     static final String DIR_PREFIX = "r";
     private static final String B64 = Base64.ALPHABET_I2P;
-    
+
     /**
      *  @param dbDir relative path
      */
@@ -392,7 +393,7 @@ public class PersistentDataStore extends TransientDataStore {
         private void readFiles() {
             int routerCount = 0;
 
-            File routerInfoFiles[] = _dbDir.listFiles(RouterInfoFilter.getInstance());
+            File routerInfoFiles[] = _dbDir.listFiles(RI_FILTER);
             if (_flat) {
                 if (routerInfoFiles != null) {
                     routerCount = routerInfoFiles.length;
@@ -421,7 +422,7 @@ public class PersistentDataStore extends TransientDataStore {
                 List<File> toRead = new ArrayList<File>(2048);
                 for (int j = 0; j < B64.length(); j++) {
                     File subdir = new File(_dbDir, DIR_PREFIX + B64.charAt(j));
-                    File[] files = subdir.listFiles(RouterInfoFilter.getInstance());
+                    File[] files = subdir.listFiles(RI_FILTER);
                     if (files == null)
                         continue;
                     long lastMod = subdir.lastModified();
@@ -583,7 +584,7 @@ public class PersistentDataStore extends TransientDataStore {
                 if (!subdir.exists())
                     subdir.mkdir();
             }
-            File routerInfoFiles[] = f.listFiles(RouterInfoFilter.getInstance());
+            File routerInfoFiles[] = f.listFiles(RI_FILTER);
             if (routerInfoFiles != null)
                 migrate(f, routerInfoFiles);
         }
@@ -597,7 +598,7 @@ public class PersistentDataStore extends TransientDataStore {
     private static void unmigrate(File dbdir) {
         for (int j = 0; j < B64.length(); j++) {
             File subdir = new File(dbdir, DIR_PREFIX + B64.charAt(j));
-            File[] files = subdir.listFiles(RouterInfoFilter.getInstance());
+            File[] files = subdir.listFiles(RI_FILTER);
             if (files == null)
                 continue;
             for (int i = 0; i < files.length; i++) {
@@ -627,6 +628,9 @@ public class PersistentDataStore extends TransientDataStore {
     private final static String LEASESET_SUFFIX = ".dat";
     private final static String ROUTERINFO_PREFIX = "routerInfo-";
     private final static String ROUTERINFO_SUFFIX = ".dat";
+
+    /** @since 0.9.34 */
+    public static final FileFilter RI_FILTER = new FileSuffixFilter(ROUTERINFO_PREFIX, ROUTERINFO_SUFFIX);
     
     private static String getLeaseSetName(Hash hash) {
         return LEASESET_PREFIX + hash.toBase64() + LEASESET_SUFFIX;
@@ -693,14 +697,4 @@ public class PersistentDataStore extends TransientDataStore {
             return;
         }
     }
-    
-    static class RouterInfoFilter implements FilenameFilter {
-        private static final FilenameFilter _instance = new RouterInfoFilter();
-        public static final FilenameFilter getInstance() { return _instance; }
-        public boolean accept(File dir, String name) {
-            if (name == null) return false;
-            name = name.toUpperCase(Locale.US);
-            return (name.startsWith(ROUTERINFO_PREFIX.toUpperCase(Locale.US)) && name.endsWith(ROUTERINFO_SUFFIX.toUpperCase(Locale.US)));
-        }
-    }
 }