blob: 203146d034fc7f9ac07dab0493ffda778a7f0ca3 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2005, 2017 IBM Corporation and others.
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.jdt.internal.launching;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.eclipse.core.expressions.PropertyTester;
import org.eclipse.core.resources.IContainer;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IAdaptable;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.jdt.core.Flags;
import org.eclipse.jdt.core.IBuffer;
import org.eclipse.jdt.core.IClassFile;
import org.eclipse.jdt.core.IClasspathEntry;
import org.eclipse.jdt.core.ICompilationUnit;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.IMember;
import org.eclipse.jdt.core.IMethod;
import org.eclipse.jdt.core.IOpenable;
import org.eclipse.jdt.core.IOrdinaryClassFile;
import org.eclipse.jdt.core.IPackageFragment;
import org.eclipse.jdt.core.IPackageFragmentRoot;
import org.eclipse.jdt.core.ISourceRange;
import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.core.Signature;
import org.eclipse.jdt.core.ToolFactory;
import org.eclipse.jdt.core.compiler.IScanner;
import org.eclipse.jdt.core.compiler.ITerminalSymbols;
import org.eclipse.jdt.core.compiler.InvalidInputException;
/**
* Property tester for context launching menu.
*
* @since 3.2
*/
public class JavaLaunchableTester extends PropertyTester {
/**
* name for the "has main" property
*/
private static final String PROPERTY_HAS_MAIN = "hasMain"; //$NON-NLS-1$
/**
* name for the "has method" property
*/
private static final String PROPERTY_HAS_METHOD = "hasMethod"; //$NON-NLS-1$
/**
* name for the "has method with annotation" property
*/
private static final String PROPERTY_HAS_METHOD_WITH_ANNOTATION = "hasMethodWithAnnotation"; //$NON-NLS-1$
/**
* name for the "has type with annotation" property
*/
private static final String PROPERTY_HAS_TYPE_WITH_ANNOTATION = "hasTypeWithAnnotation"; //$NON-NLS-1$
/**
* name for the "extends class" property
*/
private static final String PROPERTY_EXTENDS_CLASS = "extendsClass"; //$NON-NLS-1$
/**
* "is container" property
*/
private static final String PROPERTY_IS_CONTAINER = "isContainer"; //$NON-NLS-1$
/**
* "is package fragment" property
* @since 3.3
*/
private static final String PROPERTY_IS_PACKAGE_FRAGMENT = "isPackageFragment"; //$NON-NLS-1$
/**
* "is package fragment root" property
* @since 3.3
*/
private static final String PROPERTY_IS_PACKAGE_FRAGMENT_ROOT = "isPackageFragmentRoot"; //$NON-NLS-1$
/**
* name for the PROPERTY_PROJECT_NATURE property
*/
private static final String PROPERTY_PROJECT_NATURE = "hasProjectNature"; //$NON-NLS-1$
/**
* name for the "extends interface" property
*/
private static final String PROPERTY_EXTENDS_INTERFACE = "extendsInterface"; //$NON-NLS-1$
/**
* name for the PROPERTY_HAS_SWT_ON_PATH property
*/
private static final String PROPERTY_BUILDPATH_REFERENCE = "buildpathReference"; //$NON-NLS-1$
/**
* Map of modifier text to corresponding flag (Integer)
*/
private static Map<String, Integer> fgModifiers = new HashMap<>();
private static final int FLAGS_MASK= Flags.AccPublic | Flags.AccProtected | Flags.AccPrivate | Flags.AccStatic
| Flags.AccFinal | Flags.AccSynchronized | Flags.AccAbstract | Flags.AccNative;
static {
fgModifiers.put("public", Integer.valueOf(Flags.AccPublic)); //$NON-NLS-1$
fgModifiers.put("protected", Integer.valueOf(Flags.AccProtected)); //$NON-NLS-1$
fgModifiers.put("private", Integer.valueOf(Flags.AccPrivate)); //$NON-NLS-1$
fgModifiers.put("static", Integer.valueOf(Flags.AccStatic)); //$NON-NLS-1$
fgModifiers.put("final", Integer.valueOf(Flags.AccFinal)); //$NON-NLS-1$
fgModifiers.put("synchronized", Integer.valueOf(Flags.AccSynchronized)); //$NON-NLS-1$
fgModifiers.put("abstract", Integer.valueOf(Flags.AccAbstract)); //$NON-NLS-1$
fgModifiers.put("native", Integer.valueOf(Flags.AccNative)); //$NON-NLS-1$
}
/**
* gets the type of the IJavaElement
* @param element the element to inspect
* @return the type
*/
private IType getType(IJavaElement element) {
IType type = null;
if (element instanceof ICompilationUnit) {
type= ((ICompilationUnit) element).findPrimaryType();
}
else if (element instanceof IOrdinaryClassFile) {
type = ((IOrdinaryClassFile) element).getType();
}
else if (element instanceof IType) {
type = (IType) element;
}
else if (element instanceof IMember) {
type = ((IMember)element).getDeclaringType();
}
return type;
}
/**
* Determines is the java element contains a main method.
*
* @param element the element to check for the method
* @return true if the method is found in the element, false otherwise
*/
private boolean hasMain(IJavaElement element) {
try {
IType type = getType(element);
if(type != null && type.exists()) {
if(hasMainMethod(type)) {
return true;
}
//failed to find in public type, check static inner types
IJavaElement[] children = type.getChildren();
for(int i = 0; i < children.length; i++) {
if(hasMainInChildren(getType(children[i]))) {
return true;
}
}
}
}
catch (JavaModelException e) {}
catch (CoreException ce){}
return false;
}
/**
* Returns if the specified <code>IType</code> has a main method
* @param type the type to inspect for a main type
* @return true if the specified type has a main method, false otherwise
* @throws JavaModelException if there is an error in the backing Java model
* @since 3.3
*/
private boolean hasMainMethod(IType type) throws JavaModelException {
IMethod[] methods = type.getMethods();
for (int i= 0; i < methods.length; i++) {
if(methods[i].isMainMethod()) {
return true;
}
}
return false;
}
/**
* This method asks the specified <code>IType</code> if it has a main method, if not it recurses through all of its children
* When recursing we only care about child <code>IType</code>s that are static.
* @param type the <code>IType</code> to inspect for a main method
* @return true if a main method was found in specified <code>IType</code>, false otherwise
* @throws CoreException if there is an error
* @since 3.3
*/
private boolean hasMainInChildren(IType type) throws CoreException {
if(type.isClass() & Flags.isStatic(type.getFlags())) {
if(hasMainMethod(type)) {
return true;
}
IJavaElement[] children = type.getChildren();
for(int i = 0; i < children.length; i++) {
if(children[i].getElementType() == IJavaElement.TYPE) {
return hasMainInChildren((IType) children[i]);
}
}
}
return false;
}
/**
* Determines is the java element contains a specific method.
* <p>
* The syntax for the property tester is of the form: methodname,
* signature, modifiers.
* </p>
* <ol>
* <li>methodname - case sensitive method name, required. For example,
* <code>toString</code>.</li>
* <li>signature - JLS style method signature, required. For example,
* <code>(QString;)V</code>.</li>
* <li>modifiers - optional space separated list of modifiers, for
* example, <code>public static</code>.</li>
* </ol>
* @param element the element to check for the method
* @param args first arg is method name, secondary args are parameter types signatures
* @return true if the method is found in the element, false otherwise
*/
private boolean hasMethod(IJavaElement element, Object[] args) {
try {
if (args.length > 1) {
IType type = getType(element);
if (type != null && type.exists()) {
String name = (String) args[0];
String signature = (String) args[1];
String[] parms = Signature.getParameterTypes(signature);
String returnType = Signature.getReturnType(signature);
IMethod candidate = type.getMethod(name, parms);
if (candidate.exists()) {
// check return type
if (candidate.getReturnType().equals(returnType)) {
// check modifiers
if (args.length > 2) {
String modifierText = (String) args[2];
String[] modifiers = modifierText.split(" "); //$NON-NLS-1$
int flags = 0;
for (int j = 0; j < modifiers.length; j++) {
String modifier = modifiers[j];
Integer flag = fgModifiers.get(modifier);
if (flag != null) {
flags = flags | flag.intValue();
}
}
if (candidate.getFlags() == flags) {
return true;
}
}
}
}
}
}
}
catch (JavaModelException e) {}
return false;
}
/**
* Determines is the java element contains a type with a specific annotation.
* <p>
* The syntax for the property tester is of the form: qualified or unqualified annotation name
* <li>qualified or unqualified annotation name, required. For example,
* <code>org.junit.JUnit</code>.</li>
* </ol>
* @param element the element to check for the method
* @param annotationType the qualified or unqualified name of the annotation kind to look for
* @return true if the type is found in the element, false otherwise
*/
private boolean hasTypeWithAnnotation(IJavaElement element, String annotationType) {
try {
IType type= getType(element);
if (type == null || !type.exists()) {
return false;
}
IBuffer buffer= null;
IOpenable openable= type.getOpenable();
if (openable instanceof ICompilationUnit) {
buffer= ((ICompilationUnit) openable).getBuffer();
} else if (openable instanceof IClassFile) {
buffer= ((IClassFile) openable).getBuffer();
}
if (buffer == null) {
return false;
}
ISourceRange sourceRange= type.getSourceRange();
ISourceRange nameRange= type.getNameRange();
if (sourceRange != null && nameRange != null) {
IScanner scanner= ToolFactory.createScanner(false, false, true, false);
scanner.setSource(buffer.getCharacters());
scanner.resetTo(sourceRange.getOffset(), nameRange.getOffset());
if (findAnnotation(scanner, annotationType)) {
return true;
}
}
}
catch (JavaModelException e) {}
catch (InvalidInputException e) {}
return false;
}
/**
* Determines is the java element contains a method with a specific annotation.
* <p>
* The syntax for the property tester is of the form: qualified or unqualified annotation name, modifiers
* <li>qualified or unqualified annotation name, required. For example,
* <code>org.junit.JUnit</code>.</li>
* <li>modifiers - optional space separated list of modifiers, for
* example, <code>public static</code>.</li>
* </ol>
* @param element the element to check for the method
* @param args the arguments
* @return true if the method is found in the element, false otherwise
*/
private boolean hasMethodWithAnnotation(IJavaElement element, Object[] args) {
try {
String annotationType= (String) args[0];
int flags = 0;
if (args.length > 1) {
String[] modifiers = ((String) args[1]).split(" "); //$NON-NLS-1$
for (int j = 0; j < modifiers.length; j++) {
String modifier = modifiers[j];
Integer flag = fgModifiers.get(modifier);
if (flag != null) {
flags = flags | flag.intValue();
}
}
} else {
flags= -1;
}
IType type= getType(element);
if (type == null || !type.exists()) {
return false;
}
IMethod[] methods= type.getMethods();
if (methods.length == 0) {
return false;
}
IBuffer buffer= null;
IOpenable openable= type.getOpenable();
if (openable instanceof ICompilationUnit) {
buffer= ((ICompilationUnit) openable).getBuffer();
} else if (openable instanceof IClassFile) {
buffer= ((IClassFile) openable).getBuffer();
}
if (buffer == null) {
return false;
}
IScanner scanner=null; // delay initialization
for (int i= 0; i < methods.length; i++) {
IMethod curr= methods[i];
if (curr.isConstructor() || (flags != -1 && flags != (curr.getFlags() & FLAGS_MASK))) {
continue;
}
ISourceRange sourceRange= curr.getSourceRange();
ISourceRange nameRange= curr.getNameRange();
if (sourceRange != null && nameRange != null) {
if (scanner == null) {
scanner= ToolFactory.createScanner(false, false, true, false);
scanner.setSource(buffer.getCharacters());
}
scanner.resetTo(sourceRange.getOffset(), nameRange.getOffset());
if (findAnnotation(scanner, annotationType)) {
return true;
}
}
}
} catch (JavaModelException e) {
} catch (InvalidInputException e) {
}
return false;
}
private boolean findAnnotation(IScanner scanner, String annotationName) throws InvalidInputException {
String simpleName= Signature.getSimpleName(annotationName);
StringBuilder buf= new StringBuilder();
int tok= scanner.getNextToken();
while (tok != ITerminalSymbols.TokenNameEOF) {
if (tok == ITerminalSymbols.TokenNameAT) {
buf.setLength(0);
tok= readName(scanner, buf);
String name= buf.toString();
if (name.equals(annotationName) || name.equals(simpleName) || name.endsWith('.' + simpleName)) {
return true;
}
} else {
tok= scanner.getNextToken();
}
}
return false;
}
private int readName(IScanner scanner, StringBuilder buf) throws InvalidInputException {
int tok= scanner.getNextToken();
while (tok == ITerminalSymbols.TokenNameIdentifier) {
buf.append(scanner.getCurrentTokenSource());
tok= scanner.getNextToken();
if (tok != ITerminalSymbols.TokenNameDOT) {
return tok;
}
buf.append('.');
tok= scanner.getNextToken();
}
return tok;
}
/**
* determines if the project selected has the specified nature
* @param element the Java element to get the project from
* @param ntype the specified nature type
* @return true if the specified nature matches the project, false otherwise
*/
private boolean hasProjectNature(IJavaElement element, String ntype) {
try {
if(element != null) {
IJavaProject jproj = element.getJavaProject();
if(jproj != null) {
IProject proj = jproj.getProject();
return proj.isAccessible() && proj.hasNature(ntype);
}
}
return false;
}
catch (CoreException e) {return false;}
}
/**
* Determines if the element has qname as a parent class
* @param element the element to check for the parent class definition
* @param qname the fully qualified name of the (potential) parent class
* @return true if qname is a parent class, false otherwise
*/
private boolean hasSuperclass(IJavaElement element, String qname) {
try {
IType type = getType(element);
if(type != null) {
IType[] stypes = type.newSupertypeHierarchy(new NullProgressMonitor()).getAllSuperclasses(type);
for(int i = 0; i < stypes.length; i++) {
if(stypes[i].getFullyQualifiedName().equals(qname) || stypes[i].getElementName().equals(qname)) {
return true;
}
}
}
}
catch(JavaModelException e) {}
return false;
}
/**
* Determines if an item or list of items are found on the build path.
* Once any one single items matches though, the method returns true, this method is intended
* to be used in OR like situations, where we do not care if all of the items are on the build path, only that one
* of them is.
*
* @param element the element whose build path should be checked
* @param args the value(s) to search for on the build path
* @return true if any one of the args is found on the build path
*/
private boolean hasItemOnBuildPath(IJavaElement element, Object[] args) {
if(element != null && args != null) {
IJavaProject project = element.getJavaProject();
Set<IJavaProject> searched = new HashSet<>();
searched.add(project);
return hasItemsOnBuildPath(project, searched, args);
}
return false;
}
private boolean hasItemsOnBuildPath(IJavaProject project, Set<IJavaProject> searched, Object[] args) {
try {
List<IJavaProject> projects = new ArrayList<>();
if(project != null && project.exists()) {
IClasspathEntry[] entries = project.getResolvedClasspath(true);
for(int i = 0; i < entries.length; i++) {
IClasspathEntry entry = entries[i];
IPath path = entry.getPath();
String spath = path.toPortableString();
for(int j = 0; j < args.length; j++) {
if(spath.lastIndexOf((String)args[j]) != -1) {
return true;
}
}
if (entry.getEntryKind() == IClasspathEntry.CPE_PROJECT) {
String name = entry.getPath().lastSegment();
IProject dep = ResourcesPlugin.getWorkspace().getRoot().getProject(name);
IJavaProject javaProject = JavaCore.create(dep);
if (!searched.contains(javaProject)) {
projects.add(javaProject);
}
}
}
}
// search referenced projects
Iterator<IJavaProject> iterator = projects.iterator();
while (iterator.hasNext()) {
IJavaProject jp = iterator.next();
searched.add(jp);
if (hasItemsOnBuildPath(jp, searched, args)) {
return true;
}
}
} catch (JavaModelException e) {}
return false;
}
/**
* determines if the element implements a given interface
* @param element the element to check for the interface
* @param qname the fully qualified name of the interface to check for
* @return true if the element does implement the interface, false otherwise
*/
private boolean implementsInterface(IJavaElement element, String qname) {
try {
IType type = getType(element);
if(type != null) {
IType[] itypes = type.newSupertypeHierarchy(new NullProgressMonitor()).getAllInterfaces();
for(int i = 0; i < itypes.length; i++) {
if(itypes[i].getFullyQualifiedName().equals(qname)) {
return true;
}
}
}
}
catch(JavaModelException e) {}
return false;
}
/**
* Method runs the tests defined from extension points for Run As... and Debug As... menu items.
* Currently this test optimistically considers everything not a source file. In this context we
* consider an optimistic approach to mean that the test will always return true.
*
* There are many reasons for the optimistic choice some of them are outlined below.
* <ul>
* <li>Performance (in terms of time needed to display menu) cannot be preserved. To know what to allow
* in any one of the menus we would have to search all of the children of the container to determine what it contains
* and what can be launched by what.</li>
* <li>If inspection of children of containers were done, a user might want to choose a different launch type, even though our tests
* filter it out.</li>
* </ul>
* @see org.eclipse.core.expressions.IPropertyTester#test(java.lang.Object, java.lang.String, java.lang.Object[], java.lang.Object)
* @since 3.2
* @return true if the specified tests pass, or the context is a container, false otherwise
*/
@Override
public boolean test(Object receiver, String property, Object[] args, Object expectedValue) {
if (PROPERTY_IS_CONTAINER.equals(property)) {
if (receiver instanceof IAdaptable) {
IResource resource = ((IAdaptable) receiver).getAdapter(IResource.class);
if (resource != null) {
return resource instanceof IContainer;
}
}
return false;
}
IJavaElement element = null;
if (receiver instanceof IAdaptable) {
element = ((IAdaptable) receiver).getAdapter(IJavaElement.class);
if(element != null) {
if(!element.exists()) {
return false;
}
}
}
if(PROPERTY_HAS_MAIN.equals(property)) {
return hasMain(element);
}
if (PROPERTY_HAS_METHOD.equals(property)) {
return hasMethod(element, args);
}
if (PROPERTY_HAS_METHOD_WITH_ANNOTATION.equals(property)) {
return hasMethodWithAnnotation(element, args);
}
if (PROPERTY_HAS_TYPE_WITH_ANNOTATION.equals(property)) {
return hasTypeWithAnnotation(element, (String)args[0]);
}
if(PROPERTY_BUILDPATH_REFERENCE.equals(property)) {
return hasItemOnBuildPath(element, args);
}
if(PROPERTY_EXTENDS_CLASS.equals(property)) {
return hasSuperclass(element, (String)args[0]);
}
if(PROPERTY_PROJECT_NATURE.equals(property)) {
return hasProjectNature(element, (String)args[0]);
}
if(PROPERTY_EXTENDS_INTERFACE.equals(property)) {
return implementsInterface(element, (String)args[0]);
}
if (PROPERTY_IS_PACKAGE_FRAGMENT.equals(property)) {
return element instanceof IPackageFragment;
}
if (PROPERTY_IS_PACKAGE_FRAGMENT_ROOT.equals(property)) {
return element instanceof IPackageFragmentRoot;
}
return false;
}
}