EMMA Coverage Report (generated Mon Nov 01 16:48:29 PDT 2010)
[all classes][com.google.caja.ancillary.servlet]

COVERAGE SUMMARY FOR SOURCE FILE [StaticFiles.java]

nameclass, %method, %block, %line, %
StaticFiles.java100% (2/2)100% (7/7)87%  (321/371)84%  (77.4/92)

COVERAGE BREAKDOWN BY CLASS AND METHOD

nameclass, %method, %block, %line, %
     
class StaticFiles100% (1/1)100% (5/5)87%  (308/356)84%  (75.7/90)
StaticFiles (String): void 100% (1/1)100% (25/25)100% (7/7)
exists (String): boolean 100% (1/1)82%  (36/44)80%  (10.4/13)
mimeTypeFor (ContentType, String): String 100% (1/1)68%  (34/50)65%  (5.9/9)
mruCache (int): Map 100% (1/1)100% (6/6)100% (1/1)
serve (String, HttpServletRequest, HttpServletResponse): void 100% (1/1)90%  (207/231)86%  (51.4/60)
     
class StaticFiles$1100% (1/1)100% (2/2)87%  (13/15)88%  (1.8/2)
StaticFiles$1 (int): void 100% (1/1)100% (6/6)100% (1/1)
removeEldestEntry (Map$Entry): boolean 100% (1/1)78%  (7/9)77%  (0.8/1)

1// Copyright (C) 2009 Google Inc.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//      http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14 
15package com.google.caja.ancillary.servlet;
16 
17import com.google.caja.lexer.CharProducer;
18import com.google.caja.lexer.GuessContentType;
19import com.google.caja.lexer.InputSource;
20import com.google.caja.lexer.ParseException;
21import com.google.caja.reporting.DevNullMessageQueue;
22import com.google.caja.util.Charsets;
23import com.google.caja.util.ContentType;
24import com.google.caja.util.Lists;
25 
26import java.io.ByteArrayOutputStream;
27import java.io.IOException;
28import java.io.InputStream;
29import java.io.InputStreamReader;
30import java.io.OutputStream;
31import java.io.Writer;
32import java.net.URISyntaxException;
33import java.util.Collections;
34import java.util.LinkedHashMap;
35import java.util.List;
36import java.util.Map;
37import javax.servlet.http.HttpServletRequest;
38import javax.servlet.http.HttpServletResponse;
39 
40/**
41 * Serves static files from the class path.
42 * Files are stored in the <tt>files</tt> directory in this package.  There
43 * are several kinds of file:<ul>
44 * <li>Images, scripts, and help files linked to from other pages.
45 * <li>Help files based on identifiers in the java code.  E.g. the help text
46 * for a warning message {@code MessageType.FOO_BAR} would be in the html
47 * file <tt>files/FOO_BAR_tip.html</tt>.
48 * </ul>
49 *
50 * @author mikesamuel@gmail.com
51 */
52final class StaticFiles {
53  // Use a bigger cache to store the fact that a file does not exist, since
54  // we don't have to use much memory for it.
55  private final Map<String, Boolean> exists = mruCache(1024);
56  private final Map<String, Content> files = mruCache(128);
57  private final long startupTime = System.currentTimeMillis();
58  private final long expiryDate = Math.max(
59      startupTime + 3600 * 24 * 366000, (0x7fffffffL * 1000));
60  final String cacheId;
61 
62  StaticFiles(String cacheId) {
63    this.cacheId = cacheId;
64  }
65 
66  boolean exists(String path) {
67    if (files.containsKey(path)) { return true; }
68    Boolean b = exists.get(path);
69    if (b != null) { return b; }
70    InputStream in = StaticFiles.class.getResourceAsStream(path);
71    boolean fileExists;
72    if (in != null) {
73      try {
74        in.close();
75      } catch (IOException ex) {
76        ex.printStackTrace();
77      }
78      fileExists = true;
79    } else {
80      fileExists = false;
81    }
82    exists.put(path, fileExists);
83    return fileExists;
84  }
85 
86  void serve(String path, HttpServletRequest req, HttpServletResponse resp)
87      throws IOException {
88    if (req.getDateHeader("If-modified-since") >= startupTime) {
89      resp.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
90      return;
91    }
92    Content content = files.get(path);
93    if (content == null && !Boolean.FALSE.equals(exists.get(path))) {
94      InputStream in = StaticFiles.class.getResourceAsStream(path);
95      if (in == null) {
96        exists.put(path, Boolean.FALSE);
97      } else {
98        try {
99          // TODO(mikesamuel): SVN has it in svn:mime-type, but that is not
100          // available via the ClassLoader.  Is there any way to get at it?
101          ContentType t = GuessContentType.guess(null, path, null);
102          if (t != null && t.isText) {
103            InputSource is;
104            try {
105              is = new InputSource(StaticFiles.class.getResource(path).toURI());
106            } catch (URISyntaxException ex) {
107              ex.printStackTrace();
108              is = InputSource.UNKNOWN;
109            }
110            CharProducer cp = CharProducer.Factory.create(
111                new InputStreamReader(in, Charsets.UTF_8), is);
112            // Minimize it before serving.
113            Request min = Request.create(Verb.ECHO, this);
114            min.minify = true;
115            min.opt = true;
116            min.otype = t;
117            Processor p = new Processor(min, DevNullMessageQueue.singleton());
118            try {
119              Job j = p.parse(cp.clone(), t, null, is.getUri());
120              List<Job> out = p.process(Lists.newArrayList(j));
121              if (out.size() == 1) {
122                content = p.reduce(out);
123              }
124            } catch (ParseException ex) {
125              // fall through to case below
126              ex.printStackTrace();
127            }
128            if (content == null) {
129              content = new Content(cp.toString(), t);
130            }
131          } else {
132            ByteArrayOutputStream buf = new ByteArrayOutputStream();
133            byte[] bytes = new byte[4096];
134            for (int n; (n = in.read(bytes)) > 0;) { buf.write(bytes, 0, n); }
135            content = new Content(bytes, t);
136          }
137        } finally {
138          in.close();
139        }
140        files.put(path, content);
141      }
142    }
143    if (content != null) {
144      resp.setStatus(200);
145      String mimeType = mimeTypeFor(content.type, path);
146      if (mimeType != null) {
147        resp.setContentType(mimeType);
148      }
149      resp.setDateHeader("Last-modified", startupTime);
150      resp.setDateHeader("Expires", expiryDate);
151      if (content.isText()) {
152        Writer out = resp.getWriter();
153        content.toWriter(out);
154        out.close();
155      } else {
156        OutputStream out = resp.getOutputStream();
157        content.toOutputStream(out);
158        out.close();
159      }
160    } else {
161      resp.setStatus(404);
162      resp.setContentType("text/plain");
163      Writer out = resp.getWriter();
164      out.write("404 - I have no response to that.");
165      out.close();
166    }
167  }
168 
169  private static String mimeTypeFor(ContentType t, String path) {
170    if (t != null) {
171      return t.isText ? t.mimeType + "; charset=UTF-8" : t.mimeType;
172    }
173    int dot = path.lastIndexOf('.');
174    if (dot >= 0) {
175      String ext = path.substring(dot + 1);
176      if ("gif".equals(ext)) { return "image/gif"; }
177      if ("png".equals(ext)) { return "image/png"; }
178      if ("jpg".equals(ext)) { return "image/jpeg"; }
179    }
180    return null;
181  }
182 
183  private static <K, V> Map<K, V> mruCache(final int maxSize) {
184    return Collections.synchronizedMap(new LinkedHashMap<K, V>() {
185      @Override
186      public boolean removeEldestEntry(Map.Entry<K, V> e) {
187        return size() > maxSize;
188      }
189    });
190  }
191}

[all classes][com.google.caja.ancillary.servlet]
EMMA 2.0.5312 (C) Vladimir Roubtsov