1 /*
   2  * $Id$
   3  *
   4  * Copyright (c) 1996, 2011, Oracle and/or its affiliates. All rights reserved.
   5  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   6  *
   7  * This code is free software; you can redistribute it and/or modify it
   8  * under the terms of the GNU General Public License version 2 only, as
   9  * published by the Free Software Foundation.  Oracle designates this
  10  * particular file as subject to the "Classpath" exception as provided
  11  * by Oracle in the LICENSE file that accompanied this code.
  12  *
  13  * This code is distributed in the hope that it will be useful, but WITHOUT
  14  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  15  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  16  * version 2 for more details (a copy is included in the LICENSE file that
  17  * accompanied this code).
  18  *
  19  * You should have received a copy of the GNU General Public License version
  20  * 2 along with this work; if not, write to the Free Software Foundation,
  21  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  22  *
  23  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  24  * or visit www.oracle.com if you need additional information or have any
  25  * questions.
  26  */
  27 package com.sun.javatest.util;
  28 
  29 import java.io.PrintWriter;
  30 import java.util.Enumeration;
  31 import java.util.Properties;
  32 
  33 /**
  34  * Access to debugging settings which have been activated.
  35  * Indexing is by string matching; the key is usually but not always a fully qualified
  36  * classname.
  37  *
  38  * <p>
  39  * The system properties have three classes of "value" data.  The string may be
  40  * equal to the string "true" (case insensitive), it may be parsable into an
  41  * integer (by Integer.parseInt()), or it may be any other string.  In the
  42  * latter case where it is not TRUE and is not an int, it will be interpreted
  43  * as a zero if a integer is requested, and FALSE if a boolean is requested.
  44  *
  45  * <p>
  46  * The following definitions define how integer and boolean settings are
  47  * interpreted:
  48  * <dl>
  49  * <dt>A lookup that results in a valid integer:
  50  * <dd>Will return that integer for any of the getInt() methods
  51  * <dd>Will return false for any getBoolean() method if the integer is less than or equal to zero
  52  * <dd>Will return true for and getBoolean() method if the integer is greater than zero
  53  *
  54  * <dt>A lookup that results in "true" (any case combination):
  55  * <dd>Will return one for any of the getInt() methods
  56  * <dd>Will return true for any of the getBoolean() methods
  57  *
  58  * <dt>A lookup that results in neither of the above cases (not "true", nor an integer):
  59  * <dd>Will return zero for any of the getInt() methods
  60  * <dd>Will return false for any of the getBoolean() methods
  61  * </dl>
  62  *
  63  * @see java.lang.Integer#parseInt
  64  */
  65 
  66 public class Debug {
  67     private Debug() {
  68         // no instances
  69     }
  70 
  71     /**
  72      * Print a debugging message.
  73      * @param s the message to be printed.
  74      */
  75     public static void print(String s) {
  76         out.print(s);
  77         out.flush();
  78     }
  79 
  80     /**
  81      * Print a debugging message and end the line.
  82      * @param s the message to be printed.
  83      */
  84     public static void println(String s) {
  85         out.println(s);
  86         out.flush();
  87     }
  88 
  89     /**
  90      * Check if overall debugging is enabled.
  91      * If it is not enabled, methods will return null, false, and zero,
  92      * as appropriate for the type.
  93      * @return true if debugging is enabled, and false otherwise
  94      */
  95     public static boolean isEnabled() {
  96         return masterSwitch;
  97     }
  98 
  99     /**
 100      * Do a raw lookup of a key, including matching of wildcard settings.
 101      * Is the given string matched by any of the debug system properties.
 102      * The matching is done according to import-style semantics, using the dot
 103      * separator.  In addition wildcards from the system properties are allowed
 104      * <b>at the end</b> of the key specification.  Unlike imports, the wildcard can
 105      * match inner class names.
 106      *
 107      * @param key The name of the setting to be returned
 108      * @return The setting, or null if not found.  Null is returned if debugging is
 109      *  disabled entirely.
 110      */
 111     public static String getSetting(String key) {
 112         // important because there may be uninitialized objects
 113         if (masterSwitch == false)
 114             return null;
 115 
 116         String match = dProps.getProperty(key);
 117 
 118         if (match != null)
 119             return match;
 120         else {
 121             match = wildProps.search(key);
 122             // may be null
 123             return match;
 124         }
 125     }
 126 
 127     /**
 128      * Find out the debugging setting for class c.
 129      * Lookup is done by looking for fully qualified class name.
 130      *
 131      * @param c Class whose name should be used to lookup the setting, null results in
 132      *    a return value of zero.
 133      * @return the debugging setting for the specified class
 134      */
 135     public static boolean getBoolean(Class<?> c) {
 136         init(false);
 137 
 138         if (!masterSwitch)
 139             return false;
 140 
 141         String key = getName(c);
 142         String setting = getSetting(key);
 143         boolean state = convertToBool(setting);
 144 
 145         return state;
 146     }
 147 
 148     /**
 149      * Find out the debugging setting for class c.
 150      * Lookup is done by looking for fully qualified class name with a dot and the
 151      * given suffix appended.
 152      *
 153      * @param c Class whose name should be used to lookup the setting, null results in
 154      *    a return value of zero.
 155      * @param suffix String to append to the classname, null will result in a lookup
 156      *    of just the classname.
 157      * @return the debugging setting for the specified class
 158      */
 159     public static boolean getBoolean(Class<?> c, String suffix) {
 160         init(false);
 161 
 162         if (!masterSwitch)
 163             return false;
 164 
 165         StringBuffer buf = new StringBuffer(getName(c));
 166         if (suffix != null && suffix.length() != 0) {
 167             buf.append(Debug.SEPARATOR);
 168             buf.append(suffix);
 169         }
 170 
 171         String key = buf.toString();
 172         String setting = getSetting(key);
 173         boolean state = convertToBool(setting);
 174 
 175         return state;
 176     }
 177 
 178     /**
 179      * Get a named debugging setting.
 180      * @param s the name of the desired debugging setting
 181      * @return the value of the debugging setting
 182      */
 183     public static boolean getBoolean(String s) {
 184         init(false);
 185 
 186         if (!masterSwitch || s == null)
 187             return false;
 188 
 189         String setting = getSetting(s);
 190         boolean state = convertToBool(setting);
 191 
 192         return state;
 193     }
 194 
 195     /**
 196      * Find out the debugging setting for class c.
 197      * Lookup is done by looking for fully qualified class name.
 198      *
 199      * @param c the class whose name should be used to lookup the setting
 200      * @return the debugging setting for the given class, or 0 if no class
 201      *    was specified
 202      */
 203     public static int getInt(Class<?> c) {
 204         init(false);
 205 
 206         if (!masterSwitch || c == null)
 207             return 0;
 208 
 209         String key = getName(c);
 210         String setting = getSetting(key);
 211         int state = convertToInt(setting);
 212 
 213         return state;
 214     }
 215 
 216     /**
 217      * Find out the debugging setting for class c.
 218      * Lookup is done by looking for fully qualified class name with a dot and the
 219      * given suffix appended.
 220      *
 221      * @param c a class whose name should be used to lookup the setting;
 222      *    null results in a return value of zero.
 223      * @param suffix a string to append to the classname;
 224      *    null will result in a lookup of just the classname.
 225      * @return the debugging setting for the class
 226      */
 227     public static int getInt(Class<?> c, String suffix) {
 228         init(false);
 229 
 230         if (!masterSwitch || c == null)
 231             return 0;
 232 
 233         StringBuffer buf = new StringBuffer(getName(c));
 234         if (suffix != null && suffix.length() != 0) {
 235             buf.append(Debug.SEPARATOR);
 236             buf.append(suffix);
 237         }
 238 
 239         String key = buf.toString();
 240         String setting = getSetting(key);
 241         int state = convertToInt(setting);
 242 
 243         return state;
 244     }
 245 
 246     /**
 247      * Get a named debugging setting.
 248      * @param s the name of the desired debugging setting
 249      * @return the value of the debugging setting
 250      */
 251     public static int getInt(String s) {
 252         init(false);
 253 
 254         if (!masterSwitch)
 255             return 0;
 256 
 257         String setting = getSetting(s);
 258         return convertToInt(setting);
 259 
 260         /*
 261         String setting = dProps.getProperty(s);
 262         if (setting == null)
 263             return 0;
 264         else if (setting.equalsIgnoreCase("true"))
 265             return 1;
 266         else {
 267             try {
 268                 return Integer.parseInt(setting);
 269             }
 270             catch (NumberFormatException e) {
 271                 e.printStackTrace(out);
 272                 return 0;
 273             }
 274         }
 275         */
 276     }
 277 
 278     /**
 279      * Get the debugging stream, used for writing debug messages.
 280      * @return the debugging stream, used to write debug messages
 281      */
 282     public static PrintWriter getWriter() {
 283         return out;
 284     }
 285 
 286     /**
 287      * Set properties containing debugging settings.
 288      * This is required if the security manager does not allow access to
 289      * the system properties.
 290      * @param props A table of properties containing debugging settings
 291      */
 292     public static void setProperties(Properties props) {
 293         givenProps = props;
 294     }
 295 
 296     /**
 297      * Initialize (or re-initialize) debugging support.
 298      * @param force Force reprocessing of System properties.
 299      */
 300     public synchronized static void init(boolean force) {
 301         if (dProps != null && force != true)
 302             return;
 303 
 304         Properties props;
 305 
 306         try {
 307             props = System.getProperties();
 308         }
 309         catch (SecurityException e) {
 310             // this is the backup source of settings
 311             props = givenProps;
 312         }
 313 
 314         if (props == null) {
 315             // we're stuck, must disable debugging
 316             masterSwitch = false;
 317             return;
 318         }
 319 
 320         Enumeration<?> keys = props.propertyNames();
 321 
 322         dProps = new Properties();
 323         wildProps = new WildcardProperties();
 324 
 325         while (keys.hasMoreElements()) {
 326             String key = (String)(keys.nextElement());
 327             if (key.startsWith(DEBUG_PREFIX)) {
 328                 // this should be a setProperty() in JDK 1.2+
 329                 if (key.equalsIgnoreCase(MASTER_KEY)) {
 330                     String val = props.getProperty(key);
 331                     // this will disable all debugging, all methods will return zero or false
 332                     if (val.equalsIgnoreCase(TRUE_STRING))
 333                         masterSwitch = false;
 334                 }
 335                 else if (key.endsWith(WILD_SUFFIX)) {
 336                     wildProps.put(key.substring(DEBUG_PREFIX.length()), props.getProperty(key));
 337                 }
 338                 else
 339                     dProps.put(key.substring(DEBUG_PREFIX.length()), props.getProperty(key));
 340             }
 341         }   // while
 342     }
 343 
 344     // -------- Private ---------
 345 
 346     /**
 347      * Convert a class object into a string appropriate for lookup.
 348      *
 349      * @param c Must not be null.
 350      */
 351     private static String getName(Class<?> c) {
 352         // null checking skipped
 353 
 354         String name = c.getName();
 355 
 356         // compensate for inner classes for which getName() return the internal name
 357         name = name.replace('$', '.');
 358 
 359         return name;
 360     }
 361 
 362     /*
 363      * Take the user setting and interpret it.
 364      * If the setting corresponds to the true string, true is returned.  If it corresponds to
 365      * an integer greater than zero, true is returned.  Otherwise, false is returned.
 366      */
 367     private static boolean convertToBool(String setting) {
 368         if (setting == null)
 369             return false;
 370 
 371         if (setting.equalsIgnoreCase(TRUE_STRING))
 372             return true;
 373         else {
 374             try {
 375                 int num = Integer.parseInt(setting);
 376                 if (num > 0)
 377                     return true;
 378                 else
 379                     return false;
 380             }
 381             catch (NumberFormatException e) {
 382                 // not "true", not an integer
 383                 return false;
 384             }
 385         }
 386     }
 387 
 388     /**
 389      * Take the user setting and interpret it.
 390      * If it is an integer, that is returned.  If it is true, 1 is returned.  If it is
 391      * anything else, zero is returned.
 392      */
 393     private static int convertToInt(String setting) {
 394         if (setting == null)
 395             return 0;
 396 
 397         if (setting.equalsIgnoreCase(TRUE_STRING))
 398             return 1;
 399         else {
 400             try {
 401                 int num = Integer.parseInt(setting);
 402                 return num;
 403             }
 404             catch (NumberFormatException e) {
 405                 // not "true", not an integer
 406                 return 0;
 407             }
 408         }
 409     }
 410 
 411     // ---- activate as needed ----
 412     private static void setEnabled(String debugKey, boolean state) {
 413     }
 414 
 415     private static void setEnabled(String debugKey, int level) {
 416     }
 417 
 418     private static void setOutput(PrintWriter w) {
 419         out = w;
 420     }
 421 
 422     private static final String SEPARATOR = ".";
 423     private static final String DEBUG_PREFIX = "debug" + SEPARATOR;
 424     private static final String WILD_SUFFIX = "*";
 425     private static final String TRUE_STRING = "true";
 426     private static final String MASTER_KEY = "debug.disable";
 427 
 428     private static Properties givenProps;           // settings used if System won't give
 429     private static Properties dProps;               // explicit props
 430     private static WildcardProperties wildProps;    // props which contain wildcards
 431 
 432     private static PrintWriter out;
 433     private static boolean defaultBool = false;
 434     private static int defaultInt = 0;
 435 
 436     /**
 437      * True if debugging is enabled, and false otherwise.
 438      */
 439     private static boolean masterSwitch = true;
 440 
 441     static {
 442         out = new PrintWriter(System.err);
 443     }
 444 
 445     /**
 446      * Wildcards are compared against the given key minus one field.
 447      * So a search(<tt>"foo.bar.baz"</tt>) does a search for <tt>"foo.bar.*"</tt>.
 448      * <tt>"foo.*"</tt> will not be a match.
 449      */
 450     private static class WildcardProperties extends Properties {
 451         /**
 452          * This is the only method in this class that accounts for wildcards.
 453          */
 454         public String search(String key) {
 455             String lowerKey = key.toLowerCase();
 456             String target = trimTarget(lowerKey);
 457 
 458             Enumeration<?> keys = propertyNames();
 459             while (keys.hasMoreElements()) {
 460                 String k = (String)(keys.nextElement());
 461                 String lowerK = k.toLowerCase();
 462 
 463                 if (lowerK.startsWith(target)) {
 464                     // should strip target string and dot
 465                     String tail = lowerK.substring(target.length());
 466                     String head = lowerK.substring(0, target.length());
 467                     if (tail.equals(wildTail) || head.equals(lowerKey))
 468                         return getProperty(k);
 469                 }
 470             }   // while
 471 
 472             return null;
 473         }
 474 
 475         /**
 476          * Remove the last element of the requested key to see if a wildcard fits into
 477          * that position.  Wildcards can only be valid up one level, so
 478          * foo.* cannot match foo.bar.baz, but will match foo.bar.  This method
 479          * turns foo.bar into foo so it the search method can use "foo".
 480          */
 481         String trimTarget(String t) {
 482             int index = t.lastIndexOf(Debug.SEPARATOR);
 483             if (index != -1)
 484                 return t.substring(0, index);
 485             else
 486                 return t;
 487         }
 488 
 489         static final String wildTail = Debug.SEPARATOR + Debug.WILD_SUFFIX;
 490     }
 491 }
 492