1 /*
   2  * Copyright (c) 2009, 2011, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.
   8  *
   9  * This code is distributed in the hope that it will be useful, but WITHOUT
  10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  12  * version 2 for more details (a copy is included in the LICENSE file that
  13  * accompanied this code).
  14  *
  15  * You should have received a copy of the GNU General Public License version
  16  * 2 along with this work; if not, write to the Free Software Foundation,
  17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  18  *
  19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  20  * or visit www.oracle.com if you need additional information or have any
  21  * questions.
  22  */
  23 package org.graalvm.compiler.debug;
  24 
  25 import java.io.PrintStream;
  26 import java.lang.reflect.Field;
  27 import java.util.ArrayList;
  28 import java.util.Collections;
  29 import java.util.List;
  30 import java.util.Map;
  31 import java.util.regex.Pattern;
  32 
  33 import org.graalvm.compiler.serviceprovider.GraalServices;
  34 
  35 /**
  36  * A collection of static methods for printing debug and informational output to a global
  37  * {@link LogStream}. The output can be (temporarily) suppressed per thread through use of a
  38  * {@linkplain Filter filter}.
  39  */
  40 public class TTY {
  41 
  42     /**
  43      * Support for thread-local suppression of {@link TTY}.
  44      */
  45     public static class Filter implements AutoCloseable {
  46 
  47         private LogStream previous;
  48         private final Thread thread = Thread.currentThread();
  49 
  50         /**
  51          * Creates an object that will suppress {@link TTY} for the current thread if the given
  52          * filter does not match the given object. To revert the suppression state to how it was
  53          * before this call, the {@link #remove()} method must be called on the suppression object.
  54          *
  55          * @param filter the pattern for matching. If {@code null}, then the match is successful. If
  56          *            it starts with "~", then a regular expression
  57          *            {@linkplain Pattern#matches(String, CharSequence) match} is performed where
  58          *            the regular expression is specified by {@code filter} without the "~" prefix.
  59          *            Otherwise, a simple {@linkplain String#contains(CharSequence) substring} match
  60          *            is performed where {@code filter} is the substring used.
  61          * @param object an object whose {@linkplain Object#toString() string} value is matched
  62          *            against {@code filter}
  63          */
  64         public Filter(String filter, Object object) {
  65             boolean suppressed = false;
  66             if (filter != null) {
  67                 String input = object.toString();
  68                 if (filter.startsWith("~")) {
  69                     suppressed = !Pattern.matches(filter.substring(1), input);
  70                 } else {
  71                     suppressed = !input.contains(filter);
  72                 }
  73                 if (suppressed) {
  74                     previous = out();
  75                     log.set(LogStream.SINK);
  76                 }
  77             }
  78         }
  79 
  80         /**
  81          * Creates an object that will suppress {@link TTY} for the current thread. To revert the
  82          * suppression state to how it was before this call, the {@link #remove()} method must be
  83          * called on this filter object.
  84          */
  85         public Filter() {
  86             previous = out();
  87             log.set(LogStream.SINK);
  88         }
  89 
  90         /**
  91          * Creates an object that will overwrite {@link TTY} for the current thread with a custom
  92          * log stream. To revert the overwritten state to how it was before this call, the
  93          * {@link #remove()} method must be called on this filter object.
  94          */
  95         public Filter(LogStream newStream) {
  96             previous = out();
  97             log.set(newStream);
  98         }
  99 
 100         /**
 101          * Reverts the suppression state of {@link TTY} to how it was before this object was
 102          * constructed.
 103          */
 104         public void remove() {
 105             assert thread == Thread.currentThread();
 106             if (previous != null) {
 107                 log.set(previous);
 108             }
 109         }
 110 
 111         @Override
 112         public void close() {
 113             remove();
 114         }
 115     }
 116 
 117     /**
 118      * The {@link PrintStream} to which all non-suppressed output from {@link TTY} is written.
 119      */
 120     public static final PrintStream out;
 121     static {
 122         TTYStreamProvider p = GraalServices.loadSingle(TTYStreamProvider.class, false);
 123         out = p == null ? System.out : p.getStream();
 124     }
 125 
 126     private static final ThreadLocal<LogStream> log = new ThreadLocal<LogStream>() {
 127 
 128         @Override
 129         protected LogStream initialValue() {
 130             return new LogStream(out);
 131         }
 132     };
 133 
 134     public static boolean isSuppressed() {
 135         return log.get() == LogStream.SINK;
 136     }
 137 
 138     /**
 139      * Gets the thread-local log stream to which the static methods of this class send their output.
 140      * This will either be a global log stream or the global {@linkplain LogStream#SINK sink}
 141      * depending on whether any suppression {@linkplain Filter filters} are in effect for the
 142      * current thread.
 143      */
 144     public static LogStream out() {
 145         return log.get();
 146     }
 147 
 148     /**
 149      * @see LogStream#print(String)
 150      */
 151     public static void print(String s) {
 152         out().print(s);
 153     }
 154 
 155     /**
 156      * @see LogStream#print(int)
 157      */
 158     public static void print(int i) {
 159         out().print(i);
 160     }
 161 
 162     /**
 163      * @see LogStream#print(long)
 164      */
 165     public static void print(long i) {
 166         out().print(i);
 167     }
 168 
 169     /**
 170      * @see LogStream#print(char)
 171      */
 172     public static void print(char c) {
 173         out().print(c);
 174     }
 175 
 176     /**
 177      * @see LogStream#print(boolean)
 178      */
 179     public static void print(boolean b) {
 180         out().print(b);
 181     }
 182 
 183     /**
 184      * @see LogStream#print(double)
 185      */
 186     public static void print(double d) {
 187         out().print(d);
 188     }
 189 
 190     /**
 191      * @see LogStream#print(float)
 192      */
 193     public static void print(float f) {
 194         out().print(f);
 195     }
 196 
 197     /**
 198      * @see LogStream#println(String)
 199      */
 200     public static void println(String s) {
 201         out().println(s);
 202     }
 203 
 204     /**
 205      * @see LogStream#println()
 206      */
 207     public static void println() {
 208         out().println();
 209     }
 210 
 211     /**
 212      * @see LogStream#println(int)
 213      */
 214     public static void println(int i) {
 215         out().println(i);
 216     }
 217 
 218     /**
 219      * @see LogStream#println(long)
 220      */
 221     public static void println(long l) {
 222         out().println(l);
 223     }
 224 
 225     /**
 226      * @see LogStream#println(char)
 227      */
 228     public static void println(char c) {
 229         out().println(c);
 230     }
 231 
 232     /**
 233      * @see LogStream#println(boolean)
 234      */
 235     public static void println(boolean b) {
 236         out().println(b);
 237     }
 238 
 239     /**
 240      * @see LogStream#println(double)
 241      */
 242     public static void println(double d) {
 243         out().println(d);
 244     }
 245 
 246     /**
 247      * @see LogStream#println(float)
 248      */
 249     public static void println(float f) {
 250         out().println(f);
 251     }
 252 
 253     public static void printf(String format, Object... args) {
 254         out().printf(format, args);
 255     }
 256 
 257     public static void println(String format, Object... args) {
 258         out().printf(format + "%n", args);
 259     }
 260 
 261     public static void fillTo(int i) {
 262         out().fillTo(i, ' ');
 263     }
 264 
 265     public static void printFields(Class<?> javaClass) {
 266         final String className = javaClass.getSimpleName();
 267         TTY.println(className + " {");
 268         for (final Field field : javaClass.getFields()) {
 269             printField(field, false);
 270         }
 271         TTY.println("}");
 272     }
 273 
 274     public static void printField(final Field field, boolean tabbed) {
 275         final String fieldName = String.format("%35s", field.getName());
 276         try {
 277             String prefix = tabbed ? "" : "    " + fieldName + " = ";
 278             String postfix = tabbed ? "\t" : "\n";
 279             if (field.getType() == int.class) {
 280                 TTY.print(prefix + field.getInt(null) + postfix);
 281             } else if (field.getType() == boolean.class) {
 282                 TTY.print(prefix + field.getBoolean(null) + postfix);
 283             } else if (field.getType() == float.class) {
 284                 TTY.print(prefix + field.getFloat(null) + postfix);
 285             } else if (field.getType() == String.class) {
 286                 TTY.print(prefix + field.get(null) + postfix);
 287             } else if (field.getType() == Map.class) {
 288                 Map<?, ?> m = (Map<?, ?>) field.get(null);
 289                 TTY.print(prefix + printMap(m) + postfix);
 290             } else {
 291                 TTY.print(prefix + field.get(null) + postfix);
 292             }
 293         } catch (IllegalAccessException e) {
 294             // do nothing.
 295         }
 296     }
 297 
 298     private static String printMap(Map<?, ?> m) {
 299         StringBuilder sb = new StringBuilder();
 300 
 301         List<String> keys = new ArrayList<>();
 302         for (Object key : m.keySet()) {
 303             keys.add((String) key);
 304         }
 305         Collections.sort(keys);
 306 
 307         for (String key : keys) {
 308             sb.append(key);
 309             sb.append("\t");
 310             sb.append(m.get(key));
 311             sb.append("\n");
 312         }
 313 
 314         return sb.toString();
 315     }
 316 
 317     public static void flush() {
 318         out().flush();
 319     }
 320 }