001 /*
002 * Sonar, open source software quality management tool.
003 * Copyright (C) 2009 SonarSource SA
004 * mailto:contact AT sonarsource DOT com
005 *
006 * Sonar is free software; you can redistribute it and/or
007 * modify it under the terms of the GNU Lesser General Public
008 * License as published by the Free Software Foundation; either
009 * version 3 of the License, or (at your option) any later version.
010 *
011 * Sonar is distributed in the hope that it will be useful,
012 * but WITHOUT ANY WARRANTY; without even the implied warranty of
013 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
014 * Lesser General Public License for more details.
015 *
016 * You should have received a copy of the GNU Lesser General Public
017 * License along with Sonar; if not, write to the Free Software
018 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02
019 */
020 package org.sonar.api.batch;
021
022 import com.google.common.base.Predicates;
023 import com.google.common.collect.Collections2;
024 import com.google.common.collect.Lists;
025 import org.apache.commons.lang.ClassUtils;
026 import org.picocontainer.MutablePicoContainer;
027 import org.sonar.api.BatchExtension;
028 import org.sonar.api.batch.maven.DependsUponMavenPlugin;
029 import org.sonar.api.batch.maven.MavenPluginHandler;
030 import org.sonar.api.resources.Project;
031 import org.sonar.api.utils.AnnotationUtils;
032 import org.sonar.api.utils.IocContainer;
033 import org.sonar.api.utils.dag.DirectAcyclicGraph;
034
035 import java.lang.annotation.Annotation;
036 import java.lang.reflect.Method;
037 import java.lang.reflect.Modifier;
038 import java.util.ArrayList;
039 import java.util.Arrays;
040 import java.util.Collection;
041 import java.util.List;
042
043 /**
044 * @since 1.11
045 */
046 public class BatchExtensionDictionnary {
047
048 private MutablePicoContainer picoContainer;
049
050 public BatchExtensionDictionnary(IocContainer iocContainer) {
051 this.picoContainer = iocContainer.getPicoContainer();
052 }
053
054 public BatchExtensionDictionnary(MutablePicoContainer picoContainer) {
055 this.picoContainer = picoContainer;
056 }
057
058 public <T> Collection<T> select(Class<T> type) {
059 return select(type, null, false);
060 }
061
062 public <T> Collection<T> select(Class<T> type, Project project, boolean sort) {
063 List<T> result = getFilteredExtensions(type, project);
064 if (sort) {
065 return sort(result);
066 }
067 return result;
068 }
069
070 public Collection<MavenPluginHandler> selectMavenPluginHandlers(Project project) {
071 Collection<DependsUponMavenPlugin> selectedExtensions = select(DependsUponMavenPlugin.class, project, true);
072 List<MavenPluginHandler> handlers = Lists.newArrayList();
073 for (DependsUponMavenPlugin extension : selectedExtensions) {
074 MavenPluginHandler handler = extension.getMavenPluginHandler(project);
075 if (handler != null) {
076 boolean ok = true;
077 if (handler instanceof CheckProject) {
078 ok = ((CheckProject) handler).shouldExecuteOnProject(project);
079 }
080 if (ok) {
081 handlers.add(handler);
082 }
083 }
084
085 }
086 return handlers;
087 }
088
089 private List<BatchExtension> getExtensions() {
090 return picoContainer.getComponents(BatchExtension.class);
091 }
092
093 private <T> List<T> getFilteredExtensions(Class<T> type, Project project) {
094 List<T> result = Lists.newArrayList();
095 for (BatchExtension extension : getExtensions()) {
096 if (shouldKeep(type, extension, project)) {
097 result.add((T) extension);
098 }
099 }
100 return result;
101 }
102
103 private boolean shouldKeep(Class type, Object extension, Project project) {
104 boolean keep = ClassUtils.isAssignable(extension.getClass(), type);
105 if (keep && project != null && ClassUtils.isAssignable(extension.getClass(), CheckProject.class)) {
106 keep = ((CheckProject) extension).shouldExecuteOnProject(project);
107 }
108 return keep;
109 }
110
111 public <T> Collection<T> sort(Collection<T> extensions) {
112 DirectAcyclicGraph dag = new DirectAcyclicGraph();
113
114 for (T extension : extensions) {
115 dag.add(extension);
116 for (Object dependency : getDependencies(extension)) {
117 dag.add(extension, dependency);
118 }
119 for (Object generates : getDependents(extension)) {
120 dag.add(generates, extension);
121 }
122 completePhaseDependencies(dag, extension);
123 }
124 List sortedList = dag.sort();
125
126 return (Collection<T>) Collections2.filter(sortedList, Predicates.in(extensions));
127 }
128
129 /**
130 * Extension dependencies
131 */
132 private <T> List getDependencies(T extension) {
133 return evaluateAnnotatedClasses(extension, DependsUpon.class);
134 }
135
136 /**
137 * Objects that depend upon this extension.
138 */
139 public <T> List getDependents(T extension) {
140 return evaluateAnnotatedClasses(extension, DependedUpon.class);
141 }
142
143 private void completePhaseDependencies(DirectAcyclicGraph dag, Object extension) {
144 Phase.Name phase = evaluatePhase(extension);
145 dag.add(extension, phase);
146 for (Phase.Name name : Phase.Name.values()) {
147 if (phase.compareTo(name) < 0) {
148 dag.add(name, extension);
149 } else if (phase.compareTo(name) > 0) {
150 dag.add(extension, name);
151 }
152 }
153 }
154
155
156 protected List evaluateAnnotatedClasses(Object extension, Class annotation) {
157 List results = Lists.newArrayList();
158 Class aClass = extension.getClass();
159 while (aClass != null) {
160 evaluateClass(aClass, annotation, results);
161
162 for (Method method : aClass.getDeclaredMethods()) {
163 if (method.getAnnotation(annotation) != null) {
164 checkAnnotatedMethod(method);
165 evaluateMethod(extension, method, results);
166 }
167 }
168 aClass = aClass.getSuperclass();
169 }
170
171 return results;
172 }
173
174 private void evaluateClass(Class extensionClass, Class annotationClass, List results) {
175 Annotation annotation = extensionClass.getAnnotation(annotationClass);
176 if (annotation != null) {
177 if (annotation.annotationType().isAssignableFrom(DependsUpon.class)) {
178 results.addAll(Arrays.asList(((DependsUpon) annotation).value()));
179 results.addAll(Arrays.asList(((DependsUpon) annotation).classes()));
180 } else if (annotation.annotationType().isAssignableFrom(DependedUpon.class)) {
181 results.addAll(Arrays.asList(((DependedUpon) annotation).value()));
182 results.addAll(Arrays.asList(((DependedUpon) annotation).classes()));
183 }
184 }
185
186 Class[] interfaces = extensionClass.getInterfaces();
187 for (Class anInterface : interfaces) {
188 evaluateClass(anInterface, annotationClass, results);
189 }
190 }
191
192 protected Phase.Name evaluatePhase(Object extension) {
193 Phase phaseAnnotation = AnnotationUtils.getClassAnnotation(extension, Phase.class);
194 if (phaseAnnotation != null) {
195 return phaseAnnotation.name();
196 }
197 return Phase.Name.DEFAULT;
198 }
199
200 private void evaluateMethod(Object extension, Method method, List results) {
201 try {
202 Object result = method.invoke(extension);
203 if (result != null) {
204 //TODO add arrays/collections of objects/classes
205 if (result instanceof Class) {
206 results.addAll(picoContainer.getComponents((Class) result));
207
208 } else if (result instanceof Collection) {
209 results.addAll((Collection) result);
210
211 } else {
212 results.add(result);
213 }
214 }
215 } catch (Exception e) {
216 throw new IllegalStateException("Can not invoke method " + method, e);
217 }
218 }
219
220 private void checkAnnotatedMethod(Method method) {
221 if (!Modifier.isPublic(method.getModifiers())) {
222 throw new IllegalStateException("Annotated method must be public :" + method);
223 }
224 if (method.getParameterTypes().length > 0) {
225 throw new IllegalStateException("Annotated method must not have parameters :" + method);
226 }
227 }
228 }