| /******************************************************************************* |
| * 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; |
| } |
| |
| } |