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