/******************************************************************************* * Copyright (c) 2012, 2013 Pivotal Software, Inc. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Pivotal Software, Inc. - initial API and implementation *******************************************************************************/ package org.springsource.ide.eclipse.commons.frameworks.core.maintype; import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IProject; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.SubProgressMonitor; import org.eclipse.jdt.core.IJavaElement; import org.eclipse.jdt.core.IJavaProject; import org.eclipse.jdt.core.IType; import org.eclipse.jdt.core.search.IJavaSearchScope; import org.eclipse.jdt.core.search.SearchEngine; import org.eclipse.jdt.internal.debug.ui.launcher.MainMethodSearchEngine; import org.springsource.ide.eclipse.commons.frameworks.core.ExceptionUtil; /** * Provides access to heuristic algorithms to 'guess' the main type for a * project. * * Note: these algorithms may depend on project type and require access to * various optional dependencies. E.g. gradle tooling for gradle projects, m2e * for pom based algorithms etc. * <p> * Ideally in the future additional algorithms should be contributable via * extension point so that we don't need to add a gazilion dependencies in this * plugin, but clients can depend on this plugin and implicitly gain access if * the required stuff is installed. * * @author Kris De Volder */ @SuppressWarnings("restriction") public class MainTypeFinder { private static MainTypeFinder instance; private static final IType[] EMPTY = new IType[0]; /** * Find the most likely main types that should be run when a user select this * project with 'run as'. * * @return Types with main method or empty value if the 'most likely' main type * can't be determined. * @throws CoreException * if something unexpected happened.. */ public static IType[] guessMainTypes(IJavaProject project, IProgressMonitor mon) throws CoreException { return getInstance().internalGuessMainTypes(project, mon); } private static synchronized MainTypeFinder getInstance() { if (instance == null) { instance = new MainTypeFinder(); } return instance; } private Map<Confidence, IMainTypeFinder[]> algos; /** * Singelton, use getInstance or call one of the static public methods. */ private MainTypeFinder() { // TODO: support contributing additional algos with priorities via // extension point // Created as linked hash map so that iteration starts from highest to // lowest confidence this.algos = new LinkedHashMap<MainTypeFinder.Confidence, IMainTypeFinder[]>(); // Add finders based on whether they return main types based on the confidence that // they are exact types requested. For example, main types from pom files have high // confidence that they are the types requested, whereas types from source have lower // confidence that they are the types requested. Each confidence itself also has a priority // order, with more authoritative finders listed first. this.algos.put(Confidence.CERTAIN, new IMainTypeFinder[] { // add in order of which is more authoritative }); this.algos.put(Confidence.HIGH, new IMainTypeFinder[] { // add more here if they should be considered more authorative new FindInPom() // add more here if they should be considered less authorative }); this.algos.put(Confidence.LOW, new IMainTypeFinder[] { // add more here if they should be considered more authorative new FindInSource() // add more here if they should be considered less authorative }); } private IType[] internalGuessMainTypes(IJavaProject project, IProgressMonitor mon) throws CoreException { Throwable error = null; mon.beginTask("Search main types", algos.size()); Set<IType> totalTypes = new LinkedHashSet<IType>(); try { for(Entry<Confidence, IMainTypeFinder[]> entry : algos.entrySet()) { // Collect all types per confidence level for (IMainTypeFinder algo : entry.getValue()) { try { IType[] found = algo.findMain(project, new SubProgressMonitor(mon, 1)); for (IType type : found) { totalTypes.add(type); } } catch (Throwable e) { if (error == null) { // if multiple errors keep only the first one. error = e; } } } // Stop when types of the highest confidence are found if (!totalTypes.isEmpty()) { break; } } mon.worked(1); } finally { mon.done(); } if (!totalTypes.isEmpty()) { return totalTypes.toArray(new IType[0]); } else { // If there was an error throw it as an explanation of the failure. if (error != null) { throw ExceptionUtil.coreException(error); } } return EMPTY; } private static final String MAIN_CLASS_PROP = "start-class"; public static class FindInPom implements IMainTypeFinder { public IType[] findMain(IJavaProject jp, IProgressMonitor mon) throws Exception { mon.beginTask("Search main in pom", 1); try { IProject p = jp.getProject(); IFile pomFile = p.getFile("pom.xml"); if (pomFile.exists()) { PomParser pomParser = new PomParser(pomFile); String starterClassName = pomParser .getProperty(MAIN_CLASS_PROP); if (starterClassName != null) { IType mainType = jp.findType(starterClassName); if (mainType != null) { return new IType[] { mainType }; } throw ExceptionUtil.coreException("'pom.xml' defines '" + MAIN_CLASS_PROP + "' as '" + starterClassName + "' but it could not be found"); } } } finally { mon.done(); } return EMPTY; } } public static class FindInSource implements IMainTypeFinder { public IType[] findMain(IJavaProject javaProject, IProgressMonitor monitor) throws Exception { monitor.beginTask("Search main types in project source", 1); try { MainMethodSearchEngine engine = new MainMethodSearchEngine(); int constraints = IJavaSearchScope.SOURCES; // constraints |= IJavaSearchScope.APPLICATION_LIBRARIES; IJavaSearchScope scope = SearchEngine.createJavaSearchScope( new IJavaElement[] { javaProject }, constraints); boolean includeSubtypes = true; IType[] types = engine .searchMainMethods(monitor, scope, includeSubtypes); if (types == null) { types = EMPTY; } return types; } finally { monitor.done(); } } } /** * Confidence that a search result for main types in a project is what is expected. * */ public static enum Confidence { CERTAIN, HIGH, LOW } }