001 /*
002 * Sonar, open source software quality management tool.
003 * Copyright (C) 2008-2012 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
183 } else if (annotation.annotationType().isAssignableFrom(DependedUpon.class)) {
184 results.addAll(Arrays.asList(((DependedUpon) annotation).value()));
185 }
186 }
187
188 Class[] interfaces = extensionClass.getInterfaces();
189 for (Class anInterface : interfaces) {
190 evaluateClass(anInterface, annotationClass, results);
191 }
192 }
193
194 protected Phase.Name evaluatePhase(Object extension) {
195 Phase phaseAnnotation = AnnotationUtils.getClassAnnotation(extension, Phase.class);
196 if (phaseAnnotation != null) {
197 return phaseAnnotation.name();
198 }
199 return Phase.Name.DEFAULT;
200 }
201
202 private void evaluateMethod(Object extension, Method method, List results) {
203 try {
204 Object result = method.invoke(extension);
205 if (result != null) {
206 //TODO add arrays/collections of objects/classes
207 if (result instanceof Class) {
208 results.addAll(componentContainer.getComponentsByType((Class) result));
209
210 } else if (result instanceof Collection) {
211 results.addAll((Collection) result);
212
213 } else {
214 results.add(result);
215 }
216 }
217 } catch (Exception e) {
218 throw new IllegalStateException("Can not invoke method " + method, e);
219 }
220 }
221
222 private void checkAnnotatedMethod(Method method) {
223 if (!Modifier.isPublic(method.getModifiers())) {
224 throw new IllegalStateException("Annotated method must be public :" + method);
225 }
226 if (method.getParameterTypes().length > 0) {
227 throw new IllegalStateException("Annotated method must not have parameters :" + method);
228 }
229 }
230 }