/*
* SonarQube
* Copyright (C) 2009-2017 SonarSource SA
* mailto:info AT sonarsource DOT com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
package org.sonar.scanner.bootstrap;
import java.lang.annotation.Annotation;
import java.lang.reflect.Array;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.stream.Collectors;
import javax.annotation.Nullable;
import org.apache.commons.lang.ClassUtils;
import org.sonar.api.batch.CheckProject;
import org.sonar.api.batch.DependedUpon;
import org.sonar.api.batch.DependsUpon;
import org.sonar.api.batch.Phase;
import org.sonar.api.batch.fs.internal.DefaultInputModule;
import org.sonar.api.batch.postjob.PostJob;
import org.sonar.api.batch.postjob.PostJobContext;
import org.sonar.api.batch.sensor.Sensor;
import org.sonar.api.batch.sensor.SensorContext;
import org.sonar.api.resources.Project;
import org.sonar.api.utils.AnnotationUtils;
import org.sonar.api.utils.dag.DirectAcyclicGraph;
import org.sonar.core.platform.ComponentContainer;
import org.sonar.scanner.postjob.PostJobOptimizer;
import org.sonar.scanner.postjob.PostJobWrapper;
import org.sonar.scanner.sensor.DefaultSensorContext;
import org.sonar.scanner.sensor.SensorOptimizer;
import org.sonar.scanner.sensor.SensorWrapper;
/**
* @since 2.6
*/
public class ScannerExtensionDictionnary {
private final ComponentContainer componentContainer;
private final SensorContext sensorContext;
private final SensorOptimizer sensorOptimizer;
private final PostJobContext postJobContext;
private final PostJobOptimizer postJobOptimizer;
public ScannerExtensionDictionnary(ComponentContainer componentContainer, DefaultSensorContext sensorContext,
SensorOptimizer sensorOptimizer, PostJobContext postJobContext, PostJobOptimizer postJobOptimizer) {
this.componentContainer = componentContainer;
this.sensorContext = sensorContext;
this.sensorOptimizer = sensorOptimizer;
this.postJobContext = postJobContext;
this.postJobOptimizer = postJobOptimizer;
}
public <T> Collection<T> select(Class<T> type, @Nullable DefaultInputModule module, boolean sort, @Nullable ExtensionMatcher matcher) {
List<T> result = getFilteredExtensions(type, module, matcher);
if (sort) {
return sort(result);
}
return result;
}
public Collection<org.sonar.api.batch.Sensor> selectSensors(@Nullable DefaultInputModule module, boolean global) {
List<org.sonar.api.batch.Sensor> result = getFilteredExtensions(org.sonar.api.batch.Sensor.class, module, null);
Iterator<org.sonar.api.batch.Sensor> iterator = result.iterator();
while (iterator.hasNext()) {
org.sonar.api.batch.Sensor sensor = iterator.next();
if (sensor instanceof SensorWrapper) {
if (global != ((SensorWrapper) sensor).isGlobal()) {
iterator.remove();
}
} else if (global) {
// only old sensors are not wrapped, and old sensors are never global -> exclude
iterator.remove();
}
}
return sort(result);
}
private static Phase.Name evaluatePhase(Object extension) {
Object extensionToEvaluate;
if (extension instanceof SensorWrapper) {
extensionToEvaluate = ((SensorWrapper) extension).wrappedSensor();
} else {
extensionToEvaluate = extension;
}
Phase phaseAnnotation = AnnotationUtils.getAnnotation(extensionToEvaluate, Phase.class);
if (phaseAnnotation != null) {
return phaseAnnotation.name();
}
return Phase.Name.DEFAULT;
}
private <T> List<T> getFilteredExtensions(Class<T> type, @Nullable DefaultInputModule module, @Nullable ExtensionMatcher matcher) {
List<T> result = new ArrayList<>();
for (Object extension : getExtensions(type)) {
if (org.sonar.api.batch.Sensor.class.equals(type) && extension instanceof Sensor) {
extension = new SensorWrapper((Sensor) extension, sensorContext, sensorOptimizer);
}
if (shouldKeep(type, extension, module, matcher)) {
result.add((T) extension);
}
}
if (org.sonar.api.batch.Sensor.class.equals(type)) {
// Retrieve new Sensors and wrap then in SensorWrapper
for (Sensor sensor : getExtensions(Sensor.class)) {
org.sonar.api.batch.Sensor extension = new SensorWrapper(sensor, sensorContext, sensorOptimizer);
if (shouldKeep(type, extension, module, matcher)) {
result.add((T) extension);
}
}
}
if (org.sonar.api.batch.PostJob.class.equals(type)) {
// Retrieve new PostJob and wrap then in PostJobWrapper
for (PostJob postJob : getExtensions(PostJob.class)) {
org.sonar.api.batch.PostJob extension = new PostJobWrapper(postJob, postJobContext, postJobOptimizer);
if (shouldKeep(type, extension, module, matcher)) {
result.add((T) extension);
}
}
}
return result;
}
protected <T> List<T> getExtensions(Class<T> type) {
List<T> extensions = new ArrayList<>();
completeBatchExtensions(componentContainer, extensions, type);
return extensions;
}
private static <T> void completeBatchExtensions(ComponentContainer container, List<T> extensions, Class<T> type) {
extensions.addAll(container.getComponentsByType(type));
ComponentContainer parentContainer = container.getParent();
if (parentContainer != null) {
completeBatchExtensions(parentContainer, extensions, type);
}
}
public <T> Collection<T> sort(Collection<T> extensions) {
DirectAcyclicGraph dag = new DirectAcyclicGraph();
for (T extension : extensions) {
dag.add(extension);
for (Object dependency : getDependencies(extension)) {
dag.add(extension, dependency);
}
for (Object generates : getDependents(extension)) {
dag.add(generates, extension);
}
completePhaseDependencies(dag, extension);
}
List<?> sortedList = dag.sort();
return (Collection<T>) sortedList.stream()
.filter(extensions::contains)
.collect(Collectors.toList());
}
/**
* Extension dependencies
*/
private <T> List<Object> getDependencies(T extension) {
List<Object> result = new ArrayList<>();
result.addAll(evaluateAnnotatedClasses(extension, DependsUpon.class));
return result;
}
/**
* Objects that depend upon this extension.
*/
public <T> List<Object> getDependents(T extension) {
List<Object> result = new ArrayList<>();
result.addAll(evaluateAnnotatedClasses(extension, DependedUpon.class));
return result;
}
private static void completePhaseDependencies(DirectAcyclicGraph dag, Object extension) {
Phase.Name phase = evaluatePhase(extension);
dag.add(extension, phase);
for (Phase.Name name : Phase.Name.values()) {
if (phase.compareTo(name) < 0) {
dag.add(name, extension);
} else if (phase.compareTo(name) > 0) {
dag.add(extension, name);
}
}
}
protected List<Object> evaluateAnnotatedClasses(Object extension, Class<? extends Annotation> annotation) {
List<Object> results = new ArrayList<>();
Class<? extends Object> aClass = extension.getClass();
while (aClass != null) {
evaluateClass(aClass, annotation, results);
for (Method method : aClass.getDeclaredMethods()) {
if (method.getAnnotation(annotation) != null) {
checkAnnotatedMethod(method);
evaluateMethod(extension, method, results);
}
}
aClass = aClass.getSuperclass();
}
return results;
}
private static void evaluateClass(Class<?> extensionClass, Class<? extends Annotation> annotationClass, List<Object> results) {
Annotation annotation = extensionClass.getAnnotation(annotationClass);
if (annotation != null) {
if (annotation.annotationType().isAssignableFrom(DependsUpon.class)) {
results.addAll(Arrays.asList(((DependsUpon) annotation).value()));
} else if (annotation.annotationType().isAssignableFrom(DependedUpon.class)) {
results.addAll(Arrays.asList(((DependedUpon) annotation).value()));
}
}
Class<?>[] interfaces = extensionClass.getInterfaces();
for (Class<?> anInterface : interfaces) {
evaluateClass(anInterface, annotationClass, results);
}
}
private void evaluateMethod(Object extension, Method method, List<Object> results) {
try {
Object result = method.invoke(extension);
if (result != null) {
if (result instanceof Class<?>) {
results.addAll(componentContainer.getComponentsByType((Class<?>) result));
} else if (result instanceof Collection<?>) {
results.addAll((Collection<?>) result);
} else if (result.getClass().isArray()) {
for (int i = 0; i < Array.getLength(result); i++) {
results.add(Array.get(result, i));
}
} else {
results.add(result);
}
}
} catch (Exception e) {
throw new IllegalStateException("Can not invoke method " + method, e);
}
}
private static void checkAnnotatedMethod(Method method) {
if (!Modifier.isPublic(method.getModifiers())) {
throw new IllegalStateException("Annotated method must be public:" + method);
}
if (method.getParameterTypes().length > 0) {
throw new IllegalStateException("Annotated method must not have parameters:" + method);
}
}
private static boolean shouldKeep(Class<?> type, Object extension, @Nullable DefaultInputModule module, @Nullable ExtensionMatcher matcher) {
boolean keep = (ClassUtils.isAssignable(extension.getClass(), type)
|| (org.sonar.api.batch.Sensor.class.equals(type) && ClassUtils.isAssignable(extension.getClass(), Sensor.class)))
&& (matcher == null || matcher.accept(extension));
if (keep && module != null && ClassUtils.isAssignable(extension.getClass(), CheckProject.class)) {
keep = ((CheckProject) extension).shouldExecuteOnProject(new Project(module.definition()));
}
return keep;
}
}