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 }