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