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