Home | History | Annotate | Download | only in adaptor
      1 /*
      2  * Copyright (C) 2010 Google Inc.
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  * http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.google.clearsilver.jsilver.adaptor;
     18 
     19 import java.util.LinkedHashMap;
     20 import java.util.List;
     21 import java.util.Map;
     22 import java.util.concurrent.locks.ReadWriteLock;
     23 import java.util.concurrent.locks.ReentrantReadWriteLock;
     24 
     25 /**
     26  * This class implements a cache of a list of loadpaths and a file name to the absolute file name
     27  * where the file is located on the filesystem. The purpose is to avoid filesystem calls for common
     28  * operations, like in which of these directories does this file exist? This class is threadsafe.
     29  *
     30  * Some of this code is copied from {@link com.google.clearsilver.base.CSFileCache}.
     31  */
     32 public class LoadPathToFileCache {
     33 
     34   private final LRUCache<String, String> cache;
     35   private final ReadWriteLock cacheLock = new ReentrantReadWriteLock();
     36 
     37   public LoadPathToFileCache(int capacity) {
     38     cache = new LRUCache<String, String>(capacity);
     39   }
     40 
     41   /**
     42    * Lookup in the cache to see if we have a mapping from the given loadpaths and filename to an
     43    * absolute file path.
     44    *
     45    * @param loadPaths the ordered list of directories to search for the file.
     46    * @param filename the name of the file.
     47    * @return the absolute filepath location of the file, or {@code null} if not in the cache.
     48    */
     49   public String lookup(List<String> loadPaths, String filename) {
     50     String filePathMapKey = makeCacheKey(loadPaths, filename);
     51     cacheLock.readLock().lock();
     52     try {
     53       return cache.get(filePathMapKey);
     54     } finally {
     55       cacheLock.readLock().unlock();
     56     }
     57   }
     58 
     59   /**
     60    * Add a new mapping to the cache.
     61    *
     62    * @param loadPaths the ordered list of directories to search for the file.
     63    * @param filename the name of the file.
     64    * @param filePath the absolute filepath location of the file
     65    */
     66   public void add(List<String> loadPaths, String filename, String filePath) {
     67     String filePathMapKey = makeCacheKey(loadPaths, filename);
     68     cacheLock.writeLock().lock();
     69     try {
     70       cache.put(filePathMapKey, filePath);
     71     } finally {
     72       cacheLock.writeLock().unlock();
     73     }
     74   }
     75 
     76   public static String makeCacheKey(List<String> loadPaths, String filename) {
     77     if (loadPaths == null) {
     78       throw new NullPointerException("Loadpaths cannot be null");
     79     }
     80     if (filename == null) {
     81       throw new NullPointerException("Filename cannot be null");
     82     }
     83     String loadPathsHash = Long.toHexString(hashLoadPath(loadPaths));
     84     StringBuilder sb = new StringBuilder(loadPathsHash);
     85     sb.append('|').append(filename);
     86     return sb.toString();
     87   }
     88 
     89   /**
     90    * Generate a hashCode to represent the ordered list of loadpaths. Used as a key into the fileMap
     91    * since a concatenation of the loadpaths is likely to be huge (greater than 1K) but very
     92    * repetitive. Algorithm comes from Effective Java by Joshua Bloch.
     93    * <p>
     94    * We don't use the builtin hashCode because it is only 32-bits, and while the expect different
     95    * values of loadpaths is very small, we want to minimize any chance of collision since we use the
     96    * hash as the key and throw away the loadpath list
     97    *
     98    * @param list an ordered list of loadpaths.
     99    * @return a long representing a hash of the loadpaths.
    100    */
    101   static long hashLoadPath(List<String> list) {
    102     long hash = 17;
    103     for (String path : list) {
    104       hash = 37 * hash + path.hashCode();
    105     }
    106     return hash;
    107   }
    108 
    109   /**
    110    * This code is copied from {@link com.google.common.cache.LRUCache} but is distilled to basics in
    111    * order to not require importing Google code. Hopefully there is an open-source implementation,
    112    * although given the design of LinkHashMap, this is trivial.
    113    */
    114   static class LRUCache<K, V> extends LinkedHashMap<K, V> {
    115 
    116     private final int capacity;
    117 
    118     LRUCache(int capacity) {
    119       super(capacity, 0.75f, true);
    120       this.capacity = capacity;
    121     }
    122 
    123     /**
    124      * {@inheritDoc}
    125      * <p>
    126      * Necessary to override because HashMap increases the capacity of the hashtable before
    127      * inserting the elements. However, here we have set the max capacity already and will instead
    128      * remove eldest elements instead of increasing capacity.
    129      */
    130     @Override
    131     public void putAll(Map<? extends K, ? extends V> m) {
    132       for (Map.Entry<? extends K, ? extends V> e : m.entrySet()) {
    133         put(e.getKey(), e.getValue());
    134       }
    135     }
    136 
    137     /**
    138      * This method is called by LinkedHashMap to check whether the eldest entry should be removed.
    139      *
    140      * @param eldest
    141      * @return true if element should be removed.
    142      */
    143     @Override
    144     protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
    145       return size() > capacity;
    146     }
    147   }
    148 }
    149