/** * This file Copyright (c) 2005-2008 Aptana, Inc. This program is * dual-licensed under both the Aptana Public License and the GNU General * Public license. You may elect to use one or the other of these licenses. * * This program is distributed in the hope that it will be useful, but * AS-IS and WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE, TITLE, or * NONINFRINGEMENT. Redistribution, except as permitted by whichever of * the GPL or APL you select, is prohibited. * * 1. For the GPL license (GPL), you can redistribute and/or modify this * program under the terms of the GNU General Public License, * Version 3, as published by the Free Software Foundation. You should * have received a copy of the GNU General Public License, Version 3 along * with this program; if not, write to the Free Software Foundation, Inc., 51 * Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Aptana provides a special exception to allow redistribution of this file * with certain Eclipse Public Licensed code and certain additional terms * pursuant to Section 7 of the GPL. You may view the exception and these * terms on the web at http://www.aptana.com/legal/gpl/. * * 2. For the Aptana Public License (APL), this program and the * accompanying materials are made available under the terms of the APL * v1.0 which accompanies this distribution, and is available at * http://www.aptana.com/legal/apl/. * * You may view the GPL, Aptana's exception and additional terms, and the * APL in the file titled license.html at the root of the corresponding * plugin containing this source file. * * Any modifications to this file must keep this entire header intact. */ package com.aptana.ide.logging.impl; import java.io.IOException; import java.io.Reader; import java.nio.CharBuffer; import java.util.HashSet; import com.aptana.ide.core.IdeLog; import com.aptana.ide.logging.ILogResource; import com.aptana.ide.logging.ILogResourceListener; import com.aptana.ide.logging.ILogTailListener; import com.aptana.ide.logging.ILogWatcher; import com.aptana.ide.logging.IThreadProxy; import com.aptana.ide.logging.LoggingPlugin; /** * Abstract superclass for watchers. * * @author Denis Denisenko */ public abstract class AbstractLogWatcher implements ILogWatcher, Runnable { protected static class DataChange { /** * Data. */ private String data; /** * Length of text to replace. */ private long globalLength; /** * Global offset. */ private long globalOffset; /** * Data constructor. * @param data - data. * @param globalOffset - global data offset (measured in characters). * @param globalLength - global length of text to replace (measured in characters). */ public DataChange(String data, int globalOffset, int globalLength) { this.data = data; this.globalLength = globalLength; this.globalOffset = globalOffset; } /** * Gets data. * @return data. */ public String getData() { return data; } /** * Gets global data offset. * @return the globalOffset. */ public long getGlobalOffset() { return globalOffset; } /** * Gets global length to replace. * @return global length to replace. */ public long getGlobalLength() { return globalLength; } } /** * Thread stop timeout. */ private static final int TIMEOUT = 1000; /** * Thread proxy. */ private final IThreadProxy threadProxy; /** * Synchronized map of tail listeners. */ private final HashSet<ILogTailListener> tailListeners = new HashSet<ILogTailListener>(); /** * Synchronized map of resource listeners. */ private final HashSet<ILogResourceListener> resourceListeners = new HashSet<ILogResourceListener>(); /** * Watcher configuration. */ private final LogWatcherConfiguration configuration; /** * Check wait time. */ private final long checkWait; /** * Whether to check data. */ private volatile boolean checkData; /** * Current thread. */ private Thread thread; /** * Resource. */ private final ILogResource resource; private boolean _notifyListeners = true; /** * AbstractLogWatcher constructor. * * @param config - watcher configuration. */ public AbstractLogWatcher(LogWatcherConfiguration config, ILogResource resource) { this.threadProxy = config.getThreadProxy(); this.checkWait = config.getCheckWait(); this.configuration = config; this.resource = resource; } /** * {@inheritDoc} */ public final void registerListener(ILogTailListener listener) { tailListeners.add(listener); } /** * {@inheritDoc} */ public final void removeListener(ILogTailListener listener) { tailListeners.remove(listener); } /** * {@inheritDoc} */ public final void registerListener(ILogResourceListener listener) { resourceListeners.add(listener); } /** * {@inheritDoc} */ public final void removeListener(ILogResourceListener listener) { resourceListeners.remove(listener); } /** * {@inheritDoc} */ public final void startWatching() { if (thread != null && thread.isAlive()) { checkData = true; return; } checkData = true; thread = new Thread(this, resource.getURI().toString() + " watching thread"); //$NON-NLS-1$ thread.start(); } /** * {@inheritDoc} */ public final void stopWatching() { //turning the check off. checkData = false; /*if (thread == null || !thread.isAlive()) { return; } try { thread.join(TIMEOUT); } catch (InterruptedException e) { IdeLog.logError(LoggingPlugin.getDefault(), "Unexpected exception while stopping watcher thread", e); } if (thread.isAlive()) { thread.interrupt(); }*/ } /** * Stops watching and wait until the stop is complete. */ public final void synchronizedStopWatching() { //turning the check off. checkData = false; if (thread == null || !thread.isAlive()) { return; } try { //waiting for a thread to stop its work thread.join(0); } catch (InterruptedException e) { IdeLog.logError(LoggingPlugin.getDefault(), Messages.AbstractLogWatcher_ERR_Exception, e); } } /** * {@inheritDoc} */ public final void run() { try { while (checkData) { //getting data reader DataChange change = getData(); if (change == null) { try { Thread.sleep(checkWait); } catch (InterruptedException e) { return; } continue; } String toSend = change.getData(); //notifying listeners we have new data notifyListeners(toSend, change.getGlobalOffset(), change.getGlobalLength()); //notifying listeners that resource is available. notifyListenersResourceAvailable(true); try { Thread.sleep(checkWait); } catch (InterruptedException e) { return; } } } catch (Throwable th) { notifyListeners(th); stopWatching(); } } /** * Notifies listeners. * @param data - data. * @param globalLength - length of text to replace. * @param globalOffset - global offset of the data. */ protected void notifyListeners(final String data, final long globalOffset, final long globalLength) { if (_notifyListeners) { threadProxy.run(new Runnable() { public void run() { for(ILogTailListener listener : tailListeners) { listener.dataAvailable(data, globalOffset, globalLength); } } }); } } /** * Notifies listeners. * @param error - error. */ protected void notifyListeners(final Throwable error) { if (_notifyListeners) { threadProxy.run(new Runnable() { public void run() { for (ILogTailListener listener : tailListeners) { listener.errorHappened(error); } } }); } } /** * Sets whether to notify listeners. * @param notify - notify listeners or not. */ protected void setNotifyListeners(boolean notify) { _notifyListeners = notify; } /** * Notifies listeners if resource is available. * @param available - whether resource is available. */ protected void notifyListenersResourceAvailable(final boolean available) { threadProxy.run(new Runnable() { public void run() { for (ILogResourceListener listener : resourceListeners) { listener.resourceAvailable(available); } } }); } /** * Gets watcher configuration. * @return */ protected LogWatcherConfiguration getConfiguration() { return configuration; } /** * {@inheritDoc} */ public void close() { stopWatching(); tailListeners.clear(); resourceListeners.clear(); } /** * {@inheritDoc} */ public boolean isWatching() { return thread != null && thread.isAlive(); } /** * {@inheritDoc} */ public boolean watchingStatus() { return checkData; } public ILogResource getResource() { return resource; } /** * Gets data change. * * @return reader for data change as well as whether data should be appended or replaced and data offset. * Reader is not required to implement {@link Reader#read(char[], int, int)}, * but it should implement {@link Reader#read(CharBuffer)} in efficient way instead. * Also reader is expected to read 0 bytes when no data is available and >0 bytes otherwise, * even if a previous read resulted in 0 bytes read. * Returning null means no data is available at the moment. * * @throws IOException IF IO error occurred */ protected abstract DataChange getData() throws IOException; }