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 for (int i = toAppendTo.length() - 1; i >= start; i--) { // backwards: length may change 173 final char c = toAppendTo.charAt(i); 174 switch (c) { 175 case '\b': 176 toAppendTo.setCharAt(i, '\\'); 177 toAppendTo.insert(i + 1, 'b'); 178 break; 179 180 case '\t': 181 toAppendTo.setCharAt(i, '\\'); 182 toAppendTo.insert(i + 1, 't'); 183 break; 184 185 case '\f': 186 toAppendTo.setCharAt(i, '\\'); 187 toAppendTo.insert(i + 1, 'f'); 188 break; 189 190 case '\n': 191 // Json string newline character must be encoded as literal "\n" 192 toAppendTo.setCharAt(i, '\\'); 193 toAppendTo.insert(i + 1, 'n'); 194 break; 195 196 case '\r': 197 toAppendTo.setCharAt(i, '\\'); 198 toAppendTo.insert(i + 1, 'r'); 199 break; 200 201 case '"': 202 case '\\': 203 // only " and \ need to be escaped; other escapes are optional 204 toAppendTo.insert(i, '\\'); 205 break; 206 207 default: 208 if (Character.isISOControl(c)) { 209 // all iso control characters are in U+00xx 210 toAppendTo.setCharAt(i, '\\'); 211 toAppendTo.insert(i + 1, "u0000"); 212 toAppendTo.setCharAt(i + 4, Chars.getUpperCaseHex((c & 0xF0) >> 4)); 213 toAppendTo.setCharAt(i + 5, Chars.getUpperCaseHex(c & 0xF)); 214 } 215 } 216 } 217 } 218 219 public static void escapeXml(final StringBuilder toAppendTo, final int start) { 220 for (int i = toAppendTo.length() - 1; i >= start; i--) { // backwards: length may change 221 final char c = toAppendTo.charAt(i); 222 switch (c) { 223 case '&': 224 toAppendTo.setCharAt(i, '&'); 225 toAppendTo.insert(i + 1, "amp;"); 226 break; 227 case '<': 228 toAppendTo.setCharAt(i, '&'); 229 toAppendTo.insert(i + 1, "lt;"); 230 break; 231 case '>': 232 toAppendTo.setCharAt(i, '&'); 233 toAppendTo.insert(i + 1, "gt;"); 234 break; 235 case '"': 236 toAppendTo.setCharAt(i, '&'); 237 toAppendTo.insert(i + 1, "quot;"); 238 break; 239 case '\'': 240 toAppendTo.setCharAt(i, '&'); 241 toAppendTo.insert(i + 1, "apos;"); 242 break; 243 } 244 } 245 } 246}