001: /*******************************************************************************
002: * Copyright (c) 2005, 2007 IBM Corporation and others.
003: * All rights reserved. This program and the accompanying materials
004: * are made available under the terms of the Eclipse Public License v1.0
005: * which accompanies this distribution, and is available at
006: * http://www.eclipse.org/legal/epl-v10.html
007: *
008: * Contributors:
009: * IBM Corporation - initial API and implementation
010: *******************************************************************************/package org.eclipse.jdt.internal.launching;
011:
012: import java.util.ArrayList;
013: import java.util.HashMap;
014: import java.util.HashSet;
015: import java.util.Iterator;
016: import java.util.List;
017: import java.util.Map;
018: import java.util.Set;
019:
020: import org.eclipse.core.expressions.PropertyTester;
021: import org.eclipse.core.resources.IContainer;
022: import org.eclipse.core.resources.IProject;
023: import org.eclipse.core.resources.IResource;
024: import org.eclipse.core.resources.ResourcesPlugin;
025: import org.eclipse.core.runtime.CoreException;
026: import org.eclipse.core.runtime.IAdaptable;
027: import org.eclipse.core.runtime.IPath;
028: import org.eclipse.core.runtime.NullProgressMonitor;
029: import org.eclipse.jdt.core.Flags;
030: import org.eclipse.jdt.core.IBuffer;
031: import org.eclipse.jdt.core.IClassFile;
032: import org.eclipse.jdt.core.IClasspathEntry;
033: import org.eclipse.jdt.core.ICompilationUnit;
034: import org.eclipse.jdt.core.IJavaElement;
035: import org.eclipse.jdt.core.IJavaProject;
036: import org.eclipse.jdt.core.IMember;
037: import org.eclipse.jdt.core.IMethod;
038: import org.eclipse.jdt.core.IOpenable;
039: import org.eclipse.jdt.core.IPackageFragment;
040: import org.eclipse.jdt.core.IPackageFragmentRoot;
041: import org.eclipse.jdt.core.ISourceRange;
042: import org.eclipse.jdt.core.IType;
043: import org.eclipse.jdt.core.JavaCore;
044: import org.eclipse.jdt.core.JavaModelException;
045: import org.eclipse.jdt.core.Signature;
046: import org.eclipse.jdt.core.ToolFactory;
047: import org.eclipse.jdt.core.compiler.IScanner;
048: import org.eclipse.jdt.core.compiler.ITerminalSymbols;
049: import org.eclipse.jdt.core.compiler.InvalidInputException;
050:
051: /**
052: * Property tester for context launching menu.
053: *
054: * @since 3.2
055: */
056: public class JavaLaunchableTester extends PropertyTester {
057:
058: /**
059: * name for the "has main" property
060: */
061: private static final String PROPERTY_HAS_MAIN = "hasMain"; //$NON-NLS-1$
062:
063: /**
064: * name for the "has method" property
065: */
066: private static final String PROPERTY_HAS_METHOD = "hasMethod"; //$NON-NLS-1$
067:
068: /**
069: * name for the "has method with annotation" property
070: */
071: private static final String PROPERTY_HAS_METHOD_WITH_ANNOTATION = "hasMethodWithAnnotation"; //$NON-NLS-1$
072:
073: /**
074: * name for the "has type with annotation" property
075: */
076: private static final String PROPERTY_HAS_TYPE_WITH_ANNOTATION = "hasTypeWithAnnotation"; //$NON-NLS-1$
077:
078: /**
079: * name for the "extends class" property
080: */
081: private static final String PROPERTY_EXTENDS_CLASS = "extendsClass"; //$NON-NLS-1$
082:
083: /**
084: * "is container" property
085: */
086: private static final String PROPERTY_IS_CONTAINER = "isContainer"; //$NON-NLS-1$
087:
088: /**
089: * "is package fragment" property
090: * @since 3.3
091: */
092: private static final String PROPERTY_IS_PACKAGE_FRAGMENT = "isPackageFragment"; //$NON-NLS-1$
093:
094: /**
095: * "is package fragment root" property
096: * @since 3.3
097: */
098: private static final String PROPERTY_IS_PACKAGE_FRAGMENT_ROOT = "isPackageFragmentRoot"; //$NON-NLS-1$
099:
100: /**
101: * name for the PROPERTY_PROJECT_NATURE property
102: */
103: private static final String PROPERTY_PROJECT_NATURE = "hasProjectNature"; //$NON-NLS-1$
104:
105: /**
106: * name for the "extends interface" property
107: */
108: private static final String PROPERTY_EXTENDS_INTERFACE = "extendsInterface"; //$NON-NLS-1$
109:
110: /**
111: * name for the PROPERTY_HAS_SWT_ON_PATH property
112: */
113: private static final String PROPERTY_BUILDPATH_REFERENCE = "buildpathReference"; //$NON-NLS-1$
114:
115: /**
116: * Map of modifier text to corresponding flag (Integer)
117: */
118: private static Map fgModifiers = new HashMap();
119:
120: private static final int FLAGS_MASK = Flags.AccPublic
121: | Flags.AccProtected | Flags.AccPrivate | Flags.AccStatic
122: | Flags.AccFinal | Flags.AccSynchronized
123: | Flags.AccAbstract | Flags.AccNative;
124:
125: static {
126: fgModifiers.put("public", new Integer(Flags.AccPublic)); //$NON-NLS-1$
127: fgModifiers.put("protected", new Integer(Flags.AccProtected)); //$NON-NLS-1$
128: fgModifiers.put("private", new Integer(Flags.AccPrivate)); //$NON-NLS-1$
129: fgModifiers.put("static", new Integer(Flags.AccStatic)); //$NON-NLS-1$
130: fgModifiers.put("final", new Integer(Flags.AccFinal)); //$NON-NLS-1$
131: fgModifiers.put(
132: "synchronized", new Integer(Flags.AccSynchronized)); //$NON-NLS-1$
133: fgModifiers.put("abstract", new Integer(Flags.AccAbstract)); //$NON-NLS-1$
134: fgModifiers.put("native", new Integer(Flags.AccNative)); //$NON-NLS-1$
135: }
136:
137: /**
138: * gets the type of the IJavaElement
139: * @param element the element to inspect
140: * @return the type
141: * @throws JavaModelException
142: */
143: private IType getType(IJavaElement element) {
144: IType type = null;
145: if (element instanceof ICompilationUnit) {
146: type = ((ICompilationUnit) element).findPrimaryType();
147: } else if (element instanceof IClassFile) {
148: type = ((IClassFile) element).getType();
149: } else if (element instanceof IType) {
150: type = (IType) element;
151: } else if (element instanceof IMember) {
152: type = ((IMember) element).getDeclaringType();
153: }
154: return type;
155: }
156:
157: /**
158: * Determines is the java element contains a main method.
159: *
160: * @param element the element to check for the method
161: * @return true if the method is found in the element, false otherwise
162: */
163: private boolean hasMain(IJavaElement element) {
164: try {
165: IType type = getType(element);
166: if (type != null && type.exists()) {
167: if (hasMainMethod(type)) {
168: return true;
169: }
170: //failed to find in public type, check static inner types
171: IJavaElement[] children = type.getChildren();
172: for (int i = 0; i < children.length; i++) {
173: if (hasMainInChildren(getType(children[i]))) {
174: return true;
175: }
176: }
177: }
178: } catch (JavaModelException e) {
179: } catch (CoreException ce) {
180: }
181: return false;
182: }
183:
184: /**
185: * Returns if the specified <code>IType</code> has a main method
186: * @param type the type to inspect for a main type
187: * @return true if the specified type has a main method, false otherwise
188: * @throws JavaModelException
189: * @since 3.3
190: */
191: private boolean hasMainMethod(IType type) throws JavaModelException {
192: IMethod[] methods = type.getMethods();
193: for (int i = 0; i < methods.length; i++) {
194: if (methods[i].isMainMethod()) {
195: return true;
196: }
197: }
198: return false;
199: }
200:
201: /**
202: * This method asks the specified <code>IType</code> if it has a main method, if not it recurses through all of its children
203: * When recursing we only care about child <code>IType</code>s that are static.
204: * @param type the <code>IType</code> to inspect for a main method
205: * @return true if a main method was found in specified <code>IType</code>, false otherwise
206: * @throws CoreException
207: * @since 3.3
208: */
209: private boolean hasMainInChildren(IType type) throws CoreException {
210: if (type.isClass() & Flags.isStatic(type.getFlags())) {
211: if (hasMainMethod(type)) {
212: return true;
213: } else {
214: IJavaElement[] children = type.getChildren();
215: for (int i = 0; i < children.length; i++) {
216: if (children[i].getElementType() == IJavaElement.TYPE) {
217: return hasMainInChildren((IType) children[i]);
218: }
219: }
220: }
221: }
222: return false;
223: }
224:
225: /**
226: * Determines is the java element contains a specific method.
227: * <p>
228: * The syntax for the property tester is of the form: methodname,
229: * signature, modifiers.
230: * </p>
231: * <ol>
232: * <li>methodname - case sensitive method name, required. For example,
233: * <code>toString</code>.</li>
234: * <li>signature - JLS style method signature, required. For example,
235: * <code>(QString;)V</code>.</li>
236: * <li>modifiers - optional space separated list of modifiers, for
237: * example, <code>public static</code>.</li>
238: * </ol>
239: * @param element the element to check for the method
240: * @param args first arg is method name, secondary args are parameter types signatures
241: * @return true if the method is found in the element, false otherwise
242: */
243: private boolean hasMethod(IJavaElement element, Object[] args) {
244: try {
245: if (args.length > 1) {
246: IType type = getType(element);
247: if (type != null && type.exists()) {
248: String name = (String) args[0];
249: String signature = (String) args[1];
250: String[] parms = Signature
251: .getParameterTypes(signature);
252: String returnType = Signature
253: .getReturnType(signature);
254: IMethod candidate = type.getMethod(name, parms);
255: if (candidate.exists()) {
256: // check return type
257: if (candidate.getReturnType()
258: .equals(returnType)) {
259: // check modifiers
260: if (args.length > 2) {
261: String modifierText = (String) args[2];
262: String[] modifiers = modifierText
263: .split(" "); //$NON-NLS-1$
264: int flags = 0;
265: for (int j = 0; j < modifiers.length; j++) {
266: String modifier = modifiers[j];
267: Integer flag = (Integer) fgModifiers
268: .get(modifier);
269: if (flag != null) {
270: flags = flags | flag.intValue();
271: }
272: }
273: if (candidate.getFlags() == flags) {
274: return true;
275: }
276: }
277: }
278: }
279: }
280: }
281: } catch (JavaModelException e) {
282: }
283: return false;
284: }
285:
286: /**
287: * Determines is the java element contains a type with a specific annotation.
288: * <p>
289: * The syntax for the property tester is of the form: qualified or unqualified annotation name
290: * <li>qualified or unqualified annotation name, required. For example,
291: * <code>org.junit.JUnit</code>.</li>
292: * </ol>
293: * @param element the element to check for the method
294: * @param annotationName the qualified or unqualified name of the annotation to look for
295: * @return true if the type is found in the element, false otherwise
296: */
297: private boolean hasTypeWithAnnotation(IJavaElement element,
298: String annotationType) {
299: try {
300: IType type = getType(element);
301: if (type == null || !type.exists()) {
302: return false;
303: }
304:
305: IBuffer buffer = null;
306: IOpenable openable = type.getOpenable();
307: if (openable instanceof ICompilationUnit) {
308: buffer = ((ICompilationUnit) openable).getBuffer();
309: } else if (openable instanceof IClassFile) {
310: buffer = ((IClassFile) openable).getBuffer();
311: }
312: if (buffer == null) {
313: return false;
314: }
315:
316: ISourceRange sourceRange = type.getSourceRange();
317: ISourceRange nameRange = type.getNameRange();
318: if (sourceRange != null && nameRange != null) {
319: IScanner scanner = ToolFactory.createScanner(false,
320: false, true, false);
321: scanner.setSource(buffer.getCharacters());
322: scanner.resetTo(sourceRange.getOffset(), nameRange
323: .getOffset());
324: if (findAnnotation(scanner, annotationType)) {
325: return true;
326: }
327: }
328: } catch (JavaModelException e) {
329: } catch (InvalidInputException e) {
330: }
331: return false;
332: }
333:
334: /**
335: * Determines is the java element contains a method with a specific annotation.
336: * <p>
337: * The syntax for the property tester is of the form: qualified or unqualified annotation name, modifiers
338: * <li>qualified or unqualified annotation name, required. For example,
339: * <code>org.junit.JUnit</code>.</li>
340: * <li>modifiers - optional space separated list of modifiers, for
341: * example, <code>public static</code>.</li>
342: * </ol>
343: * @param element the element to check for the method
344: * @param annotationName the qualified or unqualified name of the annotation to look for
345: * @return true if the method is found in the element, false otherwise
346: */
347: private boolean hasMethodWithAnnotation(IJavaElement element,
348: Object[] args) {
349: try {
350: String annotationType = (String) args[0];
351: int flags = 0;
352: if (args.length > 1) {
353: String[] modifiers = ((String) args[1]).split(" "); //$NON-NLS-1$
354: for (int j = 0; j < modifiers.length; j++) {
355: String modifier = modifiers[j];
356: Integer flag = (Integer) fgModifiers.get(modifier);
357: if (flag != null) {
358: flags = flags | flag.intValue();
359: }
360: }
361: } else {
362: flags = -1;
363: }
364:
365: IType type = getType(element);
366: if (type == null || !type.exists()) {
367: return false;
368: }
369: IMethod[] methods = type.getMethods();
370: if (methods.length == 0) {
371: return false;
372: }
373:
374: IBuffer buffer = null;
375: IOpenable openable = type.getOpenable();
376: if (openable instanceof ICompilationUnit) {
377: buffer = ((ICompilationUnit) openable).getBuffer();
378: } else if (openable instanceof IClassFile) {
379: buffer = ((IClassFile) openable).getBuffer();
380: }
381: if (buffer == null) {
382: return false;
383: }
384: IScanner scanner = null; // delay initialization
385:
386: for (int i = 0; i < methods.length; i++) {
387: IMethod curr = methods[i];
388: if (curr.isConstructor()
389: || (flags != -1 && flags != (curr.getFlags() & FLAGS_MASK))) {
390: continue;
391: }
392:
393: ISourceRange sourceRange = curr.getSourceRange();
394: ISourceRange nameRange = curr.getNameRange();
395: if (sourceRange != null && nameRange != null) {
396: if (scanner == null) {
397: scanner = ToolFactory.createScanner(false,
398: false, true, false);
399: scanner.setSource(buffer.getCharacters());
400: }
401: scanner.resetTo(sourceRange.getOffset(), nameRange
402: .getOffset());
403: if (findAnnotation(scanner, annotationType)) {
404: return true;
405: }
406: }
407: }
408: } catch (JavaModelException e) {
409: } catch (InvalidInputException e) {
410: }
411: return false;
412: }
413:
414: private boolean findAnnotation(IScanner scanner,
415: String annotationName) throws InvalidInputException {
416: String simpleName = Signature.getSimpleName(annotationName);
417: StringBuffer buf = new StringBuffer();
418: int tok = scanner.getNextToken();
419: while (tok != ITerminalSymbols.TokenNameEOF) {
420: if (tok == ITerminalSymbols.TokenNameAT) {
421: buf.setLength(0);
422: tok = readName(scanner, buf);
423: String name = buf.toString();
424: if (name.equals(annotationName)
425: || name.equals(simpleName)
426: || name.endsWith('.' + simpleName)) {
427: return true;
428: }
429: } else {
430: tok = scanner.getNextToken();
431: }
432: }
433: return false;
434: }
435:
436: private int readName(IScanner scanner, StringBuffer buf)
437: throws InvalidInputException {
438: int tok = scanner.getNextToken();
439: while (tok == ITerminalSymbols.TokenNameIdentifier) {
440: buf.append(scanner.getCurrentTokenSource());
441: tok = scanner.getNextToken();
442: if (tok != ITerminalSymbols.TokenNameDOT) {
443: return tok;
444: }
445: buf.append('.');
446: tok = scanner.getNextToken();
447: }
448: return tok;
449: }
450:
451: /**
452: * determines if the project selected has the specified nature
453: * @param resource the resource to get the project for
454: * @param ntype the specified nature type
455: * @return true if the specified nature matches the project, false otherwise
456: */
457: private boolean hasProjectNature(IJavaElement element, String ntype) {
458: try {
459: if (element != null) {
460: IJavaProject jproj = element.getJavaProject();
461: if (jproj != null) {
462: IProject proj = jproj.getProject();
463: return proj.isAccessible() && proj.hasNature(ntype);
464: }
465: }
466: return false;
467: } catch (CoreException e) {
468: return false;
469: }
470: }
471:
472: /**
473: * Determines if the element has qname as a parent class
474: * @param element the element to check for the parent class definition
475: * @param qname the fully qualified name of the (potential) parent class
476: * @return true if qname is a parent class, false otherwise
477: */
478: private boolean hasSuperclass(IJavaElement element, String qname) {
479: try {
480: IType type = getType(element);
481: if (type != null) {
482: IType[] stypes = type.newSupertypeHierarchy(
483: new NullProgressMonitor()).getAllSuperclasses(
484: type);
485: for (int i = 0; i < stypes.length; i++) {
486: if (stypes[i].getFullyQualifiedName().equals(qname)
487: || stypes[i].getElementName().equals(qname)) {
488: return true;
489: }
490: }
491: }
492: } catch (JavaModelException e) {
493: }
494: return false;
495: }
496:
497: /**
498: * Determines if an item or list of items are found on the build path.
499: * Once any one single items matches though, the method returns true, this method is intended
500: * 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
501: * of them is.
502: *
503: * @param element the element whose build path should be checked
504: * @param args the value(s) to search for on the build path
505: * @return true if any one of the args is found on the build path
506: */
507: private boolean hasItemOnBuildPath(IJavaElement element,
508: Object[] args) {
509: if (element != null && args != null) {
510: IJavaProject project = element.getJavaProject();
511: Set searched = new HashSet();
512: searched.add(project);
513: return hasItemsOnBuildPath(project, searched, args);
514: }
515: return false;
516: }
517:
518: private boolean hasItemsOnBuildPath(IJavaProject project,
519: Set searched, Object[] args) {
520: try {
521: List projects = new ArrayList();
522: if (project != null && project.exists()) {
523: IClasspathEntry[] entries = project
524: .getResolvedClasspath(true);
525: for (int i = 0; i < entries.length; i++) {
526: IClasspathEntry entry = entries[i];
527: IPath path = entry.getPath();
528: String spath = path.toPortableString();
529: for (int j = 0; j < args.length; j++) {
530: if (spath.lastIndexOf((String) args[j]) != -1) {
531: return true;
532: }
533: }
534: if (entry.getEntryKind() == IClasspathEntry.CPE_PROJECT) {
535: String name = entry.getPath().lastSegment();
536: IProject dep = ResourcesPlugin.getWorkspace()
537: .getRoot().getProject(name);
538: IJavaProject javaProject = JavaCore.create(dep);
539: if (!searched.contains(javaProject)) {
540: projects.add(javaProject);
541: }
542: }
543: }
544: }
545: // search referenced projects
546: Iterator iterator = projects.iterator();
547: while (iterator.hasNext()) {
548: IJavaProject jp = (IJavaProject) iterator.next();
549: searched.add(jp);
550: if (hasItemsOnBuildPath(jp, searched, args)) {
551: return true;
552: }
553: }
554: } catch (JavaModelException e) {
555: }
556: return false;
557: }
558:
559: /**
560: * determines if the element implements a given interface
561: * @param element the element to check for the interface
562: * @param qname the fully qualified name of the interface to check for
563: * @return true if the element does implement the interface, false otherwise
564: */
565: private boolean implements Interface(IJavaElement element,
566: String qname) {
567: try {
568: IType type = getType(element);
569: if (type != null) {
570: IType[] itypes = type.newSupertypeHierarchy(
571: new NullProgressMonitor()).getAllInterfaces();
572: for (int i = 0; i < itypes.length; i++) {
573: if (itypes[i].getFullyQualifiedName().equals(qname)) {
574: return true;
575: }
576: }
577: }
578: } catch (JavaModelException e) {
579: }
580: return false;
581: }
582:
583: /**
584: * Method runs the tests defined from extension points for Run As... and Debug As... menu items.
585: * Currently this test optimistically considers everything not a source file. In this context we
586: * consider an optimistic approach to mean that the test will always return true.
587: *
588: * There are many reasons for the optimistic choice some of them are outlined below.
589: * <ul>
590: * <li>Performance (in terms of time needed to display menu) cannot be preserved. To know what to allow
591: * in any one of the menus we would have to search all of the children of the container to determine what it contains
592: * and what can be launched by what.</li>
593: * <li>If inspection of children of containers were done, a user might want to choose a different launch type, even though our tests
594: * filter it out.</li>
595: * </ul>
596: * @see org.eclipse.core.expressions.IPropertyTester#test(java.lang.Object, java.lang.String, java.lang.Object[], java.lang.Object)
597: * @since 3.2
598: * @return true if the specified tests pass, or the context is a container, false otherwise
599: */
600: public boolean test(Object receiver, String property,
601: Object[] args, Object expectedValue) {
602: if (PROPERTY_IS_CONTAINER.equals(property)) {
603: if (receiver instanceof IAdaptable) {
604: IResource resource = (IResource) ((IAdaptable) receiver)
605: .getAdapter(IResource.class);
606: if (resource != null) {
607: return resource instanceof IContainer;
608: }
609: }
610: return false;
611: }
612: IJavaElement element = null;
613: if (receiver instanceof IAdaptable) {
614: element = (IJavaElement) ((IAdaptable) receiver)
615: .getAdapter(IJavaElement.class);
616: if (element != null) {
617: if (!element.exists()) {
618: return false;
619: }
620: }
621: }
622: if (PROPERTY_HAS_MAIN.equals(property)) {
623: return hasMain(element);
624: }
625: if (PROPERTY_HAS_METHOD.equals(property)) {
626: return hasMethod(element, args);
627: }
628: if (PROPERTY_HAS_METHOD_WITH_ANNOTATION.equals(property)) {
629: return hasMethodWithAnnotation(element, args);
630: }
631: if (PROPERTY_HAS_TYPE_WITH_ANNOTATION.equals(property)) {
632: return hasTypeWithAnnotation(element, (String) args[0]);
633: }
634: if (PROPERTY_BUILDPATH_REFERENCE.equals(property)) {
635: return hasItemOnBuildPath(element, args);
636: }
637: if (PROPERTY_EXTENDS_CLASS.equals(property)) {
638: return hasSuperclass(element, (String) args[0]);
639: }
640: if (PROPERTY_PROJECT_NATURE.equals(property)) {
641: return hasProjectNature(element, (String) args[0]);
642: }
643: if (PROPERTY_EXTENDS_INTERFACE.equals(property)) {
644: return implements Interface(element, (String) args[0]);
645: }
646: if (PROPERTY_IS_PACKAGE_FRAGMENT.equals(property)) {
647: return element instanceof IPackageFragment;
648: }
649: if (PROPERTY_IS_PACKAGE_FRAGMENT_ROOT.equals(property)) {
650: return element instanceof IPackageFragmentRoot;
651: }
652: return false;
653: }
654:
655: }
|