Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance. Project price only 1 $
You can buy this project and download/modify it how often you want.
package edu.stanford.nlp.util.logging;
import edu.stanford.nlp.io.IOUtils;
import edu.stanford.nlp.util.ArrayIterable;
import edu.stanford.nlp.util.IterableIterator;
import java.io.IOException;
import java.io.PrintStream;
import java.io.PrintWriter;
import java.util.*;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.ReentrantLock;
/**
* A hierarchical channel based logger. Log messages are arranged hierarchically by depth
* (e.g. main->tagging->sentence 2) using the startTrack() and endTrack() methods.
* Furthermore, messages can be flagged with a number of channels, which allow filtering by channel.
* Log levels are implemented as channels (ERROR, WARNING, etc).
*
* Details on the handlers used are documented in their respective classes, which all implement
* {@link LogRecordHandler}.
* New handlers should implement this class.
*
* Details on configuring Redwood can be found in the {@link RedwoodConfiguration} class.
* New configuration methods should be implemented in this class, following the standard
* builder paradigm.
*
* There is a tutorial on Redwood on the
* NLP website.
*
* @author Gabor Angeli (angeli at cs.stanford)
* @author David McClosky
*/
/* TODO
* bug: multithreaded environment breaks RepeatedRecordHandler (doesn't print number of missed lines); this might be a general bug with RepeatedRecordHandler
* bug: RepeatedRecordHandler thinks that empty tracks are still seen (prints the "repeated messages" message)
* bug: Approximate repeated record prints wrong repeat count on top-level log messages
* bug: In multithreaded environment, time a track took is time from begin to print, not begin to end (the latter is, in general, shorter).
* feature: stack trace appending should maybe be a handler (for efficiency)
* feature: prevent interleaving of stdout/stderr (if doable...maybe via locking)
* feature: support for automatically assigning channels from calling class/method
* feature: convert the main() method into an ITest
* ideas for new handlers:
* - record histogram of messages, tell you about frequent log messages when program ends
* - if messages match certain criteria, dump a stack trace (for tracking down random print statements)
* - basic filtering: filter messages if they match certain channels, source methods, are inside a specific track, etc.
*/
public class Redwood {
/*
---------------------------------------------------------
VARIABLES
---------------------------------------------------------
*/
// -- UTILITIES --
public static final Flag ERR = Flag.ERROR;
public static final Flag WARN = Flag.WARN;
public static final Flag DBG = Flag.DEBUG;
public static final Flag FORCE = Flag.FORCE;
public static final Flag STDOUT = Flag.STDOUT;
public static final Flag STDERR = Flag.STDERR;
// -- STREAMS --
/**
* The real System.out stream
*/
private static final PrintStream realSysOut = System.out;
/**
* The real System.err stream
*/
private static final PrintStream realSysErr = System.err;
// -- BASIC LOGGING --
/**
* The tree of handlers
*/
private static RecordHandlerTree handlers = new RecordHandlerTree();
/**
* The current depth of the logger
*/
private static int depth = 0;
/**
* The stack of track titles, for consistency checking
* the endTrack() call
*/
private static Stack titleStack = new Stack();
/**
* List of known logging classes. These get excluded from stack trace selection.
*/
private static Set loggingClasses;
/**
* Signals that no more log messages should be accepted by Redwood
*/
private static boolean isClosed = false;
// -- THREADED ENVIRONMENT --
/**
* Queue of tasks to be run in various threads
*/
private static final Map> threadedLogQueue = new HashMap>();
/**
* Thread id which currently has control of the Redwood
*/
private static long currentThread = -1L;
/**
* Threads which have something they wish to log, but do not yet
* have control of Redwood
*/
private static Queue threadsWaiting = new LinkedList();
/**
* Indicator that messages are coming from multiple threads
*/
private static boolean isThreaded = false;
/**
* Synchronization
*/
private static ReentrantLock control = new ReentrantLock();
/*
---------------------------------------------------------
HELPER METHODS
---------------------------------------------------------
*/
private static void queueTask(long threadId, Runnable toRun){
assert control.isHeldByCurrentThread();
assert threadId != currentThread;
//(get queue)
if(!threadedLogQueue.containsKey(threadId)){
threadedLogQueue.put(threadId, new LinkedList());
}
Queue threadLogQueue = threadedLogQueue.get(threadId);
//(add to queue)
threadLogQueue.offer( toRun );
//(register this thread as waiting)
if(!threadsWaiting.contains(threadId)){
threadsWaiting.offer(threadId);
assert threadedLogQueue.get(threadId) != null && !threadedLogQueue.get(threadId).isEmpty();
}
}
private static void releaseThreadControl(long threadId){
assert !isThreaded || control.isHeldByCurrentThread();
assert currentThread < 0L || currentThread == threadId;
//(release control)
currentThread = -1L;
}
private static void attemptThreadControl(long threadId, Runnable r){
//(get lock)
boolean tookLock = false;
if(!control.isHeldByCurrentThread()){
control.lock();
tookLock = true;
}
//(perform action)
attemptThreadControlThreadsafe(threadId);
if(threadId == currentThread){
r.run();
} else {
queueTask(threadId, r);
}
//(release lock)
assert control.isHeldByCurrentThread();
if(tookLock){
control.unlock();
}
}
private static void attemptThreadControlThreadsafe(long threadId){
//--Assertions
assert control.isHeldByCurrentThread();
//--Update Current Thread
boolean hopeless = true;
if(currentThread < 0L){
//(case: no one has control)
if(threadsWaiting.isEmpty()){
currentThread = threadId;
} else {
currentThread = threadsWaiting.poll();
hopeless = false;
assert threadedLogQueue.get(currentThread) == null || !threadedLogQueue.get(currentThread).isEmpty();
}
} else if(currentThread == threadId) {
//(case: we have control)
threadsWaiting.remove(currentThread);
} else if(currentThread >= 0L){
//(case: someone else has control
threadsWaiting.remove(currentThread);
} else {
assert false;
}
//--Clear Backlog
long activeThread = currentThread;
Queue backlog = threadedLogQueue.get(currentThread);
if(backlog != null){
//(run backlog)
while(!backlog.isEmpty() && currentThread >= 0L){
backlog.poll().run();
}
//(requeue, if applicable)
assert activeThread == currentThread || currentThread < 0L;
if(currentThread < 0L && !backlog.isEmpty()){
threadsWaiting.offer(activeThread);
hopeless = false;
}
}
//--Recursive
if(!hopeless && currentThread != threadId){
attemptThreadControlThreadsafe(threadId);
}
assert !threadsWaiting.contains(currentThread);
assert control.isHeldByCurrentThread();
}
/*
---------------------------------------------------------
[PSEUDO]-PUBLIC FACING METHODS
---------------------------------------------------------
*/
/**
* Remove a handler from the list
* @param toRemove The handler to remove. Any handler object that
* matches this class will be removed.
* @return true if this handler was in the list.
*/
protected static boolean removeHandler(Class toRemove) {
boolean rtn = false;
Iterator iter = handlers.iterator();
while(iter.hasNext()){
if(iter.next().getClass().equals(toRemove)){
rtn = true;
iter.remove();
}
}
return rtn;
}
protected static void spliceHandler(LogRecordHandler parent, LogRecordHandler toAdd, LogRecordHandler grandchild){
RecordHandlerTree p = handlers.find(parent);
if(p != null){
//(add child)
p.addChild(toAdd);
//(remove spliced child)
Iterator iter = p.children();
RecordHandlerTree removed = null;
while(iter.hasNext()){
RecordHandlerTree cand = iter.next();
if(cand.head() == grandchild){
removed = cand;
iter.remove();
}
}
//(add grandchildren)
if(removed != null){
p.find(toAdd).addChildTree(removed);
}
} else {
throw new IllegalArgumentException("No such parent handler: " + parent);
}
}
protected static void spliceHandler(LogRecordHandler parent, LogRecordHandler toAdd, Class grandchild){
RecordHandlerTree p = handlers.find(parent);
if(p != null){
//(add child)
p.addChild(toAdd);
//(remove spliced children)
Iterator iter = p.children();
List lst = new ArrayList();
while(iter.hasNext()){
RecordHandlerTree cand = iter.next();
if(grandchild.isAssignableFrom(cand.head().getClass())){
lst.add(cand);
iter.remove();
}
}
//(add grandchildren)
for(RecordHandlerTree gc : lst){
p.find(toAdd).addChildTree(gc);
}
} else {
throw new IllegalArgumentException("No such parent handler: " + parent);
}
}
protected static void spliceHandler(Class parent, LogRecordHandler toAdd, Class grandchild){
List lst = new LinkedList();
//--Find Parents
for(LogRecordHandler term : handlers){
if(parent.isAssignableFrom(term.getClass())){
lst.add(term);
}
}
//--Add Handler
for(LogRecordHandler p : lst){
spliceHandler(p, toAdd, grandchild);
}
}
/**
* Append a Handler to a portion of the handler tree
* @param parent The parent to add the child to
* @param child The Handler to add.
*/
protected static void appendHandler(LogRecordHandler parent, LogRecordHandler child){
RecordHandlerTree p = handlers.find(parent);
if(p != null){
p.addChild(child);
} else {
throw new IllegalArgumentException("No such parent handler: " + parent);
}
}
/**
* Append a Handler to every parent of the given class
* @param parent The class of the parents to add the child to
* @param child The Handler to add.
*/
protected static void appendHandler(Class parent, LogRecordHandler child){
List toAdd = new LinkedList();
//--Find Parents
for(LogRecordHandler term : handlers){
if(parent.isAssignableFrom(term.getClass())){
toAdd.add(term);
}
}
//--Add Handler
for(LogRecordHandler p : toAdd){
appendHandler(p, child);
}
}
/**
* Append a Handler to the end of the root of the Handler tree.
* @param child The Handler to add.
*/
protected static void appendHandler(LogRecordHandler child){
handlers.addChild(child);
}
/**
* Remove all log handlers from Redwood, presumably in order to
* construct a custom pipeline afterwards
*/
protected static void clearHandlers(){
handlers = new RecordHandlerTree();
}
/**
* Get a handler based on its class
* @param clazz The class of the Handler to return.
* If multiple Handlers exist, the first one is returned.
* @param The class of the handler to return.
* @return The handler matching the class name.
*/
@Deprecated
@SuppressWarnings("unchecked")
private static E getHandler(Class clazz){
for(LogRecordHandler cand : handlers){
if(clazz == cand.getClass()){
return (E) cand;
}
}
return null;
}
/**
* Captures System.out and System.err and redirects them
* to Redwood logging.
* @param captureOut True is System.out should be captured
* @param captureErr True if System.err should be captured
*/
protected static void captureSystemStreams(boolean captureOut, boolean captureErr){
if(captureOut){
System.setOut(new RedwoodPrintStream(STDOUT, realSysOut));
}
if(captureErr){
System.setErr(new RedwoodPrintStream(STDERR, realSysErr));
}
}
/**
* Restores System.out and System.err to their original values
*/
protected static void restoreSystemStreams(){
System.setOut(realSysOut);
System.setErr(realSysErr);
}
/**
* Add a class to the list of known logging classes. Any stack trace element that starts with these class names is skipped.
* @param className The class to report as a logging class
*/
protected static void addLoggingClass(String className) {
loggingClasses.add(className);
}
/**
* Removes all classes from the list of known logging classes
*/
protected static void clearLoggingClasses(){
if(loggingClasses == null){ loggingClasses = new HashSet(); }
loggingClasses.clear();
}
/*
---------------------------------------------------------
TRUE PUBLIC FACING METHODS
---------------------------------------------------------
*/
/**
* Log a message. The last argument to this object is the message to log
* (usually a String); the first arguments are the channels to log to.
*
* For example:
*
* log(Redwood.ERR,"tag","this message is tagged with ERROR and tag")
*
* @param args The last argument is the message; the first arguments are the channels.
*/
public static void log(Object... args) {
//--Argument Check
if(args.length == 0){ return; }
if(isClosed){ return; }
//--Create Record
final Object content = args[args.length-1];
final Object[] tags = new Object[args.length-1];
final StackTraceElement ste = getStackTrace();
System.arraycopy(args,0,tags,0,args.length-1);
final long timestamp = System.currentTimeMillis();
//--Handle Record
if(isThreaded){
//(case: multithreaded)
final Runnable log = new Runnable(){
public void run(){
assert !isThreaded || control.isHeldByCurrentThread();
Record toPass = new Record(content,tags,depth,ste,timestamp);
handlers.process(toPass, MessageType.SIMPLE,depth, toPass.timesstamp);
assert !isThreaded || control.isHeldByCurrentThread();
}
};
long threadId = Thread.currentThread().getId();
attemptThreadControl( threadId, log );
} else {
//(case: no threading)
Record toPass = new Record(content,tags,depth,ste,timestamp);
handlers.process(toPass, MessageType.SIMPLE,depth, toPass.timesstamp);
}
}
public static void logf(String format, Object... args){ log(new Formatter().format(format, args)); }
/**
* Begin a "track;" that is, begin logging at one level deeper.
* Channels other than the FORCE channel are ignored.
* @param args The title of the track to begin, with an optional FORCE flag.
*/
public static void startTrack(final Object... args){
if(isClosed){ return; }
//--Create Record
final int len = args.length == 0 ? 0 : args.length-1;
final Object content = args.length == 0 ? "" : args[len];
final Object[] tags = new Object[len];
final StackTraceElement ste = getStackTrace();
final long timestamp = System.currentTimeMillis();
System.arraycopy(args,0,tags,0,len);
//--Create Task
final long threadID = Thread.currentThread().getId();
final Runnable startTrack = new Runnable(){
public void run(){
assert !isThreaded || control.isHeldByCurrentThread();
Record toPass = new Record(content,tags,depth,ste,timestamp);
depth += 1;
titleStack.push(args.length == 0 ? "" : args[len].toString());
handlers.process(toPass, MessageType.START_TRACK, depth, toPass.timesstamp);
assert !isThreaded || control.isHeldByCurrentThread();
}
};
//--Run Task
if(isThreaded){
//(case: multithreaded)
long threadId = Thread.currentThread().getId();
attemptThreadControl( threadId, startTrack );
} else {
//(case: no threading)
startTrack.run();
}
}
/**
* Helper method to start a track on the FORCE channel.
* @param arg
*/
public static void forceTrack(Object arg) {
startTrack(FORCE, arg);
}
/**
* Helper method to start an anonymous track on the FORCE channel.
*/
public static void forceTrack() {
startTrack(FORCE, "");
}
/**
* End a "track;" that is, return to logging at one level shallower.
* @param title A title that should match the beginning of this track.
*/
public static void endTrack(final String title){
if(isClosed){ return; }
//--Make Task
final long timestamp = System.currentTimeMillis();
Runnable endTrack = new Runnable(){
public void run(){
assert !isThreaded || control.isHeldByCurrentThread();
//(check name match)
String expected = titleStack.pop();
if(!expected.equalsIgnoreCase(title)){
throw new IllegalArgumentException("Track names do not match: expected: " + expected + " found: " + title);
}
//(decrement depth)
depth -= 1;
//(send signal)
handlers.process(null, MessageType.END_TRACK, depth, timestamp);
assert !isThreaded || control.isHeldByCurrentThread();
}
};
//--Run Task
if(isThreaded){
//(case: multithreaded)
long threadId = Thread.currentThread().getId();
attemptThreadControl( threadId, endTrack );
} else {
//(case: no threading)
endTrack.run();
}
}
/**
* A utility method for closing calls to the anonymous startTrack() call.
*/
public static void endTrack(){ endTrack(""); }
/**
* Start a multithreaded logging environment. Log messages will be real time
* from one of the threads; as each thread finishes, another thread begins logging,
* first by making up the backlog, and then by printing any new log messages.
* A thread signals that it has finished logging with the finishThread() function;
* the multithreaded environment is ended with the endThreads() function
* @param title The name of the thread group being started
*/
public static void startThreads(String title){
if(isThreaded){
throw new IllegalStateException("Cannot nest Redwood threaded environments");
}
startTrack(FORCE,"Threads( "+title+" )");
isThreaded = true;
}
/**
* Signal that this thread will not log any more messages in the multithreaded
* environment
*/
public static void finishThread(){
//--Create Task
final long threadId = Thread.currentThread().getId();
Runnable finish = new Runnable(){
public void run(){
releaseThreadControl(threadId);
}
};
//--Run Task
if(isThreaded){
//(case: multithreaded)
attemptThreadControl( threadId, finish );
} else {
//(case: no threading)
throw new IllegalStateException("finishThreads() called outside of threaded environment");
}
}
/**
* Signal that all threads have run to completion, and the multithreaded
* environment is over.
* @param check The name of the thread group passed to startThreads()
*/
public static void endThreads(String check){
//(error check)
if(currentThread != -1L){
throw new IllegalStateException("endThreads() called, but thread " + currentThread + " has not finished (exception in thread?)");
}
//(end threaded environment)
assert !control.isHeldByCurrentThread();
isThreaded = false;
//(write remaining threads)
boolean cleanPass = false;
while(!cleanPass){
cleanPass = true;
for(long thread : threadedLogQueue.keySet()){
assert currentThread < 0L;
if(threadedLogQueue.get(thread) != null && !threadedLogQueue.get(thread).isEmpty()){
//(mark queue as unclean)
cleanPass = false;
//(variables)
Queue backlog = threadedLogQueue.get(thread);
currentThread = thread;
//(clear buffer)
while(currentThread >= 0){
if(currentThread != thread){ throw new IllegalStateException("Redwood control shifted away from flushing thread"); }
if(backlog.isEmpty()){ throw new IllegalStateException("Forgot to call finishThread() on thread " + currentThread); }
assert !control.isHeldByCurrentThread();
backlog.poll().run();
}
//(unregister thread)
threadsWaiting.remove(thread);
}
}
}
while(threadsWaiting.size() > 0){
assert currentThread < 0L;
assert control.tryLock();
assert !threadsWaiting.isEmpty();
control.lock();
attemptThreadControlThreadsafe(-1);
control.unlock();
}
//(clean up)
for(long threadId : threadedLogQueue.keySet()){
assert threadedLogQueue.get(threadId).isEmpty();
}
assert threadsWaiting.isEmpty();
assert currentThread == -1L;
endTrack("Threads( "+check+" )");
}
/**
* Create an object representing a group of channels.
* {@link RedwoodChannels} contains a more complete description.
*
* @see RedwoodChannels
*/
public static RedwoodChannels channels(Object... channelNames) {
return new RedwoodChannels(channelNames);
}
/**
* Show only the given channel.
* @param channels The channels to show
*/
public static void showOnlyChannels(Object... channels){
for(LogRecordHandler handler : handlers){
if(handler instanceof VisibilityHandler){
VisibilityHandler visHandler = (VisibilityHandler) handler;
visHandler.hideAll();
for (Object channel : channels) {
visHandler.alsoShow(channel);
}
}
}
}
/**
* Hide multiple channels. All other channels will be shown.
* @param channels The channels to hide
*/
public static void hideOnlyChannels(Object... channels){
for(LogRecordHandler handler : handlers){
if(handler instanceof VisibilityHandler){
VisibilityHandler visHandler = (VisibilityHandler) handler;
visHandler.showAll();
for (Object channel : channels) {
visHandler.alsoHide(channel);
}
}
}
}
/**
* Show multiple channels. All other channels will be unaffected.
* @param channels The channels to show
*/
public static void showChannels(Object... channels){
// TODO this could share more code with the other show/hide(Only)Channels methods
for(LogRecordHandler handler : handlers){
if(handler instanceof VisibilityHandler){
VisibilityHandler visHandler = (VisibilityHandler) handler;
for (Object channel : channels) {
visHandler.alsoShow(channel);
}
}
}
}
/**
* Hide multiple channels. All other channels will be unaffected.
* @param channels The channels to hide
*/
public static void hideChannels(Object... channels){
// TODO this could share more code with the other show/hide(Only)Channels methods
for(LogRecordHandler handler : handlers){
if(handler instanceof VisibilityHandler){
VisibilityHandler visHandler = (VisibilityHandler) handler;
for (Object channel : channels) {
visHandler.alsoHide(channel);
}
}
}
}
/**
* Show all channels.
*/
public static void showAllChannels(){
for(LogRecordHandler handler : handlers){
if(handler instanceof VisibilityHandler){
((VisibilityHandler) handler).showAll();
}
}
}
/**
* Hide all channels.
*/
public static void hideAllChannels(){
for(LogRecordHandler handler : handlers){
if(handler instanceof VisibilityHandler){
((VisibilityHandler) handler).hideAll();
}
}
}
/**
* Stop Redwood, closing all tracks and prohibiting future log messages.
*/
public static void stop(){
//--Close logger
isClosed = true; // <- not a thread-safe boolean
Thread.yield(); //poor man's synchronization attempt (let everything else log that wants to)
Thread.yield();
//--Close Tracks
while(depth > 0){
depth -= 1;
//(send signal to handlers)
handlers.process(null, MessageType.END_TRACK, depth, System.currentTimeMillis());
}
//--Shutdown
handlers.process(null, MessageType.SHUTDOWN, 0, System.currentTimeMillis());
}
/*
---------------------------------------------------------
UTILITY METHODS
---------------------------------------------------------
*/
/**
* Utility method for formatting a time difference (maybe this should go to a util class?)
* @param diff Time difference in milliseconds
* @param b The string builder to append to
*/
protected static void formatTimeDifference(long diff, StringBuilder b){
//--Get Values
int mili = (int) diff % 1000;
long rest = diff / 1000;
int sec = (int) rest % 60;
rest = rest / 60;
int min = (int) rest % 60;
rest = rest / 60;
int hr = (int) rest % 24;
rest = rest / 24;
int day = (int) rest;
//--Make String
if(day > 0) b.append(day).append(day > 1 ? " days, " : " day, ");
if(hr > 0) b.append(hr).append(hr > 1 ? " hours, " : " hour, ");
if(min > 0) {
if(min < 10){ b.append("0"); }
b.append(min).append(":");
}
if(min > 0 && sec < 10){ b.append("0"); }
b.append(sec).append(".").append(mili);
if(min > 0) b.append(" minutes");
else b.append(" seconds");
}
/**
* Get the current stack trace element, skipping anything from known logging classes.
* @return The current stack trace for this thread
*/
private static StackTraceElement getStackTrace() {
StackTraceElement[] stack = Thread.currentThread().getStackTrace();
int i = 2; // we can skip the first two (first is getStackTrace(), second is this method)
while (i < stack.length) {
boolean isLoggingClass = false;
for (String loggingClass : loggingClasses) {
String className = stack[i].getClassName();
if (className.startsWith(loggingClass)) {
isLoggingClass = true;
break;
}
}
if (!isLoggingClass) {
break;
}
i += 1;
}
// if we didn't find anything, keep last element (probably shouldn't happen, but could if people add too many logging classes)
if (i >= stack.length) {
i = stack.length - 1;
}
return stack[i];
}
/**
* Removes logging classes from a stack trace.
*/
protected static List filterStackTrace(StackTraceElement[] stack) {
List filteredStack = new ArrayList();
int i = 2; // we can skip the first two (first is getStackTrace(), second is this method)
while (i < stack.length) {
boolean isLoggingClass = false;
for (String loggingClass : loggingClasses) {
String className = stack[i].getClassName();
if (className.startsWith(loggingClass)) {
isLoggingClass = true;
break;
}
}
if (!isLoggingClass) {
filteredStack.add(stack[i]);
}
i += 1;
}
// if we didn't find anything, keep the full stack
if (filteredStack.size() == 0) {
return Arrays.asList(stack);
}
return filteredStack;
}
protected static boolean supportsAnsi(){
String os = System.getProperty("os.name").toLowerCase();
boolean isUnix = os.contains("unix") || os.contains("linux") || os.contains("solaris");
return Boolean.getBoolean("Ansi") || (isUnix); // TODO Java 1.6 add this: "|| (isUnix && System.console()!=null))"
}
/**
* Set up the default logger.
*/
static {
RedwoodConfiguration.standard().apply();
}
/**
* An enumeration of the types of "messages" you can send a handler
*/
private static enum MessageType{ SIMPLE, START_TRACK, SHUTDOWN, END_TRACK }
/**
* A tree structure of record handlers
*/
protected static class RecordHandlerTree implements Iterable{
// -- Overhead --
private boolean isRoot = false;
private LogRecordHandler head;
private ArrayList children = new ArrayList();
public RecordHandlerTree(){ isRoot = true; }
public RecordHandlerTree(LogRecordHandler head){
this.head = head;
}
// -- Core Tree Methods --
public LogRecordHandler head(){
return head;
}
public Iterator children(){
return children.iterator();
}
// -- Utility Methods --
public void addChild(LogRecordHandler handler){
if(Redwood.depth != 0){
throw new IllegalStateException("Cannot modify Redwood when within a track");
}
children.add(new RecordHandlerTree(handler));
}
private void addChildTree(RecordHandlerTree tree){
if(Redwood.depth != 0){
throw new IllegalStateException("Cannot modify Redwood when within a track");
}
children.add(tree);
}
public LogRecordHandler removeChild(LogRecordHandler handler){
if(Redwood.depth != 0){
throw new IllegalStateException("Cannot modify Redwood when within a track");
}
Iterator iter = children();
while(iter.hasNext()){
LogRecordHandler cand = iter.next().head();
if(cand == handler){
iter.remove();
return cand;
}
}
return null;
}
public RecordHandlerTree find(LogRecordHandler toFind){
if(toFind == head()){
return this;
} else {
Iterator iter = children();
while(iter.hasNext()){
RecordHandlerTree cand = iter.next().find(toFind);
if(cand != null){ return cand; }
}
}
return null;
}
public Iterator iterator() {
return new Iterator(){
// -- Variables
private boolean seenHead = isRoot;
private Iterator childrenIter = children();
private RecordHandlerTree childOnPrix = childrenIter.hasNext() ? childrenIter.next() : null;
private Iterator childIter = childOnPrix == null ? null : childOnPrix.iterator();
private LogRecordHandler lastReturned = null;
// -- HasNext
public boolean hasNext() {
while(childIter != null && !childIter.hasNext()){
if(!childrenIter.hasNext()) {
break;
} else {
childIter = childrenIter.next().iterator();
}
}
return !seenHead || (childIter != null && childIter.hasNext());
}
// -- Next
public LogRecordHandler next() {
if(!seenHead){ seenHead = true; return head(); }
lastReturned = childIter.next();
return lastReturned;
}
// -- Remove
public void remove() {
if(!seenHead){ throw new IllegalStateException("INTERNAL: this shouldn't happen..."); }
if(lastReturned == null){ throw new IllegalStateException("Called remove() before any elements returned"); }
if(childOnPrix != null && lastReturned == childOnPrix.head()){
childrenIter.remove();
} else if(childIter != null){
childIter.remove();
} else {
throw new IllegalStateException("INTERNAL: not sure what we're removing");
}
}
};
}
private List append(List lst, Record toAppend){
if(lst == LogRecordHandler.EMPTY){
lst = new ArrayList();
}
lst.add(toAppend);
return lst;
}
private void process(Record toPass, MessageType type, int newDepth, long timestamp){
//--Handle Message
//(records to pass on)
List toPassOn = null;
if(head != null){
//(case: not root)
switch(type){
case SIMPLE:
//(case: simple log message)
toPassOn = head.handle(toPass);
break;
case START_TRACK:
//(case: begin a new track)
toPassOn = head.signalStartTrack(toPass);
break;
case END_TRACK:
//case: end a track)
toPassOn = head.signalEndTrack(newDepth, timestamp);
break;
case SHUTDOWN:
//case: end a track)
toPassOn = head.signalShutdown();
break;
default:
throw new IllegalStateException("MessageType was non-exhaustive: " + type);
}
} else {
//(case: is root)
toPassOn = new ArrayList();
switch(type){
case SIMPLE:
toPassOn = append(toPassOn, toPass);
break;
case START_TRACK: break;
case END_TRACK: break;
case SHUTDOWN: break;
default: throw new IllegalStateException("MessageType was non-exhaustive: " + type);
}
}
//--Propagate Children
Iterator iter = children();
while(iter.hasNext()){ //for each child...
RecordHandlerTree child = iter.next();
//(auxilliary records)
for(Record r : toPassOn){ //for each record...
child.process(r, MessageType.SIMPLE, newDepth, timestamp);
}
//(special record)
switch(type){
case START_TRACK:
case END_TRACK:
case SHUTDOWN:
child.process(toPass, type, newDepth, timestamp);
break;
case SIMPLE: break;
default: throw new IllegalStateException("MessageType was non-exhaustive: " + type);
}
}
}
private StringBuilder toStringHelper(StringBuilder b, int depth){
for(int i=0; i 1){
Arrays.sort(channels, new Comparator