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 }