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 */
23package org.graalvm.compiler.debug;
24
25import java.io.PrintStream;
26import java.lang.reflect.Field;
27import java.util.ArrayList;
28import java.util.Collections;
29import java.util.List;
30import java.util.Map;
31import java.util.regex.Pattern;
32
33import 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 */
40public 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}
321