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