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    }