ccw.ClojureCore.java Source code

Java tutorial

Introduction

Here is the source code for ccw.ClojureCore.java

Source

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

}