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

COVERAGE SUMMARY FOR SOURCE FILE [BuildServiceImplementation.java]

nameclass, %method, %block, %line, %
BuildServiceImplementation.java0%   (0/3)0%   (0/15)0%   (0/983)0%   (0/186)

COVERAGE BREAKDOWN BY CLASS AND METHOD

nameclass, %method, %block, %line, %
     
class BuildServiceImplementation0%   (0/1)0%   (0/11)0%   (0/864)0%   (0/167)
BuildServiceImplementation (): void 0%   (0/1)0%   (0/6)0%   (0/2)
access$000 (BuildServiceImplementation, InputSource): String 0%   (0/1)0%   (0/4)0%   (0/1)
cajole (PrintWriter, List, List, File, Map): boolean 0%   (0/1)0%   (0/535)0%   (0/98)
getSourceContent (InputSource): String 0%   (0/1)0%   (0/65)0%   (0/13)
hasErrors (MessageQueue): boolean 0%   (0/1)0%   (0/21)0%   (0/4)
loadEnvJsonFile (File, JsOptimizer, MessageQueue): void 0%   (0/1)0%   (0/63)0%   (0/18)
minify (PrintWriter, List, List, File, Map): boolean 0%   (0/1)0%   (0/60)0%   (0/9)
parseInput (InputSource, MessageQueue): ParseTreeNode 0%   (0/1)0%   (0/17)0%   (0/5)
parser (CharProducer, MessageQueue): Parser 0%   (0/1)0%   (0/19)0%   (0/3)
read (File): CharProducer 0%   (0/1)0%   (0/17)0%   (0/2)
transfInnocent (PrintWriter, List, List, File, Map): boolean 0%   (0/1)0%   (0/57)0%   (0/13)
     
class BuildServiceImplementation$10%   (0/1)0%   (0/2)0%   (0/85)0%   (0/17)
BuildServiceImplementation$1 (BuildServiceImplementation, Set): void 0%   (0/1)0%   (0/9)0%   (0/1)
fetch (ExternalReference, String): FetchedData 0%   (0/1)0%   (0/76)0%   (0/16)
     
class BuildServiceImplementation$20%   (0/1)0%   (0/2)0%   (0/34)0%   (0/2)
BuildServiceImplementation$2 (BuildServiceImplementation): void 0%   (0/1)0%   (0/6)0%   (0/1)
rewriteUri (ExternalReference, UriEffect, LoaderType, Map): String 0%   (0/1)0%   (0/28)0%   (0/1)

1// Copyright (C) 2008 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.plugin;
16 
17import com.google.caja.ancillary.opt.JsOptimizer;
18import com.google.caja.lexer.CharProducer;
19import com.google.caja.lexer.ExternalReference;
20import com.google.caja.lexer.FetchedData;
21import com.google.caja.lexer.InputSource;
22import com.google.caja.lexer.JsLexer;
23import com.google.caja.lexer.JsTokenQueue;
24import com.google.caja.lexer.ParseException;
25import com.google.caja.lexer.TokenConsumer;
26import com.google.caja.lexer.escaping.UriUtil;
27import com.google.caja.parser.ParseTreeNode;
28import com.google.caja.parser.html.DomParser;
29import com.google.caja.parser.html.Namespaces;
30import com.google.caja.parser.html.Nodes;
31import com.google.caja.parser.js.Expression;
32import com.google.caja.parser.js.Minify;
33import com.google.caja.parser.js.ObjectConstructor;
34import com.google.caja.parser.js.Parser;
35import com.google.caja.parser.js.Statement;
36import com.google.caja.reporting.MarkupRenderMode;
37import com.google.caja.reporting.Message;
38import com.google.caja.reporting.MessageContext;
39import com.google.caja.reporting.MessageLevel;
40import com.google.caja.reporting.MessagePart;
41import com.google.caja.reporting.MessageQueue;
42import com.google.caja.reporting.MessageType;
43import com.google.caja.reporting.RenderContext;
44import com.google.caja.reporting.SimpleMessageQueue;
45import com.google.caja.reporting.SnippetProducer;
46import com.google.caja.reporting.BuildInfo;
47import com.google.caja.render.Concatenator;
48import com.google.caja.render.Innocent;
49import com.google.caja.render.JsMinimalPrinter;
50import com.google.caja.render.JsPrettyPrinter;
51import com.google.caja.tools.BuildService;
52import com.google.caja.util.Charsets;
53import com.google.caja.util.Lists;
54import com.google.caja.util.Maps;
55import com.google.caja.util.Pair;
56import com.google.caja.util.Sets;
57 
58import java.io.File;
59import java.io.FileInputStream;
60import java.io.FileOutputStream;
61import java.io.IOException;
62import java.io.InputStreamReader;
63import java.io.OutputStreamWriter;
64import java.io.PrintWriter;
65import java.io.Reader;
66import java.io.Writer;
67import java.net.URI;
68import java.util.Collections;
69import java.util.List;
70import java.util.Map;
71import java.util.Set;
72 
73import org.w3c.dom.Document;
74import org.w3c.dom.Element;
75import org.w3c.dom.Node;
76 
77/**
78 * Build integration to {@link PluginCompiler} and {@link Minify}.
79 *
80 * @author mikesamuel@gmail.com
81 */
82public class BuildServiceImplementation implements BuildService {
83  private final Map<InputSource, String> originalSources = Maps.newHashMap();
84 
85  /**
86   * Cajoles inputs to output writing any messages to logger, returning true
87   * iff the task passes.
88   */
89  public boolean cajole(
90      PrintWriter logger, List<File> dependees, List<File> inputs, File output,
91      Map<String, Object> options) {
92    final Set<File> canonFiles = Sets.newHashSet();
93    try {
94      for (File f : dependees) { canonFiles.add(f.getCanonicalFile()); }
95      for (File f : inputs) { canonFiles.add(f.getCanonicalFile()); }
96    } catch (IOException ex) {
97      logger.println(ex.toString());
98      return false;
99    }
100    final MessageQueue mq = new SimpleMessageQueue();
101 
102    UriFetcher fetcher = new UriFetcher() {
103        public FetchedData fetch(ExternalReference ref, String mimeType)
104            throws UriFetchException {
105          URI uri = ref.getUri();
106          uri = ref.getReferencePosition().source().getUri().resolve(uri);
107          InputSource is = new InputSource(uri);
108 
109          try {
110            if (!canonFiles.contains(new File(uri).getCanonicalFile())) {
111              throw new UriFetchException(ref, mimeType);
112            }
113          } catch (IllegalArgumentException ex) {
114            throw new UriFetchException(ref, mimeType, ex);
115          } catch (IOException ex) {
116            throw new UriFetchException(ref, mimeType, ex);
117          }
118 
119          try {
120            String content = getSourceContent(is);
121            if (content == null) {
122              throw new UriFetchException(ref, mimeType);
123            }
124            return FetchedData.fromCharProducer(
125                CharProducer.Factory.fromString(content, is),
126                mimeType, Charsets.UTF_8.name());
127          } catch (IOException ex) {
128            throw new UriFetchException(ref, mimeType, ex);
129          }
130        }
131      };
132 
133    UriPolicy policy = new UriPolicy() {
134      public String rewriteUri(
135          ExternalReference u, UriEffect effect, LoaderType loader,
136          Map<String, ?> hints) {
137        // TODO(ihab.awad): Need to pass in the URI rewriter from the build
138        // file somehow (as a Cajita program?). The below is a stub.
139        return URI.create(
140            "http://example.com/"
141            + "?effect=" + effect + "&loader=" + loader
142            + "&uri=" + UriUtil.encode("" + u.getUri()))
143            .toString();
144      }
145    };
146 
147    MessageContext mc = new MessageContext();
148 
149    // Set up the cajoler
150    String language = (String) options.get("language");
151    boolean passed = true;
152    ParseTreeNode outputJs;
153    Node outputHtml;
154    if ("caja".equals(language)) {
155      PluginCompiler compiler = new PluginCompiler(
156          BuildInfo.getInstance(), new PluginMeta(fetcher, policy), mq);
157      compiler.setMessageContext(mc);
158      if (Boolean.TRUE.equals(options.get("debug"))) {
159        compiler.setGoals(compiler.getGoals()
160            .without(PipelineMaker.ONE_CAJOLED_MODULE)
161            .with(PipelineMaker.ONE_CAJOLED_MODULE_DEBUG));
162      }
163      if (Boolean.TRUE.equals(options.get("onlyJsEmitted"))) {
164        compiler.setGoals(
165            compiler.getGoals().without(PipelineMaker.HTML_SAFE_STATIC));
166      }
167 
168      // Parse inputs
169      for (File f : inputs) {
170        try {
171          URI fileUri = f.getCanonicalFile().toURI();
172          ParseTreeNode parsedInput = parseInput(new InputSource(fileUri), mq);
173          if (parsedInput == null) {
174            passed = false;
175          } else {
176            compiler.addInput(parsedInput, fileUri);
177          }
178        } catch (IOException ex) {
179          logger.println("Failed to read " + f);
180          passed = false;
181        }
182      }
183 
184      // Cajole
185      passed = passed && compiler.run();
186 
187      outputJs = passed ? compiler.getJavascript() : null;
188      outputHtml = passed ? compiler.getStaticHtml() : null;
189    } else if ("javascript".equals(language)) {
190      passed = true;
191      JsOptimizer optimizer = new JsOptimizer(mq);
192      for (File f : inputs) {
193        try {
194          if (f.getName().endsWith(".env.json")) {
195            loadEnvJsonFile(f, optimizer, mq);
196          } else {
197            ParseTreeNode parsedInput = parseInput(
198                new InputSource(f.getCanonicalFile().toURI()), mq);
199            if (parsedInput != null) {
200              optimizer.addInput((Statement) parsedInput);
201            }
202          }
203        } catch (IOException ex) {
204          logger.println("Failed to read " + f);
205          passed = false;
206        }
207      }
208      outputJs = optimizer.optimize();
209      outputHtml = null;
210    } else {
211      throw new RuntimeException("Unrecognized language: " + language);
212    }
213    passed = passed && !hasErrors(mq);
214 
215    // From the ignore attribute to the <transform> element.
216    Set<?> toIgnore = (Set<?>) options.get("toIgnore");
217    if (toIgnore == null) { toIgnore = Collections.emptySet(); }
218 
219    // Log messages
220    SnippetProducer snippetProducer = new SnippetProducer(originalSources, mc);
221    for (Message msg : mq.getMessages()) {
222      if (passed && MessageLevel.LOG.compareTo(msg.getMessageLevel()) >= 0) {
223        continue;
224      }
225      String snippet = snippetProducer.getSnippet(msg);
226      if (!"".equals(snippet)) { snippet = "\n" + snippet; }
227      if (!passed || !toIgnore.contains(msg.getMessageType().name())) {
228        logger.println(
229            msg.getMessageLevel() + " : " + msg.format(mc) + snippet);
230      }
231    }
232 
233    // Write the output
234    if (passed) {
235      // Write out as HTML if the output file has the right extension.
236      boolean asXml = output.getName().endsWith(".xhtml");
237      boolean emitMarkup = asXml || output.getName().endsWith(".html");
238 
239      StringBuilder jsOut = new StringBuilder();
240      TokenConsumer renderer;
241      String rendererType = (String) options.get("renderer");
242      if ("pretty".equals(rendererType)) {
243        renderer = new JsPrettyPrinter(new Concatenator(jsOut));
244      } else if ("minify".equals(rendererType)) {
245        renderer = new JsMinimalPrinter(new Concatenator(jsOut));
246      } else {
247        throw new RuntimeException("Unrecognized renderer " + rendererType);
248      }
249      RenderContext rc = new RenderContext(renderer).withEmbeddable(emitMarkup);
250      outputJs.render(rc);
251      rc.getOut().noMoreTokens();
252 
253      String htmlOut = "";
254      if (outputHtml != null) {
255        htmlOut = Nodes.render(
256            outputHtml, asXml ? MarkupRenderMode.XML : MarkupRenderMode.HTML);
257      }
258 
259      String translatedCode;
260      if (emitMarkup) {
261        Document doc = DomParser.makeDocument(null, null);
262        String ns = Namespaces.HTML_NAMESPACE_URI;
263        Element script = doc.createElementNS(ns, "script");
264        script.setAttributeNS(ns, "type", "text/javascript");
265        script.appendChild(doc.createCDATASection(jsOut.toString()));
266        translatedCode = htmlOut + Nodes.render(
267            script, asXml ? MarkupRenderMode.XML : MarkupRenderMode.HTML);
268      } else {
269        if (!"".equals(htmlOut)) {
270          throw new RuntimeException("Can't emit HTML to " + output);
271        }
272        translatedCode = jsOut.toString();
273      }
274 
275      try {
276        Writer w = new OutputStreamWriter(new FileOutputStream(output));
277        try {
278          w.write(translatedCode);
279        } finally {
280          w.close();
281        }
282      } catch (IOException ex) {
283        logger.println("Failed to write " + output);
284        return false;
285      }
286    }
287    return passed;
288  }
289 
290  private String getSourceContent(InputSource is) throws IOException {
291    String content = originalSources.get(is);
292    if (content == null) {
293      File f = new File(is.getUri());
294      // Read it in and stuff it back in the map so we can generate
295      // snippets.
296      Reader in = new InputStreamReader(new FileInputStream(f), Charsets.UTF_8);
297      try {
298        char[] buf = new char[4096];
299        StringBuilder sb = new StringBuilder();
300        for (int n; (n = in.read(buf, 0, buf.length)) > 0;) {
301          sb.append(buf, 0, n);
302        }
303        content = sb.toString();
304      } finally {
305        in.close();
306      }
307      originalSources.put(is, content);
308    }
309    return content;
310  }
311 
312  private ParseTreeNode parseInput(InputSource is, MessageQueue mq)
313      throws IOException {
314    CharProducer cp = CharProducer.Factory.fromString(getSourceContent(is), is);
315    try {
316      return PluginCompilerMain.parseInput(is, cp, mq);
317    } catch (ParseException ex) {
318      ex.toMessageQueue(mq);
319      return null;
320    }
321  }
322 
323  /**
324   * Minifies inputs to output writing any messages to logger, returning true
325   * iff the task passes.
326   */
327  public boolean minify(
328      PrintWriter logger, List<File> dependees, List<File> inputs, File output,
329      Map<String, Object> options) {
330    try {
331      List<Pair<InputSource, File>> inputSources = Lists.newArrayList();
332      for (File f : inputs) {
333        inputSources.add(
334            Pair.pair(new InputSource(f.getAbsoluteFile().toURI()), f));
335      }
336      Writer outputWriter = new OutputStreamWriter(
337          new FileOutputStream(output), Charsets.UTF_8);
338      try {
339        return Minify.minify(inputSources, outputWriter, logger);
340      } finally {
341        outputWriter.close();
342      }
343    } catch (IOException ex) {
344      logger.println("Minifying failed: " + ex);
345      return false;
346    }
347  }
348 
349  /**
350   * Applies the innocent code transformer to inputs.  Writes
351   * any messages to logger and returns true iff the task passes.
352   */
353  public boolean transfInnocent(
354      PrintWriter logger, List<File> dependees, List<File> inputs, File output,
355      Map<String, Object> options) {
356    try {
357      boolean ret;
358      Writer outputWriter = new OutputStreamWriter(
359          new FileOutputStream(output), Charsets.UTF_8);
360      for (File f : inputs) {
361        Pair<InputSource, File> inputSource =
362          Pair.pair(new InputSource(f.getAbsoluteFile().toURI()), f);
363        ret = Innocent.transfInnocent(inputSource, outputWriter, logger);
364        if (!ret) {
365          outputWriter.close();
366          return false;
367        }
368      }
369      outputWriter.close();
370      return true;
371    } catch (IOException ex) {
372      logger.println("Innocent transform failed: " + ex);
373      return false;
374    }
375  }
376 
377  private static void loadEnvJsonFile(File f, JsOptimizer op, MessageQueue mq) {
378    CharProducer cp;
379    try {
380      cp = read(f);
381    } catch (IOException ex) {
382      mq.addMessage(
383          MessageType.IO_ERROR, MessagePart.Factory.valueOf(ex.toString()));
384      return;
385    }
386    ObjectConstructor envJson;
387    try {
388      Parser p = parser(cp, mq);
389      Expression e = p.parseExpression(true); // TODO(mikesamuel): limit to JSON
390      p.getTokenQueue().expectEmpty();
391      if (!(e instanceof ObjectConstructor)) {
392        mq.addMessage(
393            MessageType.IO_ERROR,
394            MessagePart.Factory.valueOf("Invalid JSON in " + f));
395        return;
396      }
397      envJson = (ObjectConstructor) e;
398    } catch (ParseException ex) {
399      ex.toMessageQueue(mq);
400      return;
401    }
402    op.setEnvJson(envJson);
403  }
404 
405  private static CharProducer read(File f) throws IOException {
406    InputSource is = new InputSource(f.toURI());
407    return CharProducer.Factory.create(
408        new InputStreamReader(new FileInputStream(f), Charsets.UTF_8), is);
409  }
410 
411  private static Parser parser(CharProducer cp, MessageQueue errs) {
412    JsLexer lexer = new JsLexer(cp);
413    JsTokenQueue tq = new JsTokenQueue(lexer, cp.getCurrentPosition().source());
414    return new Parser(tq, errs);
415  }
416 
417  private static boolean hasErrors(MessageQueue mq) {
418    for (Message msg : mq.getMessages()) {
419      if (MessageLevel.ERROR.compareTo(msg.getMessageLevel()) <= 0) {
420        return true;
421      }
422    }
423    return false;
424  }
425}

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