view graal/com.oracle.jvmci.debug/src/com/oracle/jvmci/debug/internal/DebugScope.java @ 21554:b1530a6cce8c

renamed com.oracle.graal.[debug|options|hotspotvmconfig]* modules to com.oracle.jvmci.[debug|options|hotspotvmconfig]* modules (JBS:GRAAL-53)
author Doug Simon <doug.simon@oracle.com>
date Tue, 26 May 2015 23:21:15 +0200
parents graal/com.oracle.graal.debug/src/com/oracle/graal/debug/internal/DebugScope.java@0a50a8e82e1b
children f5b549811bac
line wrap: on
line source

/*
 * Copyright (c) 2012, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */
package com.oracle.jvmci.debug.internal;

import java.io.*;
import java.util.*;
import java.util.concurrent.*;

import com.oracle.jvmci.debug.*;


public final class DebugScope implements Debug.Scope {

    private final class IndentImpl implements Indent {

        private static final String INDENTATION_INCREMENT = "  ";

        final String indent;
        final IndentImpl parentIndent;

        IndentImpl(IndentImpl parentIndent) {
            this.parentIndent = parentIndent;
            this.indent = (parentIndent == null ? "" : parentIndent.indent + INDENTATION_INCREMENT);
        }

        private void printScopeName(StringBuilder str) {
            if (logScopeName) {
                if (parentIndent != null) {
                    parentIndent.printScopeName(str);
                }
                str.append(indent).append("[thread:").append(Thread.currentThread().getId()).append("] scope: ").append(getQualifiedName()).append(System.lineSeparator());
                logScopeName = false;
            }
        }

        public void log(int logLevel, String msg, Object... args) {
            if (isLogEnabled(logLevel)) {
                StringBuilder str = new StringBuilder();
                printScopeName(str);
                str.append(indent);
                String result = args.length == 0 ? msg : String.format(msg, args);
                String lineSep = System.lineSeparator();
                str.append(result.replace(lineSep, lineSep.concat(indent)));
                str.append(lineSep);
                output.append(str);
                lastUsedIndent = this;
            }
        }

        IndentImpl indent() {
            lastUsedIndent = new IndentImpl(this);
            return lastUsedIndent;
        }

        @Override
        public void close() {
            if (parentIndent != null) {
                lastUsedIndent = parentIndent;
            }
        }
    }

    private static final ThreadLocal<DebugScope> instanceTL = new ThreadLocal<>();
    private static final ThreadLocal<DebugScope> lastClosedTL = new ThreadLocal<>();
    private static final ThreadLocal<DebugConfig> configTL = new ThreadLocal<>();
    private static final ThreadLocal<Throwable> lastExceptionThrownTL = new ThreadLocal<>();

    private final DebugScope parent;
    private final DebugConfig parentConfig;
    private final boolean sandbox;
    private IndentImpl lastUsedIndent;
    private boolean logScopeName;

    private final Object[] context;

    private DebugValueMap valueMap;

    private String qualifiedName;
    private final String unqualifiedName;

    private static final char SCOPE_SEP = '.';

    private boolean meterEnabled;
    private boolean timeEnabled;
    private boolean memUseTrackingEnabled;
    private boolean verifyEnabled;

    private int currentDumpLevel;
    private int currentLogLevel;

    private PrintStream output;

    public static DebugScope getInstance() {
        DebugScope result = instanceTL.get();
        if (result == null) {
            DebugScope topLevelDebugScope = new DebugScope(Thread.currentThread());
            instanceTL.set(topLevelDebugScope);
            return topLevelDebugScope;
        } else {
            return result;
        }
    }

    public static DebugConfig getConfig() {
        return configTL.get();
    }

    static final Object[] EMPTY_CONTEXT = new Object[0];

    private DebugScope(Thread thread) {
        this(thread.getName(), null, false);
        computeValueMap(thread.getName());
        DebugValueMap.registerTopLevel(getValueMap());
    }

    private DebugScope(String unqualifiedName, DebugScope parent, boolean sandbox, Object... context) {
        this.parent = parent;
        this.sandbox = sandbox;
        this.parentConfig = getConfig();
        this.context = context;
        this.unqualifiedName = unqualifiedName;
        if (parent != null) {
            logScopeName = !unqualifiedName.equals("");
        } else {
            logScopeName = true;
        }

        // Be pragmatic: provide a default log stream to prevent a crash if the stream is not
        // set while logging
        this.output = TTY.cachedOut;
        assert context != null;
    }

    private void computeValueMap(String name) {
        if (parent != null) {
            for (DebugValueMap child : parent.getValueMap().getChildren()) {
                if (child.getName().equals(name)) {
                    this.valueMap = child;
                    return;
                }
            }
            this.valueMap = new DebugValueMap(name);
            parent.getValueMap().addChild(this.valueMap);
        } else {
            this.valueMap = new DebugValueMap(name);
        }
    }

    public void close() {
        instanceTL.set(parent);
        configTL.set(parentConfig);
        lastClosedTL.set(this);
    }

    public boolean isDumpEnabled(int dumpLevel) {
        assert dumpLevel > 0;
        return currentDumpLevel >= dumpLevel;
    }

    /**
     * Enable dumping at the new {@code dumpLevel} for the remainder of enclosing scopes. This only
     * works if a {@link TopLevelDebugConfig} was installed at a higher scope.
     *
     * @param dumpLevel
     */
    public static void setDumpLevel(int dumpLevel) {
        TopLevelDebugConfig config = fetchTopLevelDebugConfig("setDebugLevel");
        if (config != null) {
            config.override(DelegatingDebugConfig.Level.DUMP, dumpLevel);
            recursiveUpdateFlags();
        }
    }

    /**
     * Enable logging at the new {@code logLevel} for the remainder of enclosing scopes. This only
     * works if a {@link TopLevelDebugConfig} was installed at a higher scope.
     *
     * @param logLevel
     */
    public static void setLogLevel(int logLevel) {
        TopLevelDebugConfig config = fetchTopLevelDebugConfig("setLogLevel");
        if (config != null) {
            config.override(DelegatingDebugConfig.Level.LOG, logLevel);
            config.delegate(DelegatingDebugConfig.Feature.LOG_METHOD);
            recursiveUpdateFlags();
        }
    }

    private static void recursiveUpdateFlags() {
        DebugScope c = DebugScope.getInstance();
        while (c != null) {
            c.updateFlags();
            c = c.parent;
        }
    }

    private static TopLevelDebugConfig fetchTopLevelDebugConfig(String msg) {
        DebugConfig config = getConfig();
        if (config instanceof TopLevelDebugConfig) {
            return (TopLevelDebugConfig) config;
        } else {
            if (config == null) {
                TTY.println("DebugScope.%s ignored because debugging is disabled", msg);
            } else {
                TTY.println("DebugScope.%s ignored because top level delegate config missing", msg);
            }
            return null;
        }
    }

    public boolean isVerifyEnabled() {
        return verifyEnabled;
    }

    public boolean isLogEnabled(int logLevel) {
        assert logLevel > 0;
        return currentLogLevel >= logLevel;
    }

    public boolean isMeterEnabled() {
        return meterEnabled;
    }

    public boolean isTimeEnabled() {
        return timeEnabled;
    }

    public boolean isMemUseTrackingEnabled() {
        return memUseTrackingEnabled;
    }

    public void log(int logLevel, String msg, Object... args) {
        if (isLogEnabled(logLevel)) {
            getLastUsedIndent().log(logLevel, msg, args);
        }
    }

    public void dump(int dumpLevel, Object object, String formatString, Object... args) {
        if (isDumpEnabled(dumpLevel)) {
            DebugConfig config = getConfig();
            if (config != null) {
                String message = String.format(formatString, args);
                for (DebugDumpHandler dumpHandler : config.dumpHandlers()) {
                    dumpHandler.dump(object, message);
                }
            }
        }
    }

    /**
     * This method exists mainly to allow a debugger (e.g., Eclipse) to force dump a graph.
     */
    public static void forceDump(Object object, String format, Object... args) {
        DebugConfig config = getConfig();
        if (config != null) {
            String message = String.format(format, args);
            for (DebugDumpHandler dumpHandler : config.dumpHandlers()) {
                dumpHandler.dump(object, message);
            }
        } else {
            TTY.println("Forced dump ignored because debugging is disabled - use -G:Dump=xxx option");
        }
    }

    /**
     * @see Debug#verify(Object, String)
     */
    public void verify(Object object, String formatString, Object... args) {
        if (isVerifyEnabled()) {
            DebugConfig config = getConfig();
            if (config != null) {
                String message = String.format(formatString, args);
                for (DebugVerifyHandler handler : config.verifyHandlers()) {
                    handler.verify(object, message);
                }
            }
        }
    }

    /**
     * Creates and enters a new debug scope which is either a child of the current scope or a
     * disjoint top level scope.
     *
     * @param name the name of the new scope
     * @param sandboxConfig the configuration to use for a new top level scope, or null if the new
     *            scope should be a child scope
     * @param newContextObjects objects to be appended to the debug context
     * @return the new scope which will be exited when its {@link #close()} method is called
     */
    public DebugScope scope(CharSequence name, DebugConfig sandboxConfig, Object... newContextObjects) {
        DebugScope newScope = null;
        if (sandboxConfig != null) {
            newScope = new DebugScope(name.toString(), this, true, newContextObjects);
            configTL.set(sandboxConfig);
        } else {
            newScope = this.createChild(name.toString(), newContextObjects);
        }
        instanceTL.set(newScope);
        newScope.updateFlags();
        return newScope;
    }

    public RuntimeException handle(Throwable e) {
        DebugScope lastClosed = lastClosedTL.get();
        assert lastClosed.parent == this : "Debug.handle() used with no matching Debug.scope(...) or Debug.sandbox(...)";
        if (e != lastExceptionThrownTL.get()) {
            RuntimeException newException = null;
            instanceTL.set(lastClosed);
            try (DebugScope s = lastClosed) {
                newException = s.interceptException(e);
            }
            assert instanceTL.get() == this;
            assert lastClosed == lastClosedTL.get();
            if (newException == null) {
                lastExceptionThrownTL.set(e);
            } else {
                lastExceptionThrownTL.set(newException);
                throw newException;
            }
        }
        if (e instanceof Error) {
            throw (Error) e;
        }
        if (e instanceof RuntimeException) {
            throw (RuntimeException) e;
        }
        throw new RuntimeException(e);
    }

    private void updateFlags() {
        DebugConfig config = getConfig();
        if (config == null) {
            meterEnabled = false;
            memUseTrackingEnabled = false;
            timeEnabled = false;
            verifyEnabled = false;

            currentDumpLevel = 0;

            // Be pragmatic: provide a default log stream to prevent a crash if the stream is not
            // set while logging
            output = TTY.cachedOut;
        } else {
            meterEnabled = config.isMeterEnabled();
            memUseTrackingEnabled = config.isMemUseTrackingEnabled();
            timeEnabled = config.isTimeEnabled();
            verifyEnabled = config.isVerifyEnabled();
            output = config.output();
            currentDumpLevel = config.getDumpLevel();
            currentLogLevel = config.getLogLevel();
        }
    }

    private RuntimeException interceptException(final Throwable e) {
        final DebugConfig config = getConfig();
        if (config != null) {
            try (DebugScope s = scope("InterceptException", null, e)) {
                return config.interceptException(e);
            } catch (Throwable t) {
                return new RuntimeException("Exception while intercepting exception", t);
            }
        }
        return null;
    }

    private DebugValueMap getValueMap() {
        if (valueMap == null) {
            computeValueMap(unqualifiedName);
        }
        return valueMap;
    }

    long getCurrentValue(int index) {
        return getValueMap().getCurrentValue(index);
    }

    void setCurrentValue(int index, long l) {
        getValueMap().setCurrentValue(index, l);
    }

    private DebugScope createChild(String newName, Object[] newContext) {
        return new DebugScope(newName, this, false, newContext);
    }

    public Iterable<Object> getCurrentContext() {
        final DebugScope scope = this;
        return new Iterable<Object>() {

            @Override
            public Iterator<Object> iterator() {
                return new Iterator<Object>() {

                    DebugScope currentScope = scope;
                    int objectIndex;

                    @Override
                    public boolean hasNext() {
                        selectScope();
                        return currentScope != null;
                    }

                    private void selectScope() {
                        while (currentScope != null && currentScope.context.length <= objectIndex) {
                            currentScope = currentScope.sandbox ? null : currentScope.parent;
                            objectIndex = 0;
                        }
                    }

                    @Override
                    public Object next() {
                        selectScope();
                        if (currentScope != null) {
                            return currentScope.context[objectIndex++];
                        }
                        throw new IllegalStateException("May only be called if there is a next element.");
                    }

                    @Override
                    public void remove() {
                        throw new UnsupportedOperationException("This iterator is read only.");
                    }
                };
            }
        };
    }

    public static <T> T call(Callable<T> callable) {
        try {
            return callable.call();
        } catch (Exception e) {
            if (e instanceof RuntimeException) {
                throw (RuntimeException) e;
            } else {
                throw new RuntimeException(e);
            }
        }
    }

    public void setConfig(DebugConfig newConfig) {
        configTL.set(newConfig);
        updateFlags();
    }

    public String getQualifiedName() {
        if (qualifiedName == null) {
            if (parent == null) {
                qualifiedName = unqualifiedName;
            } else {
                qualifiedName = parent.getQualifiedName() + SCOPE_SEP + unqualifiedName;
            }
        }
        return qualifiedName;
    }

    public Indent pushIndentLogger() {
        lastUsedIndent = getLastUsedIndent().indent();
        return lastUsedIndent;
    }

    public IndentImpl getLastUsedIndent() {
        if (lastUsedIndent == null) {
            if (parent != null) {
                lastUsedIndent = new IndentImpl(parent.getLastUsedIndent());
            } else {
                lastUsedIndent = new IndentImpl(null);
            }
        }
        return lastUsedIndent;
    }
}