/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package java.util.logging;

// BEGIN android-note
// this file contains cleaned up documentation and style for contribution
// upstream
// It also contains Android-specific optimizations that aren't appropriate for
// general purpose platforms
// END android-note

import dalvik.system.DalvikLogHandler;
import dalvik.system.DalvikLogging;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.MissingResourceException;
import java.util.ResourceBundle;
import java.util.concurrent.CopyOnWriteArrayList;

/**
 * Loggers are used to log records to a variety of destinations such as log files or
 * the console. They use instances of {@link Handler} to actually do the destination-specific
 * operations.
 *
 * <p>Client applications can get named loggers by calling the {@code getLogger}
 * methods. They can also get anonymous loggers by calling the
 * {@code getAnonymousLogger} methods. Named loggers are organized in a
 * namespace hierarchy managed by a log manager. The naming convention is
 * usually the Java package naming convention. Anonymous loggers do not belong to any namespace.
 *
 * <p>Developers should use named loggers to enable logging to be controlled on a
 * per-{@code Logger} granularity. The recommended idiom is to create and assign the logger to
 * a {@code static final} field. This ensures that there's always a strong reference to the logger,
 * preventing it from being garbage collected. In particular, {@link LogManager#addLogger(Logger)}
 * will <i>not</i> keep your logger live.
 *
 * <p>Loggers "inherit" log level setting from their parent if their own level is
 * set to {@code null}. This is also true for the resource bundle. The logger's
 * resource bundle is used to localize the log messages if no resource bundle
 * name is given when a log method is called. If {@code getUseParentHandlers()}
 * returns {@code true}, loggers also inherit their parent's handlers. In this
 * context, "inherit" only means that "behavior" is inherited. The internal
 * field values will not change, for example, {@code getLevel()} still returns
 * {@code null}.
 * <p>
 * When loading a given resource bundle, the logger first tries to use the
 * context {@code ClassLoader}. If that fails, it tries the system {@code ClassLoader}. And if
 * that still fails, it searches up the class stack and uses each class's
 * {@code ClassLoader} to try to locate the resource bundle.
 * <p>
 * Some log methods accept log requests that do not specify the source class and
 * source method. In these cases, the logging framework will automatically infer
 * the calling class and method, but this is not guaranteed to be accurate.
 * <p>
 * Once a {@code LogRecord} object has been passed into the logging framework,
 * it is owned by the logging framework and the client applications should not
 * use it any longer.
 * <p>
 * All methods of this class are thread-safe.
 *
 * @see LogManager
 */
public class Logger {

    // BEGIN android-only
    /** A handler for use when no handler optimization is possible. */
    private static final DalvikLogHandler GENERAL_LOG_HANDLER = new DalvikLogHandler() {
        public void publish(Logger source, String tag, Level level, String message) {
            LogRecord record = new LogRecord(level, message);
            record.setLoggerName(source.name);
            source.setResourceBundle(record);
            source.log(record);
        }
    };
    // END android-only

    /**
     * The name of the global logger. Before using this, see the discussion of how to use
     * {@code Logger} in the class documentation.
     * @since 1.6
     */
    public static final String GLOBAL_LOGGER_NAME = "global";

    /**
     * The global logger is provided as convenience for casual use.
     * @deprecated deadlock-prone. Use {@code Logger.getLogger(Logger.GLOBAL_LOGGER_NAME)} as
     * a direct replacement, but see the discussion of how to use {@code Logger} in the class
     * documentation.
     */
    @Deprecated
    public final static Logger global = new Logger(GLOBAL_LOGGER_NAME, null);

    /**
     * When converting the concurrent collection of handlers to an array, we
     * always pass a zero-length array to avoid size miscalculations. Passing
     * properly-sized arrays is non-atomic, and risks a null element in the
     * result.
     */
    private static final Handler[] EMPTY_HANDLERS_ARRAY = new Handler[0];

    /** The name of this logger. */
    private volatile String name;

    /** The parent logger of this logger. */
    Logger parent;

    /** The logging level of this logger, or null if none is set. */
    volatile Level levelObjVal;

    /**
     * The effective logging level of this logger. In order of preference this
     * is the first applicable of:
     * <ol>
     * <li>the int value of this logger's {@link #levelObjVal}
     * <li>the logging level of the parent
     * <li>the default level ({@link Level#INFO})
     * </ol>
     */
    volatile int levelIntVal = Level.INFO.intValue();

    /** The filter. */
    private Filter filter;

    /**
     * The resource bundle used to localize logging messages. If null, no
     * localization will be performed.
     */
    private volatile String resourceBundleName;

    /** The loaded resource bundle according to the specified name. */
    private volatile ResourceBundle resourceBundle;

    /**
     * The handlers attached to this logger. Eagerly initialized and
     * concurrently modified.
     */
    private final List<Handler> handlers = new CopyOnWriteArrayList<Handler>();

    /** True to notify the parent's handlers of each log message. */
    private boolean notifyParentHandlers = true;

    /**
     * Indicates whether this logger is named. Only {@link #getAnonymousLogger
     * anonymous loggers} are unnamed.
     */
    private boolean isNamed = true;

    /**
     * Child loggers. Should be accessed only while synchronized on {@code
     * LogManager.getLogManager()}.
     */
    final List<Logger> children = new ArrayList<Logger>();

    // BEGIN android-only
    /** the tag used for optimized logging. Derived from the logger name. */
    private final String androidTag;

    /** Handler delegate for either optimized or standard logging. */
    private volatile DalvikLogHandler dalvikLogHandler = GENERAL_LOG_HANDLER;

    /**
     * We've optimized for the common case: logging to a single handler that
     * implements {@link DalvikLogHandler}. This is how Android framework
     * applications are configured by default.
     *
     * <p>This optimization has been measured to show a 2.75x improvement in
     * throughput in the common case: 154ns vs. 56ns per message on a Cortex-A8.
     * Direct use of {@code android.util.Log} takes 29ns per message.
     *
     * <p>Each time the handler configuration changes, either directly or
     * indirectly, it's necessary to either turn on or off this optimization.
     * When the optimization is off, {@link #dalvikLogHandler} is assigned to
     * {@link #GENERAL_LOG_HANDLER} which can satisfy arbitrary configuration.
     * When the optimization is possible, {@link #dalvikLogHandler} is assigned
     * to the user's efficient implementation. In pratice this is usually the
     * {@code com.android.internal.logging.AndroidHandler}.
     */
    void updateDalvikLogHandler() {
        DalvikLogHandler newLogHandler = GENERAL_LOG_HANDLER;

        Logger parent = this.parent;

        if (getClass() != Logger.class) {
            /*
             * Do nothing. Subclasses aren't eligible for the optimization
             * because they may override methods like getHandlers() or
             * log(LogRecord).
             */

        } else if (parent == null) {
            // we use an iterator rather than size()+get() for safe concurrency
            Iterator<Handler> h = handlers.iterator();
            if (h.hasNext()) {
                Handler firstHandler = h.next();
                if (!h.hasNext() && firstHandler instanceof DalvikLogHandler) {
                    /*
                     * At this point, we're eligible for the optimization. We've
                     * satisfied these constraints:
                     *   1. This is not a subclass of logger
                     *   2. This is a root logger (no parent)
                     *   3. There is exactly one handler installed
                     *   4. That handler is a DalvikLogHandler
                     */
                    newLogHandler = (DalvikLogHandler) firstHandler;
                }
            }
        } else if (handlers.isEmpty() && notifyParentHandlers) {
            /*
             * At this point, we're eligible for the optimization if our parent
             * logger is eligible. We've satisfied these constraints:
             *   1. This is not a subclass of logger
             *   2. our parent exists
             *   3. we have no handlers of our own
             *   4. we notify our parent's handlers
             */
            newLogHandler = parent.dalvikLogHandler;
        }

        if (newLogHandler == this.dalvikLogHandler) {
            return;
        }

        this.dalvikLogHandler = newLogHandler;

        for (Logger logger : children) {
            logger.updateDalvikLogHandler();
        }
    }
    // END android-only

    /**
     * Constructs a {@code Logger} object with the supplied name and resource
     * bundle name; {@code notifiyParentHandlers} is set to {@code true}.
     * <p>
     * Notice : Loggers use a naming hierarchy. Thus "z.x.y" is a child of "z.x".
     *
     * @param name
     *            the name of this logger, may be {@code null} for anonymous
     *            loggers.
     * @param resourceBundleName
     *            the name of the resource bundle used to localize logging
     *            messages, may be {@code null}.
     * @throws MissingResourceException
     *             if the specified resource bundle can not be loaded.
     */
    protected Logger(String name, String resourceBundleName) {
        this.name = name;
        initResourceBundle(resourceBundleName);
        // BEGIN android-only
        this.androidTag = DalvikLogging.loggerNameToTag(name);
        updateDalvikLogHandler();
        // END android-only
    }

    /**
     * Load the specified resource bundle, use privileged code.
     *
     * @param resourceBundleName
     *            the name of the resource bundle to load, cannot be {@code null}.
     * @return the loaded resource bundle.
     * @throws MissingResourceException
     *             if the specified resource bundle can not be loaded.
     */
    static ResourceBundle loadResourceBundle(String resourceBundleName) {
        // try context class loader to load the resource
        ClassLoader cl = AccessController
                .doPrivileged(new PrivilegedAction<ClassLoader>() {
                    public ClassLoader run() {
                        return Thread.currentThread().getContextClassLoader();
                    }
                });
        if (cl != null) {
            try {
                return ResourceBundle.getBundle(resourceBundleName, Locale
                        .getDefault(), cl);
            } catch (MissingResourceException e) {
                // Failed to load using context classloader, ignore
            }
        }
        // try system class loader to load the resource
        cl = AccessController.doPrivileged(new PrivilegedAction<ClassLoader>() {
            public ClassLoader run() {
                return ClassLoader.getSystemClassLoader();
            }
        });
        if (cl != null) {
            try {
                return ResourceBundle.getBundle(resourceBundleName, Locale
                        .getDefault(), cl);
            } catch (MissingResourceException e) {
                // Failed to load using system classloader, ignore
            }
        }
        // try all class loaders up the class stack
        final Class<?>[] classes = AccessController
                .doPrivileged(new PrivilegedAction<Class<?>[]>() {
                    public Class<?>[] run() {
                        return (new PrivateSecurityManager())
                                .privateGetClassContext();
                    }
                });
        // the first class, which is PrivateSecurityManager, is skipped
        for (int i = 1; i < classes.length; i++) {
            final int index = i;
            try {
                cl = AccessController
                        .doPrivileged(new PrivilegedAction<ClassLoader>() {
                            public ClassLoader run() {
                                return classes[index].getClassLoader();
                            }
                        });
                if (cl == null) {
                    continue;
                }
                return ResourceBundle.getBundle(resourceBundleName, Locale
                        .getDefault(), cl);
            } catch (MissingResourceException e) {
                // Failed to load using the current class's classloader, ignore
            }
        }
        throw new MissingResourceException("Failed to load the specified resource bundle \"" +
                resourceBundleName + "\"", resourceBundleName, null);
    }

    /**
     * Gets an anonymous logger to use internally in a thread. Anonymous loggers
     * are not registered in the log manager's namespace. No security checks
     * will be performed when updating an anonymous logger's control settings.
     * <p>
     * The anonymous loggers' parent is set to be the root logger. This way it
     * inherits the default logging level and handlers from the root logger.
     *
     * @return a new instance of anonymous logger.
     */
    public static Logger getAnonymousLogger() {
        return getAnonymousLogger(null);
    }

    /**
     * Gets an anonymous logger to use internally in a thread. Anonymous loggers
     * are not registered in the log manager's namespace. No security checks
     * will be performed when updating an anonymous logger's control settings.
     * <p>
     * The anonymous loggers' parent is set to be the root logger. This way it
     * inherits default logging level and handlers from the root logger.
     *
     * @param resourceBundleName
     *            the name of the resource bundle used to localize log messages.
     * @return a new instance of anonymous logger.
     * @throws MissingResourceException
     *             if the specified resource bundle can not be loaded.
     */
    public static Logger getAnonymousLogger(String resourceBundleName) {
        Logger result = new Logger(null, resourceBundleName);
        result.isNamed = false;
        LogManager logManager = LogManager.getLogManager();
        logManager.setParent(result, logManager.getLogger(""));
        return result;
    }

    /**
     * Initializes this logger's resource bundle.
     *
     * @throws IllegalArgumentException if this logger's resource bundle already
     *      exists and is different from the resource bundle specified.
     */
    private synchronized void initResourceBundle(String resourceBundleName) {
        String current = this.resourceBundleName;

        if (current != null) {
            if (current.equals(resourceBundleName)) {
                return;
            } else {
                throw new IllegalArgumentException("Resource bundle name '" + resourceBundleName + "' is inconsistent with the existing '" + current + "'");
            }
        }

        if (resourceBundleName != null) {
            this.resourceBundle = loadResourceBundle(resourceBundleName);
            this.resourceBundleName = resourceBundleName;
        }
    }

    /**
     * Gets a named logger. The returned logger may already exist or may be
     * newly created. In the latter case, its level will be set to the
     * configured level according to the {@code LogManager}'s properties.
     *
     * @param name
     *            the name of the logger to get, cannot be {@code null}.
     * @return a named logger.
     * @throws MissingResourceException
     *             If the specified resource bundle can not be loaded.
     */
    public static Logger getLogger(String name) {
        return LogManager.getLogManager().getOrCreate(name, null);
    }

    /**
     * Gets a named logger associated with the supplied resource bundle. The
     * resource bundle will be used to localize logging messages.
     *
     * @param name
     *            the name of the logger to get, cannot be {@code null}.
     * @param resourceBundleName
     *            the name of the resource bundle, may be {@code null}.
     * @throws IllegalArgumentException
     *             if the logger identified by {@code name} is associated with a
     *             resource bundle and its name is not equal to
     *             {@code resourceBundleName}.
     * @throws MissingResourceException
     *             if the name of the resource bundle cannot be found.
     * @return a named logger.
     */
    public static Logger getLogger(String name, String resourceBundleName) {
        Logger result = LogManager.getLogManager()
                .getOrCreate(name, resourceBundleName);
        result.initResourceBundle(resourceBundleName);
        return result;
    }

    /**
     * Adds a handler to this logger. The {@code name} will be fed with log
     * records received by this logger.
     *
     * @param handler
     *            the handler object to add, cannot be {@code null}.
     * @throws SecurityException
     *             if a security manager determines that the caller does not
     *             have the required permission.
     */
    public void addHandler(Handler handler) {
        if (handler == null) {
            throw new NullPointerException("handler == null");
        }
        // Anonymous loggers can always add handlers
        if (this.isNamed) {
            LogManager.getLogManager().checkAccess();
        }
        this.handlers.add(handler);
        updateDalvikLogHandler(); // android-only
    }

    /**
     * Set the logger's manager and initializes its configuration from the
     * manager's properties.
     */
    @SuppressWarnings("nls")
    void setManager(LogManager manager) {
        String levelProperty = manager.getProperty(name + ".level");
        if (levelProperty != null) {
            try {
                manager.setLevelRecursively(Logger.this, Level.parse(levelProperty));
            } catch (IllegalArgumentException invalidLevel) {
                invalidLevel.printStackTrace();
            }
        }

        String handlersPropertyName = name.isEmpty() ? "handlers" : name + ".handlers";
        String handlersProperty = manager.getProperty(handlersPropertyName);
        if (handlersProperty != null) {
            for (String handlerName : handlersProperty.split(",|\\s")) {
                if (handlerName.isEmpty()) {
                    continue;
                }

                final Handler handler;
                try {
                    handler = (Handler) LogManager.getInstanceByClass(handlerName);
                } catch (Exception invalidHandlerName) {
                    invalidHandlerName.printStackTrace();
                    continue;
                }

                try {
                    String level = manager.getProperty(handlerName + ".level");
                    if (level != null) {
                        handler.setLevel(Level.parse(level));
                    }
                } catch (Exception invalidLevel) {
                    invalidLevel.printStackTrace();
                }

                handlers.add(handler);
            }
        }

        updateDalvikLogHandler(); // android-only
    }

    /**
     * Gets all the handlers associated with this logger.
     *
     * @return an array of all the handlers associated with this logger.
     */
    public Handler[] getHandlers() {
        return handlers.toArray(EMPTY_HANDLERS_ARRAY);
    }

    /**
     * Removes a handler from this logger. If the specified handler does not
     * exist then this method has no effect.
     *
     * @param handler
     *            the handler to be removed.
     * @throws SecurityException
     *             if a security manager determines that the caller does not
     *             have the required permission.
     */
    public void removeHandler(Handler handler) {
        // Anonymous loggers can always remove handlers
        if (this.isNamed) {
            LogManager.getLogManager().checkAccess();
        }
        if (handler == null) {
            return;
        }
        this.handlers.remove(handler);
        updateDalvikLogHandler(); // android-only
    }

    /**
     * Gets the filter used by this logger.
     *
     * @return the filter used by this logger, may be {@code null}.
     */
    public Filter getFilter() {
        return this.filter;
    }

    /**
     * Sets the filter used by this logger.
     *
     * @param newFilter
     *            the filter to set, may be {@code null}.
     * @throws SecurityException
     *             if a security manager determines that the caller does not
     *             have the required permission.
     */
    public void setFilter(Filter newFilter) {
        // Anonymous loggers can always set the filter
        if (this.isNamed) {
            LogManager.getLogManager().checkAccess();
        }
        filter = newFilter;
    }

    /**
     * Gets the logging level of this logger. A {@code null} level indicates
     * that this logger inherits its parent's level.
     *
     * @return the logging level of this logger.
     */
    public Level getLevel() {
        return levelObjVal;
    }

    /**
     * Sets the logging level for this logger. A {@code null} level indicates
     * that this logger will inherit its parent's level.
     *
     * @param newLevel
     *            the logging level to set.
     * @throws SecurityException
     *             if a security manager determines that the caller does not
     *             have the required permission.
     */
    public void setLevel(Level newLevel) {
        // Anonymous loggers can always set the level
        LogManager logManager = LogManager.getLogManager();
        if (this.isNamed) {
            logManager.checkAccess();
        }
        logManager.setLevelRecursively(this, newLevel);
    }

    /**
     * Gets the flag which indicates whether to use the handlers of this
     * logger's parent to publish incoming log records, potentially recursively
     * up the namespace.
     *
     * @return {@code true} if set to use parent's handlers, {@code false}
     *         otherwise.
     */
    public boolean getUseParentHandlers() {
        return this.notifyParentHandlers;
    }

    /**
     * Sets the flag which indicates whether to use the handlers of this
     * logger's parent, potentially recursively up the namespace.
     *
     * @param notifyParentHandlers
     *            the new flag indicating whether to use the parent's handlers.
     * @throws SecurityException
     *             if a security manager determines that the caller does not
     *             have the required permission.
     */
    public void setUseParentHandlers(boolean notifyParentHandlers) {
        // Anonymous loggers can always set the useParentHandlers flag
        if (this.isNamed) {
            LogManager.getLogManager().checkAccess();
        }
        this.notifyParentHandlers = notifyParentHandlers;
        updateDalvikLogHandler(); // android-only
    }

    /**
     * Gets the nearest parent of this logger in the namespace, a {@code null}
     * value will be returned if called on the root logger.
     *
     * @return the parent of this logger in the namespace.
     */
    public Logger getParent() {
        return parent;
    }

    /**
     * Sets the parent of this logger in the namespace. This method should be
     * used by the {@code LogManager} object only.
     *
     * @param parent
     *            the parent logger to set.
     * @throws SecurityException
     *             if a security manager determines that the caller does not
     *             have the required permission.
     */
    public void setParent(Logger parent) {
        if (parent == null) {
            throw new NullPointerException("parent == null");
        }

        // even anonymous loggers are checked
        LogManager logManager = LogManager.getLogManager();
        logManager.checkAccess();
        logManager.setParent(this, parent);
    }

    /**
     * Gets the name of this logger, {@code null} for anonymous loggers.
     *
     * @return the name of this logger.
     */
    public String getName() {
        return this.name;
    }

    /**
     * Gets the loaded resource bundle used by this logger to localize logging
     * messages. If the value is {@code null}, the parent's resource bundle will be
     * inherited.
     *
     * @return the loaded resource bundle used by this logger.
     */
    public ResourceBundle getResourceBundle() {
        return this.resourceBundle;
    }

    /**
     * Gets the name of the loaded resource bundle used by this logger to
     * localize logging messages. If the value is {@code null}, the parent's resource
     * bundle name will be inherited.
     *
     * @return the name of the loaded resource bundle used by this logger.
     */
    public String getResourceBundleName() {
        return this.resourceBundleName;
    }

    /**
     * This method is for compatibility. Tests written to the reference
     * implementation API imply that the isLoggable() method is not called
     * directly. This behavior is important because subclass may override
     * isLoggable() method, so that affect the result of log methods.
     */
    private boolean internalIsLoggable(Level l) {
        int effectiveLevel = levelIntVal;
        if (effectiveLevel == Level.OFF.intValue()) {
            // always return false if the effective level is off
            return false;
        }
        return l.intValue() >= effectiveLevel;
    }

    /**
     * Determines whether this logger will actually log messages of the
     * specified level. The effective level used to do the determination may be
     * inherited from its parent. The default level is {@code Level.INFO}.
     *
     * @param l
     *            the level to check.
     * @return {@code true} if this logger will actually log this level,
     *         otherwise {@code false}.
     */
    public boolean isLoggable(Level l) {
        return internalIsLoggable(l);
    }

    /**
     * Sets the resource bundle and its name for a supplied LogRecord object.
     * This method first tries to use this logger's resource bundle if any,
     * otherwise try to inherit from this logger's parent, recursively up the
     * namespace.
     */
    private void setResourceBundle(LogRecord record) {
        for (Logger p = this; p != null; p = p.parent) {
            String resourceBundleName = p.resourceBundleName;
            if (resourceBundleName != null) {
                record.setResourceBundle(p.resourceBundle);
                record.setResourceBundleName(resourceBundleName);
                return;
            }
        }
    }

    /**
     * Logs a message indicating that a method has been entered. A log record
     * with log level {@code Level.FINER}, log message "ENTRY", the specified
     * source class name and source method name is submitted for logging.
     *
     * @param sourceClass
     *            the calling class name.
     * @param sourceMethod
     *            the method name.
     */
    public void entering(String sourceClass, String sourceMethod) {
        if (!internalIsLoggable(Level.FINER)) {
            return;
        }

        LogRecord record = new LogRecord(Level.FINER, "ENTRY");
        record.setLoggerName(this.name);
        record.setSourceClassName(sourceClass);
        record.setSourceMethodName(sourceMethod);
        setResourceBundle(record);
        log(record);
    }

    /**
     * Logs a message indicating that a method has been entered. A log record
     * with log level {@code Level.FINER}, log message "ENTRY", the specified
     * source class name, source method name and one parameter is submitted for
     * logging.
     *
     * @param sourceClass
     *            the source class name.
     * @param sourceMethod
     *            the source method name.
     * @param param
     *            the parameter for the method call.
     */
    public void entering(String sourceClass, String sourceMethod, Object param) {
        if (!internalIsLoggable(Level.FINER)) {
            return;
        }

        LogRecord record = new LogRecord(Level.FINER, "ENTRY" + " {0}");
        record.setLoggerName(this.name);
        record.setSourceClassName(sourceClass);
        record.setSourceMethodName(sourceMethod);
        record.setParameters(new Object[] { param });
        setResourceBundle(record);
        log(record);
    }

    /**
     * Logs a message indicating that a method has been entered. A log record
     * with log level {@code Level.FINER}, log message "ENTRY", the specified
     * source class name, source method name and array of parameters is
     * submitted for logging.
     *
     * @param sourceClass
     *            the source class name.
     * @param sourceMethod
     *            the source method name.
     * @param params
     *            an array of parameters for the method call.
     */
    @SuppressWarnings("nls")
    public void entering(String sourceClass, String sourceMethod,
            Object[] params) {
        if (!internalIsLoggable(Level.FINER)) {
            return;
        }

        String msg = "ENTRY";
        if (params != null) {
            StringBuilder msgBuffer = new StringBuilder("ENTRY");
            for (int i = 0; i < params.length; i++) {
                msgBuffer.append(" {").append(i).append("}");
            }
            msg = msgBuffer.toString();
        }
        LogRecord record = new LogRecord(Level.FINER, msg);
        record.setLoggerName(this.name);
        record.setSourceClassName(sourceClass);
        record.setSourceMethodName(sourceMethod);
        record.setParameters(params);
        setResourceBundle(record);
        log(record);
    }

    /**
     * Logs a message indicating that a method is exited. A log record with log
     * level {@code Level.FINER}, log message "RETURN", the specified source
     * class name and source method name is submitted for logging.
     *
     * @param sourceClass
     *            the calling class name.
     * @param sourceMethod
     *            the method name.
     */
    public void exiting(String sourceClass, String sourceMethod) {
        if (!internalIsLoggable(Level.FINER)) {
            return;
        }

        LogRecord record = new LogRecord(Level.FINER, "RETURN");
        record.setLoggerName(this.name);
        record.setSourceClassName(sourceClass);
        record.setSourceMethodName(sourceMethod);
        setResourceBundle(record);
        log(record);
    }

    /**
     * Logs a message indicating that a method is exited. A log record with log
     * level {@code Level.FINER}, log message "RETURN", the specified source
     * class name, source method name and return value is submitted for logging.
     *
     * @param sourceClass
     *            the source class name.
     * @param sourceMethod
     *            the source method name.
     * @param result
     *            the return value of the method call.
     */
    public void exiting(String sourceClass, String sourceMethod, Object result) {
        if (!internalIsLoggable(Level.FINER)) {
            return;
        }

        LogRecord record = new LogRecord(Level.FINER, "RETURN" + " {0}");
        record.setLoggerName(this.name);
        record.setSourceClassName(sourceClass);
        record.setSourceMethodName(sourceMethod);
        record.setParameters(new Object[] { result });
        setResourceBundle(record);
        log(record);
    }

    /**
     * Logs a message indicating that an exception is thrown. A log record with
     * log level {@code Level.FINER}, log message "THROW", the specified source
     * class name, source method name and the {@code Throwable} object is
     * submitted for logging.
     *
     * @param sourceClass
     *            the source class name.
     * @param sourceMethod
     *            the source method name.
     * @param thrown
     *            the {@code Throwable} object.
     */
    public void throwing(String sourceClass, String sourceMethod,
            Throwable thrown) {
        if (!internalIsLoggable(Level.FINER)) {
            return;
        }

        LogRecord record = new LogRecord(Level.FINER, "THROW");
        record.setLoggerName(this.name);
        record.setSourceClassName(sourceClass);
        record.setSourceMethodName(sourceMethod);
        record.setThrown(thrown);
        setResourceBundle(record);
        log(record);
    }

    /**
     * Logs a message of level {@code Level.SEVERE}; the message is transmitted
     * to all subscribed handlers.
     *
     * @param msg
     *            the message to log.
     */
    public void severe(String msg) {
        log(Level.SEVERE, msg);
    }

    /**
     * Logs a message of level {@code Level.WARNING}; the message is
     * transmitted to all subscribed handlers.
     *
     * @param msg
     *            the message to log.
     */
    public void warning(String msg) {
        log(Level.WARNING, msg);
    }

    /**
     * Logs a message of level {@code Level.INFO}; the message is transmitted
     * to all subscribed handlers.
     *
     * @param msg
     *            the message to log.
     */
    public void info(String msg) {
        log(Level.INFO, msg);
    }

    /**
     * Logs a message of level {@code Level.CONFIG}; the message is transmitted
     * to all subscribed handlers.
     *
     * @param msg
     *            the message to log.
     */
    public void config(String msg) {
        log(Level.CONFIG, msg);
    }

    /**
     * Logs a message of level {@code Level.FINE}; the message is transmitted
     * to all subscribed handlers.
     *
     * @param msg
     *            the message to log.
     */
    public void fine(String msg) {
        log(Level.FINE, msg);
    }

    /**
     * Logs a message of level {@code Level.FINER}; the message is transmitted
     * to all subscribed handlers.
     *
     * @param msg
     *            the message to log.
     */
    public void finer(String msg) {
        log(Level.FINER, msg);
    }

    /**
     * Logs a message of level {@code Level.FINEST}; the message is transmitted
     * to all subscribed handlers.
     *
     * @param msg
     *            the message to log.
     */
    public void finest(String msg) {
        log(Level.FINEST, msg);
    }

    /**
     * Logs a message of the specified level. The message is transmitted to all
     * subscribed handlers.
     *
     * @param logLevel
     *            the level of the specified message.
     * @param msg
     *            the message to log.
     */
    public void log(Level logLevel, String msg) {
        if (!internalIsLoggable(logLevel)) {
            return;
        }

        // BEGIN android-only
        dalvikLogHandler.publish(this, androidTag, logLevel, msg);
        // END android-only
    }

    /**
     * Logs a message of the specified level with the supplied parameter. The
     * message is then transmitted to all subscribed handlers.
     *
     * @param logLevel
     *            the level of the given message.
     * @param msg
     *            the message to log.
     * @param param
     *            the parameter associated with the event that is logged.
     */
    public void log(Level logLevel, String msg, Object param) {
        if (!internalIsLoggable(logLevel)) {
            return;
        }

        LogRecord record = new LogRecord(logLevel, msg);
        record.setLoggerName(this.name);
        record.setParameters(new Object[] { param });
        setResourceBundle(record);
        log(record);
    }

    /**
     * Logs a message of the specified level with the supplied parameter array.
     * The message is then transmitted to all subscribed handlers.
     *
     * @param logLevel
     *            the level of the given message
     * @param msg
     *            the message to log.
     * @param params
     *            the parameter array associated with the event that is logged.
     */
    public void log(Level logLevel, String msg, Object[] params) {
        if (!internalIsLoggable(logLevel)) {
            return;
        }

        LogRecord record = new LogRecord(logLevel, msg);
        record.setLoggerName(this.name);
        record.setParameters(params);
        setResourceBundle(record);
        log(record);
    }

    /**
     * Logs a message of the specified level with the supplied {@code Throwable}
     * object. The message is then transmitted to all subscribed handlers.
     *
     * @param logLevel
     *            the level of the given message.
     * @param msg
     *            the message to log.
     * @param thrown
     *            the {@code Throwable} object associated with the event that is
     *            logged.
     */
    public void log(Level logLevel, String msg, Throwable thrown) {
        if (!internalIsLoggable(logLevel)) {
            return;
        }

        LogRecord record = new LogRecord(logLevel, msg);
        record.setLoggerName(this.name);
        record.setThrown(thrown);
        setResourceBundle(record);
        log(record);
    }

    /**
     * Logs a given log record. Only records with a logging level that is equal
     * or greater than this logger's level will be submitted to this logger's
     * handlers for logging. If {@code getUseParentHandlers()} returns {@code
     * true}, the log record will also be submitted to the handlers of this
     * logger's parent, potentially recursively up the namespace.
     * <p>
     * Since all other log methods call this method to actually perform the
     * logging action, subclasses of this class can override this method to
     * catch all logging activities.
     * </p>
     *
     * @param record
     *            the log record to be logged.
     */
    public void log(LogRecord record) {
        if (!internalIsLoggable(record.getLevel())) {
            return;
        }

        // apply the filter if any
        Filter f = filter;
        if (f != null && !f.isLoggable(record)) {
            return;
        }

        /*
         * call the handlers of this logger, throw any exception that occurs
         */
        Handler[] allHandlers = getHandlers();
        for (Handler element : allHandlers) {
            element.publish(record);
        }
        // call the parent's handlers if set useParentHandlers
        Logger temp = this;
        Logger theParent = temp.parent;
        while (theParent != null && temp.getUseParentHandlers()) {
            Handler[] ha = theParent.getHandlers();
            for (Handler element : ha) {
                element.publish(record);
            }
            temp = theParent;
            theParent = temp.parent;
        }
    }

    /**
     * Logs a message of the given level with the specified source class name
     * and source method name.
     *
     * @param logLevel
     *            the level of the given message.
     * @param sourceClass
     *            the source class name.
     * @param sourceMethod
     *            the source method name.
     * @param msg
     *            the message to be logged.
     */
    public void logp(Level logLevel, String sourceClass, String sourceMethod,
            String msg) {
        if (!internalIsLoggable(logLevel)) {
            return;
        }

        LogRecord record = new LogRecord(logLevel, msg);
        record.setLoggerName(this.name);
        record.setSourceClassName(sourceClass);
        record.setSourceMethodName(sourceMethod);
        setResourceBundle(record);
        log(record);
    }

    /**
     * Logs a message of the given level with the specified source class name,
     * source method name and parameter.
     *
     * @param logLevel
     *            the level of the given message
     * @param sourceClass
     *            the source class name
     * @param sourceMethod
     *            the source method name
     * @param msg
     *            the message to be logged
     * @param param
     *            the parameter associated with the event that is logged.
     */
    public void logp(Level logLevel, String sourceClass, String sourceMethod,
            String msg, Object param) {
        if (!internalIsLoggable(logLevel)) {
            return;
        }

        LogRecord record = new LogRecord(logLevel, msg);
        record.setLoggerName(this.name);
        record.setSourceClassName(sourceClass);
        record.setSourceMethodName(sourceMethod);
        record.setParameters(new Object[] { param });
        setResourceBundle(record);
        log(record);
    }

    /**
     * Logs a message of the given level with the specified source class name,
     * source method name and parameter array.
     *
     * @param logLevel
     *            the level of the given message.
     * @param sourceClass
     *            the source class name.
     * @param sourceMethod
     *            the source method name.
     * @param msg
     *            the message to be logged.
     * @param params
     *            the parameter array associated with the event that is logged.
     */
    public void logp(Level logLevel, String sourceClass, String sourceMethod,
            String msg, Object[] params) {
        if (!internalIsLoggable(logLevel)) {
            return;
        }

        LogRecord record = new LogRecord(logLevel, msg);
        record.setLoggerName(this.name);
        record.setSourceClassName(sourceClass);
        record.setSourceMethodName(sourceMethod);
        record.setParameters(params);
        setResourceBundle(record);
        log(record);
    }

    /**
     * Logs a message of the given level with the specified source class name,
     * source method name and {@code Throwable} object.
     *
     * @param logLevel
     *            the level of the given message.
     * @param sourceClass
     *            the source class name.
     * @param sourceMethod
     *            the source method name.
     * @param msg
     *            the message to be logged.
     * @param thrown
     *            the {@code Throwable} object.
     */
    public void logp(Level logLevel, String sourceClass, String sourceMethod,
            String msg, Throwable thrown) {
        if (!internalIsLoggable(logLevel)) {
            return;
        }

        LogRecord record = new LogRecord(logLevel, msg);
        record.setLoggerName(this.name);
        record.setSourceClassName(sourceClass);
        record.setSourceMethodName(sourceMethod);
        record.setThrown(thrown);
        setResourceBundle(record);
        log(record);
    }

    /**
     * Logs a message of the given level with the specified source class name
     * and source method name, using the given resource bundle to localize the
     * message. If {@code bundleName} is null, the empty string or not valid then
     * the message is not localized.
     *
     * @param logLevel
     *            the level of the given message.
     * @param sourceClass
     *            the source class name.
     * @param sourceMethod
     *            the source method name.
     * @param bundleName
     *            the name of the resource bundle used to localize the message.
     * @param msg
     *            the message to be logged.
     */
    public void logrb(Level logLevel, String sourceClass, String sourceMethod,
            String bundleName, String msg) {
        if (!internalIsLoggable(logLevel)) {
            return;
        }

        LogRecord record = new LogRecord(logLevel, msg);
        if (bundleName != null) {
            try {
                record.setResourceBundle(loadResourceBundle(bundleName));
            } catch (MissingResourceException e) {
                // ignore
            }
            record.setResourceBundleName(bundleName);
        }
        record.setLoggerName(this.name);
        record.setSourceClassName(sourceClass);
        record.setSourceMethodName(sourceMethod);
        log(record);
    }

    /**
     * Logs a message of the given level with the specified source class name,
     * source method name and parameter, using the given resource bundle to
     * localize the message. If {@code bundleName} is null, the empty string
     * or not valid then the message is not localized.
     *
     * @param logLevel
     *            the level of the given message.
     * @param sourceClass
     *            the source class name.
     * @param sourceMethod
     *            the source method name.
     * @param bundleName
     *            the name of the resource bundle used to localize the message.
     * @param msg
     *            the message to be logged.
     * @param param
     *            the parameter associated with the event that is logged.
     */
    public void logrb(Level logLevel, String sourceClass, String sourceMethod,
            String bundleName, String msg, Object param) {
        if (!internalIsLoggable(logLevel)) {
            return;
        }

        LogRecord record = new LogRecord(logLevel, msg);
        if (bundleName != null) {
            try {
                record.setResourceBundle(loadResourceBundle(bundleName));
            } catch (MissingResourceException e) {
                // ignore
            }
            record.setResourceBundleName(bundleName);
        }
        record.setLoggerName(this.name);
        record.setSourceClassName(sourceClass);
        record.setSourceMethodName(sourceMethod);
        record.setParameters(new Object[] { param });
        log(record);
    }

    /**
     * Logs a message of the given level with the specified source class name,
     * source method name and parameter array, using the given resource bundle
     * to localize the message. If {@code bundleName} is null, the empty string
     * or not valid then the message is not localized.
     *
     * @param logLevel
     *            the level of the given message.
     * @param sourceClass
     *            the source class name.
     * @param sourceMethod
     *            the source method name.
     * @param bundleName
     *            the name of the resource bundle used to localize the message.
     * @param msg
     *            the message to be logged.
     * @param params
     *            the parameter array associated with the event that is logged.
     */
    public void logrb(Level logLevel, String sourceClass, String sourceMethod,
            String bundleName, String msg, Object[] params) {
        if (!internalIsLoggable(logLevel)) {
            return;
        }

        LogRecord record = new LogRecord(logLevel, msg);
        if (bundleName != null) {
            try {
                record.setResourceBundle(loadResourceBundle(bundleName));
            } catch (MissingResourceException e) {
                // ignore
            }
            record.setResourceBundleName(bundleName);
        }
        record.setLoggerName(this.name);
        record.setSourceClassName(sourceClass);
        record.setSourceMethodName(sourceMethod);
        record.setParameters(params);
        log(record);
    }

    /**
     * Logs a message of the given level with the specified source class name,
     * source method name and {@code Throwable} object, using the given resource
     * bundle to localize the message. If {@code bundleName} is null, the empty
     * string or not valid then the message is not localized.
     *
     * @param logLevel
     *            the level of the given message
     * @param sourceClass
     *            the source class name
     * @param sourceMethod
     *            the source method name
     * @param bundleName
     *            the name of the resource bundle used to localize the message.
     * @param msg
     *            the message to be logged.
     * @param thrown
     *            the {@code Throwable} object.
     */
    public void logrb(Level logLevel, String sourceClass, String sourceMethod,
            String bundleName, String msg, Throwable thrown) {
        if (!internalIsLoggable(logLevel)) {
            return;
        }

        LogRecord record = new LogRecord(logLevel, msg);
        if (bundleName != null) {
            try {
                record.setResourceBundle(loadResourceBundle(bundleName));
            } catch (MissingResourceException e) {
                // ignore
            }
            record.setResourceBundleName(bundleName);
        }
        record.setLoggerName(this.name);
        record.setSourceClassName(sourceClass);
        record.setSourceMethodName(sourceMethod);
        record.setThrown(thrown);
        log(record);
    }

    /*
     * This security manager is used to access the class context.
     */
    static class PrivateSecurityManager extends SecurityManager {
        public Class<?>[] privateGetClassContext() {
            return super.getClassContext();
        }
    }

    void reset() {
        levelObjVal = null;
        levelIntVal = Level.INFO.intValue();

        for (Handler handler : handlers) {
            try {
                if (handlers.remove(handler)) {
                    handler.close();
                }
            } catch (Exception ignored) {
            }
        }

        updateDalvikLogHandler(); // android-only
    }
}
