/******************************************************************************* * Copyright 2011 Google Inc. All Rights Reserved. * * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * 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.google.gwt.eclipse.oophm.model; import com.google.gwt.core.ext.TreeLogger; import com.google.gwt.eclipse.oophm.LogSniffer; import com.google.gwt.eclipse.oophm.model.BrowserTab.ModuleHandle; import java.util.ArrayList; import java.util.List; /** * A log, for either a {@link BrowserTab} or a {@link Server}. A log consists of * a list of log entries. * * This class is thread-safe. * * @param <T> */ public class Log<T extends IModelNode> { final Object instanceLock = new Object(); private final T entity; private final List<ILogListener<T>> logListeners = new ArrayList<ILogListener<T>>(); private final LogEntry<T> rootLogEntry; /** * Create a new instance of a log. * * @param entity The entity associated with this log (i.e. a browser tab or a * server) */ public Log(T entity) { this.entity = entity; rootLogEntry = new LogEntry<T>(null, 0, new ModuleHandle( "Hidden Root Module", "Hidden Root Module Session Key")); rootLogEntry.setLog(this); } /** * Add a listener for changes to this log. */ public void addLogListener(ILogListener<T> listener) { synchronized (instanceLock) { logListeners.add(listener); // Log this event LogSniffer.log("AddLogListener: {0}", listener.toString()); } } /** * Get the entity associated with this log. */ public T getEntity() { return entity; } /** * Return the most-deeply-nested first child of this log that has an attention * level which is greater than or equal to all other entries in the log. */ public LogEntry<T> getFirstDeeplyNestedChildWithMaxAttn() { synchronized (instanceLock) { return getFirstDeeplyNestedChildWithMaxAttnRecurse(rootLogEntry.getDisclosedChildren()); } } /** * Returns a list of the {@link ILogListener} instances that are registered * with the log. */ public List<ILogListener<T>> getLogListeners() { synchronized (instanceLock) { return new ArrayList<ILogListener<T>>(logListeners); } } /** * Gets the root log entry associated with this log. Entries can be added to * this log via the root log entry. * * @return the root log entry */ public LogEntry<T> getRootLogEntry() { return rootLogEntry; } /** * Returns <code>true</code> if there are any child {@link LogEntry * LogEntries} that are disclosed. */ public boolean hasDisclosedLogEntries() { /* * Note: if any child anywhere down the tree is disclosed the all parents * back to the root are disclosed. We only need to check the first set of * children. */ return !getRootLogEntry().getDisclosedChildren().isEmpty(); } /** * Remove a listener from the list of listeners. * * @return <code>true</code> if the listener was successfully removed */ public boolean removeLogListener(ILogListener<T> listener) { synchronized (instanceLock) { // Log this event LogSniffer.log("RemoveLogListener: {0}", listener.toString()); return logListeners.remove(listener); } } /** * Marks all entries in this log as undisclosed. Fires an event to all * listeners of this log. * * NOTE: This method fires events. If you're invoking this method from other * model classes, make sure that no locks are being held. */ public void undiscloseAllLogEntries() { List<LogEntry<T>> undisclosedLogEntries; synchronized (instanceLock) { LogEntry<T> logEntry = getRootLogEntry(); undisclosedLogEntries = setUndisclosed(logEntry); } fireEntriesRemoved(undisclosedLogEntries); } void fireNewEntryAdded(int insertionIndex, LogEntry<T> newEntry, boolean needsAttention, boolean parentsChanged) { LogEntryAddedEvent<T> e = new LogEntryAddedEvent<T>(insertionIndex, newEntry, needsAttention, parentsChanged); for (ILogListener<T> listener : getLogListeners()) { listener.newLogEntry(e); } } private void fireEntriesRemoved(List<LogEntry<T>> logEntriesRemoved) { LogEntriesRemovedEvent<T> e = new LogEntriesRemovedEvent<T>( logEntriesRemoved); for (ILogListener<T> listener : getLogListeners()) { listener.logEntriesRemoved(e); } } private LogEntry<T> getFirstDeeplyNestedChildWithMaxAttnRecurse( List<LogEntry<T>> logEntries) { LogEntry<T> maxEntryNeedsAttn = null; TreeLogger.Type maxEntryAttnLevel = null; for (LogEntry<T> child : logEntries) { /* * Determine the child's attention level. * * TODO: It is somewhat confusing that getAttentionLevel() is null for * those entries that are marked as needsAttention. Perhaps if an entry * needs attention, we could also set it's attention level. That would * avoid the need for this conditional below. */ TreeLogger.Type childAttnLevel = null; if (child.getLogData().getNeedsAttention()) { childAttnLevel = LogEntry.toTreeLoggerType(child.getLogData().getLogLevel()); } else if (child.getLogData().getAttentionLevel() != null) { childAttnLevel = LogEntry.toTreeLoggerType(child.getLogData().getAttentionLevel()); } if (childAttnLevel == null) { continue; } if (maxEntryNeedsAttn == null || maxEntryAttnLevel.isLowerPriorityThan(childAttnLevel)) { maxEntryNeedsAttn = child; maxEntryAttnLevel = childAttnLevel; /* * TODO: Optimization - once you find a node that has an attention level * of ERROR, you can break out of the loop. However, this presumes that * we know what the highest attention level is. As it is written right * now, the attention levels could be re-ordered and this algorithm * would still work. */ } } if (maxEntryNeedsAttn == null) { return null; } LogEntry<T> childOfMaxEntryNeedsAttn = getFirstDeeplyNestedChildWithMaxAttnRecurse(maxEntryNeedsAttn.getDisclosedChildren()); if (childOfMaxEntryNeedsAttn == null) { return maxEntryNeedsAttn; } // Always favor the child, since we're going for the deepest nesting return childOfMaxEntryNeedsAttn; } private List<LogEntry<T>> setUndisclosed(LogEntry<T> logEntry) { List<LogEntry<T>> logEntriesRemoved = new ArrayList<LogEntry<T>>(); setUndisclosedRecursive(logEntry, logEntriesRemoved); return logEntriesRemoved; } private void setUndisclosedRecursive(LogEntry<T> logEntry, List<LogEntry<T>> logEntriesRemoved) { List<LogEntry<T>> disclosedChildren = logEntry.getDisclosedChildren(); for (LogEntry<T> disclosedChild : disclosedChildren) { setUndisclosedRecursive(disclosedChild, logEntriesRemoved); } // Hide all entries except the root if (logEntry != getRootLogEntry()) { logEntriesRemoved.add(logEntry); logEntry.setDisclosed(false); } } }