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.picocontainer.MutablePicoContainer;
027    import org.picocontainer.PicoContainer;
028    import org.sonar.api.BatchExtension;
029    import org.sonar.api.batch.maven.DependsUponMavenPlugin;
030    import org.sonar.api.batch.maven.MavenPluginHandler;
031    import org.sonar.api.resources.Project;
032    import org.sonar.api.utils.AnnotationUtils;
033    import org.sonar.api.utils.IocContainer;
034    import org.sonar.api.utils.dag.DirectAcyclicGraph;
035    
036    import java.lang.annotation.Annotation;
037    import java.lang.reflect.Method;
038    import java.lang.reflect.Modifier;
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        List<BatchExtension> extensions = Lists.newArrayList();
091        completeBatchExtensions(picoContainer, extensions);
092        return extensions;
093      }
094    
095      private void completeBatchExtensions(PicoContainer picoContainer, List<BatchExtension> extensions) {
096        if (picoContainer!=null) {
097          extensions.addAll(picoContainer.getComponents(BatchExtension.class));
098          completeBatchExtensions(picoContainer.getParent(), extensions);
099        }
100      }
101    
102      private <T> List<T> getFilteredExtensions(Class<T> type, Project project) {
103        List<T> result = Lists.newArrayList();
104        for (BatchExtension extension : getExtensions()) {
105          if (shouldKeep(type, extension, project)) {
106            result.add((T) extension);
107          }
108        }
109        return result;
110      }
111    
112      private boolean shouldKeep(Class type, Object extension, Project project) {
113        boolean keep = ClassUtils.isAssignable(extension.getClass(), type);
114        if (keep && project != null && ClassUtils.isAssignable(extension.getClass(), CheckProject.class)) {
115          keep = ((CheckProject) extension).shouldExecuteOnProject(project);
116        }
117        return keep;
118      }
119    
120      public <T> Collection<T> sort(Collection<T> extensions) {
121        DirectAcyclicGraph dag = new DirectAcyclicGraph();
122    
123        for (T extension : extensions) {
124          dag.add(extension);
125          for (Object dependency : getDependencies(extension)) {
126            dag.add(extension, dependency);
127          }
128          for (Object generates : getDependents(extension)) {
129            dag.add(generates, extension);
130          }
131          completePhaseDependencies(dag, extension);
132        }
133        List sortedList = dag.sort();
134    
135        return (Collection<T>) Collections2.filter(sortedList, Predicates.in(extensions));
136      }
137    
138      /**
139       * Extension dependencies
140       */
141      private <T> List getDependencies(T extension) {
142        return evaluateAnnotatedClasses(extension, DependsUpon.class);
143      }
144    
145      /**
146       * Objects that depend upon this extension.
147       */
148      public <T> List getDependents(T extension) {
149        return evaluateAnnotatedClasses(extension, DependedUpon.class);
150      }
151    
152      private void completePhaseDependencies(DirectAcyclicGraph dag, Object extension) {
153        Phase.Name phase = evaluatePhase(extension);
154        dag.add(extension, phase);
155        for (Phase.Name name : Phase.Name.values()) {
156          if (phase.compareTo(name) < 0) {
157            dag.add(name, extension);
158          } else if (phase.compareTo(name) > 0) {
159            dag.add(extension, name);
160          }
161        }
162      }
163    
164    
165      protected List evaluateAnnotatedClasses(Object extension, Class annotation) {
166        List results = Lists.newArrayList();
167        Class aClass = extension.getClass();
168        while (aClass != null) {
169          evaluateClass(aClass, annotation, results);
170    
171          for (Method method : aClass.getDeclaredMethods()) {
172            if (method.getAnnotation(annotation) != null) {
173              checkAnnotatedMethod(method);
174              evaluateMethod(extension, method, results);
175            }
176          }
177          aClass = aClass.getSuperclass();
178        }
179    
180        return results;
181      }
182    
183      private void evaluateClass(Class extensionClass, Class annotationClass, List results) {
184        Annotation annotation = extensionClass.getAnnotation(annotationClass);
185        if (annotation != null) {
186          if (annotation.annotationType().isAssignableFrom(DependsUpon.class)) {
187            results.addAll(Arrays.asList(((DependsUpon) annotation).value()));
188            results.addAll(Arrays.asList(((DependsUpon) annotation).classes()));
189          } else if (annotation.annotationType().isAssignableFrom(DependedUpon.class)) {
190            results.addAll(Arrays.asList(((DependedUpon) annotation).value()));
191            results.addAll(Arrays.asList(((DependedUpon) annotation).classes()));
192          }
193        }
194    
195        Class[] interfaces = extensionClass.getInterfaces();
196        for (Class anInterface : interfaces) {
197          evaluateClass(anInterface, annotationClass, results);
198        }
199      }
200    
201      protected Phase.Name evaluatePhase(Object extension) {
202        Phase phaseAnnotation = AnnotationUtils.getClassAnnotation(extension, Phase.class);
203        if (phaseAnnotation != null) {
204          return phaseAnnotation.name();
205        }
206        return Phase.Name.DEFAULT;
207      }
208    
209      private void evaluateMethod(Object extension, Method method, List results) {
210        try {
211          Object result = method.invoke(extension);
212          if (result != null) {
213            //TODO add arrays/collections of objects/classes
214            if (result instanceof Class) {
215              results.addAll(picoContainer.getComponents((Class) result));
216    
217            } else if (result instanceof Collection) {
218              results.addAll((Collection) result);
219    
220            } else {
221              results.add(result);
222            }
223          }
224        } catch (Exception e) {
225          throw new IllegalStateException("Can not invoke method " + method, e);
226        }
227      }
228    
229      private void checkAnnotatedMethod(Method method) {
230        if (!Modifier.isPublic(method.getModifiers())) {
231          throw new IllegalStateException("Annotated method must be public :" + method);
232        }
233        if (method.getParameterTypes().length > 0) {
234          throw new IllegalStateException("Annotated method must not have parameters :" + method);
235        }
236      }
237    }