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 17 package com.google.clearsilver.jsilver.functions.escape; 18 19 import com.google.clearsilver.jsilver.functions.TextFilter; 20 21 import 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 */ 33 public 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 & 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