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.core.util; 018 019import java.util.Locale; 020import java.util.Properties; 021 022import org.apache.logging.log4j.Logger; 023import org.apache.logging.log4j.status.StatusLogger; 024import org.apache.logging.log4j.util.LoaderUtil; 025import org.apache.logging.log4j.util.PropertiesUtil; 026import org.apache.logging.log4j.util.Strings; 027 028/** 029 * A convenience class to convert property values to specific types. 030 */ 031public final class OptionConverter { 032 033 private static final Logger LOGGER = StatusLogger.getLogger(); 034 035 private static final String DELIM_START = "${"; 036 private static final char DELIM_STOP = '}'; 037 private static final int DELIM_START_LEN = 2; 038 private static final int DELIM_STOP_LEN = 1; 039 private static final int ONE_K = 1024; 040 041 /** 042 * OptionConverter is a static class. 043 */ 044 private OptionConverter() { 045 } 046 047 public static String[] concatenateArrays(final String[] l, final String[] r) { 048 final int len = l.length + r.length; 049 final String[] a = new String[len]; 050 051 System.arraycopy(l, 0, a, 0, l.length); 052 System.arraycopy(r, 0, a, l.length, r.length); 053 054 return a; 055 } 056 057 public static String convertSpecialChars(final String s) { 058 char c; 059 final int len = s.length(); 060 final StringBuilder sbuf = new StringBuilder(len); 061 062 int i = 0; 063 while (i < len) { 064 c = s.charAt(i++); 065 if (c == '\\') { 066 c = s.charAt(i++); 067 switch (c) { 068 case 'n': 069 c = '\n'; 070 break; 071 case 'r': 072 c = '\r'; 073 break; 074 case 't': 075 c = '\t'; 076 break; 077 case 'f': 078 c = '\f'; 079 break; 080 case 'b': 081 c = '\b'; 082 break; 083 case '"': 084 c = '\"'; 085 break; 086 case '\'': 087 c = '\''; 088 break; 089 case '\\': 090 c = '\\'; 091 break; 092 default: 093 // there is no default case. 094 } 095 } 096 sbuf.append(c); 097 } 098 return sbuf.toString(); 099 } 100 101 public static Object instantiateByKey(final Properties props, final String key, final Class<?> superClass, 102 final Object defaultValue) { 103 104 // Get the value of the property in string form 105 final String className = findAndSubst(key, props); 106 if (className == null) { 107 LOGGER.error("Could not find value for key {}", key); 108 return defaultValue; 109 } 110 // Trim className to avoid trailing spaces that cause problems. 111 return OptionConverter.instantiateByClassName(className.trim(), superClass, 112 defaultValue); 113 } 114 115 /** 116 * If <code>value</code> is "true", then {@code true} is 117 * returned. If <code>value</code> is "false", then 118 * {@code false} is returned. Otherwise, <code>default</code> is 119 * returned. 120 * 121 * <p>Case of value is unimportant.</p> 122 * @param value The value to convert. 123 * @param defaultValue The default value. 124 * @return true or false, depending on the value and/or default. 125 */ 126 public static boolean toBoolean(final String value, final boolean defaultValue) { 127 if (value == null) { 128 return defaultValue; 129 } 130 final String trimmedVal = value.trim(); 131 if ("true".equalsIgnoreCase(trimmedVal)) { 132 return true; 133 } 134 if ("false".equalsIgnoreCase(trimmedVal)) { 135 return false; 136 } 137 return defaultValue; 138 } 139 140 /** 141 * Convert the String value to an int. 142 * @param value The value as a String. 143 * @param defaultValue The default value. 144 * @return The value as an int. 145 */ 146 public static int toInt(final String value, final int defaultValue) { 147 if (value != null) { 148 final String s = value.trim(); 149 try { 150 return Integer.parseInt(s); 151 } catch (final NumberFormatException e) { 152 LOGGER.error("[{}] is not in proper int form.", s, e); 153 } 154 } 155 return defaultValue; 156 } 157 158 /** 159 * 160 * @param value The size of the file as a String. 161 * @param defaultValue The default value. 162 * @return The size of the file as a long. 163 */ 164 public static long toFileSize(final String value, final long defaultValue) { 165 if (value == null) { 166 return defaultValue; 167 } 168 169 String str = value.trim().toUpperCase(Locale.ENGLISH); 170 long multiplier = 1; 171 int index; 172 173 if ((index = str.indexOf("KB")) != -1) { 174 multiplier = ONE_K; 175 str = str.substring(0, index); 176 } else if ((index = str.indexOf("MB")) != -1) { 177 multiplier = ONE_K * ONE_K; 178 str = str.substring(0, index); 179 } else if ((index = str.indexOf("GB")) != -1) { 180 multiplier = ONE_K * ONE_K * ONE_K; 181 str = str.substring(0, index); 182 } 183 try { 184 return Long.parseLong(str) * multiplier; 185 } catch (final NumberFormatException e) { 186 LOGGER.error("[{}] is not in proper int form.", str); 187 LOGGER.error("[{}] not in expected format.", value, e); 188 } 189 return defaultValue; 190 } 191 192 /** 193 * Find the value corresponding to <code>key</code> in 194 * <code>props</code>. Then perform variable substitution on the 195 * found value. 196 * @param key The key to locate. 197 * @param props The properties. 198 * @return The String after substitution. 199 */ 200 public static String findAndSubst(final String key, final Properties props) { 201 final String value = props.getProperty(key); 202 if (value == null) { 203 return null; 204 } 205 206 try { 207 return substVars(value, props); 208 } catch (final IllegalArgumentException e) { 209 LOGGER.error("Bad option value [{}].", value, e); 210 return value; 211 } 212 } 213 214 /** 215 * Instantiate an object given a class name. Check that the 216 * <code>className</code> is a subclass of 217 * <code>superClass</code>. If that test fails or the object could 218 * not be instantiated, then <code>defaultValue</code> is returned. 219 * 220 * @param className The fully qualified class name of the object to instantiate. 221 * @param superClass The class to which the new object should belong. 222 * @param defaultValue The object to return in case of non-fulfillment 223 * @return The created object. 224 */ 225 public static Object instantiateByClassName(final String className, final Class<?> superClass, 226 final Object defaultValue) { 227 if (className != null) { 228 try { 229 final Class<?> classObj = LoaderUtil.loadClass(className); 230 if (!superClass.isAssignableFrom(classObj)) { 231 LOGGER.error("A \"{}\" object is not assignable to a \"{}\" variable.", className, 232 superClass.getName()); 233 LOGGER.error("The class \"{}\" was loaded by [{}] whereas object of type [{}] was loaded by [{}].", 234 superClass.getName(), superClass.getClassLoader(), classObj.getName()); 235 return defaultValue; 236 } 237 return classObj.newInstance(); 238 } catch (final Exception e) { 239 LOGGER.error("Could not instantiate class [{}].", className, e); 240 } 241 } 242 return defaultValue; 243 } 244 245 246 /** 247 * Perform variable substitution in string <code>val</code> from the 248 * values of keys found in the system propeties. 249 * 250 * <p>The variable substitution delimiters are <b>${</b> and <b>}</b>.</p> 251 * 252 * <p>For example, if the System properties contains "key=value", then 253 * the call</p> 254 * <pre> 255 * String s = OptionConverter.substituteVars("Value of key is ${key}."); 256 * </pre> 257 * <p> 258 * will set the variable <code>s</code> to "Value of key is value.". 259 * </p> 260 * <p>If no value could be found for the specified key, then the 261 * <code>props</code> parameter is searched, if the value could not 262 * be found there, then substitution defaults to the empty string.</p> 263 * 264 * <p>For example, if system properties contains no value for the key 265 * "inexistentKey", then the call 266 * </p> 267 * <pre> 268 * String s = OptionConverter.subsVars("Value of inexistentKey is [${inexistentKey}]"); 269 * </pre> 270 * <p> 271 * will set <code>s</code> to "Value of inexistentKey is []" 272 * </p> 273 * <p>An {@link java.lang.IllegalArgumentException} is thrown if 274 * <code>val</code> contains a start delimeter "${" which is not 275 * balanced by a stop delimeter "}". </p> 276 * 277 * @param val The string on which variable substitution is performed. 278 * @param props The properties to use for substitution. 279 * @return The String after substitution. 280 * @throws IllegalArgumentException if <code>val</code> is malformed. 281 */ 282 public static String substVars(final String val, final Properties props) throws 283 IllegalArgumentException { 284 285 final StringBuilder sbuf = new StringBuilder(); 286 287 int i = 0; 288 int j; 289 int k; 290 291 while (true) { 292 j = val.indexOf(DELIM_START, i); 293 if (j == -1) { 294 // no more variables 295 if (i == 0) { // this is a simple string 296 return val; 297 } 298 // add the tail string which contails no variables and return the result. 299 sbuf.append(val.substring(i, val.length())); 300 return sbuf.toString(); 301 } 302 sbuf.append(val.substring(i, j)); 303 k = val.indexOf(DELIM_STOP, j); 304 if (k == -1) { 305 throw new IllegalArgumentException(Strings.dquote(val) 306 + " has no closing brace. Opening brace at position " + j 307 + '.'); 308 } 309 j += DELIM_START_LEN; 310 final String key = val.substring(j, k); 311 // first try in System properties 312 String replacement = PropertiesUtil.getProperties().getStringProperty(key, null); 313 // then try props parameter 314 if (replacement == null && props != null) { 315 replacement = props.getProperty(key); 316 } 317 318 if (replacement != null) { 319 // Do variable substitution on the replacement string 320 // such that we can solve "Hello ${x2}" as "Hello p1" 321 // the where the properties are 322 // x1=p1 323 // x2=${x1} 324 final String recursiveReplacement = substVars(replacement, props); 325 sbuf.append(recursiveReplacement); 326 } 327 i = k + DELIM_STOP_LEN; 328 } 329 } 330}