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    Collection<DependsUponMavenPlugin> selectedExtensions = select(DependsUponMavenPlugin.class, project, true);
069    List<MavenPluginHandler> handlers = Lists.newArrayList();
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  protected List<BatchExtension> getExtensions() {
087    List<BatchExtension> extensions = Lists.newArrayList();
088    completeBatchExtensions(componentContainer, extensions);
089    return extensions;
090  }
091
092  private static void completeBatchExtensions(ComponentContainer container, List<BatchExtension> extensions) {
093    if (container != null) {
094      extensions.addAll(container.getComponentsByType(BatchExtension.class));
095      completeBatchExtensions(container.getParent(), extensions);
096    }
097  }
098
099  private <T> List<T> getFilteredExtensions(Class<T> type, Project project) {
100    List<T> result = Lists.newArrayList();
101    for (BatchExtension extension : getExtensions()) {
102      if (shouldKeep(type, extension, project)) {
103        result.add((T) extension);
104      }
105    }
106    return result;
107  }
108
109  private boolean shouldKeep(Class type, Object extension, Project project) {
110    boolean keep = ClassUtils.isAssignable(extension.getClass(), type);
111    if (keep && project != null && ClassUtils.isAssignable(extension.getClass(), CheckProject.class)) {
112      keep = ((CheckProject) extension).shouldExecuteOnProject(project);
113    }
114    return keep;
115  }
116
117  public <T> Collection<T> sort(Collection<T> extensions) {
118    DirectAcyclicGraph dag = new DirectAcyclicGraph();
119
120    for (T extension : extensions) {
121      dag.add(extension);
122      for (Object dependency : getDependencies(extension)) {
123        dag.add(extension, dependency);
124      }
125      for (Object generates : getDependents(extension)) {
126        dag.add(generates, extension);
127      }
128      completePhaseDependencies(dag, extension);
129    }
130    List sortedList = dag.sort();
131
132    return Collections2.filter(sortedList, Predicates.in(extensions));
133  }
134
135  /**
136   * Extension dependencies
137   */
138  private <T> List getDependencies(T extension) {
139    return evaluateAnnotatedClasses(extension, DependsUpon.class);
140  }
141
142  /**
143   * Objects that depend upon this extension.
144   */
145  public <T> List getDependents(T extension) {
146    return evaluateAnnotatedClasses(extension, DependedUpon.class);
147  }
148
149  private void completePhaseDependencies(DirectAcyclicGraph dag, Object extension) {
150    Phase.Name phase = evaluatePhase(extension);
151    dag.add(extension, phase);
152    for (Phase.Name name : Phase.Name.values()) {
153      if (phase.compareTo(name) < 0) {
154        dag.add(name, extension);
155      } else if (phase.compareTo(name) > 0) {
156        dag.add(extension, name);
157      }
158    }
159  }
160
161  protected List evaluateAnnotatedClasses(Object extension, Class<? extends Annotation> annotation) {
162    List<Object> results = Lists.newArrayList();
163    Class aClass = extension.getClass();
164    while (aClass != null) {
165      evaluateClass(aClass, annotation, results);
166
167      for (Method method : aClass.getDeclaredMethods()) {
168        if (method.getAnnotation(annotation) != null) {
169          checkAnnotatedMethod(method);
170          evaluateMethod(extension, method, results);
171        }
172      }
173      aClass = aClass.getSuperclass();
174    }
175
176    return results;
177  }
178
179  private void evaluateClass(Class extensionClass, Class annotationClass, List<Object> results) {
180    Annotation annotation = extensionClass.getAnnotation(annotationClass);
181    if (annotation != null) {
182      if (annotation.annotationType().isAssignableFrom(DependsUpon.class)) {
183        results.addAll(Arrays.asList(((DependsUpon) annotation).value()));
184
185      } else if (annotation.annotationType().isAssignableFrom(DependedUpon.class)) {
186        results.addAll(Arrays.asList(((DependedUpon) annotation).value()));
187      }
188    }
189
190    Class[] interfaces = extensionClass.getInterfaces();
191    for (Class anInterface : interfaces) {
192      evaluateClass(anInterface, annotationClass, results);
193    }
194  }
195
196  protected Phase.Name evaluatePhase(Object extension) {
197    Phase phaseAnnotation = AnnotationUtils.getAnnotation(extension, Phase.class);
198    if (phaseAnnotation != null) {
199      return phaseAnnotation.name();
200    }
201    return Phase.Name.DEFAULT;
202  }
203
204  private void evaluateMethod(Object extension, Method method, List<Object> results) {
205    try {
206      Object result = method.invoke(extension);
207      if (result != null) {
208        if (result instanceof Class<?>) {
209          results.addAll(componentContainer.getComponentsByType((Class<?>) result));
210
211        } else if (result instanceof Collection<?>) {
212          results.addAll((Collection<?>) result);
213
214        } else if (result.getClass().isArray()) {
215          for (int i = 0; i < Array.getLength(result); i++) {
216            results.add(Array.get(result, i));
217          }
218
219        } else {
220          results.add(result);
221        }
222      }
223    } catch (Exception e) {
224      throw new IllegalStateException("Can not invoke method " + method, e);
225    }
226  }
227
228  private void checkAnnotatedMethod(Method method) {
229    if (!Modifier.isPublic(method.getModifiers())) {
230      throw new IllegalStateException("Annotated method must be public:" + method);
231    }
232    if (method.getParameterTypes().length > 0) {
233      throw new IllegalStateException("Annotated method must not have parameters:" + method);
234    }
235  }
236}