Home | History | Annotate | Download | only in escape
      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 &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