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.GwtCompatible; 020import com.google.errorprone.annotations.CanIgnoreReturnValue; 021import java.util.HashMap; 022import java.util.Map; 023import java.util.Map.Entry; 024import org.checkerframework.checker.nullness.qual.Nullable; 025 026/** 027 * Simple helper class to build a "sparse" array of objects based on the indexes that were added to 028 * it. The array will be from 0 to the maximum index given. All non-set indexes will contain null 029 * (so it's not really a sparse array, just a pseudo sparse array). The builder can also return a 030 * CharEscaper based on the generated array. 031 * 032 * @author Sven Mawson 033 * @since 15.0 034 */ 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[] @Nullable [] replacements; 043 private final int replaceLength; 044 045 CharArrayDecorator(char[] @Nullable [] 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 @Nullable [] 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 /** Construct a new sparse array builder. */ 079 public CharEscaperBuilder() { 080 this.map = new HashMap<>(); 081 } 082 083 /** Add a new mapping from an index to an object to the escaping. */ 084 @CanIgnoreReturnValue 085 public CharEscaperBuilder addEscape(char c, String r) { 086 map.put(c, checkNotNull(r)); 087 if (c > max) { 088 max = c; 089 } 090 return this; 091 } 092 093 /** Add multiple mappings at once for a particular index. */ 094 @CanIgnoreReturnValue 095 public CharEscaperBuilder addEscapes(char[] cs, String r) { 096 checkNotNull(r); 097 for (char c : cs) { 098 addEscape(c, r); 099 } 100 return this; 101 } 102 103 /** 104 * Convert this builder into an array of char[]s where the maximum index is the value of the 105 * highest character that has been seen. The array will be sparse in the sense that any unseen 106 * index will default to null. 107 * 108 * @return a "sparse" array that holds the replacement mappings. 109 */ 110 public char[] @Nullable [] toArray() { 111 char[][] result = new char[max + 1][]; 112 for (Entry<Character, String> entry : map.entrySet()) { 113 result[entry.getKey()] = entry.getValue().toCharArray(); 114 } 115 return result; 116 } 117 118 /** 119 * Convert this builder into a char escaper which is just a decorator around the underlying array 120 * of replacement char[]s. 121 * 122 * @return an escaper that escapes based on the underlying array. 123 */ 124 public Escaper toEscaper() { 125 return new CharArrayDecorator(toArray()); 126 } 127}