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 */
020package org.sonar.api.batch;
021
022import com.google.common.base.Predicates;
023import com.google.common.collect.Collections2;
024import com.google.common.collect.Lists;
025import org.apache.commons.lang.ClassUtils;
026import org.sonar.api.BatchExtension;
027import org.sonar.api.batch.maven.DependsUponMavenPlugin;
028import org.sonar.api.batch.maven.MavenPluginHandler;
029import org.sonar.api.batch.sensor.Sensor;
030import org.sonar.api.batch.sensor.internal.DefaultSensorDescriptor;
031import org.sonar.api.platform.ComponentContainer;
032import org.sonar.api.resources.Project;
033import org.sonar.api.utils.AnnotationUtils;
034import org.sonar.api.utils.dag.DirectAcyclicGraph;
035
036import javax.annotation.Nullable;
037
038import java.lang.annotation.Annotation;
039import java.lang.reflect.Array;
040import java.lang.reflect.Method;
041import java.lang.reflect.Modifier;
042import java.util.ArrayList;
043import java.util.Arrays;
044import java.util.Collection;
045import java.util.List;
046
047/**
048 * @since 1.11
049 * @deprecated since 2.6 was only used by views
050 */
051@Deprecated
052public 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(), Sensor.class)) {
152      DefaultSensorDescriptor descriptor = new DefaultSensorDescriptor();
153      ((Sensor) 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(), Sensor.class)) {
166      DefaultSensorDescriptor descriptor = new DefaultSensorDescriptor();
167      ((Sensor) 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}