Java tutorial
/******************************************************************************* * Copyright (c) 2009 Casey Marshall and others. * 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: * Casey Marshall - initial API and implementation *******************************************************************************/ /* * Adapted from the... ** ________ ___ / / ___ Scala Plugin for Eclipse ** ** / __/ __// _ | / / / _ | (c) 2004-2005, LAMP/EPFL ** ** __\ \/ /__/ __ |/ /__/ __ | ** ** /____/\___/_/ |_/____/_/ | | ** ** |/ ** * * by Casey Marshall for the Clojure plugin, ccw \* */ // Created on 2004-10-25 by Thierry Monney //$Id: ScalaCore.java,v 1.3 2006/02/03 12:41:22 mcdirmid Exp $ package ccw; import java.io.File; import java.io.IOException; import java.util.HashMap; import java.util.Map; import java.util.jar.JarFile; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.zip.ZipEntry; import java.util.zip.ZipFile; import org.eclipse.core.filesystem.EFS; import org.eclipse.core.filesystem.IFileStore; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IFolder; import org.eclipse.core.resources.IProject; import org.eclipse.core.resources.IProjectDescription; import org.eclipse.core.resources.IResource; import org.eclipse.core.resources.ResourcesPlugin; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IPath; import org.eclipse.core.runtime.Preferences; import org.eclipse.debug.core.sourcelookup.containers.LocalFileStorage; import org.eclipse.debug.core.sourcelookup.containers.ZipEntryStorage; import org.eclipse.jdt.core.IJarEntryResource; import org.eclipse.jdt.core.IJavaProject; import org.eclipse.jdt.core.IPackageFragment; import org.eclipse.jdt.core.IPackageFragmentRoot; import org.eclipse.jdt.core.JavaCore; import org.eclipse.jdt.core.JavaModelException; import org.eclipse.jdt.internal.debug.ui.LocalFileStorageEditorInput; import org.eclipse.jdt.internal.debug.ui.ZipEntryStorageEditorInput; import org.eclipse.jdt.internal.ui.javaeditor.EditorUtility; import org.eclipse.jdt.internal.ui.javaeditor.JarEntryEditorInput; import org.eclipse.jdt.launching.IJavaLaunchConfigurationConstants; import org.eclipse.jface.text.BadLocationException; import org.eclipse.jface.text.IRegion; import org.eclipse.ui.IEditorInput; import org.eclipse.ui.IEditorPart; import org.eclipse.ui.PartInitException; import org.eclipse.ui.ide.FileStoreEditorInput; import org.eclipse.ui.ide.IDE; import org.eclipse.ui.part.FileEditorInput; import org.eclipse.ui.texteditor.ITextEditor; import ccw.editors.clojure.ClojureEditor; import ccw.repl.REPLView; import ccw.util.ClojureInvoker; import clojure.lang.RT; import clojure.lang.Var; /** * This class acts as a Facade for all SDT core functionality. * */ public final class ClojureCore { private static ClojureInvoker staticAnalysis = ClojureInvoker.newInvoker(CCWPlugin.getDefault(), "paredit.static-analysis"); static public final String NATURE_ID = "ccw.nature"; /** * Clojure file extension */ static public final String CLOJURE_FILE_EXTENSION = "clj"; /** * Clojure source file filter (used to avoid the Java builder to copy * Clojure sources to the output folder */ static public final String CLOJURE_FILE_FILTER = "*." + CLOJURE_FILE_EXTENSION; static final Map<IProject, ClojureProject> clojureProjects = new HashMap<IProject, ClojureProject>(); static final Map<IProject, IJavaProject> javaProjects = new HashMap<IProject, IJavaProject>(); /** * Gets the SDT core preferences * * @return the preferences root node */ public Preferences getPreferences() { return CCWPlugin.getDefault().getPluginPreferences(); } private static boolean addNature(IProject project, String natureID) { IProjectDescription desc; try { desc = project.getDescription(); } catch (CoreException e) { CCWPlugin.logError("Could not get project description", e); return false; } String[] ids = desc.getNatureIds(); String[] newIDs = new String[ids.length + 1]; System.arraycopy(ids, 0, newIDs, 1, ids.length); newIDs[0] = natureID; desc.setNatureIds(newIDs); try { project.setDescription(desc, null); } catch (CoreException e) { CCWPlugin.logError("Could not set project description", e); return false; } return true; } /** * Adds tha Scala nature to the given project * * @param project * the project * @return <code>true</code> if the nature was correctly added, * <code>false</code> otherwise */ public static boolean addClojureNature(IProject project) { return addNature(project, NATURE_ID); } /** * Adds tha Java nature to the given project * * @param project * the project * @return <code>true</code> if the nature was correctly added, * <code>false</code> otherwise */ public static boolean addJavaNature(IProject project) { return addNature(project, JavaCore.NATURE_ID); } // public static final String[] SCALA_JARS = getScalaJars(); private static final String EXCLUSION_FILTER_ID = "org.eclipse.jdt.core.builder.resourceCopyExclusionFilter"; /** * Gets the Java project associated to the given project * * @param project * the Eclipse project * @return the associated Java project */ public static IJavaProject getJavaProject(IProject project) { if (project == null) return null; try { if (!project.exists() || !project.isOpen() || !project.hasNature(NATURE_ID)) return null; } catch (CoreException e) { CCWPlugin.logError(e); return null; } IJavaProject p = (IJavaProject) javaProjects.get(project); if (p == null) { p = JavaCore.create(project); javaProjects.put(project, p); } return p; } /** * Gets the Clojure project associated to the given project * * @param project * the Eclipse project * @return the associated Scala project */ public static ClojureProject getClojureProject(IProject project) { ClojureProject p = clojureProjects.get(project); if (p != null) return p; try { if (!project.exists() || !project.isOpen() || !project.hasNature(NATURE_ID)) return null; } catch (CoreException e) { CCWPlugin.logError(e); return null; } p = new ClojureProject(project); return p; } /** * Gets all the Clojure projects in the workspace * * @return an array containing all the Scala projects */ public static ClojureProject[] getClojureProjects() { return clojureProjects.values().toArray(new ClojureProject[] {}); } /* * TODO Still 1 more case to handle: * - when a LIBRARY does not have source file in its classpath, then search attached source files folder/archive */ public static void openInEditor(String searchedNS, String searchedFileName, int line) { try { REPLView replView = REPLView.activeREPL.get(); if (replView != null) { String projectName = replView.getLaunch().getLaunchConfiguration() .getAttribute(IJavaLaunchConfigurationConstants.ATTR_PROJECT_NAME, (String) null); openInEditor(searchedNS, searchedFileName, line, projectName, false); } } catch (CoreException e) { CCWPlugin.logError( "error while trying to obtain project's name from configuration, while trying to show source file of a symbol", e); } } /** * Tries to open a clojure file in an editor * @return an editor input if the file has been found, or null */ private static IEditorInput findEditorInput(IPackageFragmentRoot packageFragmentRoot, IPackageFragment packageFragment, String searchedPackage, String searchedFileName) throws JavaModelException { if (packageFragment.exists() && packageFragment.getElementName().equals(searchedPackage)) { for (Object njr : packageFragment.isDefaultPackage() ? packageFragmentRoot.getNonJavaResources() : packageFragment.getNonJavaResources()) { if (njr instanceof IJarEntryResource) { IJarEntryResource jer = (IJarEntryResource) njr; if (jer.getName().equals(searchedFileName)) { return new JarEntryEditorInput(jer); } } else if (njr instanceof IFile) { IFile file = (IFile) njr; if (file.getName().equals(searchedFileName)) { return new FileEditorInput(file); } } else if (njr instanceof File) { File f = (File) njr; if (f.getName().equals(searchedFileName)) { IFileStore fileStore = EFS.getLocalFileSystem().getStore(f.toURI()); return new FileStoreEditorInput(fileStore); } } } } return null; } /** * Tries to open a clojure file in an editor * @return an editor input if the file has been found, or null */ private static IEditorInput findEditorInput(IPackageFragmentRoot packageFragmentRoot, String searchedPackage, String searchedFileName) throws JavaModelException { // Find in package fragment IPackageFragment packageFragment = packageFragmentRoot.getPackageFragment(searchedPackage); IEditorInput editorInput = findEditorInput(packageFragmentRoot, packageFragment, searchedPackage, searchedFileName); if (editorInput != null) { return editorInput; } return findEditorInputInSourceAttachment(packageFragmentRoot, searchedPackage, searchedFileName); } private static IEditorInput findEditorInputInSourceAttachment(IPackageFragmentRoot packageFragmentRoot, String searchedPackage, String searchedFileName) throws JavaModelException { final IPath sourceAttachmentPath = packageFragmentRoot.getSourceAttachmentPath(); if (sourceAttachmentPath == null) { return null; } final String searchedPath = searchedPackage.replaceAll("\\.", "/"); final IResource workspaceResource = ResourcesPlugin.getWorkspace().getRoot() .findMember(sourceAttachmentPath); // Find in workspace if (workspaceResource != null) { if (workspaceResource.getType() == IResource.FOLDER) { IFolder folder = (IFolder) workspaceResource; IFile r = (IFile) folder.findMember(searchedPath + "/" + searchedFileName); if (r != null && r.exists()) { return new FileEditorInput(r); } } else { // Don't know what to do here } } // Find outside workspace or in archive final IPath sourceAbsolutePath = toOSAbsoluteIPath(sourceAttachmentPath); final File sourceFile = sourceAbsolutePath.toFile(); if (!sourceFile.exists()) { CCWPlugin.logWarning("sourceFile " + sourceFile + " does not exist form sourceAttachmentPath " + sourceAttachmentPath); // Nothing can be done } else if (sourceFile.isDirectory()) { final File maybeSourceFile = sourceAbsolutePath.append(searchedPath + "/" + searchedFileName).toFile(); if (maybeSourceFile.exists()) { return new LocalFileStorageEditorInput(new LocalFileStorage(maybeSourceFile)); } else { // Nothing, alas } } else { ZipFile zipFile; try { zipFile = new JarFile(sourceFile, true, JarFile.OPEN_READ); ZipEntry zipEntry = zipFile.getEntry(searchedPath + "/" + searchedFileName); if (zipEntry != null) { return new ZipEntryStorageEditorInput(new ZipEntryStorage(zipFile, zipEntry)); } else { // Nothing, alas } } catch (IOException e) { CCWPlugin.logError("Error trying to open " + sourceAbsolutePath, e); } } return null; } /** * File name, without extension */ private static String getFileName(final String path) { return (path.contains("/")) ? path.substring(1 + path.lastIndexOf('/')) : path; } private static boolean openInEditor(final String searchedNS, final String initialSearchedFileName, final int line, final String projectName, final boolean onlyExportedEntries) throws PartInitException { if (initialSearchedFileName == null) { return false; } final String searchedFileName = getFileName(initialSearchedFileName); final IProject project = ResourcesPlugin.getWorkspace().getRoot().getProject(projectName); final IJavaProject javaProject = JavaCore.create(project); try { System.out.println("search file name : " + searchedFileName); System.out.println("searched ns : " + searchedNS); final String searchedPackage = namespaceToPackage(searchedNS); System.out.println("searched package: " + searchedPackage); for (IPackageFragmentRoot packageFragmentRoot : javaProject.getAllPackageFragmentRoots()) { final IEditorInput editorInput = findEditorInput(packageFragmentRoot, searchedPackage, searchedFileName); if (editorInput != null) { IEditorPart editor = IDE.openEditor(CCWPlugin.getActivePage(), editorInput, ClojureEditor.ID); gotoEditorLine(editor, line); return true; } else { continue; } } } catch (JavaModelException e) { e.printStackTrace(); } return false; } private static String namespaceToPackage(final String searchedNS) { String packagePart = (searchedNS.contains(".")) ? searchedNS.substring(0, searchedNS.lastIndexOf(".")) : ""; return packagePart.replace('-', '_'); } public static IPath toOSAbsoluteIPath(IPath path) { if (ClojureCore.isWorkspaceRelativeIPath(path)) { boolean isFolder = path.getFileExtension() == null; if (isFolder) { path = ResourcesPlugin.getWorkspace().getRoot().getFolder(path).getLocation(); } else { path = ResourcesPlugin.getWorkspace().getRoot().getFile(path).getLocation(); } } return path; } public static boolean isWorkspaceRelativeIPath(IPath path) { return ResourcesPlugin.getWorkspace().getRoot().exists(path); } public static String getPackageNameFromNamespaceName(String nsName) { return (nsName.lastIndexOf(".") < 0) ? "" : nsName.substring(0, nsName.lastIndexOf(".")).replace('-', '_'); } public static String getNamespaceNameFromPackageName(String packageName) { return packageName.toString().replace('/', '.').replace('_', '-'); } private final static Pattern SEARCH_DECLARING_NAMESPACE_PATTERN = Pattern .compile("\\(\\s*(?:in-)?ns\\s+([^\\s\\)#\\[\\'\\{]+)"); public static String findDeclaringNamespace(String sourceText) { Var sexp = RT.var("paredit.parser", "sexp"); try { return (String) findDeclaringNamespace((Map) sexp.invoke(sourceText)); } catch (Exception e) { return null; } } public static String findDeclaringNamespace(Map tree) { try { return (String) staticAnalysis._("find-namespace", tree); } catch (Exception e) { CCWPlugin.logError("exception while trying to find declaring namespace for " + tree, e); return null; } } private final static Pattern HAS_NS_CALL_PATTERN = Pattern.compile("^\\s*\\(ns(\\s.*|$)", Pattern.MULTILINE); /** * @return true if a ns call is detected by a regex-based heuristic */ private static boolean hasNsCall(String sourceCode) { Matcher matcher = HAS_NS_CALL_PATTERN.matcher(sourceCode); return matcher.find(); } /** * Get the file's namespace name if the file is a lib. * <p> * Checks: * <ul> * <li>if the file is in the classpath</li> * <li>if the file ends with .clj</li> * <li>if the file contains a ns call(*)</li> * </ul> * If check is ko, returns nil, the file does not correspond to a 'lib' * <br/> * If check is ok, deduce the 'lib' name from the file path, e.g. a file * path such as <code>/projectName/src/foo/bar_baz/core.clj</code> will * return "foo.bar-baz.core". * </p> * <p> * (*): based on a simplistic regex based heuristic for maximum speed * </p> * @param file * @return null if not a lib, String with lib namespace name if a lib */ public static String findMaybeLibNamespace(IFile file) { try { IJavaProject jProject = JavaCore.create(file.getProject()); IPackageFragmentRoot[] froots = jProject.getAllPackageFragmentRoots(); for (IPackageFragmentRoot froot : froots) { if (froot.getPath().isPrefixOf(file.getFullPath())) { String ret = findMaybeLibNamespace(file, froot.getPath()); if (ret != null) { return ret; } else { continue; } } } } catch (JavaModelException e) { CCWPlugin.logError("unable to determine the fragment root of the file " + file, e); } return null; } /** * @return starting with a leading slash "/", the root classpath relative * path of this file */ public static String getAsRootClasspathRelativePath(IFile file) { try { IJavaProject jProject = JavaCore.create(file.getProject()); IPackageFragmentRoot[] froots = jProject.getAllPackageFragmentRoots(); for (IPackageFragmentRoot froot : froots) { if (froot.getPath().isPrefixOf(file.getFullPath())) { String ret = "/" + file.getFullPath().makeRelativeTo(froot.getPath()).toString(); return ret; } } } catch (JavaModelException e) { CCWPlugin.logError("unable to determine the fragment root of the file " + file, e); } return null; } /** * @see <code>findMaybeLibNamespace(IFile file)</code> */ public static String findMaybeLibNamespace(IFile file, IPath sourcePath) { if (!CLOJURE_FILE_EXTENSION.equals(file.getFileExtension())) { return null; } IPath path = file.getFullPath().removeFirstSegments(sourcePath.segmentCount()).removeFileExtension(); if (path == null) { // file is not on the classpath return null; } else { String sourceCode = getFileText(file); if (hasNsCall(sourceCode)) { // System.out.println("path.toPortableString()" + path.toPortableString()); return getNamespaceNameFromPackageName(path.toPortableString()); } else { // System.out.println("did not find a ns call in source code of " + file); return null; } } } private static String getFileText(IFile file) { try { return (String) RT.var("clojure.core", "slurp").invoke(file.getLocation().toOSString()); } catch (Exception e) { CCWPlugin.logError("error while getting text from file " + file, e); return null; } } private static boolean tryNonJavaResources(Object[] nonJavaResources, String searchedFileName, int line) throws PartInitException { for (Object nonJavaResource : nonJavaResources) { String nonJavaResourceName = null; if (IFile.class.isInstance(nonJavaResource)) { nonJavaResourceName = ((IFile) nonJavaResource).getName(); } else if (IJarEntryResource.class.isInstance(nonJavaResource)) { nonJavaResourceName = ((IJarEntryResource) nonJavaResource).getName(); } if (searchedFileName.equals(nonJavaResourceName)) { IEditorPart editor = EditorUtility.openInEditor(nonJavaResource); gotoEditorLine(editor, line); return true; } } return false; } /** * If line == -1 => goto last line */ public static void gotoEditorLine(Object editor, int line) { if (ITextEditor.class.isInstance(editor)) { ITextEditor textEditor = (ITextEditor) editor; IRegion lineRegion; try { if (line == -1) { line = textEditor.getDocumentProvider().getDocument(textEditor.getEditorInput()) .getNumberOfLines(); } lineRegion = textEditor.getDocumentProvider().getDocument(textEditor.getEditorInput()) .getLineInformation(line - 1); textEditor.selectAndReveal(lineRegion.getOffset(), lineRegion.getLength()); } catch (BadLocationException e) { // TODO popup for a feedback to the user ? CCWPlugin.logError("unable to select line " + line + " in the file", e); } } } }