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