From 68900548c194321ef9d4a9a3fcb7a082a3705d07 Mon Sep 17 00:00:00 2001 From: samskivert Date: Wed, 27 May 2009 19:15:18 +0000 Subject: [PATCH] Lifecycle management helper classes. git-svn-id: https://samskivert.googlecode.com/svn/trunk@2559 6335cc39-0255-0410-8fd6-9bcaacd3b74c --- .../com/samskivert/util/DependencyGraph.java | 179 ++++++++++++++++++ src/java/com/samskivert/util/Lifecycle.java | 170 +++++++++++++++++ 2 files changed, 349 insertions(+) create mode 100644 src/java/com/samskivert/util/DependencyGraph.java create mode 100644 src/java/com/samskivert/util/Lifecycle.java diff --git a/src/java/com/samskivert/util/DependencyGraph.java b/src/java/com/samskivert/util/DependencyGraph.java new file mode 100644 index 00000000..666f4b68 --- /dev/null +++ b/src/java/com/samskivert/util/DependencyGraph.java @@ -0,0 +1,179 @@ +// +// $Id$ +// +// samskivert library - useful routines for java programs +// Copyright (C) 2001-2009 Michael Bayne +// +// This library is free software; you can redistribute it and/or modify it +// under the terms of the GNU Lesser General Public License as published +// by the Free Software Foundation; either version 2.1 of the License, or +// (at your option) any later version. +// +// This library 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 +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +package com.samskivert.util; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * Maintains a bidirectional graph to manage the order that the items are removed. Children must + * wait until their parents are accessed - thus removing an available element means that a node + * without parents (an orphan) is removed and returned and the rest of the graph is updated to + * reflect that removal. + * + * @param the type of object maintained in the graph. + */ +public class DependencyGraph +{ + /** + * Adds an element with no initial dependencies from the graph. + */ + public void add (T element) + { + DependencyNode node = new DependencyNode(element); + _nodes.put(element, node); + _orphans.add(element); + } + + /** + * Removes an element and its dependencies from the graph. + */ + public void remove (T element) + { + DependencyNode node = _nodes.remove(element); + _orphans.remove(element); + + // Remove ourselves as a child of our parents. + for (DependencyNode parent : node.parents) { + parent.children.remove(node); + } + + // Remove ourselves as a parent of our children, possibly orphaning them. + for (DependencyNode child : node.children) { + child.parents.remove(node); + if (child.parents.isEmpty()) { + _orphans.add(child.content); + } + } + } + + /** + * Removes and returns an element which is available, meaning not dependent upon any other + * still in the graph. + */ + public T removeAvailableElement () + { + T elem = _orphans.get(0); + remove(elem); + return elem; + } + + /** + * Returns the number of elements in the graph. + */ + public int size () + { + return _nodes.size(); + } + + /** + * Returns whether there are no more elements in the graph. + */ + public boolean isEmpty () + { + return size() == 0; + } + + /** + * Records a new dependency of the dependant upon the dependee. + */ + public void addDependency (T dependant, T dependee) + { + _orphans.remove(dependant); + DependencyNode dependantNode = _nodes.get(dependant); + DependencyNode dependeeNode = _nodes.get(dependee); + + if (dependsOn(dependee, dependant)) { + throw new IllegalArgumentException("Refusing to create circular dependency."); + } + dependantNode.parents.add(dependeeNode); + dependeeNode.children.add(dependantNode); + + } + + /** + * Returns whether elem1 is designated to depend on elem2. + */ + public boolean dependsOn (T elem1, T elem2) + { + DependencyNode node1 = _nodes.get(elem1); + DependencyNode node2 = _nodes.get(elem2); + + List> nodesToCheck = new ArrayList>(); + List> nodesAlreadyChecked = new ArrayList>(); + nodesToCheck.addAll(node1.parents); + + // We prevent circular dependencies when we add dependencies. Otherwise, this'd be + // potentially non-terminating. + while (!nodesToCheck.isEmpty()) { + // We take it off the end since we don't care about order and this is faster. + DependencyNode checkNode = nodesToCheck.remove(nodesToCheck.size() - 1); + + if (nodesAlreadyChecked.contains(checkNode)) { + // We've seen him before, no need to check again. + continue; + + } else if (checkNode == node2) { + // We've found our dependency + return true; + + } else { + nodesAlreadyChecked.add(checkNode); + nodesToCheck.addAll(checkNode.parents); + } + } + + return false; + } + + /** + * Flattens this graph into an observer list in dependencys order. Empties the graph in the + * process. + */ + public ObserverList toObserverList () + { + ObserverList list = ObserverList.newSafeInOrder(); + while (!isEmpty()) { + list.add(removeAvailableElement()); + } + return list; + } + + /** All the nodes included in the graph. */ + protected Map> _nodes = new HashMap>(); + + /** Nodes in the graph with no parents/dependencies. */ + protected List _orphans = new ArrayList(); + + /** Represents a node in our dependency graph. */ + protected static class DependencyNode
+ { + public DT content; + public List> parents = new ArrayList>(); + public List> children = new ArrayList>(); + + public DependencyNode (DT contents) { + this.content = contents; + } + } +} diff --git a/src/java/com/samskivert/util/Lifecycle.java b/src/java/com/samskivert/util/Lifecycle.java new file mode 100644 index 00000000..c80f68e0 --- /dev/null +++ b/src/java/com/samskivert/util/Lifecycle.java @@ -0,0 +1,170 @@ +// +// $Id$ +// +// samskivert library - useful routines for java programs +// Copyright (C) 2001-2009 Michael Bayne +// +// This library is free software; you can redistribute it and/or modify it +// under the terms of the GNU Lesser General Public License as published +// by the Free Software Foundation; either version 2.1 of the License, or +// (at your option) any later version. +// +// This library 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 +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +package com.samskivert.util; + +import static com.samskivert.Log.log; + +/** + * Manages the lifecycle (initialization and shutdown) of a collection of components. + */ +public class Lifecycle +{ + /** An interface implemented by components which wish to participate in the lifecycle. */ + public interface BaseComponent + { + } + + /** An interface implemented by components which wish to participate in the lifecycle. */ + public interface InitComponent extends BaseComponent + { + /** Called after dependencies have been fully resolved to initialize this component. */ + public void init (); + } + + /** An interface implemented by components which wish to participate in the lifecycle. */ + public interface ShutdownComponent extends BaseComponent + { + /** Called when the server is shutting down. */ + public void shutdown (); + } + + /** An interface implemented by components which wish to participate in the lifecycle. */ + public interface Component extends InitComponent, ShutdownComponent + { + } + + /** Constraints for use with {@link #addConstraint}. */ + public static enum Constraint { RUNS_BEFORE, RUNS_AFTER }; + + /** + * Registers a component with the lifecycle. This should be done during dependency resolution + * by injecting the Lifecycle into your constructor and calling this method there. + */ + public void addComponent (BaseComponent comp) + { + if (_initers == null || _downers == null) { + throw new IllegalStateException("Too late to register component."); + } + if (comp instanceof InitComponent) { + _initers.add((InitComponent)comp); + } + if (comp instanceof ShutdownComponent) { + _downers.add((ShutdownComponent)comp); + } + } + + /** + * Removes a component from the lifecycle. This is generally not used. + */ + public void removeComponent (BaseComponent comp) + { + if (_initers != null && comp instanceof InitComponent) { + _initers.remove((InitComponent)comp); + } + if (_downers != null && comp instanceof ShutdownComponent) { + _downers.remove((ShutdownComponent)comp); + } + } + + /** + * Adds a constraint that a certain component must be initialized before another. + */ + public void addInitConstraint (InitComponent lhs, Constraint constraint, InitComponent rhs) + { + if (lhs == null || rhs == null) { + throw new IllegalArgumentException("Cannot add constraint about null component."); + } + InitComponent before = (constraint == Constraint.RUNS_BEFORE) ? lhs : rhs; + InitComponent after = (constraint == Constraint.RUNS_BEFORE) ? rhs : lhs; + _initers.addDependency(after, before); + } + + /** + * Adds a constraint that a certain component must be shutdown before another. + */ + public void addShutdownConstraint (ShutdownComponent lhs, Constraint constraint, + ShutdownComponent rhs) + { + if (lhs == null || rhs == null) { + throw new IllegalArgumentException("Cannot add constraint about null component."); + } + ShutdownComponent before = (constraint == Constraint.RUNS_BEFORE) ? lhs : rhs; + ShutdownComponent after = (constraint == Constraint.RUNS_BEFORE) ? rhs : lhs; + _downers.addDependency(after, before); + } + + /** + * Returns true if we're in the process of shutting down. + */ + public boolean isShuttingDown () + { + return (_downers == null); + } + + /** + * Initializes all components immediately on the caller's thread. + */ + public void init () + { + if (_initers == null) { + log.warning("Refusing repeat init() request."); + return; + } + + ObserverList list = _initers.toObserverList(); + _initers = null; + list.apply(new ObserverList.ObserverOp() { + public boolean apply (InitComponent comp) { + log.debug("Initializing component", "comp", comp); + comp.init(); + return true; + } + }); + } + + /** + * Shuts down all components immediately on the caller's thread. + */ + public void shutdown () + { + if (_downers == null) { + log.warning("Refusing repeat shutdown() request."); + return; + } + + ObserverList list = _downers.toObserverList(); + _downers = null; + list.apply(new ObserverList.ObserverOp() { + public boolean apply (ShutdownComponent comp) { + log.debug("Shutting down component", "comp", comp); + comp.shutdown(); + return true; + } + }); + } + + /** A dependency graph of our components arranged by initialization dependencies. */ + protected DependencyGraph _initers = new DependencyGraph(); + + /** A dependency graph of our components arranged by shutdown dependencies. */ + protected DependencyGraph _downers = + new DependencyGraph(); +}