LoadPathToFileCache.java revision 712c262ddf6790c16a8567053f616c71da7e1856
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
17package com.google.clearsilver.jsilver.adaptor;
18
19import java.util.LinkedHashMap;
20import java.util.List;
21import java.util.Map;
22import java.util.concurrent.locks.ReadWriteLock;
23import 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 */
32public 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