From f6996c7d8b616d55657b5323e08ffbc19a40251d Mon Sep 17 00:00:00 2001
From: zzz <zzz@mail.i2p>
Date: Sun, 2 Nov 2008 21:41:08 +0000
Subject: [PATCH]     * NamingServices: Implement caching in the abstract class

---
 .../client/naming/EepGetNamingService.java    | 19 ++---
 .../i2p/client/naming/ExecNamingService.java  | 19 ++---
 .../client/naming/HostsTxtNamingService.java  | 19 +++--
 .../net/i2p/client/naming/NamingService.java  | 81 +++++++++++++++++++
 4 files changed, 110 insertions(+), 28 deletions(-)

diff --git a/core/java/src/net/i2p/client/naming/EepGetNamingService.java b/core/java/src/net/i2p/client/naming/EepGetNamingService.java
index 0b3c7d08e5..a90b53d4dc 100644
--- a/core/java/src/net/i2p/client/naming/EepGetNamingService.java
+++ b/core/java/src/net/i2p/client/naming/EepGetNamingService.java
@@ -7,7 +7,6 @@ package net.i2p.client.naming;
 import java.io.ByteArrayOutputStream;
 import java.util.ArrayList;
 import java.util.List;
-import java.util.Properties;
 import java.util.StringTokenizer;
 
 import net.i2p.I2PAppContext;
@@ -38,7 +37,6 @@ public class EepGetNamingService extends NamingService {
 
     private final static String PROP_EEPGET_LIST = "i2p.naming.eepget.list";
     private final static String DEFAULT_EEPGET_LIST = "http://i2host.i2p/cgi-bin/i2hostquery?";
-    private static Properties _hosts;
     private final static Log _log = new Log(EepGetNamingService.class);
 
     /** 
@@ -49,7 +47,6 @@ public class EepGetNamingService extends NamingService {
      */
     public EepGetNamingService(I2PAppContext context) {
         super(context);
-        _hosts = new Properties();
     }
     
     private List getURLs() {
@@ -69,11 +66,9 @@ public class EepGetNamingService extends NamingService {
         hostname = hostname.toLowerCase();
 
         // check the cache
-        String key = _hosts.getProperty(hostname);
-        if (key != null) {
-            _log.error("Found in cache: " + hostname);
-            return lookupBase64(key);
-        }
+        Destination d = getCache(hostname);
+        if (d != null)
+            return d;
 
         List URLs = getURLs();
         if (URLs.size() == 0)
@@ -91,16 +86,18 @@ public class EepGetNamingService extends NamingService {
         // lookup
         for (int i = 0; i < URLs.size(); i++) { 
             String url = (String)URLs.get(i);
-            key = fetchAddr(url, hostname);	  	
+            String key = fetchAddr(url, hostname);	  	
             if (key != null) {
                 _log.error("Success: " + url + hostname);
-                _hosts.setProperty(hostname, key);  // cache
-                return lookupBase64(key);
+                d = lookupBase64(key);
+                putCache(hostname, d);
+                return d;
             }
         }
         return null;
     }
 
+    // FIXME allow larger Dests for non-null Certs
     private static final int DEST_SIZE = 516;                    // Std. Base64 length (no certificate)
     private static final int MAX_RESPONSE = DEST_SIZE + 68 + 10; // allow for hostname= and some trailing stuff
     private String fetchAddr(String url, String hostname) {
diff --git a/core/java/src/net/i2p/client/naming/ExecNamingService.java b/core/java/src/net/i2p/client/naming/ExecNamingService.java
index 15b770ec88..b5dd442465 100644
--- a/core/java/src/net/i2p/client/naming/ExecNamingService.java
+++ b/core/java/src/net/i2p/client/naming/ExecNamingService.java
@@ -5,7 +5,6 @@
 package net.i2p.client.naming;
 
 import java.io.InputStream;
-import java.util.Properties;
 
 import net.i2p.I2PAppContext;
 import net.i2p.data.Destination;
@@ -47,7 +46,6 @@ public class ExecNamingService extends NamingService {
     private final static String DEFAULT_EXEC_CMD = "/usr/local/bin/i2presolve";
     private final static String PROP_SHELL_CMD = "i2p.naming.exec.shell";
     private final static String DEFAULT_SHELL_CMD = "/bin/bash";
-    private static Properties _hosts;
     private final static Log _log = new Log(ExecNamingService.class);
 
     /** 
@@ -58,7 +56,6 @@ public class ExecNamingService extends NamingService {
      */
     public ExecNamingService(I2PAppContext context) {
         super(context);
-        _hosts = new Properties();
     }
         
     public Destination lookup(String hostname) {
@@ -69,22 +66,22 @@ public class ExecNamingService extends NamingService {
         hostname = hostname.toLowerCase();
 
         // check the cache
-        String key = _hosts.getProperty(hostname);
-        if (key != null) {
-            _log.error("Found in cache: " + hostname);
-            return lookupBase64(key);
-        }
+        Destination d = getCache(hostname);
+        if (d != null)
+            return d;
 
         // lookup
-        key = fetchAddr(hostname);	  	
+        String key = fetchAddr(hostname);	  	
         if (key != null) {
             _log.error("Success: " + hostname);
-            _hosts.setProperty(hostname, key);  // cache
-            return lookupBase64(key);
+            d = lookupBase64(key);
+            putCache(hostname, d);
+            return d;
         }
         return null;
     }
 
+    // FIXME allow larger Dests for non-null Certs
     private static final int DEST_SIZE = 516;                    // Std. Base64 length (no certificate)
     private static final int MAX_RESPONSE = DEST_SIZE + 68 + 10; // allow for hostname= and some trailing stuff
     private String fetchAddr(String hostname) {
diff --git a/core/java/src/net/i2p/client/naming/HostsTxtNamingService.java b/core/java/src/net/i2p/client/naming/HostsTxtNamingService.java
index c07c1a7711..2307816be8 100644
--- a/core/java/src/net/i2p/client/naming/HostsTxtNamingService.java
+++ b/core/java/src/net/i2p/client/naming/HostsTxtNamingService.java
@@ -56,13 +56,18 @@ public class HostsTxtNamingService extends NamingService {
     }
         
     public Destination lookup(String hostname) {
+        Destination d = getCache(hostname);
+        if (d != null)
+            return d;
+
         // If it's long, assume it's a key.
-        if (hostname.length() >= 516)
-            return lookupBase64(hostname);
+        if (hostname.length() >= 516) {
+            d = lookupBase64(hostname);
+            // What the heck, cache these too
+            putCache(hostname, d);
+            return d;
+        }
 
-        // check the list each time, reloading the file on each
-        // lookup
-        
         List filenames = getFilenames();
         for (int i = 0; i < filenames.size(); i++) { 
             String hostsfile = (String)filenames.get(i);
@@ -74,7 +79,9 @@ public class HostsTxtNamingService extends NamingService {
                     
                     String key = hosts.getProperty(hostname.toLowerCase());
                     if ( (key != null) && (key.trim().length() > 0) ) {
-                        return lookupBase64(key);
+                        d = lookupBase64(key);
+                        putCache(hostname, d);
+                        return d;
                     }
                     
                 } else {
diff --git a/core/java/src/net/i2p/client/naming/NamingService.java b/core/java/src/net/i2p/client/naming/NamingService.java
index 5f8f80bf95..5b61b1bcf8 100644
--- a/core/java/src/net/i2p/client/naming/NamingService.java
+++ b/core/java/src/net/i2p/client/naming/NamingService.java
@@ -8,6 +8,10 @@
 package net.i2p.client.naming;
 
 import java.lang.reflect.Constructor;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
 
 import net.i2p.I2PAppContext;
 import net.i2p.data.DataFormatException;
@@ -21,11 +25,14 @@ public abstract class NamingService {
 
     private final static Log _log = new Log(NamingService.class);
     protected I2PAppContext _context;
+    private HashMap _cache;
 
     /** what classname should be used as the naming service impl? */
     public static final String PROP_IMPL = "i2p.naming.impl";
     private static final String DEFAULT_IMPL = "net.i2p.client.naming.HostsTxtNamingService";
 
+    protected static final int CACHE_MAX_SIZE = 8;
+
     
     /** 
      * The naming service should only be constructed and accessed through the 
@@ -35,6 +42,7 @@ public abstract class NamingService {
      */
     protected NamingService(I2PAppContext context) {
         _context = context;
+        _cache = new HashMap(CACHE_MAX_SIZE);
     }
     private NamingService() { // nop
     }
@@ -89,4 +97,77 @@ public abstract class NamingService {
         }
         return instance;
     }
+
+    /**
+     *  Provide basic caching for the service
+     *  The service may override the age and/or size limit
+     */
+    /** Don't know why a dest would ever change but keep this short anyway */
+    protected static final long CACHE_MAX_AGE = 60*1000;
+
+    private class CacheEntry {
+        public Destination dest;
+        public long exp;
+        public CacheEntry(Destination d) {
+            dest = d;
+            exp = _context.clock().now() + CACHE_MAX_AGE;
+        }
+        public boolean isExpired() {
+            return exp < _context.clock().now();
+        }
+    }
+
+    /**
+     * Clean up when full.
+     * Don't bother removing old entries unless full.
+     * Caller must synchronize on _cache.
+     */
+    private void cacheClean() {
+        if (_cache.size() < CACHE_MAX_SIZE)
+            return;
+        boolean full = true;
+        Object oldestKey = null;
+        long oldestExp = Long.MAX_VALUE;
+        ArrayList deleteList = new ArrayList(CACHE_MAX_SIZE);
+        for (Iterator iter = _cache.entrySet().iterator(); iter.hasNext(); ) {
+            Map.Entry entry = (Map.Entry) iter.next();
+            CacheEntry ce = (CacheEntry) entry.getValue();
+            if (ce.isExpired()) {
+                deleteList.add(entry.getKey());
+                full = false;
+                continue;
+            }
+            if (oldestKey == null || ce.exp < oldestExp) {
+                oldestKey = entry.getKey();
+                oldestExp = ce.exp;
+            }
+        }
+        if (full && oldestKey != null)
+            deleteList.add(oldestKey);
+        for (Iterator iter = deleteList.iterator(); iter.hasNext(); ) {
+            _cache.remove(iter.next());
+        }
+    }
+
+    protected void putCache(String s, Destination d) {
+        if (d == null)
+            return;
+        synchronized (_cache) {
+            _cache.put(s, new CacheEntry(d));
+            cacheClean();
+        }
+    }
+
+    protected Destination getCache(String s) {
+        synchronized (_cache) {
+            CacheEntry ce = (CacheEntry) _cache.get(s);
+            if (ce == null)
+                return null;
+            if (ce.isExpired()) {
+                _cache.remove(s);
+                return null;
+            }
+            return ce.dest;
+        }
+    }
 }
-- 
GitLab