001/* 002 * Copyright (C) 2006 The Guava Authors 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 005 * in compliance with the License. You may obtain a copy of the License at 006 * 007 * http://www.apache.org/licenses/LICENSE-2.0 008 * 009 * Unless required by applicable law or agreed to in writing, software distributed under the License 010 * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 011 * or implied. See the License for the specific language governing permissions and limitations under 012 * the License. 013 */ 014 015package com.google.common.escape; 016 017import static com.google.common.base.Preconditions.checkNotNull; 018 019import com.google.common.annotations.Beta; 020import com.google.common.annotations.GwtCompatible; 021import com.google.errorprone.annotations.CanIgnoreReturnValue; 022import java.util.HashMap; 023import java.util.Map; 024 025/** 026 * Simple helper class to build a "sparse" array of objects based on the indexes that were added to 027 * it. The array will be from 0 to the maximum index given. All non-set indexes will contain null 028 * (so it's not really a sparse array, just a pseudo sparse array). The builder can also return a 029 * CharEscaper based on the generated array. 030 * 031 * @author Sven Mawson 032 * @since 15.0 033 */ 034@Beta 035@GwtCompatible 036public final class CharEscaperBuilder { 037 /** 038 * Simple decorator that turns an array of replacement char[]s into a CharEscaper, this results in 039 * a very fast escape method. 040 */ 041 private static class CharArrayDecorator extends CharEscaper { 042 private final char[][] replacements; 043 private final int replaceLength; 044 045 CharArrayDecorator(char[][] replacements) { 046 this.replacements = replacements; 047 this.replaceLength = replacements.length; 048 } 049 050 /* 051 * Overriding escape method to be slightly faster for this decorator. We test the replacements 052 * array directly, saving a method call. 053 */ 054 @Override 055 public String escape(String s) { 056 int slen = s.length(); 057 for (int index = 0; index < slen; index++) { 058 char c = s.charAt(index); 059 if (c < replacements.length && replacements[c] != null) { 060 return escapeSlow(s, index); 061 } 062 } 063 return s; 064 } 065 066 @Override 067 protected char[] escape(char c) { 068 return c < replaceLength ? replacements[c] : null; 069 } 070 } 071 072 // Replacement mappings. 073 private final Map<Character, String> map; 074 075 // The highest index we've seen so far. 076 private int max = -1; 077 078 /** 079 * Construct a new sparse array builder. 080 */ 081 public CharEscaperBuilder() { 082 this.map = new HashMap<Character, String>(); 083 } 084 085 /** 086 * Add a new mapping from an index to an object to the escaping. 087 */ 088 @CanIgnoreReturnValue 089 public CharEscaperBuilder addEscape(char c, String r) { 090 map.put(c, checkNotNull(r)); 091 if (c > max) { 092 max = c; 093 } 094 return this; 095 } 096 097 /** 098 * Add multiple mappings at once for a particular index. 099 */ 100 @CanIgnoreReturnValue 101 public CharEscaperBuilder addEscapes(char[] cs, String r) { 102 checkNotNull(r); 103 for (char c : cs) { 104 addEscape(c, r); 105 } 106 return this; 107 } 108 109 /** 110 * Convert this builder into an array of char[]s where the maximum index is the value of the 111 * highest character that has been seen. The array will be sparse in the sense that any unseen 112 * index will default to null. 113 * 114 * @return a "sparse" array that holds the replacement mappings. 115 */ 116 public char[][] toArray() { 117 char[][] result = new char[max + 1][]; 118 for (Map.Entry<Character, String> entry : map.entrySet()) { 119 result[entry.getKey()] = entry.getValue().toCharArray(); 120 } 121 return result; 122 } 123 124 /** 125 * Convert this builder into a char escaper which is just a decorator around the underlying array 126 * of replacement char[]s. 127 * 128 * @return an escaper that escapes based on the underlying array. 129 */ 130 public Escaper toEscaper() { 131 return new CharArrayDecorator(toArray()); 132 } 133}