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.functions.escape;
18
19import com.google.clearsilver.jsilver.functions.TextFilter;
20
21import java.io.IOException;
22
23/**
24 * Base class to make writing fast, simple escaping functions easy. A simple escaping function is
25 * one where each character in the input is treated independently and there is no runtime state. The
26 * only decision you make is whether the current character should be escaped into some different
27 * string or not.
28 *
29 * The only serious limitation on using this class it that only low valued characters can be
30 * escaped. This is because (for speed) we use an array of escaped strings, indexed by character
31 * value. In future this limitation may be lifted if there's a call for it.
32 */
33public abstract class SimpleEscapingFunction implements TextFilter {
34  // The limit for how many strings we can store here (max)
35  private static final int CHAR_INDEX_LIMIT = 256;
36
37  // Our fast lookup array of escaped strings. This array is indexed by char
38  // value so it's important not to have it grow too large. For now we have
39  // an artificial limit on it.
40  private String[] ESCAPE_STRINGS;
41
42  /**
43   * Creates an instance to escape the given set of characters.
44   */
45  protected SimpleEscapingFunction(char[] ESCAPE_CHARS) {
46    setEscapeChars(ESCAPE_CHARS);
47  }
48
49  protected SimpleEscapingFunction() {
50    ESCAPE_STRINGS = new String[0];
51  }
52
53  protected void setEscapeChars(char[] ESCAPE_CHARS) throws AssertionError {
54    int highestChar = -1;
55    for (char c : ESCAPE_CHARS) {
56      if (c > highestChar) {
57        highestChar = c;
58      }
59    }
60    if (highestChar >= CHAR_INDEX_LIMIT) {
61      throw new AssertionError("Cannot escape characters with values above " + CHAR_INDEX_LIMIT);
62    }
63    ESCAPE_STRINGS = new String[highestChar + 1];
64    for (char c : ESCAPE_CHARS) {
65      ESCAPE_STRINGS[c] = getEscapeString(c);
66    }
67  }
68
69  /**
70   * Given one of the escape characters supplied to this instance's constructor, return the escape
71   * string for it. This method does not need to be efficient.
72   */
73  protected abstract String getEscapeString(char c);
74
75  /**
76   * Algorithm is as follows:
77   * <ol>
78   * <li>Scan block for contiguous unescaped sequences
79   * <li>Append unescaped sequences to output
80   * <li>Append escaped string to output (if found)
81   * <li>Rinse &amp; Repeat
82   * </ol>
83   */
84  @Override
85  public void filter(String in, Appendable out) throws IOException {
86    final int len = in.length();
87    int pos = 0;
88    int start = pos;
89    while (pos < len) {
90      // We really hope that the hotspot compiler inlines this call properly
91      // (without optimization it accounts for > 50% of the time in this call)
92      final char chr = in.charAt(pos);
93      final String escapeString;
94      if (chr < ESCAPE_STRINGS.length && (escapeString = ESCAPE_STRINGS[chr]) != null) {
95        // We really hope our appendable handles sub-strings nicely
96        // (we know that StringBuilder / StringBuffer does).
97        if (pos > start) {
98          out.append(in, start, pos);
99        }
100        out.append(escapeString);
101        pos += 1;
102        start = pos;
103        continue;
104      }
105      pos += 1;
106    }
107    if (pos > start) {
108      out.append(in, start, pos);
109    }
110  }
111}
112