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

COVERAGE SUMMARY FOR SOURCE FILE [SideBySideRenderer.java]

nameclass, %method, %block, %line, %
SideBySideRenderer.java100% (2/2)92%  (11/12)99%  (392/396)98%  (74.6/76)

COVERAGE BREAKDOWN BY CLASS AND METHOD

nameclass, %method, %block, %line, %
     
class SideBySideRenderer100% (1/1)89%  (8/9)99%  (377/381)98%  (69.6/71)
SideBySideRenderer (Map): void 100% (1/1)100% (53/53)100% (9/9)
consume (String): void 100% (1/1)98%  (54/55)97%  (8.8/9)
emitLine (): void 100% (1/1)100% (76/76)100% (13/13)
lastLineNo (InputSource): int 100% (1/1)100% (13/13)100% (2/2)
mark (FilePosition): void 100% (1/1)100% (10/10)100% (3/3)
noMoreTokens (): void 100% (1/1)100% (93/93)100% (17/17)
originalSourceSnippet (InputSource, int, int): String 100% (1/1)94%  (31/33)98%  (5.9/6)
splitChunks (String): List 100% (1/1)100% (47/47)100% (11/11)
switchSource (InputSource, InputSource): void 0%   (0/1)0%   (0/1)0%   (0/1)
     
class SideBySideRenderer$Chunk100% (1/1)100% (3/3)100% (15/15)100% (5/5)
SideBySideRenderer$Chunk (String, FilePosition): void 100% (1/1)100% (9/9)100% (4/4)
access$000 (SideBySideRenderer$Chunk): FilePosition 100% (1/1)100% (3/3)100% (1/1)
access$100 (SideBySideRenderer$Chunk): String 100% (1/1)100% (3/3)100% (1/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.render;
16 
17import com.google.caja.lexer.FilePosition;
18import com.google.caja.lexer.InputSource;
19import com.google.caja.lexer.TokenConsumer;
20import com.google.caja.util.Join;
21import com.google.caja.util.Pair;
22 
23import java.util.ArrayList;
24import java.util.Arrays;
25import java.util.HashMap;
26import java.util.List;
27import java.util.Map;
28import java.util.regex.Matcher;
29import java.util.regex.Pattern;
30 
31/**
32 * Renders rewritten source code interleaved with the original.  E.g.
33 * {@code
34 *   // Rewritten by cajoler.
35 *   muckWith(                    IMPORTS___.muckWith(
36 *       document.forms[0])           IMPORTS___.document.forms[0]);
37 * }
38 *
39 * @author mikesamuel@gmail.com
40 */
41public abstract class SideBySideRenderer implements TokenConsumer {
42  private final Map<InputSource, String[]> originalSourceLines;
43  private final Map<InputSource, Integer> maxLineSeen
44      = new HashMap<InputSource, Integer>();
45  private final TokenConsumer renderer;
46  private FilePosition lastPos;
47  private FilePosition mark;
48  private FilePosition chunkStart;
49  /** Chunks of original source. */
50  private final List<Chunk> chunks = new ArrayList<Chunk>();
51  private StringBuilder renderedBuf;
52 
53  public SideBySideRenderer(
54      Map<InputSource, ? extends CharSequence> originalSource) {
55    this.originalSourceLines = new HashMap<InputSource, String[]>();
56    for (Map.Entry<InputSource, ? extends CharSequence> e
57         : originalSource.entrySet()) {
58      this.originalSourceLines.put(
59          e.getKey(), e.getValue().toString().split("\r\n?|\n"));
60    }
61    this.renderedBuf = new StringBuilder();
62    this.renderer = makeRenderer(this.renderedBuf);
63  }
64 
65  /**
66   * Called when rendered tokens have been processed for a line of original
67   * source.
68   *
69   * @param startOfLine a file position into the original source code.
70   * @param original zero or more lines of original source code.
71   * @param rendered one or more lines of rendered source code.
72   */
73  protected abstract void emitLine(
74      FilePosition startOfLine, String original, String rendered);
75 
76  /**
77   * Called when we render a token from a different source than previously.
78   * This method does nothing, but may be overridden.
79   * @param previous the source from which the last rendered token came.
80   * @param next the source from which the next rendered token will come,
81   *   unless switchSource is called again before {@link #consume}.
82   */
83  protected void switchSource(InputSource previous, InputSource next) {
84    // noop
85  }
86 
87  protected abstract TokenConsumer makeRenderer(StringBuilder renderedSrc);
88 
89  public void mark(FilePosition pos) {
90    if (pos != null) { this.mark = pos; }
91    renderer.mark(pos);
92  }
93 
94  public void consume(String text) {
95    if (TokenClassification.isComment(text)) { return; }
96    if (!(mark != null
97          ? lastPos != null && mark.source().equals(lastPos.source())
98          : lastPos == null)) {
99      emitLine();
100    } else if (lastPos != null) {
101      if (mark.startLineNo() > lastPos.startLineNo()
102          && mark.startLineNo() >= lastLineNo(mark.source())) {
103        emitLine();
104      }
105    }
106 
107    renderer.consume(text);
108    lastPos = mark;
109  }
110 
111  public void noMoreTokens() {
112    emitLine();
113    renderer.noMoreTokens();
114 
115    String renderedSrc = renderedBuf.toString();
116    renderedBuf.setLength(0);
117 
118    InputSource lastSource = null;
119    for (Pair<String, Integer> chunk : splitChunks(renderedSrc)) {
120      String renderedChunk = chunk.a;
121      int chunkIndex = chunk.b;
122      Chunk originalChunk = (
123          chunkIndex >= 0 ? chunks.get(chunkIndex) : new Chunk("", null));
124      InputSource source = (
125          originalChunk.start != null ? originalChunk.start.source() : null);
126 
127      if (!"".equals(renderedChunk) || !"".equals(originalChunk.src)) {
128        if (!(source != null
129            ? lastSource != null && source.equals(lastSource)
130            : lastSource == null)) {
131          switchSource(lastSource, source);
132        }
133 
134        emitLine(originalChunk.start, originalChunk.src, renderedChunk);
135      }
136      lastSource = source;
137    }
138  }
139 
140  private void emitLine() {
141    String originalSrc = "";
142    if (chunkStart != null) {
143      int startLine = lastLineNo(chunkStart.source()) + 1;
144      int endLine = lastPos.endLineNo();
145      if (lastPos.endCharInLine() == 1) { --endLine; }
146      originalSrc = originalSourceSnippet(
147          chunkStart.source(), startLine, endLine);
148      maxLineSeen.put(chunkStart.source(), endLine);
149    }
150 
151    int chunkId = chunks.size();
152    chunks.add(new Chunk(originalSrc, chunkStart));
153    renderer.consume("/*@" + chunkId + "*/");
154    renderer.consume("\n");
155 
156    chunkStart = mark;
157  }
158 
159  private static List<Pair<String, Integer>> splitChunks(String renderedSrc) {
160    Pattern p = Pattern.compile(" */\\*@([0-9]+)\\*/(?:\n|\r\n?|$)");
161    Matcher m = p.matcher(renderedSrc);
162    int start = 0;
163    List<Pair<String, Integer>> chunks = new ArrayList<Pair<String, Integer>>();
164    while (m.find()) {
165      int chunkIndex = Integer.parseInt(m.group(1));
166      chunks.add(
167          Pair.pair(renderedSrc.substring(start, m.start()), chunkIndex));
168      start = m.end();
169    }
170    chunks.add(Pair.pair(renderedSrc.substring(start), -1));
171    return chunks;
172  }
173 
174  private String originalSourceSnippet(
175      InputSource src, int startLine, int endLine) {
176    // FilePosition lines are 1-indexed, but arrays are zero-indexed.
177    startLine -= 1;
178    endLine -= 1;
179 
180    String[] lines = originalSourceLines.get(src);
181    if (lines == null || startLine >= lines.length) { return ""; }
182    endLine = Math.min(endLine, lines.length - 1);
183 
184    return Join.join(
185        "\n", Arrays.asList(lines).subList(startLine, endLine + 1));
186  }
187 
188  private int lastLineNo(InputSource src) {
189    Integer ln = maxLineSeen.get(src);
190    return ln != null ? ln : 0;
191  }
192 
193  private static final class Chunk {
194    private final String src;
195    private final FilePosition start;
196    Chunk(String src, FilePosition start) {
197      this.start = start;
198      this.src = src;
199    }
200  }
201}

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