/******************************************************************************* * Copyright 2014 Analog Devices, Inc. * * Licensed 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 com.analog.lyric.dimple.events; import java.io.Closeable; import java.io.File; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.PrintStream; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; import net.jcip.annotations.GuardedBy; import net.jcip.annotations.ThreadSafe; import org.eclipse.jdt.annotation.Nullable; import com.analog.lyric.dimple.environment.DimpleEnvironment; import com.analog.lyric.dimple.environment.IDimpleEnvironmentHolder; /** * Provides basic logging of dimple events. * <p> * This class provides a simpler interface for than {@link DimpleEventListener} when * you only want to print the text of events to an output stream. * <p> * For instance, to log all dimple events to stderr for a graph {@code fg}, you only need to write: * <pre> * DimpleEventLogger logger = new DimpleEventLogger(); * logger.log(DimpleEvent.class, fg); * </pre> * and to write to a specified file instead, you just need to open the logger with the * given file, as in: * <pre> * logger.open(new File("events.log")); * </pre> * * The event logger will use the existing {@link DimpleEventListener} that is associated * with the root graph of each event source, if it has been set. Otherwise, it will automatically * set the root graph to use the listener {@link DimpleEventListener#getDefault()}. * * @since 0.06 * @author Christopher Barber */ @ThreadSafe public class DimpleEventLogger implements Closeable, IDimpleEnvironmentHolder { /*------- * State */ private final DimpleEnvironment _env; /** * Events will be printed on this stream, if non-null. */ @GuardedBy("this") private volatile @Nullable PrintStream _out; /** * The file used for {@link #_out}, if opened by {@link #open(File)}. */ @GuardedBy("this") private volatile @Nullable File _file = null; private volatile int _verbosity; private final DimpleEventHandler<DimpleEvent> _handler = new EventPrinter(); @GuardedBy("this") private final Set<LogEntry> _logEntries = new HashSet<LogEntry>(); private class EventPrinter extends DimpleEventHandler<DimpleEvent> { @Override public void handleEvent(DimpleEvent event) { PrintStream out = _out; if (out != null) { event.println(out, _verbosity); out.flush(); } } } /** * A simple pairing of event source and event type. * <p> * @see DimpleEventLogger#logEntries() * @since 0.06 */ public static class LogEntry { private final Class<? extends DimpleEvent> _eventClass; private final IDimpleEventSource _eventSource; private LogEntry(Class<? extends DimpleEvent> eventType, IDimpleEventSource eventSource) { _eventClass = eventType; _eventSource = eventSource; } /*---------------- * Object methods */ @Override public boolean equals(@Nullable Object other) { if (this == other) { return true; } if (other instanceof LogEntry) { LogEntry that = (LogEntry)other; return this._eventClass == that._eventClass && this._eventSource == that._eventSource; } return false; } @Override public int hashCode() { return _eventClass.hashCode() * 11 + _eventSource.hashCode(); } /*------------------ * LogEntry methods */ public Class<? extends DimpleEvent> eventClass() { return _eventClass; } public IDimpleEventSource eventSource() { return _eventSource; } } /*-------------- * Construction */ /** * Constructs logger with output going to {@link System#err} and * {@link #verbosity()} set to zero. * <p> * The {@linkplain #getEnvironment environment} will be set to the * {@linkplain DimpleEnvironment#active active environment}. * <p> * @since 0.06 */ public DimpleEventLogger() { _env = DimpleEnvironment.active(); _out = System.err; _verbosity = 0; } /*------------------- * Closeable methods */ /** * Closes underlying stream. * <p> * If {@link #out()} is non-null, closes it if not one of {@link System#out} or {@link System#err}, * and then sets to null. */ @Override public synchronized void close() { final PrintStream out = _out; if (out != null) { if (out != System.out && out != System.err) { out.close(); } _out = null; } } /*---------------------------------- * IDimpleEnvironmentHolder methods */ /** * {@inheritDoc} * @see #DimpleEventLogger() * @since 0.07 */ @Override public DimpleEnvironment getEnvironment() { return _env; } /*--------- * Methods */ /** * File last used by {@link #open(File)} or null if last opened by {@link #open(PrintStream)}. * * @since 0.06 */ public @Nullable File file() { return _file; } /** * True if object is known to not have any active log entries, no calls have been made * to {@link #log(Class, IDimpleEventSource...)} since object was created or last call * to {@link #clear()}. * * @since 0.06 */ public synchronized boolean isClear() { return _logEntries.isEmpty(); } /** * True if {@link #out()} is non-null. * * @since 0.06 */ public boolean isOpen() { return _out != null; } /** * Enable logging of given event type on specified targets. * <p> * Enables logging by registering an event handler with the event listener of the * {@linkplain #getEnvironment() environment} creating a new one if necessary. It * is assumed that all of the listed {@code sources} have the same environment. * <p> * @param eventType is the superclass of the type of events that will be logged. If {@code eventType} * is abstract then all subtypes will be logged, otherwise only that specific type will be logged. * @param sources lists the objects that should log events. This will affect both those objects and their * children unless blocked. * @since 0.06 */ public synchronized void log(Class<? extends DimpleEvent> eventType, IDimpleEventSource ... sources) { final DimpleEventListener listener = _env.createEventListener(); for (IDimpleEventSource source : sources) { listener.register(_handler, eventType, source); _logEntries.add(new LogEntry(eventType, source)); } } /** * Returns newly created list of current log entries for this object in no particular order. * * @since 0.06 */ public synchronized List<LogEntry> logEntries() { return new ArrayList<LogEntry>(_logEntries); } /** * Removes log configuration of given event type on specified targets. * <p> * Note that this will only remove logging set up for the same {@code eventType} and source combination. * It will not block logging when {@link #log(Class, IDimpleEventSource...)} was called on a parent * object of the source. * <p> * @param eventType is type used in a previous call to {@link #log}. * @param sources are one or more sources previously used with {@code eventType} in a previous call * to {@link #log}. * @return the number of log entries that were removed by this call. * @since 0.06 */ public synchronized int unlog(Class<? extends DimpleEvent> eventType, IDimpleEventSource ... sources) { int nRemoved = 0; DimpleEventListener listener = _env.getEventListener(); if (listener != null) { for (IDimpleEventSource source : sources) { _logEntries.remove(new LogEntry(eventType, source)); if (listener.unregister(_handler, eventType, source)) { ++nRemoved; } } if (listener.isEmpty()) { // Remove the listener from the environment if it contains no more entries. _env.setEventListener(null); } } return nRemoved; } /** * Directs log output to append to file. * <p> * Invokes {@link #close()} before opening new file. * <p> * @param file is non-null file that will be opened in append mode. * @throws FileNotFoundException * @since 0.06 * @see #open(File, boolean) */ public void open(File file) throws FileNotFoundException { open(file, true); } /** * Directs log output to a file. * <p> * Invokes {@link #close()} before opening new file. * <p> * @param file is non-null file that will be opened in append mode. * @param append indicates whether to append to the file or overwrite the existing contents. * @throws FileNotFoundException * @since 0.06 * @see #open(File) * @see #open(PrintStream) */ public synchronized void open(File file, boolean append) throws FileNotFoundException { close(); _out = new PrintStream(new FileOutputStream(file, append)); _file = file; } /** * Directs log output to given stream. * * @param out * @since 0.06 * @see #open(File) * @see #open(File, boolean) */ public synchronized void open(PrintStream out) { close(); _out = out; _file = null; } /** * Clears all logging handlers controlled by this object. * * @since 0.06 */ public synchronized void clear() { DimpleEventListener listener = _env.getEventListener(); if (listener != null) { for (LogEntry entry : _logEntries) { listener.unregister(_handler, entry.eventClass(), entry.eventSource()); } if (listener.isEmpty()) { _env.setEventListener(null); } } _logEntries.clear(); } /** * The current stream used for logging. May be null. * @since 0.06 */ public @Nullable PrintStream out() { return _out; } /** * The verbosity of logged events. * <p> * This is the value that will be passed to {@link DimpleEvent#println(PrintStream, int)} * when output an event. * * @since 0.06 */ public int verbosity() { return _verbosity; } /** * Sets the value of {@link #verbosity()} to the specified value. * @since 0.06 */ public void verbosity(int verbosity) { _verbosity = verbosity; } }