001    // Copyright 2006, 2007 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.tapestry5.ioc.internal.util;
016    
017    import org.apache.tapestry5.ioc.IdMatcher;
018    import org.apache.tapestry5.ioc.Orderable;
019    import org.apache.tapestry5.ioc.internal.IdMatcherImpl;
020    import org.apache.tapestry5.ioc.internal.OrIdMatcher;
021    import static org.apache.tapestry5.ioc.internal.util.CollectionFactory.newList;
022    import org.slf4j.Logger;
023    
024    import java.util.Collection;
025    import java.util.List;
026    import java.util.Map;
027    
028    /**
029     * Used to order objects into an "execution" order. Each object must have a unique id. It may specify a list of
030     * constraints which identify the ordering of the objects.
031     */
032    public class IdToDependencyNode<T>
033    {
034      private final OneShotLock lock = new OneShotLock();
035    
036      private final Logger logger;
037    
038      private final List<Orderable> orderables = CollectionFactory.newList();
039    
040      private final Map<String, Orderable<T>> idToOrderable = CollectionFactory.newCaseInsensitiveMap();
041    
042      private final Map<String, DependencyNode<T>> idToDependencyNode = CollectionFactory.newCaseInsensitiveMap();
043    
044      // Special node that is always dead last: all other nodes are a dependency
045      // of the trailer.
046    
047      private DependencyNode<T> trailer;
048    
049      interface DependencyLinker<T>
050      {
051        void link(DependencyNode<T> source, DependencyNode<T> target);
052      }
053    
054      // before: source is added as a dependency of target, so source will
055      // appear before target.
056    
057      final DependencyLinker<T> before = new DependencyLinker<T>()
058      {
059        public void link(DependencyNode<T> source, DependencyNode<T> target)
060        {
061          target.addDependency(source);
062        }
063      };
064    
065      // after: target is added as a dependency of source, so source will appear
066      // after target.
067    
068      final DependencyLinker<T> after = new DependencyLinker<T>()
069      {
070        public void link(DependencyNode<T> source, DependencyNode<T> target)
071        {
072          source.addDependency(target);
073        }
074      };
075    
076      public IdToDependencyNode(Logger logger)
077      {
078        this.logger = logger;
079      }
080    
081      /**
082       * Adds an object to be ordered.
083       *
084       * @param orderable
085       */
086      public void add(Orderable<T> orderable)
087      {
088        lock.check();
089    
090        String id = orderable.getId();
091    
092        if (idToOrderable.containsKey(id))
093        {
094          logger.warn(UtilMessages.duplicateOrderer(id));
095          return;
096        }
097    
098        orderables.add(orderable);
099    
100        idToOrderable.put(id, orderable);
101      }
102    
103      /**
104       * Adds an object to be ordered.
105       *
106       * @param id          unique, qualified id for the target
107       * @param target      the object to be ordered (or null as a placeholder)
108       * @param constraints optional, variable constraints
109       * @see #add(org.apache.tapestry5.ioc.Orderable)
110       */
111    
112      public void add(String id, T target, String... constraints)
113      {
114        lock.check();
115    
116        add(new Orderable<T>(id, target, constraints));
117      }
118    
119      public List<T> getOrdered()
120      {
121        lock.lock();
122    
123        initializeGraph();
124    
125        List<T> result = newList();
126    
127        for (Orderable<T> orderable : trailer.getOrdered())
128        {
129          T target = orderable.getTarget();
130    
131          // Nulls are placeholders that are skipped.
132    
133          if (target != null) result.add(target);
134        }
135    
136        return result;
137      }
138    
139      private void initializeGraph()
140      {
141        trailer = new DependencyNode<T>(logger, new Orderable<T>("*-trailer-*", null));
142    
143        addNodes();
144    
145        addDependencies();
146      }
147    
148      private void addNodes()
149      {
150        for (Orderable<T> orderable : orderables)
151        {
152          DependencyNode<T> node = new DependencyNode<T>(logger, orderable);
153    
154          idToDependencyNode.put(orderable.getId(), node);
155    
156          trailer.addDependency(node);
157        }
158      }
159    
160      private void addDependencies()
161      {
162        for (Orderable<T> orderable : orderables)
163        {
164          addDependencies(orderable);
165        }
166      }
167    
168      private void addDependencies(Orderable<T> orderable)
169      {
170        String sourceId = orderable.getId();
171    
172        for (String constraint : orderable.getConstraints())
173        {
174          addDependencies(sourceId, constraint);
175        }
176      }
177    
178      private void addDependencies(String sourceId, String constraint)
179      {
180        int colonx = constraint.indexOf(':');
181    
182        String type = colonx > 0 ? constraint.substring(0, colonx) : null;
183    
184        DependencyLinker<T> linker = null;
185    
186        if ("after".equals(type))
187          linker = after;
188        else if ("before".equals(type)) linker = before;
189    
190        if (linker == null)
191        {
192          logger.warn(UtilMessages.constraintFormat(constraint, sourceId));
193          return;
194        }
195    
196        String patternList = constraint.substring(colonx + 1);
197    
198        linkNodes(sourceId, patternList, linker);
199      }
200    
201      private void linkNodes(String sourceId, String patternList, DependencyLinker<T> linker)
202      {
203        Collection<DependencyNode<T>> nodes = findDependencies(sourceId, patternList);
204    
205        DependencyNode<T> source = idToDependencyNode.get(sourceId);
206    
207        for (DependencyNode<T> target : nodes)
208        {
209          linker.link(source, target);
210        }
211      }
212    
213      private Collection<DependencyNode<T>> findDependencies(String sourceId, String patternList)
214      {
215        IdMatcher matcher = buildMatcherForPattern(patternList);
216    
217        Collection<DependencyNode<T>> result = newList();
218    
219        for (String id : idToDependencyNode.keySet())
220        {
221          if (sourceId.equals(id)) continue;
222    
223          if (matcher.matches(id)) result.add(idToDependencyNode.get(id));
224        }
225    
226        return result;
227      }
228    
229      private IdMatcher buildMatcherForPattern(String patternList)
230      {
231        List<IdMatcher> matchers = newList();
232    
233        for (String pattern : patternList.split(","))
234        {
235          IdMatcher matcher = new IdMatcherImpl(pattern.trim());
236    
237          matchers.add(matcher);
238        }
239    
240        return matchers.size() == 1 ? matchers.get(0) : new OrIdMatcher(matchers);
241      }
242    }