Package uk.co.caprica.vlcj.log

Source Code of uk.co.caprica.vlcj.log.NativeLog$NotifyEventListenersRunnable

/*
* This file is part of VLCJ.
*
* VLCJ is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* VLCJ 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 for more details.
*
* You should have received a copy of the GNU General Public License
* along with VLCJ.  If not, see <http://www.gnu.org/licenses/>.
*
* Copyright 2009, 2010, 2011, 2012, 2013, 2014 Caprica Software Limited.
*/

package uk.co.caprica.vlcj.log;

import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicBoolean;

import uk.co.caprica.vlcj.binding.LibC;
import uk.co.caprica.vlcj.binding.LibVlc;
import uk.co.caprica.vlcj.binding.internal.libvlc_instance_t;
import uk.co.caprica.vlcj.binding.internal.libvlc_log_cb;
import uk.co.caprica.vlcj.binding.internal.libvlc_log_level_e;
import uk.co.caprica.vlcj.binding.internal.libvlc_log_t;
import uk.co.caprica.vlcj.logger.Logger;
import uk.co.caprica.vlcj.version.LibVlcVersion;

import com.sun.jna.Pointer;
import com.sun.jna.ptr.IntByReference;
import com.sun.jna.ptr.PointerByReference;

/**
* Encapsulation of the vlc native log.
* <p>
* The native library specifies that implementations of native log handlers (like
* that encapsulated within this class) must be thread-safe.
* <p>
* The default log level is {@link libvlc_log_level_e#NOTICE}, this can be changed
* by invoking {@link #setLevel(libvlc_log_level_e)}.
* <p>
* <strong>The native log requires vlc 2.1.0 or later.</strong>
*/
public class NativeLog {

    /**
     * Default string buffer size.
     * <p>
     * Plus one for the null terminator.
     */
    private static final int BUFFER_SIZE = 200 + 1;

    /**
     * List of registered event listeners.
     */
    private final List<LogEventListener> eventListenerList = new ArrayList<LogEventListener>();

    /**
     * Background thread to send event notifications to listeners.
     * <p>
     * The single-threaded nature of this executor service ensures that events are delivered to
     * listeners in a thread-safe manner and in their proper sequence.
     */
    private final ExecutorService listenersService = Executors.newSingleThreadExecutor();

    /**
     * Native library instance.
     */
    private final LibVlc libvlc;

    /**
     * LibVlc instance.
     */
    private final libvlc_instance_t instance;

    /**
     * Native log callback.
     */
    private libvlc_log_cb callback;

    /**
     * Set to true when the log has been released.
     */
    private final AtomicBoolean released = new AtomicBoolean();

    /**
     * Log level.
     * <p>
     * Set to <code>null</code> to suppress all log messages.
     */
    private libvlc_log_level_e logLevel = libvlc_log_level_e.NOTICE;

    /**
     * Create a new native log component.
     *
     * @param libvlc native library instance
     * @param instance libvlc instance
     */
    public NativeLog(LibVlc libvlc, libvlc_instance_t instance) {
        if(LibVlcVersion.getVersion().atLeast(LibVlcVersion.LIBVLC_210)) {
            this.libvlc = libvlc;
            this.instance = instance;
            createInstance();
        }
        else {
            throw new RuntimeException("Native log requires libvlc 2.1.0 or later");
        }
    }

    /**
     * Add a component to be notified of log messages.
     *
     * @param listener component to add
     */
    public final void addLogListener(LogEventListener listener) {
       Logger.debug("addLogListener(listener={})", listener);
       eventListenerList.add(listener);
    }

    /**
     * Remove a component previously added so it is no longer notified of log messages.
     *
     * @param listener component to remove
     */
    public final void removeLogListener(LogEventListener listener) {
        Logger.debug("removeLogListener(listener={})", listener);
        eventListenerList.remove(listener);
    }

    /**
     * Set the log threshold level.
     * <p>
     * Only log messages that are equal to or exceed this threshold are notified to
     * listeners.
     *
     * @param logLevel log threshold level
     */
    public final void setLevel(libvlc_log_level_e logLevel) {
        this.logLevel = logLevel;
    }

    /**
     * Get the log threshold level.
     *
     * @return level
     */
    public final libvlc_log_level_e getLevel() {
        return logLevel;
    }

    /**
     * Release the native log component.
     */
    public final void release() {
        Logger.debug("release()");
        if(released.compareAndSet(false, true)) {
            destroyInstance();
        }
    }

    /**
     * Create the native resources and prepare the log component.
     */
    private void createInstance() {
        Logger.debug("createInstance()");
        // Create a native callback to receive log messages
        callback = new NativeLogCallback();
        // Subscribe to the native log
        libvlc.libvlc_log_set(instance, callback, null);
    }

    /**
     * Destroy the native resources and shut down the log component.
     */
    private void destroyInstance() {
        Logger.debug("destroyInstance()");
        // Stop receiving native log messages
        libvlc.libvlc_log_unset(instance);
        // Clear all registered listeners
        eventListenerList.clear();
        // Shut down the listener service
        Logger.debug("Shut down listeners...");
        listenersService.shutdown();
        Logger.debug("Listeners shut down.");
    }

    @Override
    protected void finalize() throws Throwable {
        Logger.debug("finalize()");
        Logger.debug("Native log has been garbage collected");
        super.finalize();
        // FIXME should this invoke release()?
    }

    /**
     *
     *
     * This implementation must be thread-safe.
     */
    private final class NativeLogCallback implements libvlc_log_cb {

        @Override
        public void log(Pointer data, int level, libvlc_log_t ctx, String format, Pointer args) {
            // If the log is not being suppressed...
            if(logLevel != null && level >= logLevel.intValue()) {
                // Allocate a new buffer to hold the formatted log message
                ByteBuffer byteBuffer = ByteBuffer.allocateDirect(BUFFER_SIZE);
                // Delegate to the native library to format the log message
                int size = LibC.INSTANCE.vsnprintf(byteBuffer, byteBuffer.capacity(), format, args);
                // If the message was formatted without error...
                if(size >= 0) {
                    // FIXME could reallocate a new buffer here and try again if size > capacity?
                    // Determine the number of available characters (actually number of bytes)
                    size = Math.min(size, BUFFER_SIZE);
                    // Create a new string from the byte buffer contents
                    byte[] bytes = new byte[size];
                    byteBuffer.get(bytes);
                    String message = new String(bytes);
                    if(message.length() > 0) {
                        // Get the information about the object that emitted the log statement
                        PointerByReference modulePointer = new PointerByReference();
                        PointerByReference filePointer = new PointerByReference();
                        IntByReference linePointer = new IntByReference();
                        libvlc.libvlc_log_get_context(ctx, modulePointer, filePointer, linePointer);
                        PointerByReference namePointer = new PointerByReference();
                        PointerByReference headerPointer = new PointerByReference();
                        IntByReference idPointer = new IntByReference();
                        libvlc.libvlc_log_get_object(ctx, namePointer, headerPointer, idPointer);
                        String module = getString(modulePointer);
                        String file = getString(filePointer);
                        Integer line = linePointer.getValue();
                        String name = getString(namePointer);
                        String header = getString(headerPointer);
                        Integer id = idPointer.getValue();
                        // ...send the event
                        raiseLogEvent(libvlc_log_level_e.level(level), module, file, line, name, header, id, message);
                    }
                }
                else {
                    Logger.error("Failed to format log message");
                }
            }
        }
    }

    /**
     * Dereference a pointer (that may be <code>null</code>) to get a string.
     *
     * @param pointer pointer
     * @return string, or <code>null</code> if the pointer is <code>null</code>
     */
    private String getString(PointerByReference pointer) {
        Pointer value = pointer.getValue();
        return value != null ? value.getString(0) : null;
    }

    /**
     * Raise a log event.
     *
     * @param level log level
     * @param module module
     * @param file file
     * @param line line number
     * @param name name
     * @param header header
     * @param id object identifier
     * @param message log message
     */
    private void raiseLogEvent(libvlc_log_level_e level, String module, String file, Integer line, String name, String header, Integer id, String message) {
        Logger.trace("raiseLogEvent(level={},module={},line={},name={},header={},id={},message={}", level, module, file, line, name, header, id, message);
        // Submit a new log event so message are sent serially and asynchronously
        listenersService.submit(new NotifyEventListenersRunnable(level, module, file, line, name, header, id, message));

    }

    /**
     * A runnable task used to fire event notifications.
     * <p>
     * Care must be taken not to re-enter the native library during an event notification so the
     * notifications are off-loaded to a separate thread.
     * <p>
     * These events therefore do <em>not</em> run on the Event Dispatch Thread.
     */
    private final class NotifyEventListenersRunnable implements Runnable {

        /**
         * Log level.
         */
        private final libvlc_log_level_e level;

        /**
         * Module.
         */
        private final String module;

        /**
         * File.
         */
        private final String file;

        /**
         * Line number.
         */
        private final Integer line;

        /**
         * Name.
         */
        private final String name;

        /**
         * Header.
         */
        private final String header;

        /**
         * Object identifier.
         */
        private final Integer id;

        /**
         * Log message.
         */
        private final String message;

        /**
         * Create a runnable.
         *
         * @param level log level
         * @param module module
         * @param file file
         * @param line line number
         * @param name name
         * @param header header
         * @param id object identifier
         * @param message log message
         *
         * @param mediaPlayerEvent event to notify
         */
        private NotifyEventListenersRunnable(libvlc_log_level_e level, String module, String file, Integer line, String name, String header, Integer id, String message) {
            this.level = level;
            this.module = module;
            this.file = file;
            this.line = line;
            this.name = name;
            this.header = header;
            this.id = id;
            this.message = message;
        }

        @Override
        public void run() {
            Logger.trace("run()");
            for(int i = eventListenerList.size() - 1; i >= 0; i -- ) {
                LogEventListener listener = eventListenerList.get(i);
                try {
                    listener.log(level, module, file, line, name, header, id, message);
                }
                catch(Exception e) {
                    Logger.warn("Event listener {} threw an exception", e, listener);
                    // Continue with the next listener...
                }
            }
            Logger.trace("runnable exits");
        }
    }
}
TOP

Related Classes of uk.co.caprica.vlcj.log.NativeLog$NotifyEventListenersRunnable

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.