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