001    /*
002     * SonarQube, open source software quality management tool.
003     * Copyright (C) 2008-2014 SonarSource
004     * mailto:contact AT sonarsource DOT com
005     *
006     * SonarQube 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     * SonarQube 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 License
017     * along with this program; if not, write to the Free Software Foundation,
018     * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
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.analyzer.Analyzer;
028    import org.sonar.api.batch.analyzer.internal.DefaultAnalyzerDescriptor;
029    import org.sonar.api.batch.maven.DependsUponMavenPlugin;
030    import org.sonar.api.batch.maven.MavenPluginHandler;
031    import org.sonar.api.platform.ComponentContainer;
032    import org.sonar.api.resources.Project;
033    import org.sonar.api.utils.AnnotationUtils;
034    import org.sonar.api.utils.dag.DirectAcyclicGraph;
035    
036    import javax.annotation.Nullable;
037    
038    import java.lang.annotation.Annotation;
039    import java.lang.reflect.Array;
040    import java.lang.reflect.Method;
041    import java.lang.reflect.Modifier;
042    import java.util.ArrayList;
043    import java.util.Arrays;
044    import java.util.Collection;
045    import java.util.List;
046    
047    /**
048     * @since 1.11
049     * @deprecated since 2.6 was only used by views
050     */
051    @Deprecated
052    public class BatchExtensionDictionnary {
053    
054      private final ComponentContainer componentContainer;
055    
056      public BatchExtensionDictionnary(ComponentContainer componentContainer) {
057        this.componentContainer = componentContainer;
058      }
059    
060      public <T> Collection<T> select(Class<T> type) {
061        return select(type, null, false);
062      }
063    
064      public <T> Collection<T> select(Class<T> type, @Nullable Project project, boolean sort) {
065        List<T> result = getFilteredExtensions(type, project);
066        if (sort) {
067          return sort(result);
068        }
069        return result;
070      }
071    
072      public Collection<MavenPluginHandler> selectMavenPluginHandlers(Project project) {
073        List<DependsUponMavenPlugin> selectedExtensions = Lists.newArrayList();
074        for (Object extension : getExtensions(null)) {
075          if (ClassUtils.isAssignable(extension.getClass(), DependsUponMavenPlugin.class)) {
076            selectedExtensions.add((DependsUponMavenPlugin) extension);
077          }
078        }
079        List<MavenPluginHandler> handlers = Lists.newArrayList();
080        for (DependsUponMavenPlugin extension : selectedExtensions) {
081          MavenPluginHandler handler = extension.getMavenPluginHandler(project);
082          if (handler != null) {
083            boolean ok = true;
084            if (handler instanceof CheckProject) {
085              ok = ((CheckProject) handler).shouldExecuteOnProject(project);
086            }
087            if (ok) {
088              handlers.add(handler);
089            }
090          }
091    
092        }
093        return handlers;
094      }
095    
096      protected List<Object> getExtensions(@Nullable Class type) {
097        List<Object> extensions = Lists.newArrayList();
098        completeBatchExtensions(componentContainer, extensions, type);
099        return extensions;
100      }
101    
102      private static void completeBatchExtensions(ComponentContainer container, List<Object> extensions, @Nullable Class type) {
103        if (container != null) {
104          extensions.addAll(container.getComponentsByType(type != null ? type : BatchExtension.class));
105          completeBatchExtensions(container.getParent(), extensions, type);
106        }
107      }
108    
109      private <T> List<T> getFilteredExtensions(Class<T> type, @Nullable Project project) {
110        List<T> result = Lists.newArrayList();
111        for (Object extension : getExtensions(type)) {
112          if (shouldKeep(type, extension, project)) {
113            result.add((T) extension);
114          }
115        }
116        return result;
117      }
118    
119      private boolean shouldKeep(Class type, Object extension, @Nullable Project project) {
120        boolean keep = ClassUtils.isAssignable(extension.getClass(), type);
121        if (keep && project != null && ClassUtils.isAssignable(extension.getClass(), CheckProject.class)) {
122          keep = ((CheckProject) extension).shouldExecuteOnProject(project);
123        }
124        return keep;
125      }
126    
127      public <T> Collection<T> sort(Collection<T> extensions) {
128        DirectAcyclicGraph dag = new DirectAcyclicGraph();
129    
130        for (T extension : extensions) {
131          dag.add(extension);
132          for (Object dependency : getDependencies(extension)) {
133            dag.add(extension, dependency);
134          }
135          for (Object generates : getDependents(extension)) {
136            dag.add(generates, extension);
137          }
138          completePhaseDependencies(dag, extension);
139        }
140        List sortedList = dag.sort();
141    
142        return Collections2.filter(sortedList, Predicates.in(extensions));
143      }
144    
145      /**
146       * Extension dependencies
147       */
148      private <T> List<Object> getDependencies(T extension) {
149        List<Object> result = new ArrayList<Object>();
150        result.addAll(evaluateAnnotatedClasses(extension, DependsUpon.class));
151        if (ClassUtils.isAssignable(extension.getClass(), Analyzer.class)) {
152          DefaultAnalyzerDescriptor descriptor = new DefaultAnalyzerDescriptor();
153          ((Analyzer) extension).describe(descriptor);
154          result.addAll(Arrays.asList(descriptor.dependsOn()));
155        }
156        return result;
157      }
158    
159      /**
160       * Objects that depend upon this extension.
161       */
162      public <T> List<Object> getDependents(T extension) {
163        List<Object> result = new ArrayList<Object>();
164        result.addAll(evaluateAnnotatedClasses(extension, DependedUpon.class));
165        if (ClassUtils.isAssignable(extension.getClass(), Analyzer.class)) {
166          DefaultAnalyzerDescriptor descriptor = new DefaultAnalyzerDescriptor();
167          ((Analyzer) extension).describe(descriptor);
168          result.addAll(Arrays.asList(descriptor.provides()));
169        }
170        return result;
171      }
172    
173      private void completePhaseDependencies(DirectAcyclicGraph dag, Object extension) {
174        Phase.Name phase = evaluatePhase(extension);
175        dag.add(extension, phase);
176        for (Phase.Name name : Phase.Name.values()) {
177          if (phase.compareTo(name) < 0) {
178            dag.add(name, extension);
179          } else if (phase.compareTo(name) > 0) {
180            dag.add(extension, name);
181          }
182        }
183      }
184    
185      protected List<Object> evaluateAnnotatedClasses(Object extension, Class<? extends Annotation> annotation) {
186        List<Object> results = Lists.newArrayList();
187        Class aClass = extension.getClass();
188        while (aClass != null) {
189          evaluateClass(aClass, annotation, results);
190    
191          for (Method method : aClass.getDeclaredMethods()) {
192            if (method.getAnnotation(annotation) != null) {
193              checkAnnotatedMethod(method);
194              evaluateMethod(extension, method, results);
195            }
196          }
197          aClass = aClass.getSuperclass();
198        }
199    
200        return results;
201      }
202    
203      private void evaluateClass(Class extensionClass, Class annotationClass, List<Object> results) {
204        Annotation annotation = extensionClass.getAnnotation(annotationClass);
205        if (annotation != null) {
206          if (annotation.annotationType().isAssignableFrom(DependsUpon.class)) {
207            results.addAll(Arrays.asList(((DependsUpon) annotation).value()));
208    
209          } else if (annotation.annotationType().isAssignableFrom(DependedUpon.class)) {
210            results.addAll(Arrays.asList(((DependedUpon) annotation).value()));
211          }
212        }
213    
214        Class[] interfaces = extensionClass.getInterfaces();
215        for (Class anInterface : interfaces) {
216          evaluateClass(anInterface, annotationClass, results);
217        }
218      }
219    
220      protected Phase.Name evaluatePhase(Object extension) {
221        Phase phaseAnnotation = AnnotationUtils.getAnnotation(extension, Phase.class);
222        if (phaseAnnotation != null) {
223          return phaseAnnotation.name();
224        }
225        return Phase.Name.DEFAULT;
226      }
227    
228      private void evaluateMethod(Object extension, Method method, List<Object> results) {
229        try {
230          Object result = method.invoke(extension);
231          if (result != null) {
232            if (result instanceof Class<?>) {
233              results.addAll(componentContainer.getComponentsByType((Class<?>) result));
234    
235            } else if (result instanceof Collection<?>) {
236              results.addAll((Collection<?>) result);
237    
238            } else if (result.getClass().isArray()) {
239              for (int i = 0; i < Array.getLength(result); i++) {
240                results.add(Array.get(result, i));
241              }
242    
243            } else {
244              results.add(result);
245            }
246          }
247        } catch (Exception e) {
248          throw new IllegalStateException("Can not invoke method " + method, e);
249        }
250      }
251    
252      private void checkAnnotatedMethod(Method method) {
253        if (!Modifier.isPublic(method.getModifiers())) {
254          throw new IllegalStateException("Annotated method must be public:" + method);
255        }
256        if (method.getParameterTypes().length > 0) {
257          throw new IllegalStateException("Annotated method must not have parameters:" + method);
258        }
259      }
260    }