001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache license, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the license for the specific language governing permissions and 015 * limitations under the license. 016 */ 017package org.apache.logging.log4j.util; 018 019import java.util.Map.Entry; 020 021import static java.lang.Character.toLowerCase; 022 023/** 024 * <em>Consider this class private.</em> 025 */ 026public final class StringBuilders { 027 private StringBuilders() { 028 } 029 030 /** 031 * Appends in the following format: double quoted value. 032 * 033 * @param sb a string builder 034 * @param value a value 035 * @return {@code "value"} 036 */ 037 public static StringBuilder appendDqValue(final StringBuilder sb, final Object value) { 038 return sb.append(Chars.DQUOTE).append(value).append(Chars.DQUOTE); 039 } 040 041 /** 042 * Appends in the following format: key=double quoted value. 043 * 044 * @param sb a string builder 045 * @param entry a map entry 046 * @return {@code key="value"} 047 */ 048 public static StringBuilder appendKeyDqValue(final StringBuilder sb, final Entry<String, String> entry) { 049 return appendKeyDqValue(sb, entry.getKey(), entry.getValue()); 050 } 051 052 /** 053 * Appends in the following format: key=double quoted value. 054 * 055 * @param sb a string builder 056 * @param key a key 057 * @param value a value 058 * @return the specified StringBuilder 059 */ 060 public static StringBuilder appendKeyDqValue(final StringBuilder sb, final String key, final Object value) { 061 return sb.append(key).append(Chars.EQ).append(Chars.DQUOTE).append(value).append(Chars.DQUOTE); 062 } 063 064 /** 065 * Appends a text representation of the specified object to the specified StringBuilder, 066 * if possible without allocating temporary objects. 067 * 068 * @param stringBuilder the StringBuilder to append the value to 069 * @param obj the object whose text representation to append to the StringBuilder 070 */ 071 public static void appendValue(final StringBuilder stringBuilder, final Object obj) { 072 if (!appendSpecificTypes(stringBuilder, obj)) { 073 stringBuilder.append(obj); 074 } 075 } 076 077 public static boolean appendSpecificTypes(final StringBuilder stringBuilder, final Object obj) { 078 if (obj == null || obj instanceof String) { 079 stringBuilder.append((String) obj); 080 } else if (obj instanceof StringBuilderFormattable) { 081 ((StringBuilderFormattable) obj).formatTo(stringBuilder); 082 } else if (obj instanceof CharSequence) { 083 stringBuilder.append((CharSequence) obj); 084 } else if (obj instanceof Integer) { // LOG4J2-1437 unbox auto-boxed primitives to avoid calling toString() 085 stringBuilder.append(((Integer) obj).intValue()); 086 } else if (obj instanceof Long) { 087 stringBuilder.append(((Long) obj).longValue()); 088 } else if (obj instanceof Double) { 089 stringBuilder.append(((Double) obj).doubleValue()); 090 } else if (obj instanceof Boolean) { 091 stringBuilder.append(((Boolean) obj).booleanValue()); 092 } else if (obj instanceof Character) { 093 stringBuilder.append(((Character) obj).charValue()); 094 } else if (obj instanceof Short) { 095 stringBuilder.append(((Short) obj).shortValue()); 096 } else if (obj instanceof Float) { 097 stringBuilder.append(((Float) obj).floatValue()); 098 } else if (obj instanceof Byte) { 099 stringBuilder.append(((Byte) obj).byteValue()); 100 } else { 101 return false; 102 } 103 return true; 104 } 105 106 /** 107 * Returns true if the specified section of the left CharSequence equals the specified section of the right 108 * CharSequence. 109 * 110 * @param left the left CharSequence 111 * @param leftOffset start index in the left CharSequence 112 * @param leftLength length of the section in the left CharSequence 113 * @param right the right CharSequence to compare a section of 114 * @param rightOffset start index in the right CharSequence 115 * @param rightLength length of the section in the right CharSequence 116 * @return true if equal, false otherwise 117 */ 118 public static boolean equals(final CharSequence left, final int leftOffset, final int leftLength, 119 final CharSequence right, final int rightOffset, final int rightLength) { 120 if (leftLength == rightLength) { 121 for (int i = 0; i < rightLength; i++) { 122 if (left.charAt(i + leftOffset) != right.charAt(i + rightOffset)) { 123 return false; 124 } 125 } 126 return true; 127 } 128 return false; 129 } 130 131 /** 132 * Returns true if the specified section of the left CharSequence equals, ignoring case, the specified section of 133 * the right CharSequence. 134 * 135 * @param left the left CharSequence 136 * @param leftOffset start index in the left CharSequence 137 * @param leftLength length of the section in the left CharSequence 138 * @param right the right CharSequence to compare a section of 139 * @param rightOffset start index in the right CharSequence 140 * @param rightLength length of the section in the right CharSequence 141 * @return true if equal ignoring case, false otherwise 142 */ 143 public static boolean equalsIgnoreCase(final CharSequence left, final int leftOffset, final int leftLength, 144 final CharSequence right, final int rightOffset, final int rightLength) { 145 if (leftLength == rightLength) { 146 for (int i = 0; i < rightLength; i++) { 147 if (toLowerCase(left.charAt(i + leftOffset)) != toLowerCase(right.charAt(i + rightOffset))) { 148 return false; 149 } 150 } 151 return true; 152 } 153 return false; 154 } 155 156 /** 157 * Ensures that the char[] array of the specified StringBuilder does not exceed the specified number of characters. 158 * This method is useful to ensure that excessively long char[] arrays are not kept in memory forever. 159 * 160 * @param stringBuilder the StringBuilder to check 161 * @param maxSize the maximum number of characters the StringBuilder is allowed to have 162 * @since 2.9 163 */ 164 public static void trimToMaxSize(final StringBuilder stringBuilder, final int maxSize) { 165 if (stringBuilder != null && stringBuilder.capacity() > maxSize) { 166 stringBuilder.setLength(maxSize); 167 stringBuilder.trimToSize(); 168 } 169 } 170 171 public static void escapeJson(final StringBuilder toAppendTo, final int start) { 172 int escapeCount = 0; 173 for (int i = start; i < toAppendTo.length(); i++) { 174 final char c = toAppendTo.charAt(i); 175 switch (c) { 176 case '\b': 177 case '\t': 178 case '\f': 179 case '\n': 180 case '\r': 181 case '"': 182 case '\\': 183 escapeCount++; 184 break; 185 default: 186 if (Character.isISOControl(c)) { 187 escapeCount += 5; 188 } 189 } 190 } 191 192 final int lastChar = toAppendTo.length() - 1; 193 toAppendTo.setLength(toAppendTo.length() + escapeCount); 194 int lastPos = toAppendTo.length() - 1; 195 196 for (int i = lastChar; lastPos > i; i--) { 197 final char c = toAppendTo.charAt(i); 198 switch (c) { 199 case '\b': 200 lastPos = escapeAndDecrement(toAppendTo, lastPos, 'b'); 201 break; 202 203 case '\t': 204 lastPos = escapeAndDecrement(toAppendTo, lastPos, 't'); 205 break; 206 207 case '\f': 208 lastPos = escapeAndDecrement(toAppendTo, lastPos, 'f'); 209 break; 210 211 case '\n': 212 lastPos = escapeAndDecrement(toAppendTo, lastPos, 'n'); 213 break; 214 215 case '\r': 216 lastPos = escapeAndDecrement(toAppendTo, lastPos, 'r'); 217 break; 218 219 case '"': 220 case '\\': 221 lastPos = escapeAndDecrement(toAppendTo, lastPos, c); 222 break; 223 224 default: 225 if (Character.isISOControl(c)) { 226 // all iso control characters are in U+00xx, JSON output format is "\\u00XX" 227 toAppendTo.setCharAt(lastPos--, Chars.getUpperCaseHex(c & 0xF)); 228 toAppendTo.setCharAt(lastPos--, Chars.getUpperCaseHex((c & 0xF0) >> 4)); 229 toAppendTo.setCharAt(lastPos--, '0'); 230 toAppendTo.setCharAt(lastPos--, '0'); 231 toAppendTo.setCharAt(lastPos--, 'u'); 232 toAppendTo.setCharAt(lastPos--, '\\'); 233 } else { 234 toAppendTo.setCharAt(lastPos, c); 235 lastPos--; 236 } 237 } 238 } 239 } 240 241 private static int escapeAndDecrement(final StringBuilder toAppendTo, int lastPos, final char c) { 242 toAppendTo.setCharAt(lastPos--, c); 243 toAppendTo.setCharAt(lastPos--, '\\'); 244 return lastPos; 245 } 246 247 public static void escapeXml(final StringBuilder toAppendTo, final int start) { 248 int escapeCount = 0; 249 for (int i = start; i < toAppendTo.length(); i++) { 250 final char c = toAppendTo.charAt(i); 251 switch (c) { 252 case '&': 253 escapeCount += 4; 254 break; 255 case '<': 256 case '>': 257 escapeCount += 3; 258 break; 259 case '"': 260 case '\'': 261 escapeCount += 5; 262 } 263 } 264 265 final int lastChar = toAppendTo.length() - 1; 266 toAppendTo.setLength(toAppendTo.length() + escapeCount); 267 int lastPos = toAppendTo.length() - 1; 268 269 for (int i = lastChar; lastPos > i; i--) { 270 final char c = toAppendTo.charAt(i); 271 switch (c) { 272 case '&': 273 toAppendTo.setCharAt(lastPos--, ';'); 274 toAppendTo.setCharAt(lastPos--, 'p'); 275 toAppendTo.setCharAt(lastPos--, 'm'); 276 toAppendTo.setCharAt(lastPos--, 'a'); 277 toAppendTo.setCharAt(lastPos--, '&'); 278 break; 279 case '<': 280 toAppendTo.setCharAt(lastPos--, ';'); 281 toAppendTo.setCharAt(lastPos--, 't'); 282 toAppendTo.setCharAt(lastPos--, 'l'); 283 toAppendTo.setCharAt(lastPos--, '&'); 284 break; 285 case '>': 286 toAppendTo.setCharAt(lastPos--, ';'); 287 toAppendTo.setCharAt(lastPos--, 't'); 288 toAppendTo.setCharAt(lastPos--, 'g'); 289 toAppendTo.setCharAt(lastPos--, '&'); 290 break; 291 case '"': 292 toAppendTo.setCharAt(lastPos--, ';'); 293 toAppendTo.setCharAt(lastPos--, 't'); 294 toAppendTo.setCharAt(lastPos--, 'o'); 295 toAppendTo.setCharAt(lastPos--, 'u'); 296 toAppendTo.setCharAt(lastPos--, 'q'); 297 toAppendTo.setCharAt(lastPos--, '&'); 298 break; 299 case '\'': 300 toAppendTo.setCharAt(lastPos--, ';'); 301 toAppendTo.setCharAt(lastPos--, 's'); 302 toAppendTo.setCharAt(lastPos--, 'o'); 303 toAppendTo.setCharAt(lastPos--, 'p'); 304 toAppendTo.setCharAt(lastPos--, 'a'); 305 toAppendTo.setCharAt(lastPos--, '&'); 306 break; 307 default: 308 toAppendTo.setCharAt(lastPos--, c); 309 } 310 } 311 } 312}