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 }