package com.anjlab.eclipse.tapestry5; import java.util.ArrayList; import java.util.List; import java.util.concurrent.atomic.AtomicReference; import org.apache.commons.lang3.ObjectUtils; import org.apache.commons.lang3.StringUtils; import org.eclipse.core.resources.IContainer; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IProject; import org.eclipse.core.resources.IResource; import org.eclipse.core.resources.IResourceDelta; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.Platform; import org.eclipse.jdt.core.IAnnotation; import org.eclipse.jdt.core.ICompilationUnit; import org.eclipse.jdt.core.IField; import org.eclipse.jdt.core.IImportDeclaration; import org.eclipse.jdt.core.IJavaElement; import org.eclipse.jdt.core.IMember; import org.eclipse.jdt.core.IMemberValuePair; import org.eclipse.jdt.core.IPackageFragmentRoot; import org.eclipse.jdt.core.ISourceReference; 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.dom.AST; import org.eclipse.jdt.core.dom.ASTNode; import org.eclipse.jdt.core.dom.ASTParser; import org.eclipse.jdt.core.dom.ASTVisitor; import org.eclipse.jdt.core.dom.BooleanLiteral; import org.eclipse.jdt.core.dom.CompilationUnit; import org.eclipse.jdt.core.dom.Expression; import org.eclipse.jdt.core.dom.FieldDeclaration; import org.eclipse.jdt.core.dom.ImportDeclaration; import org.eclipse.jdt.core.dom.Name; import org.eclipse.jdt.core.dom.NullLiteral; import org.eclipse.jdt.core.dom.NumberLiteral; import org.eclipse.jdt.core.dom.ParameterizedType; import org.eclipse.jdt.core.dom.QualifiedType; import org.eclipse.jdt.core.dom.SimpleType; import org.eclipse.jdt.core.dom.StringLiteral; import org.eclipse.jdt.core.dom.Type; import org.eclipse.jdt.core.dom.TypeLiteral; import org.eclipse.jdt.core.dom.VariableDeclarationFragment; import org.eclipse.jdt.core.search.IJavaSearchConstants; import org.eclipse.jdt.core.search.IJavaSearchScope; import org.eclipse.jdt.core.search.SearchEngine; import org.eclipse.jdt.core.search.SearchMatch; import org.eclipse.jdt.core.search.SearchParticipant; import org.eclipse.jdt.core.search.SearchPattern; import org.eclipse.jdt.core.search.SearchRequestor; import org.eclipse.jdt.core.search.TypeNameMatch; import org.eclipse.jdt.core.search.TypeNameMatchRequestor; import org.eclipse.jdt.internal.ui.javaeditor.EditorUtility; import org.eclipse.jdt.ui.JavaUI; import org.eclipse.jface.dialogs.MessageDialog; import org.eclipse.jface.viewers.ISelection; import org.eclipse.jface.viewers.ITreeSelection; import org.eclipse.swt.graphics.Point; import org.eclipse.swt.graphics.Rectangle; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Control; import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.Monitor; import org.eclipse.swt.widgets.Shell; import org.eclipse.ui.IEditorInput; import org.eclipse.ui.IEditorPart; import org.eclipse.ui.IEditorReference; import org.eclipse.ui.IFileEditorInput; import org.eclipse.ui.IWorkbenchWindow; import org.eclipse.ui.PartInitException; import org.eclipse.ui.PlatformUI; import org.eclipse.ui.ide.IDE; import org.eclipse.ui.internal.EditorReference; import org.eclipse.ui.internal.PartPane; @SuppressWarnings("restriction") public class EclipseUtils { public static final String ECLIPSE_INTEGRATION_FOR_TAPESTRY5 = "Eclipse Integration for Tapestry5"; public static final String SOURCE_NOT_FOUND = "source not found"; public static ISelection getProjectExplorerSelection(IWorkbenchWindow window) { return window.getSelectionService().getSelection("org.eclipse.jdt.ui.PackageExplorer"); } public static interface EditorCallback { void editorOpened(IEditorPart editorPart); } public static void openFile(final IWorkbenchWindow window, final IFile file) { openFile(window, file, null); } public static void openFile(final IWorkbenchWindow window, TapestryFile file) { openFile(window, file, null); } public static void openFile(final IWorkbenchWindow window, TapestryFile file, final EditorCallback editorCallback) { if (file instanceof TapestryFileReference) { TapestryFileReference reference = (TapestryFileReference) file; try { file = reference.resolveFile(false); } catch (UnresolvableReferenceException e) { EclipseUtils.openError(window, "Unable to resolve '" + reference.getReference() + "': " + e.getLocalizedMessage()); return; } } if (file instanceof LocalFile) { openFile(window, ((LocalFile) file).getFile(), editorCallback); } else if (file instanceof JarEntryFile) { openInEditor(((JarEntryFile) file).getJarEntry(), editorCallback); } else if (file instanceof ClassFile) { openInEditor(((ClassFile) file).getClassFile(), editorCallback); } } private static void openInEditor(Object inputElement, final EditorCallback editorCallback) { try { IEditorPart editorPart = EditorUtility.openInEditor(inputElement); if (editorCallback != null) { editorCallback.editorOpened(editorPart); } } catch (PartInitException e) { Activator.getDefault().logError("Unable to open editor", e); } } public static void openFile(final IWorkbenchWindow window, final IFile file, final EditorCallback editorCallback) { asyncExec(window.getShell(), new Runnable() { public void run() { try { IEditorPart editor = IDE.openEditor(window.getActivePage(), file, true); if (editorCallback != null) { editorCallback.editorOpened(editor); } } catch (Exception e) { Activator.getDefault().logError("Unable to open editor", e); openError(window, "Unable to open editor: " + e.getLocalizedMessage()); } } }); } public static void openError(final IWorkbenchWindow window, String message) { MessageDialog.openError( window.getShell(), ECLIPSE_INTEGRATION_FOR_TAPESTRY5, message); } public static void openInformation(final IWorkbenchWindow window, String message) { MessageDialog.openInformation( window.getShell(), ECLIPSE_INTEGRATION_FOR_TAPESTRY5, message); } public static <T> List<T> getAllAffectedResources(IResourceDelta delta, Class<T> clazz) { return getAllAffectedResources(delta, clazz, 0xFFFFFFFF); } @SuppressWarnings("unchecked") public static <T> List<T> getAllAffectedResources(IResourceDelta delta, Class<T> clazz, int deltaKind) { List<T> files = new ArrayList<T>(); for (IResourceDelta child : delta.getAffectedChildren()) { IResource resource = child.getResource(); if (resource != null && clazz.isAssignableFrom(resource.getClass())) { if ((child.getKind() & deltaKind) != 0) { files.add((T) resource); } } else { files.addAll(getAllAffectedResources(child, clazz, deltaKind)); } } return files; } public static boolean isSourceFolder(IContainer container) throws JavaModelException { return EclipseUtils.isSourceFolder((IJavaElement) container.getAdapter(IJavaElement.class)); } public static boolean isSourceFolder(IJavaElement javaElement) throws JavaModelException { return javaElement != null && (javaElement instanceof IPackageFragmentRoot) && (((IPackageFragmentRoot) javaElement).getKind() == IPackageFragmentRoot.K_SOURCE); } public static IFile findFileCaseInsensitive(IContainer container, String componentPath) { String[] parts = (componentPath.startsWith("/") ? componentPath.substring(1) : componentPath).split("/"); for (int i = 0; i < parts.length; i++) { String part = parts[i]; try { boolean found = false; for (IResource member : container.members()) { if (part.equalsIgnoreCase(member.getName())) { if (member instanceof IFile && i == parts.length - 1) { return (IFile) member; } if (!(member instanceof IContainer)) { return null; } container = (IContainer) member; found = true; break; } } if (!found) { return null; } } catch (CoreException e) { } } return null; } public static IField findFieldDeclaration(IProject project, Name name) { SearchPattern pattern = SearchPattern.createPattern(name.getFullyQualifiedName(), IJavaSearchConstants.FIELD, IJavaSearchConstants.DECLARATIONS, SearchPattern.R_EXACT_MATCH | SearchPattern.R_CASE_SENSITIVE); final List<SearchMatch> matches = searchJava(project, pattern); return exactMatchOrNull(matches, IField.class); } @SuppressWarnings("unchecked") private static <T> T exactMatchOrNull(final List<SearchMatch> matches, Class<T> clazz) { for (SearchMatch match : matches) { if (match.isExact() && clazz.isAssignableFrom(match.getElement().getClass())) { return (T) match.getElement(); } } return null; } public static IType findTypeDeclaration(IProject project, String className) { return findTypeDeclaration(project, IJavaSearchConstants.CLASS_AND_INTERFACE, className); } public static IType findTypeDeclaration(IProject project, int searchFor, String className) { if (StringUtils.isEmpty(className)) { return null; } // Try to break className to type/package name // assuming PascalCase notation used for type names int typeNameIndex = -1; for (int i = className.length() - 1; i>= 0; i--) { if (className.charAt(i) == '.') { if (className.length() > i + 1) { if (Character.isUpperCase(className.charAt(i + 1))) { // Class name begins after `i`, // check if this a nested class typeNameIndex = i + 1; } else { break; } } } } if (typeNameIndex == -1) { return null; } String packageName = typeNameIndex == 0 ? "" : className.substring(0, typeNameIndex - 1); String typeName = className.substring(typeNameIndex); return findTypeDeclarationExact(project, searchFor, packageName, typeName); } public static IType findTypeDeclarationExact( IProject project, int searchFor, String packageName, String typeName) { final List<TypeNameMatch> matches = new ArrayList<TypeNameMatch>(); SearchEngine searchEngine = new SearchEngine(); try { IJavaSearchScope scope = SearchEngine.createJavaSearchScope( new IJavaElement[] { JavaCore.create(project) }, true); IProgressMonitor progressMonitor = null; searchEngine.searchAllTypeNames( packageName.toCharArray(), SearchPattern.R_EXACT_MATCH | SearchPattern.R_CASE_SENSITIVE, typeName.toCharArray(), SearchPattern.R_EXACT_MATCH | SearchPattern.R_CASE_SENSITIVE, searchFor, scope, new TypeNameMatchRequestor() { @Override public void acceptTypeNameMatch(TypeNameMatch match) { matches.add(match); } }, IJavaSearchConstants.WAIT_UNTIL_READY_TO_SEARCH, progressMonitor); } catch (CoreException e) { // XXX May happen, say, if classpath is incorrectly set. // Probably shouldn't invoke this method if classpath errors can be detected Activator.getDefault().logWarning("Error performing search", e); } return matches.isEmpty() ? null : matches.get(0).getType(); } private static List<SearchMatch> searchJava(IProject project, SearchPattern pattern) { IJavaSearchScope scope = SearchEngine.createJavaSearchScope( new IJavaElement[] { JavaCore.create(project) }, true); final List<SearchMatch> matches = new ArrayList<SearchMatch>(); SearchRequestor requestor = new SearchRequestor() { @Override public void acceptSearchMatch(SearchMatch match) throws CoreException { matches.add(match); } }; SearchEngine searchEngine = new SearchEngine(); try { searchEngine.search(pattern, new SearchParticipant[] { SearchEngine.getDefaultSearchParticipant() }, scope, requestor, null); } catch (CoreException e) { Activator.getDefault().logWarning("Error performing search", e);; } return matches; } public static ASTNode parse(ISourceReference reference, int kind) { String source = getSource(reference); return parse(source, kind); } private static String getSource(ISourceReference reference) { String source; try { source = reference.getSource(); } catch (JavaModelException e) { throw new IllegalStateException(SOURCE_NOT_FOUND, e); } return source; } public static ASTNode parse(String source, int kind) { if (source == null) { throw new IllegalStateException(SOURCE_NOT_FOUND); } ASTParser parser = ASTParser.newParser(getParserLevel()); parser.setKind(kind); parser.setSource(source.toCharArray()); parser.setResolveBindings(true); return parser.createAST(null); } private static int parserLevel = -1; @SuppressWarnings("deprecation") public static int getParserLevel() { if (parserLevel == -1) { try { int JLS8 = 8; // Try to use Java 8's AST.JLS8 ASTParser.newParser(JLS8); parserLevel = JLS8; } catch (IllegalArgumentException e) { // Fallback to Java 7 parserLevel = AST.JLS4; } } return parserLevel; } public static IProject getProjectFromSelection(ISelection selection) { if (selection instanceof ITreeSelection) { Object firstElement = ((ITreeSelection) selection).getFirstElement(); if (firstElement != null) { IResource resource = (IResource) Platform.getAdapterManager().getAdapter( firstElement, IResource.class); if (resource != null) { return resource.getProject(); } } } return null; } public static String eval(Object value, int valueKind, AST ast, IProject project) { if (ast != null && (valueKind == IMemberValuePair.K_SIMPLE_NAME || valueKind == IMemberValuePair.K_QUALIFIED_NAME)) { Name name = ast.newName((String) value); return evalExpression(project, name); } return String.valueOf(value); } public static String evalExpression(IProject project, Object expr) { if (expr instanceof String) { return (String) expr; } if (expr instanceof StringLiteral) { return ((StringLiteral) expr).getLiteralValue(); } if (expr instanceof BooleanLiteral) { return String.valueOf(((BooleanLiteral) expr).booleanValue()); } if (expr instanceof NumberLiteral) { return String.valueOf(((NumberLiteral) expr).getToken()); } if (expr instanceof NullLiteral) { return null; } if (expr instanceof Name) { IField field = findFieldDeclaration(project, ((Name) expr)); if (field != null) { try { // XXX String literals sometimes returned in quotes as they present in source code, // for example: // String foo = "bar"; // may be returned as "bar" (in quotes) instead of just bar (without quotes). if (field.isBinary() && field.isResolved() && field.getConstant() == null) { String source = field.getSource(); if (source != null) { ASTNode node = parse(source, ASTParser.K_CLASS_BODY_DECLARATIONS); final AtomicReference<Expression> initializer = new AtomicReference<Expression>(); if (node != null) { node.accept(new ASTVisitor() { @Override public boolean visit(FieldDeclaration node) { for (Object fragment : node.fragments()) { if (fragment instanceof VariableDeclarationFragment) { initializer.set(((VariableDeclarationFragment) fragment).getInitializer()); break; } } return false; } }); } if (initializer.get() != null) { return evalExpression(project, initializer.get()); } } } else { return evalExpression(project, field.getConstant()); } } catch (JavaModelException e) { // Ignore } } } return "<" + expr + ">"; } public static void ensureFileIsOpenedInEditor(IWorkbenchWindow window, IFile file, EditorCallback editorCallback) { IEditorReference[] editors = window.getActivePage().getEditorReferences(); for (IEditorReference editor : editors) { try { IEditorInput editorInput = editor.getEditorInput(); if (editorInput instanceof IFileEditorInput) { if (ObjectUtils.equals(((IFileEditorInput) editorInput).getFile(), file)) { // The file is opened in editor editorCallback.editorOpened(null); return; } } } catch (PartInitException e) { // Ignore } } openFile(window, file, editorCallback); } public static boolean isJavaProject(IProject project) throws CoreException { return project.hasNature(JavaCore.NATURE_ID); } public static IWorkbenchWindow getWorkbenchWindow(Shell shell) { IWorkbenchWindow currentWindow = null; for (IWorkbenchWindow window : PlatformUI.getWorkbench().getWorkbenchWindows()) { if (shell == window.getShell()) { currentWindow = window; break; } } return currentWindow; } public static boolean isInFolder(IFile file, IContainer folder) { IContainer parent = file.getParent(); while (parent != null) { if (parent.equals(folder)) { return true; } parent = parent.getParent(); } return false; } public static void openDeclaration(IJavaElement element, EditorCallback editorCallback) { if (element == null) { return; } try { IEditorPart editor = JavaUI.openInEditor(element); if (editorCallback != null) { editorCallback.editorOpened(editor); } } catch (Exception e) { Activator.getDefault().logError("Error opening " + element.getElementName(), e); } } public static String resolveTypeNameForMember(IType type, IMember member, String typeSignature) throws JavaModelException { String typeName = Signature.toString(typeSignature); if (member.isBinary()) { return typeName; } return resolveTypeName(type, typeName); } public static String resolveTypeName(IType type, String typeName) throws JavaModelException { String[][] resolvedTypes = type.resolveType(typeName); if (resolvedTypes == null) { return typeName; } else { return resolvedTypes[0][0] + "." + resolvedTypes[0][1]; } } public static String toClassName(IProject project, TypeLiteral typeLiteral) { return toClassName(project, typeLiteral.getType()); } public static String toClassName(IProject project, Type type) { Name name = null; if (type instanceof SimpleType) { name = ((SimpleType) type).getName(); } else if (type instanceof QualifiedType) { name = ((QualifiedType) type).getName(); } else if (type instanceof ParameterizedType) { return toClassName(project, ((ParameterizedType) type).getType()); } else { // Unsupported type, i.e., primitive types are not supported at the moment return null; } return name.isQualifiedName() ? name.getFullyQualifiedName() : tryResolveFQNameFromImports(project, type.getRoot(), name.getFullyQualifiedName()); } public static String toClassNameFromImports(IProject project, IType relativeTo, String className) throws JavaModelException { if (!className.contains(".")) { ICompilationUnit compilationUnit = relativeTo.getCompilationUnit(); if (compilationUnit != null) { IImportDeclaration[] imports = compilationUnit.getImports(); if (imports.length > 0) { for (IImportDeclaration importDecl : imports) { if (importDecl.getElementName().endsWith("." + className)) { return importDecl.getElementName(); } if (importDecl.isOnDemand()) { String packageName = importDecl.getElementName() .substring(0, importDecl.getElementName().length() - 2); String candidate = packageName + "." + className; if (EclipseUtils.findTypeDeclarationExact( project, IJavaSearchConstants.CLASS_AND_INTERFACE, packageName, className) != null) { return candidate; } } } } // Class is from the same package return relativeTo.getPackageFragment().getElementName() + "." + className; } } return className; } private static String tryResolveFQNameFromImports(IProject project, ASTNode root, String simpleName) { if (!(root instanceof CompilationUnit)) { return simpleName; } CompilationUnit compilationUnit = (CompilationUnit) root; for (Object importObj : compilationUnit.imports()) { ImportDeclaration importDecl = (ImportDeclaration) importObj; if (importDecl.getName().getFullyQualifiedName().endsWith("." + simpleName)) { return importDecl.getName().getFullyQualifiedName(); } else if (importDecl.isOnDemand()) { String packageName = importDecl.getName().getFullyQualifiedName(); String candidate = packageName + "." + simpleName; if (EclipseUtils.findTypeDeclarationExact( project, IJavaSearchConstants.CLASS_AND_INTERFACE, packageName, simpleName) != null) { return candidate; } } } // Assume it's from the same package return compilationUnit.getPackage().getName().getFullyQualifiedName() + "." + simpleName; } public static IType findParentType(IJavaElement element) { while (!(element instanceof IType) && element != null) { element = element.getParent(); } return (IType) element; } public static void readValueFromAnnotation( IAnnotation annotation, String memberName, IProject project, AST ast, ObjectCallback<String, JavaModelException> callback) throws JavaModelException { if (annotation == null) { return; } IMemberValuePair[] pairs = annotation.getMemberValuePairs(); for (IMemberValuePair pair : pairs) { if (memberName.equals(pair.getMemberName())) { if (pair.getValueKind() == IMemberValuePair.K_UNKNOWN) { // The value is unknown at this stage continue; } else { Object[] values = pair.getValue().getClass().isArray() ? (Object[]) pair.getValue() : new Object[] { pair.getValue() }; for (Object value : values) { String eval = eval(value, pair.getValueKind(), ast, project); callback.callback(eval); } } } } } public static String[] readValuesFromAnnotation(IProject project, IAnnotation annotation, String name) throws JavaModelException { final List<String> values = new ArrayList<String>(); readValueFromAnnotation( annotation, name, project, AST.newAST(getParserLevel()), new ObjectCallback<String, JavaModelException>() { @Override public void callback(String value) throws JavaModelException { values.add(value); } }); return values.toArray(new String[values.size()]); } public static String readFirstValueFromAnnotation(IProject project, IAnnotation annotation, String name) throws JavaModelException { String[] values = readValuesFromAnnotation(project, annotation, name); return values.length > 0 ? values[0] : null; } public static void syncExec(Shell shell, Runnable runnable) { Display display; if (shell == null || (display = shell.getDisplay()) == null) { return; } display.syncExec(runnable); } public static void asyncExec(Shell shell, Runnable runnable) { Display display; if (shell == null || (display = shell.getDisplay()) == null) { return; } display.asyncExec(runnable); } public static Point getPopupLocation( IWorkbenchWindow window, Object relativeTo, Point popupSize) { Point centerPoint = null; if (relativeTo instanceof EditorReference) { PartPane pane = ((EditorReference) relativeTo).getPane(); Control control = pane.getControl(); Rectangle partBounds = control.getBounds(); centerPoint = new Point(partBounds.x, partBounds.y); alignPoint(centerPoint, control.getParent(), true, true); centerPoint.x += partBounds.width / 2; centerPoint.y += partBounds.height / 2; } if (centerPoint == null) { Monitor mon = window.getShell().getMonitor(); Rectangle bounds = mon.getClientArea(); Point screenCenter = new Point(bounds.x + bounds.width / 2, bounds.y + bounds.height / 2); centerPoint = screenCenter; } centerPoint.x -= popupSize.x / 2; centerPoint.y -= popupSize.y / 2; return centerPoint; } private static Composite alignPoint(Point centerPoint, Composite parent, boolean left, boolean top) { while (parent != null) { if (left) { centerPoint.x += parent.getBounds().x; } if (top) { centerPoint.y += parent.getBounds().y; } parent = parent.getParent(); } return parent; } public static String getClassName(IFile file) { IJavaElement javaElement = (IJavaElement) file.getAdapter(IJavaElement.class); if (javaElement instanceof ICompilationUnit) { ICompilationUnit compilationUnit = (ICompilationUnit) javaElement; try { for (IJavaElement child : compilationUnit.getChildren()) { if (child instanceof IType) { return ((IType) child).getFullyQualifiedName(); } } return null; } catch (JavaModelException e) { // Ignore return null; } } return null; } }