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