001 // Copyright 2006, 2007, 2009 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 Orderer<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>> dependencyNodesById = 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 Orderer(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 public void override(Orderable<T> orderable) 104 { 105 lock.check(); 106 107 String id = orderable.getId(); 108 109 Orderable<T> existing = idToOrderable.get(id); 110 111 if (existing == null) 112 throw new IllegalArgumentException( 113 String.format("Override for object '%s' is invalid as it does not match an existing object.", id)); 114 115 orderables.remove(existing); 116 orderables.add(orderable); 117 118 idToOrderable.put(id, orderable); 119 } 120 121 /** 122 * Adds an object to be ordered. 123 * 124 * @param id unique, qualified id for the target 125 * @param target the object to be ordered (or null as a placeholder) 126 * @param constraints optional, variable constraints 127 * @see #add(Orderable) 128 */ 129 130 public void add(String id, T target, String... constraints) 131 { 132 lock.check(); 133 134 add(new Orderable<T>(id, target, constraints)); 135 } 136 137 public void override(String id, T target, String... constraints) 138 { 139 lock.check(); 140 141 override(new Orderable<T>(id, target, constraints)); 142 } 143 144 public List<T> getOrdered() 145 { 146 lock.lock(); 147 148 initializeGraph(); 149 150 List<T> result = newList(); 151 152 for (Orderable<T> orderable : trailer.getOrdered()) 153 { 154 T target = orderable.getTarget(); 155 156 // Nulls are placeholders that are skipped. 157 158 if (target != null) result.add(target); 159 } 160 161 return result; 162 } 163 164 private void initializeGraph() 165 { 166 trailer = new DependencyNode<T>(logger, new Orderable<T>("*-trailer-*", null)); 167 168 addNodes(); 169 170 addDependencies(); 171 } 172 173 private void addNodes() 174 { 175 for (Orderable<T> orderable : orderables) 176 { 177 DependencyNode<T> node = new DependencyNode<T>(logger, orderable); 178 179 dependencyNodesById.put(orderable.getId(), node); 180 181 trailer.addDependency(node); 182 } 183 } 184 185 private void addDependencies() 186 { 187 for (Orderable<T> orderable : orderables) 188 { 189 addDependencies(orderable); 190 } 191 } 192 193 private void addDependencies(Orderable<T> orderable) 194 { 195 String sourceId = orderable.getId(); 196 197 for (String constraint : orderable.getConstraints()) 198 { 199 addDependencies(sourceId, constraint); 200 } 201 } 202 203 private void addDependencies(String sourceId, String constraint) 204 { 205 int colonx = constraint.indexOf(':'); 206 207 String type = colonx > 0 ? constraint.substring(0, colonx) : null; 208 209 DependencyLinker<T> linker = null; 210 211 if ("after".equals(type)) 212 linker = _after; 213 else if ("before".equals(type)) linker = _before; 214 215 if (linker == null) 216 { 217 logger.warn(UtilMessages.constraintFormat(constraint, sourceId)); 218 return; 219 } 220 221 String patternList = constraint.substring(colonx + 1); 222 223 linkNodes(sourceId, patternList, linker); 224 } 225 226 private void linkNodes(String sourceId, String patternList, DependencyLinker<T> linker) 227 { 228 Collection<DependencyNode<T>> nodes = findDependencies(sourceId, patternList); 229 230 DependencyNode<T> source = dependencyNodesById.get(sourceId); 231 232 for (DependencyNode<T> target : nodes) 233 { 234 linker.link(source, target); 235 } 236 } 237 238 private Collection<DependencyNode<T>> findDependencies(String sourceId, String patternList) 239 { 240 IdMatcher matcher = buildMatcherForPattern(patternList); 241 242 Collection<DependencyNode<T>> result = newList(); 243 244 for (String id : dependencyNodesById.keySet()) 245 { 246 if (sourceId.equals(id)) continue; 247 248 if (matcher.matches(id)) result.add(dependencyNodesById.get(id)); 249 } 250 251 return result; 252 } 253 254 private IdMatcher buildMatcherForPattern(String patternList) 255 { 256 List<IdMatcher> matchers = newList(); 257 258 for (String pattern : patternList.split(",")) 259 { 260 IdMatcher matcher = new IdMatcherImpl(pattern.trim()); 261 262 matchers.add(matcher); 263 } 264 265 return matchers.size() == 1 ? matchers.get(0) : new OrIdMatcher(matchers); 266 } 267 }