TAP5-2687: optimize AssetChecksumGeneratorImpl

to avoid storing whole StreamableResource instances in its cache. In
addition, use computeIfAbsent() instead of put() to avoid the
possibility of duplicate entries in the cache.

Also:
* StreamableResourceImpl now implements hashCode() and equals()
* ContentType used to implement just equals() and now implements
hashCode()
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/assets/AssetChecksumGeneratorImpl.java b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/assets/AssetChecksumGeneratorImpl.java
index f8b8c88..724fc0d 100644
--- a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/assets/AssetChecksumGeneratorImpl.java
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/assets/AssetChecksumGeneratorImpl.java
@@ -33,7 +33,7 @@
 
     private final ResourceChangeTracker tracker;
 
-    private final Map<StreamableResource, String> cache = CollectionFactory.newConcurrentMap();
+    private final Map<Integer, String> cache = CollectionFactory.newConcurrentMap();
 
     public AssetChecksumGeneratorImpl(StreamableResourceSource streamableResourceSource, ResourceChangeTracker tracker)
     {
@@ -53,16 +53,14 @@
 
     public String generateChecksum(StreamableResource resource) throws IOException
     {
-        String result = cache.get(resource);
-
-        if (result == null)
-        {
-            result = toChecksum(resource.openStream());
-
-            cache.put(resource, result);
-        }
-
-        return result;
+        return cache.computeIfAbsent(resource.hashCode(), 
+                r -> {
+                    try {
+                        return toChecksum(resource.openStream());
+                    } catch (IOException e) {
+                        throw new RuntimeException(e);
+                    }
+                });
     }
 
     private String toChecksum(InputStream is) throws IOException
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/assets/StreamableResourceImpl.java b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/assets/StreamableResourceImpl.java
index 8c10d1c..76fe12b 100644
--- a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/assets/StreamableResourceImpl.java
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/assets/StreamableResourceImpl.java
@@ -22,6 +22,7 @@
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
+import java.util.Objects;
 
 public class StreamableResourceImpl implements StreamableResource
 {
@@ -136,4 +137,28 @@
     {
         return new StreamableResourceImpl(description, contentType, compression, lastModified, bytestreamCache, assetChecksumGenerator, customizer);
     }
+
+    @Override
+    public int hashCode() 
+    {
+        return Objects.hash(bytestreamCache.size(), compression, contentType, description, lastModified);
+    }
+
+    @Override
+    public boolean equals(Object obj) 
+    {
+        if (this == obj) 
+        {
+            return true;
+        }
+        if (!(obj instanceof StreamableResourceImpl)) 
+        {
+            return false;
+        }
+        StreamableResourceImpl other = (StreamableResourceImpl) obj;
+        return Objects.equals(bytestreamCache.size(), other.bytestreamCache.size()) && compression == other.compression && Objects.equals(contentType, other.contentType)
+                && Objects.equals(description, other.description) && lastModified == other.lastModified;
+    }
+    
+    
 }
diff --git a/tapestry-http/src/main/java/org/apache/tapestry5/http/ContentType.java b/tapestry-http/src/main/java/org/apache/tapestry5/http/ContentType.java
index 0c8b01a..bf5da4a 100644
--- a/tapestry-http/src/main/java/org/apache/tapestry5/http/ContentType.java
+++ b/tapestry-http/src/main/java/org/apache/tapestry5/http/ContentType.java
@@ -15,6 +15,7 @@
 import java.util.Collections;
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
 import java.util.StringTokenizer;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
@@ -106,6 +107,12 @@
         return baseType.equals(ct.baseType) && subType.equals(ct.subType) && parameters.equals(ct.parameters);
     }
 
+    @Override
+    public int hashCode() 
+    {
+        return Objects.hash(baseType, subType, parameters);
+    }
+
     /**
      * @return the base type of the content type
      */