1 /*
   2  * Copyright (c) 2009, 2018, 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      * Substituted by {@code com.oracle.svm.graal.Target_org_graalvm_compiler_debug_TTY}.
 122      */
 123     public static final PrintStream out;
 124     static {
 125         TTYStreamProvider p = GraalServices.loadSingle(TTYStreamProvider.class, false);
 126         out = p == null ? System.out : p.getStream();
 127     }
 128 
 129     private static final ThreadLocal<LogStream> log = new ThreadLocal<LogStream>() {
 130 
 131         @Override
 132         protected LogStream initialValue() {
 133             return new LogStream(out);
 134         }
 135     };
 136 
 137     public static boolean isSuppressed() {
 138         return log.get() == LogStream.SINK;
 139     }
 140 
 141     /**
 142      * Gets the thread-local log stream to which the static methods of this class send their output.
 143      * This will either be a global log stream or the global {@linkplain LogStream#SINK sink}
 144      * depending on whether any suppression {@linkplain Filter filters} are in effect for the
 145      * current thread.
 146      */
 147     public static LogStream out() {
 148         return log.get();
 149     }
 150 
 151     /**
 152      * @see LogStream#print(String)
 153      */
 154     public static void print(String s) {
 155         out().print(s);
 156     }
 157 
 158     /**
 159      * @see LogStream#print(int)
 160      */
 161     public static void print(int i) {
 162         out().print(i);
 163     }
 164 
 165     /**
 166      * @see LogStream#print(long)
 167      */
 168     public static void print(long i) {
 169         out().print(i);
 170     }
 171 
 172     /**
 173      * @see LogStream#print(char)
 174      */
 175     public static void print(char c) {
 176         out().print(c);
 177     }
 178 
 179     /**
 180      * @see LogStream#print(boolean)
 181      */
 182     public static void print(boolean b) {
 183         out().print(b);
 184     }
 185 
 186     /**
 187      * @see LogStream#print(double)
 188      */
 189     public static void print(double d) {
 190         out().print(d);
 191     }
 192 
 193     /**
 194      * @see LogStream#print(float)
 195      */
 196     public static void print(float f) {
 197         out().print(f);
 198     }
 199 
 200     /**
 201      * @see LogStream#println(String)
 202      */
 203     public static void println(String s) {
 204         out().println(s);
 205     }
 206 
 207     /**
 208      * @see LogStream#println()
 209      */
 210     public static void println() {
 211         out().println();
 212     }
 213 
 214     /**
 215      * @see LogStream#println(int)
 216      */
 217     public static void println(int i) {
 218         out().println(i);
 219     }
 220 
 221     /**
 222      * @see LogStream#println(long)
 223      */
 224     public static void println(long l) {
 225         out().println(l);
 226     }
 227 
 228     /**
 229      * @see LogStream#println(char)
 230      */
 231     public static void println(char c) {
 232         out().println(c);
 233     }
 234 
 235     /**
 236      * @see LogStream#println(boolean)
 237      */
 238     public static void println(boolean b) {
 239         out().println(b);
 240     }
 241 
 242     /**
 243      * @see LogStream#println(double)
 244      */
 245     public static void println(double d) {
 246         out().println(d);
 247     }
 248 
 249     /**
 250      * @see LogStream#println(float)
 251      */
 252     public static void println(float f) {
 253         out().println(f);
 254     }
 255 
 256     public static void printf(String format, Object... args) {
 257         out().printf(format, args);
 258     }
 259 
 260     public static void println(String format, Object... args) {
 261         out().printf(format + "%n", args);
 262     }
 263 
 264     public static void fillTo(int i) {
 265         out().fillTo(i, ' ');
 266     }
 267 
 268     public static void printFields(Class<?> javaClass) {
 269         final String className = javaClass.getSimpleName();
 270         TTY.println(className + " {");
 271         for (final Field field : javaClass.getFields()) {
 272             printField(field, false);
 273         }
 274         TTY.println("}");
 275     }
 276 
 277     public static void printField(final Field field, boolean tabbed) {
 278         final String fieldName = String.format("%35s", field.getName());
 279         try {
 280             String prefix = tabbed ? "" : "    " + fieldName + " = ";
 281             String postfix = tabbed ? "\t" : "\n";
 282             if (field.getType() == int.class) {
 283                 TTY.print(prefix + field.getInt(null) + postfix);
 284             } else if (field.getType() == boolean.class) {
 285                 TTY.print(prefix + field.getBoolean(null) + postfix);
 286             } else if (field.getType() == float.class) {
 287                 TTY.print(prefix + field.getFloat(null) + postfix);
 288             } else if (field.getType() == String.class) {
 289                 TTY.print(prefix + field.get(null) + postfix);
 290             } else if (field.getType() == Map.class) {
 291                 Map<?, ?> m = (Map<?, ?>) field.get(null);
 292                 TTY.print(prefix + printMap(m) + postfix);
 293             } else {
 294                 TTY.print(prefix + field.get(null) + postfix);
 295             }
 296         } catch (IllegalAccessException e) {
 297             // do nothing.
 298         }
 299     }
 300 
 301     private static String printMap(Map<?, ?> m) {
 302         StringBuilder sb = new StringBuilder();
 303 
 304         List<String> keys = new ArrayList<>();
 305         for (Object key : m.keySet()) {
 306             keys.add((String) key);
 307         }
 308         Collections.sort(keys);
 309 
 310         for (String key : keys) {
 311             sb.append(key);
 312             sb.append("\t");
 313             sb.append(m.get(key));
 314             sb.append("\n");
 315         }
 316 
 317         return sb.toString();
 318     }
 319 
 320     public static void flush() {
 321         out().flush();
 322     }
 323 }