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