001    // Copyright 2004, 2005 The Apache Software Foundation
002    //
003    // Licensed under the Apache License, Version 2.0 (the "License");
004    // you may not use this file except in compliance with the License.
005    // You may obtain a copy of the License at
006    //
007    //     http://www.apache.org/licenses/LICENSE-2.0
008    //
009    // Unless required by applicable law or agreed to in writing, software
010    // distributed under the License is distributed on an "AS IS" BASIS,
011    // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
012    // See the License for the specific language governing permissions and
013    // limitations under the License.
014    
015    package org.apache.hivemind.order;
016    
017    import java.util.ArrayList;
018    import java.util.Collections;
019    import java.util.HashMap;
020    import java.util.Iterator;
021    import java.util.List;
022    import java.util.Map;
023    
024    import org.apache.commons.logging.Log;
025    import org.apache.commons.logging.LogFactory;
026    import org.apache.hivemind.ApplicationRuntimeException;
027    import org.apache.hivemind.ErrorHandler;
028    import org.apache.hivemind.ErrorLog;
029    import org.apache.hivemind.HiveMind;
030    import org.apache.hivemind.impl.ErrorLogImpl;
031    import org.apache.hivemind.util.Defense;
032    import org.apache.hivemind.util.StringUtils;
033    
034    /**
035     * Used to order objects into an "execution" order. Each object must have a name. It may specify a
036     * list of pre-requisites and a list of post-requisites.
037     * 
038     * @author Howard Lewis Ship
039     */
040    public class Orderer
041    {
042        private final ErrorLog _errorLog;
043    
044        private final String _objectType;
045    
046        private List _orderingsList = null;
047    
048        private Map _orderingsMap = null;
049    
050        private Map _nodeMap = null;
051    
052        private Node _leader;
053    
054        private Node _trailer;
055    
056        /**
057         * Creates an instance using <code>org.apache.hivemind.order.Orderer</code> as the Log.
058         */
059        public Orderer(ErrorHandler errorHandler, String objectType)
060        {
061            this(LogFactory.getLog(Orderer.class), errorHandler, objectType);
062        }
063    
064        /**
065         * Creates a new instance, but directs all debug and error logging output to the provided log.
066         * 
067         * @param log
068         *            Used for logging any errors
069         * @param objectType
070         *            user presentable name for the type of object to be ordered; used in some error
071         *            messages
072         */
073        public Orderer(Log log, ErrorHandler errorHandler, String objectType)
074        {
075            this(new ErrorLogImpl(errorHandler, log), objectType);
076        }
077    
078        /**
079         * Creates a new instance.
080         * 
081         * @param errorLog
082         *            Used for log any recoverable errors.
083         * @param objectType
084         *            user presentable name for the type of object to be ordered; used in some error
085         *            messages
086         */
087        public Orderer(ErrorLog errorLog, String objectType)
088        {
089            Defense.notNull(errorLog, "errorLog");
090            Defense.notNull(objectType, "objectType");
091    
092            _errorLog = errorLog;
093            _objectType = objectType;
094        }
095    
096        /**
097         * Adds a new object. All invocations of {@link #add(Object, String, String, String)} should
098         * occur before invoking {@link #getOrderedObjects()}.
099         * 
100         * @param object
101         *            an object to be sorted into order based on prereqs and postreqs
102         * @param name
103         *            a unique name for the
104         * @param prereqs
105         *            a comma-separated list of the names of objects that should precede this object in
106         *            the list (or null)
107         * @param postreqs
108         *            a comma-separated list of the names of objects that should follow this object in
109         *            the list (or null)
110         */
111        public void add(Object object, String name, String prereqs, String postreqs)
112        {
113            if (_orderingsMap == null)
114            {
115                _orderingsMap = new HashMap();
116                _orderingsList = new ArrayList();
117            }
118    
119            ObjectOrdering o = getOrderable(name);
120    
121            if (o != null)
122            {
123                _errorLog.error(
124                        OrdererMessages.duplicateName(_objectType, name, object, o.getObject()),
125                        HiveMind.getLocation(object),
126                        null);
127    
128                return;
129            }
130    
131            o = new ObjectOrdering(object, name, prereqs, postreqs);
132    
133            _orderingsMap.put(name, o);
134            _orderingsList.add(o);
135        }
136    
137        private ObjectOrdering getOrderable(String name)
138        {
139            return (ObjectOrdering) _orderingsMap.get(name);
140        }
141    
142        /**
143         * Uses the information provided by {@link #add(Object, String, String, String)} to order the
144         * objects into an appropriate order based on the pre- and post-reqts provided. Errors such as
145         * cyclic dependencies or unrecognized names are logged and ignored.
146         */
147        public List getOrderedObjects()
148        {
149            if (_orderingsMap == null)
150                return Collections.EMPTY_LIST;
151    
152            try
153            {
154                _nodeMap = new HashMap();
155    
156                initializeGraph();
157    
158                return _trailer.getOrder();
159            }
160            finally
161            {
162                _nodeMap = null;
163                _leader = null;
164                _trailer = null;
165            }
166        }
167    
168        private void initializeGraph()
169        {
170            addNodes();
171    
172            if (_leader == null)
173                _leader = new Node(null, "*-leader-*");
174    
175            if (_trailer == null)
176                _trailer = new Node(null, "*-trailer-*");
177    
178            addDependencies();
179        }
180    
181        private Node getNode(String name)
182        {
183            return (Node) _nodeMap.get(getOrderable(name));
184        }
185    
186        private void addNodes()
187        {
188            Iterator i = _orderingsList.iterator();
189    
190            while (i.hasNext())
191            {
192                ObjectOrdering o = (ObjectOrdering) i.next();
193    
194                Node node = new Node(o.getObject(), o.getName());
195    
196                _nodeMap.put(o, node);
197    
198                if ("*".equals(o.getPostreqs()))
199                {
200                    if (_leader == null)
201                        _leader = node;
202                    else
203                        _errorLog.error(OrdererMessages.dupeLeader(_objectType, o, getOrderable(_leader
204                                .getName())), HiveMind.getLocation(o.getObject()), null);
205                }
206    
207                if ("*".equals(o.getPrereqs()))
208                {
209                    if (_trailer == null)
210                        _trailer = node;
211                    else
212                        _errorLog.error(
213                                OrdererMessages.dupeTrailer(_objectType, o, getOrderable(_trailer
214                                        .getName())),
215                                HiveMind.getLocation(o.getObject()),
216                                null);
217                }
218    
219            }
220        }
221    
222        private void addDependencies()
223        {
224            Iterator i = _orderingsList.iterator();
225    
226            while (i.hasNext())
227            {
228                ObjectOrdering o = (ObjectOrdering) i.next();
229    
230                addDependencies(o, getNode(o.getName()));
231            }
232        }
233    
234        private void addDependencies(ObjectOrdering orderable, Node node)
235        {
236            addPreRequisites(orderable, node);
237            addPostRequisites(orderable, node);
238    
239            try
240            {
241                if (node != _leader)
242                    node.addDependency(_leader);
243    
244                if (node != _trailer)
245                    _trailer.addDependency(node);
246            }
247            catch (ApplicationRuntimeException ex)
248            {
249                // This code is unreachable ... but nonetheless.
250    
251                String name = node.getName();
252                ObjectOrdering trigger = getOrderable(name);
253    
254                _errorLog.error(OrdererMessages.dependencyCycle(_objectType, orderable, ex), HiveMind
255                        .getLocation(trigger.getObject()), ex);
256            }
257        }
258    
259        private void addPreRequisites(ObjectOrdering ordering, Node node)
260        {
261            String prereqs = ordering.getPrereqs();
262    
263            if ("*".equals(prereqs))
264                return;
265    
266            String[] names = StringUtils.split(prereqs);
267    
268            for (int i = 0; i < names.length; i++)
269            {
270                String prename = names[i];
271    
272                Node prenode = getNode(prename);
273    
274                if (prenode == null)
275                {
276                    _errorLog.error(
277                            OrdererMessages.badDependency(_objectType, prename, ordering),
278                            HiveMind.getLocation(ordering.getObject()),
279                            null);
280                    continue;
281                }
282    
283                try
284                {
285                    node.addDependency(prenode);
286                }
287                catch (ApplicationRuntimeException ex)
288                {
289                    _errorLog.error(
290                            OrdererMessages.dependencyCycle(_objectType, ordering, ex),
291                            HiveMind.getLocation(ordering.getObject()),
292                            ex);
293                }
294    
295            }
296        }
297    
298        private void addPostRequisites(ObjectOrdering ordering, Node node)
299        {
300            String postreqs = ordering.getPostreqs();
301    
302            if ("*".equals(postreqs))
303                return;
304    
305            String[] names = StringUtils.split(postreqs);
306    
307            for (int i = 0; i < names.length; i++)
308            {
309                String postname = names[i];
310    
311                Node postnode = getNode(postname);
312    
313                if (postnode == null)
314                {
315                    _errorLog.error(
316                            OrdererMessages.badDependency(_objectType, postname, ordering),
317                            HiveMind.getLocation(ordering.getObject()),
318                            null);
319                }
320                else
321                {
322                    try
323                    {
324                        postnode.addDependency(node);
325                    }
326                    catch (ApplicationRuntimeException ex)
327                    {
328                        _errorLog.error(
329                                OrdererMessages.dependencyCycle(_objectType, ordering, ex),
330                                HiveMind.getLocation(ordering.getObject()),
331                                ex);
332                    }
333                }
334            }
335        }
336    
337        private static class Node
338        {
339            private Object _object;
340    
341            private String _name;
342    
343            private List _dependencies;
344    
345            public Node(Object o, String name)
346            {
347                _object = o;
348                _name = name;
349                _dependencies = new ArrayList();
350            }
351    
352            public String getName()
353            {
354                return _name;
355            }
356    
357            public void addDependency(Node n)
358            {
359                if (n.isReachable(this))
360                    throw new ApplicationRuntimeException(
361                            "A cycle has been detected from the initial object [" + _name + "]",
362                            HiveMind.getLocation(_object), null);
363    
364                _dependencies.add(n);
365            }
366    
367            private boolean isReachable(Node n)
368            {
369                boolean reachable = (n == this);
370                Iterator i = _dependencies.iterator();
371    
372                while (i.hasNext() && !reachable)
373                {
374                    Node dep = (Node) i.next();
375                    reachable = (dep == n) ? true : dep.isReachable(n);
376                }
377    
378                return reachable;
379            }
380    
381            public List getOrder()
382            {
383                List result = new ArrayList();
384                fillOrder(result);
385    
386                return result;
387            }
388    
389            private void fillOrder(List result)
390            {
391                if (result.contains(_object))
392                    return;
393    
394                Iterator i = _dependencies.iterator();
395    
396                while (i.hasNext())
397                {
398                    Node dep = (Node) i.next();
399                    dep.fillOrder(result);
400                }
401    
402                if (_object != null)
403                    result.add(_object);
404            }
405        }
406    
407    }