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