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 org.apache.commons.lang.ClassUtils;
025 import org.picocontainer.MutablePicoContainer;
026 import org.sonar.api.BatchExtension;
027 import org.sonar.api.batch.maven.DependsUponMavenPlugin;
028 import org.sonar.api.batch.maven.MavenPluginHandler;
029 import org.sonar.api.resources.Project;
030 import org.sonar.api.utils.AnnotationUtils;
031 import org.sonar.api.utils.IocContainer;
032 import org.sonar.api.utils.dag.DirectAcyclicGraph;
033
034 import java.lang.annotation.Annotation;
035 import java.lang.reflect.Method;
036 import java.lang.reflect.Modifier;
037 import java.util.ArrayList;
038 import java.util.Arrays;
039 import java.util.Collection;
040 import java.util.List;
041
042 /**
043 * @since 1.11
044 */
045 public class BatchExtensionDictionnary {
046
047 private MutablePicoContainer picoContainer;
048
049 public BatchExtensionDictionnary(IocContainer iocContainer) {
050 this.picoContainer = iocContainer.getPicoContainer();
051 }
052
053 public BatchExtensionDictionnary(MutablePicoContainer picoContainer) {
054 this.picoContainer = picoContainer;
055 }
056
057 public <T> Collection<T> select(Class<T> type) {
058 return select(type, null, false);
059 }
060
061 public <T> Collection<T> select(Class<T> type, Project project, boolean sort) {
062 List<T> result = getFilteredExtensions(type, project);
063 if (sort) {
064 return sort(result);
065 }
066 return result;
067 }
068
069 public Collection<MavenPluginHandler> selectMavenPluginHandlers(Project project) {
070 Collection<DependsUponMavenPlugin> selectedExtensions = select(DependsUponMavenPlugin.class, project, true);
071 List<MavenPluginHandler> handlers = new ArrayList<MavenPluginHandler>();
072 for (DependsUponMavenPlugin extension : selectedExtensions) {
073 MavenPluginHandler handler = extension.getMavenPluginHandler(project);
074 if (handler != null) {
075 boolean ok = true;
076 if (handler instanceof CheckProject) {
077 ok = ((CheckProject) handler).shouldExecuteOnProject(project);
078 }
079 if (ok) {
080 handlers.add(handler);
081 }
082 }
083
084 }
085 return handlers;
086 }
087
088 private List<BatchExtension> getExtensions() {
089 return picoContainer.getComponents(BatchExtension.class);
090 }
091
092 private <T> List<T> getFilteredExtensions(Class<T> type, Project project) {
093 List<T> result = new ArrayList<T>();
094 for (BatchExtension extension : getExtensions()) {
095 if (shouldKeep(type, extension, project)) {
096 result.add((T) extension);
097 }
098 }
099 return result;
100 }
101
102 private boolean shouldKeep(Class type, Object extension, Project project) {
103 boolean keep = ClassUtils.isAssignable(extension.getClass(), type);
104 if (keep && project != null && ClassUtils.isAssignable(extension.getClass(), CheckProject.class)) {
105 keep = ((CheckProject) extension).shouldExecuteOnProject(project);
106 }
107 return keep;
108 }
109
110 public <T> Collection<T> sort(Collection<T> extensions) {
111 DirectAcyclicGraph dag = new DirectAcyclicGraph();
112
113 for (T extension : extensions) {
114 dag.add(extension);
115 for (Object dependency : getDependencies(extension)) {
116 dag.add(extension, dependency);
117 }
118 for (Object generates : getDependents(extension)) {
119 dag.add(generates, extension);
120 }
121 completePhaseDependencies(dag, extension);
122 }
123 List sortedList = dag.sort();
124
125 return (Collection<T>) Collections2.filter(sortedList, Predicates.in(extensions));
126 }
127
128 /**
129 * Extension dependencies
130 */
131 private <T> List getDependencies(T extension) {
132 return evaluateAnnotatedClasses(extension, DependsUpon.class);
133 }
134
135 /**
136 * Objects that depend upon this extension.
137 */
138 public <T> List getDependents(T extension) {
139 return evaluateAnnotatedClasses(extension, DependedUpon.class);
140 }
141
142 private void completePhaseDependencies(DirectAcyclicGraph dag, Object extension) {
143 Phase.Name phase = evaluatePhase(extension);
144 dag.add(extension, phase);
145 for (Phase.Name name : Phase.Name.values()) {
146 if (phase.compareTo(name) < 0) {
147 dag.add(name, extension);
148 } else if (phase.compareTo(name) > 0) {
149 dag.add(extension, name);
150 }
151 }
152 }
153
154
155 protected List evaluateAnnotatedClasses(Object extension, Class annotation) {
156 List results = new ArrayList();
157 Class aClass = extension.getClass();
158 while (aClass != null) {
159 evaluateClass(aClass, annotation, results);
160
161 for (Method method : aClass.getDeclaredMethods()) {
162 if (method.getAnnotation(annotation) != null) {
163 checkAnnotatedMethod(method);
164 evaluateMethod(extension, method, results);
165 }
166 }
167 aClass = aClass.getSuperclass();
168 }
169
170 return results;
171 }
172
173 private void evaluateClass(Class extensionClass, Class annotationClass, List results) {
174 Annotation annotation = extensionClass.getAnnotation(annotationClass);
175 if (annotation != null) {
176 if (annotation.annotationType().isAssignableFrom(DependsUpon.class)) {
177 results.addAll(Arrays.asList(((DependsUpon) annotation).value()));
178 results.addAll(Arrays.asList(((DependsUpon) annotation).classes()));
179 } else if (annotation.annotationType().isAssignableFrom(DependedUpon.class)) {
180 results.addAll(Arrays.asList(((DependedUpon) annotation).value()));
181 results.addAll(Arrays.asList(((DependedUpon) annotation).classes()));
182 }
183 }
184
185 Class[] interfaces = extensionClass.getInterfaces();
186 for (Class anInterface : interfaces) {
187 evaluateClass(anInterface, annotationClass, results);
188 }
189 }
190
191 protected Phase.Name evaluatePhase(Object extension) {
192 Phase phaseAnnotation = AnnotationUtils.getClassAnnotation(extension, Phase.class);
193 if (phaseAnnotation != null) {
194 return phaseAnnotation.name();
195 }
196 return Phase.Name.DEFAULT;
197 }
198
199 private void evaluateMethod(Object extension, Method method, List results) {
200 try {
201 Object result = method.invoke(extension);
202 if (result != null) {
203 //TODO add arrays/collections of objects/classes
204 if (result instanceof Class) {
205 results.addAll(picoContainer.getComponents((Class) result));
206
207 } else if (result instanceof Collection) {
208 results.addAll((Collection) result);
209
210 } else {
211 results.add(result);
212 }
213 }
214 } catch (Exception e) {
215 throw new IllegalStateException("Can not invoke method " + method, e);
216 }
217 }
218
219 private void checkAnnotatedMethod(Method method) {
220 if (!Modifier.isPublic(method.getModifiers())) {
221 throw new IllegalStateException("Annotated method must be public :" + method);
222 }
223 if (method.getParameterTypes().length > 0) {
224 throw new IllegalStateException("Annotated method must not have parameters :" + method);
225 }
226 }
227 }