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