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

COVERAGE SUMMARY FOR SOURCE FILE [PluginCompilerMain.java]

nameclass, %method, %block, %line, %
PluginCompilerMain.java0%   (0/6)0%   (0/27)0%   (0/1039)0%   (0/209)

COVERAGE BREAKDOWN BY CLASS AND METHOD

nameclass, %method, %block, %line, %
     
class PluginCompilerMain0%   (0/1)0%   (0/17)0%   (0/899)0%   (0/192)
PluginCompilerMain (): void 0%   (0/1)0%   (0/31)0%   (0/7)
access$000 (PluginCompilerMain): MessageQueue 0%   (0/1)0%   (0/3)0%   (0/1)
access$100 (PluginCompilerMain, InputSource, InputStream): Reader 0%   (0/1)0%   (0/5)0%   (0/1)
buildOriginalInputCharSequences (): Map 0%   (0/1)0%   (0/27)0%   (0/4)
createReader (InputSource, InputStream): Reader 0%   (0/1)0%   (0/31)0%   (0/6)
dumpMessages (MessageQueue, MessageContext, Appendable): MessageLevel 0%   (0/1)0%   (0/97)0%   (0/25)
main (String []): void 0%   (0/1)0%   (0/19)0%   (0/10)
makeRenderContext (TokenConsumer): RenderContext 0%   (0/1)0%   (0/9)0%   (0/1)
parseInput (InputSource, CharProducer, MessageQueue): ParseTreeNode 0%   (0/1)0%   (0/122)0%   (0/22)
parseInput (URI): ParseTreeNode 0%   (0/1)0%   (0/24)0%   (0/4)
parseInputs (Collection, PluginCompiler): boolean 0%   (0/1)0%   (0/50)0%   (0/12)
run (String []): int 0%   (0/1)0%   (0/309)0%   (0/62)
writeFile (Appendable, CajoledModule): void 0%   (0/1)0%   (0/15)0%   (0/4)
writeFile (File, CajoledModule): void 0%   (0/1)0%   (0/45)0%   (0/11)
writeFile (File, String): void 0%   (0/1)0%   (0/32)0%   (0/8)
writeFileNonDebug (Appendable, CajoledModule): void 0%   (0/1)0%   (0/68)0%   (0/13)
writeFileWithDebug (Appendable, CajoledModule): void 0%   (0/1)0%   (0/12)0%   (0/2)
     
class PluginCompilerMain$10%   (0/1)0%   (0/2)0%   (0/20)0%   (0/3)
PluginCompilerMain$1 (PluginCompilerMain): void 0%   (0/1)0%   (0/6)0%   (0/1)
handle (IOException): void 0%   (0/1)0%   (0/14)0%   (0/2)
     
class PluginCompilerMain$20%   (0/1)0%   (0/2)0%   (0/31)0%   (0/4)
PluginCompilerMain$2 (PluginCompilerMain, Set, UriPolicy): void 0%   (0/1)0%   (0/12)0%   (0/1)
rewriteUri (ExternalReference, UriEffect, LoaderType, Map): String 0%   (0/1)0%   (0/19)0%   (0/3)
     
class PluginCompilerMain$30%   (0/1)0%   (0/2)0%   (0/39)0%   (0/7)
PluginCompilerMain$3 (PluginCompilerMain, Set): void 0%   (0/1)0%   (0/9)0%   (0/1)
fetch (ExternalReference, String): FetchedData 0%   (0/1)0%   (0/30)0%   (0/6)
     
class PluginCompilerMain$40%   (0/1)0%   (0/1)0%   (0/26)0%   (0/1)
<static initializer> 0%   (0/1)0%   (0/26)0%   (0/1)
     
class PluginCompilerMain$CachingUriFetcher0%   (0/1)0%   (0/3)0%   (0/24)0%   (0/3)
PluginCompilerMain$CachingUriFetcher (PluginCompilerMain, UriToFile): void 0%   (0/1)0%   (0/7)0%   (0/1)
newInputStream (File): InputStream 0%   (0/1)0%   (0/5)0%   (0/1)
newReader (File): Reader 0%   (0/1)0%   (0/12)0%   (0/1)

1// Copyright (C) 2006 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.SomethingWidgyHappenedError;
18import com.google.caja.lexer.CharProducer;
19import com.google.caja.lexer.CssTokenType;
20import com.google.caja.lexer.ExternalReference;
21import com.google.caja.lexer.FetchedData;
22import com.google.caja.lexer.HtmlLexer;
23import com.google.caja.lexer.InputSource;
24import com.google.caja.lexer.JsLexer;
25import com.google.caja.lexer.JsTokenQueue;
26import com.google.caja.lexer.ParseException;
27import com.google.caja.lexer.TokenConsumer;
28import com.google.caja.lexer.TokenQueue;
29import com.google.caja.parser.ParseTreeNode;
30import com.google.caja.parser.css.CssParser;
31import com.google.caja.parser.html.Dom;
32import com.google.caja.parser.html.DomParser;
33import com.google.caja.parser.html.Nodes;
34import com.google.caja.parser.js.CajoledModule;
35import com.google.caja.parser.js.Parser;
36import com.google.caja.plugin.UriFetcher.ChainingUriFetcher;
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.BuildInfo;
46import com.google.caja.util.Callback;
47import com.google.caja.util.CapturingReader;
48import com.google.caja.util.Charsets;
49import com.google.caja.util.Maps;
50import com.google.caja.render.Concatenator;
51import com.google.caja.render.JsMinimalPrinter;
52import com.google.caja.render.SourceSnippetRenderer;
53 
54import java.io.File;
55import java.io.FileOutputStream;
56import java.io.IOException;
57import java.io.InputStream;
58import java.io.InputStreamReader;
59import java.io.OutputStreamWriter;
60import java.io.Writer;
61import java.io.Reader;
62import java.io.FileNotFoundException;
63import java.io.FileInputStream;
64import java.net.URI;
65import java.net.URL;
66import java.util.Collection;
67import java.util.Map;
68import java.util.Set;
69 
70import org.w3c.dom.Node;
71 
72/**
73 * An executable that invokes the {@link PluginCompiler}.
74 *
75 * @author mikesamuel@gmail.com
76 */
77public final class PluginCompilerMain {
78  private final MessageQueue mq;
79  private final MessageContext mc;
80  private final Map<InputSource, CapturingReader> originalInputs
81      = Maps.newHashMap();
82  private final Config config = new Config(
83      getClass(), System.err, "Cajoles HTML, CSS, and JS files to JS.");
84  private final Callback<IOException> exHandler = new Callback<IOException>() {
85    public void handle(IOException ex) {
86      mq.addMessage(
87          MessageType.IO_ERROR, MessagePart.Factory.valueOf(ex.toString()));
88    }
89  };
90 
91  private class CachingUriFetcher extends FileSystemUriFetcher {
92    public CachingUriFetcher(UriToFile u2f) { super(u2f); }
93 
94    @Override
95    protected Reader newReader(File f) throws FileNotFoundException {
96      return createReader(new InputSource(f), new FileInputStream(f));
97    }
98 
99    @Override
100    protected InputStream newInputStream(File f) throws FileNotFoundException {
101      return new FileInputStream(f);
102    }
103  }
104 
105  private PluginCompilerMain() {
106    mq = new SimpleMessageQueue();
107    mc = new MessageContext();
108  }
109 
110  private int run(String[] argv) {
111    if (!config.processArguments(argv)) {
112      return -1;
113    }
114 
115    boolean success = false;
116    MessageContext mc = null;
117    CajoledModule compiledJsOutput = null;
118    Node compiledDomOutput = null;
119    String compiledHtmlOutput = null;
120    File fileLimitAncestor = config.getFetcherBase();
121 
122    File jsOutputDest = config.getOutputJsFile();
123    File htmlOutputDest = config.getOutputHtmlFile();
124 
125    try {
126      UriFetcher fetcher;
127      UriPolicy policy;
128      try {
129        if (fileLimitAncestor != null) {
130          UriToFile u2f = new UriToFile(fileLimitAncestor);
131          fetcher = ChainingUriFetcher.make(
132              new DataUriFetcher(),
133              new CachingUriFetcher(u2f));
134          policy = new FileSystemUriPolicy(u2f);
135        } else {
136          fetcher = new DataUriFetcher();
137          policy = UriPolicy.DENY_ALL;
138        }
139      } catch (IOException e) {  // Could not resolve file name
140        fetcher = new DataUriFetcher();
141        policy = UriPolicy.DENY_ALL;
142      }
143      final Set<String> lUrls = config.getLinkableUris();
144      if (!lUrls.isEmpty()) {
145        final UriPolicy prePolicy = policy;
146        policy = new UriPolicy() {
147          public String rewriteUri(
148              ExternalReference u, UriEffect effect,
149              LoaderType loader, Map<String, ?> hints) {
150            String uri = u.getUri().toString();
151            if (lUrls.contains(uri)) { return uri; }
152            return prePolicy.rewriteUri(u, effect, loader, hints);
153          }
154        };
155      }
156      final Set<String> fUrls = config.getFetchableUris();
157      if (!fUrls.isEmpty()) {
158        fetcher = ChainingUriFetcher.make(
159            fetcher,
160            new UriFetcher() {
161              public FetchedData fetch(ExternalReference ref, String mimeType)
162                  throws UriFetchException {
163                String uri = ref.getUri().toString();
164                if (!fUrls.contains(uri)) {
165                  throw new UriFetchException(ref, mimeType);
166                }
167                try {
168                  return FetchedData.fromConnection(
169                      new URL(uri).openConnection());
170                } catch (IOException ex) {
171                  throw new UriFetchException(ref, mimeType, ex);
172                }
173              }
174            });
175      }
176 
177      PluginMeta meta = new PluginMeta(fetcher, policy);
178      meta.setIdClass(config.getIdClass());
179      meta.setEnableES53(config.getES53());
180      PluginCompiler compiler = new PluginCompiler(
181          BuildInfo.getInstance(), meta, mq);
182      compiler.setPreconditions(
183          config.preconditions(compiler.getPreconditions()));
184      compiler.setGoals(config.goals(compiler.getGoals()));
185 
186      mc = compiler.getMessageContext();
187      compiler.setCssSchema(config.getCssSchema(mq));
188      compiler.setHtmlSchema(config.getHtmlSchema(mq));
189 
190      success = parseInputs(config.getInputUris(), compiler) && compiler.run();
191      if (success) {
192        compiledJsOutput = compiler.getJavascript();
193        compiledDomOutput = compiler.getStaticHtml();
194        compiledHtmlOutput = compiledDomOutput != null ?
195            Nodes.render(compiledDomOutput) : "";
196      }
197    } finally {
198      if (mc == null) { mc = new MessageContext(); }
199      MessageLevel maxMessageLevel = dumpMessages(mq, mc, System.err);
200      success &= MessageLevel.ERROR.compareTo(maxMessageLevel) > 0;
201    }
202 
203    if (success) {
204      if (jsOutputDest != null) {
205        writeFile(jsOutputDest, compiledJsOutput);
206      } else {
207        StringBuilder compiledJsOutputBuf = new StringBuilder();
208        compiledJsOutputBuf.append("<script>");
209        try {
210          writeFile(compiledJsOutputBuf, compiledJsOutput);
211        } catch (IOException ex) {
212          throw new SomethingWidgyHappenedError(ex);
213        }
214        compiledJsOutputBuf.append("</script>");
215        compiledHtmlOutput += compiledJsOutputBuf;
216      }
217      if (htmlOutputDest != null) {
218        writeFile(htmlOutputDest, compiledHtmlOutput);
219      }
220    } else {
221      // Make sure there is no previous output file from a failed run.
222      if (jsOutputDest != null) { jsOutputDest.delete(); }
223      if (htmlOutputDest != null) { htmlOutputDest.delete(); }
224      // If it wasn't there in the first place, or is not writable, that's OK,
225      // so ignore the return value.
226    }
227 
228    return success ? 0 : -1;
229  }
230 
231  private boolean parseInputs(Collection<URI> inputs, PluginCompiler pluginc) {
232    boolean parsePassed = true;
233    for (URI input : inputs) {
234      try {
235        ParseTreeNode parseTree = parseInput(input);
236        if (null != parseTree) { pluginc.addInput(parseTree, input); }
237      } catch (ParseException ex) {
238        ex.toMessageQueue(mq);
239        parsePassed = false;
240      } catch (IOException ex) {
241        mq.addMessage(MessageType.IO_ERROR,
242                      MessagePart.Factory.valueOf(ex.toString()));
243        parsePassed = false;
244      }
245    }
246    return parsePassed;
247  }
248 
249  /** Parse one input from a URI. */
250  private ParseTreeNode parseInput(URI input)
251      throws IOException, ParseException {
252    InputSource is = new InputSource(input);
253    mc.addInputSource(is);
254 
255    CharProducer cp = CharProducer.Factory.create(
256        createReader(is, input.toURL().openStream()), is);
257    return parseInput(is, cp, mq);
258  }
259 
260  /** Classify an input by extension and use the appropriate parser. */
261  static ParseTreeNode parseInput(
262      InputSource is, CharProducer cp, MessageQueue mq)
263      throws ParseException {
264 
265    String path = is.getUri().getPath();
266 
267    ParseTreeNode input;
268    if (path.endsWith(".js")) {
269      JsLexer lexer = new JsLexer(cp);
270      JsTokenQueue tq = new JsTokenQueue(lexer, is);
271      if (tq.isEmpty()) { return null; }
272      Parser p = new Parser(tq, mq);
273      input = p.parse();
274      tq.expectEmpty();
275    } else if (path.endsWith(".css")) {
276      TokenQueue<CssTokenType> tq = CssParser.makeTokenQueue(cp, mq, false);
277      if (tq.isEmpty()) { return null; }
278 
279      CssParser p = new CssParser(tq, mq, MessageLevel.WARNING);
280      input = p.parseStyleSheet();
281      tq.expectEmpty();
282    } else if (path.endsWith(".html") || path.endsWith(".xhtml")
283               || (!cp.isEmpty() && cp.getBuffer()[cp.getOffset()] == '<')) {
284      DomParser p = new DomParser(new HtmlLexer(cp), false, is, mq);
285      if (p.getTokenQueue().isEmpty()) { return null; }
286      input = new Dom(p.parseFragment());
287      p.getTokenQueue().expectEmpty();
288    } else {
289      throw new SomethingWidgyHappenedError("Can't classify input " + is);
290    }
291    return input;
292  }
293 
294  /** Write the given HTML to the given file. */
295  private void writeFile(File outputHtmlFile, String compiledHtmlOutput) {
296    try {
297      OutputStreamWriter out = new OutputStreamWriter(
298            new FileOutputStream(outputHtmlFile), Charsets.UTF_8);
299      try {
300        out.append(compiledHtmlOutput);
301      } finally {
302        try { out.close(); } catch (IOException e) { /* close quietly */ }
303      }
304    } catch (IOException ex) {
305      exHandler.handle(ex);
306    }
307  }
308 
309  /** Write the given parse tree to the given file. */
310  private void writeFile(File f, CajoledModule module) {
311    if (module == null) { return; }
312 
313    Writer out = null;
314 
315    try {
316      out = new OutputStreamWriter(new FileOutputStream(f), Charsets.UTF_8);
317      writeFile(out, module);
318    } catch (IOException ex) {
319      ex.printStackTrace();
320    } finally {
321      if (out != null) {
322        try {
323          out.close();
324        } catch (IOException e) {
325          /* no zero-argument ctor */
326        }
327      }
328    }
329  }
330 
331  private void writeFile(Appendable out, CajoledModule module)
332      throws IOException {
333    if (config.renderer() == Config.SourceRenderMode.DEBUGGER) {
334      // Debugger rendering is weird enough to warrant its own method
335      writeFileWithDebug(out, module);
336    } else {
337      writeFileNonDebug(out, module);
338    }
339  }
340 
341  private void writeFileNonDebug(Appendable out, CajoledModule module)
342      throws IOException {
343    TokenConsumer tc;
344    switch (config.renderer()) {
345      case PRETTY:
346        tc = module.makeRenderer(out, exHandler);
347        break;
348      case MINIFY:
349        tc = new JsMinimalPrinter(new Concatenator(out,  exHandler));
350        break;
351      case SIDEBYSIDE:
352        tc = new SourceSnippetRenderer(
353            buildOriginalInputCharSequences(), mc,
354            makeRenderContext(new Concatenator(out, exHandler)));
355        break;
356      default:
357        throw new SomethingWidgyHappenedError(
358            "Unrecognized renderer: " + config.renderer());
359    }
360    RenderContext rc = makeRenderContext(tc);
361    module.render(rc);
362    tc.noMoreTokens();
363    out.append('\n');
364  }
365 
366  private void writeFileWithDebug(Appendable out, CajoledModule module)
367      throws IOException {
368    module.renderWithDebugSymbols(
369        buildOriginalInputCharSequences(),
370        makeRenderContext(new Concatenator(out, exHandler)));
371  }
372 
373  private static RenderContext makeRenderContext(TokenConsumer tc) {
374    return new RenderContext(tc).withAsciiOnly(true).withEmbeddable(true);
375  }
376 
377  /**
378   * Dumps messages to the given output stream, returning the highest message
379   * level seen.
380   */
381  static MessageLevel dumpMessages(
382      MessageQueue mq, MessageContext mc, Appendable out) {
383    MessageLevel maxLevel = MessageLevel.values()[0];
384    for (Message m : mq.getMessages()) {
385      MessageLevel level = m.getMessageLevel();
386      if (maxLevel.compareTo(level) < 0) { maxLevel = level; }
387    }
388    MessageLevel ignoreLevel = null;
389    if (maxLevel.compareTo(MessageLevel.LINT) < 0) {
390      // If there's only checkpoints, be quiet.
391      ignoreLevel = MessageLevel.LOG;
392    }
393    try {
394      for (Message m : mq.getMessages()) {
395        MessageLevel level = m.getMessageLevel();
396        if (ignoreLevel != null && level.compareTo(ignoreLevel) <= 0) {
397          continue;
398        }
399        String levelName = level.name();
400        out.append(levelName);
401        if (levelName.length() < 7) {
402          out.append("       ".substring(levelName.length()));
403        }
404        out.append(": ");
405        m.format(mc, out);
406        out.append("\n");
407 
408        if (maxLevel.compareTo(level) < 0) { maxLevel = level; }
409      }
410    } catch (IOException ex) {
411      ex.printStackTrace();
412    }
413    return maxLevel;
414  }
415 
416  private Reader createReader(InputSource is, InputStream stream) {
417    InputStreamReader isr = new InputStreamReader(stream, Charsets.UTF_8);
418 
419    if (config.renderer() == Config.SourceRenderMode.SIDEBYSIDE ||
420        config.renderer() == Config.SourceRenderMode.DEBUGGER) {
421      CapturingReader cr = new CapturingReader(isr);
422      originalInputs.put(is, cr);
423      return cr;
424    } else {
425      return isr;
426    }
427  }
428 
429  private Map<InputSource, CharSequence> buildOriginalInputCharSequences()
430      throws IOException {
431    Map<InputSource, CharSequence> results = Maps.newHashMap();
432    for (InputSource is : originalInputs.keySet()) {
433      results.put(is, originalInputs.get(is).getCapture());
434    }
435    return results;
436  }
437 
438  public static void main(String[] args) {
439    int exitCode;
440    try {
441      PluginCompilerMain main = new PluginCompilerMain();
442      exitCode = main.run(args);
443    } catch (Exception ex) {
444      ex.printStackTrace();
445      exitCode = -1;
446    }
447    try {
448      System.exit(exitCode);
449    } catch (SecurityException ex) {
450      // This method may be invoked under a SecurityManager, e.g. by Ant,
451      // so just suppress the security exception and return normally.
452    }
453  }
454}

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