org.codecover.eclipse.utils.EclipseMASTLinkage.java Source code

Java tutorial

Introduction

Here is the source code for org.codecover.eclipse.utils.EclipseMASTLinkage.java

Source

/******************************************************************************
 * Copyright (c) 2007 Stefan Franke, Robert Hanussek, Benjamin Keil,          *
 *                    Steffen Kie, Johannes Langauf,                         *
 *                    Christoph Marian Mller, Igor Podolskiy,                *
 *                    Tilmann Scheller, Michael Starzmann, Markus Wittlinger  *
 * 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                                  *
 ******************************************************************************/

package org.codecover.eclipse.utils;

import org.codecover.eclipse.CodeCoverPlugin;
import org.codecover.model.MASTBuilder;
import org.codecover.model.TestSessionContainer;
import org.codecover.model.mast.*;
import org.codecover.model.utils.Logger;
import org.codecover.model.utils.file.FileTool;

import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.jdt.core.*;
import org.eclipse.jdt.core.search.*;
import org.eclipse.jdt.internal.core.SourceType;
import org.eclipse.jdt.ui.JavaUI;
import org.eclipse.jface.text.ITextSelection;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.ui.IEditorPart;
import org.eclipse.ui.PartInitException;
import org.eclipse.ui.texteditor.ITextEditor;

import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.Charset;
import java.util.*;

/**
 * Utilities to provide mappings between Eclipse Models and the MAST.
 * 
 * @author Johannes Langauf
 * @version 1.0 ($Id$)
 */
public abstract class EclipseMASTLinkage {

    /* Note: Mapping between SourceFile and IFile is unavailable, because:
     * 1. source files don't have paths relative to root (could be added)
     * 2. instrumentation root is unknown (could be added)
     * 3. no Mapping between instrumentation root and project root is possible
     *   (no way, if TSC has no added information from Eclipse)
     * 
     * Currently the best approximation is to search the workspace for
     * equivalent FileName and Content.
     */

    /**
     * Select and show <code>loc</code> in an editor with coverage
     * highlighting.
     * 
     * @param editor
     * the editor showing the unchanged <code>loc.getSourceFile()</code>
     * @param loc
     * the position to show
     * 
     * @return true
     */
    public static boolean showInEditor(ITextEditor editor, Location loc) {
        editor.selectAndReveal(loc.getStartOffset(), loc.getLength());

        return true;
    }

    /**
     * Open a java editor that contains the code of the header of hLev.
     * 
     * @param hLev
     *            the hLev that is a top level class or contained in one
     * @param tsc
     *            the Java test session container that contains
     *            <code>hLev</code>
     * @return the java editor
     */
    public static ITextEditor openClassInEditor(HierarchyLevel hLev, TestSessionContainer tsc) {
        ITextEditor result = null;

        hLev = MAST.getTopLevelClass(hLev, tsc);

        /* find corresponding compilation unit by class name */
        String fqn = MAST.getFQName(hLev, tsc);
        Set<ICompilationUnit> cuSet;
        cuSet = EclipseMASTLinkage.Eclipse.findCompilationUnit(fqn);

        /* got matches by qualified class name */

        /* filter for matches that fit class */
        Location target = MAST.getHighlightLocation(hLev);
        SourceFile source = target.getFile();
        Set<ICompilationUnit> unchangedCu = null;
        unchangedCu = new HashSet<ICompilationUnit>(cuSet.size());
        for (ICompilationUnit cu : cuSet) {
            /* check identity */
            IResource resource = cu.getResource();
            if (resource != null) {
                IFile file = (IFile) resource;
                if (equals(file, source)) {
                    unchangedCu.add(cu);
                }
            }
        }

        int matchCount = unchangedCu.size();
        if (matchCount > 0) {
            if (matchCount > 1) {
                CodeCoverPlugin.getDefault().getLogger()
                        .warning("found too many matching cu (using first):" + matchCount); //$NON-NLS-1$
            }
            result = Eclipse.openMember(unchangedCu.iterator().next());
        } else {
            CodeCoverPlugin.getDefault().getLogger().debug("found no matching cu for: " + fqn); //$NON-NLS-1$
        }

        return result;
    }

    /**
     * Check if the given resouce and sourceFile are identical. i.e.
     * instrumenting the resource with the current settings would produce the
     * same SourceFile as instrumenting <code>sourceFile</code>.
     *
     * @param file
     * a synchronized Eclipse resource
     * @param sourceFile
     * the original source file to match
     * 
     * @return true, iff <code>resource</code> matches all attributes in
     * <code>sourceFile</code>
     */
    @SuppressWarnings("nls")
    public static boolean equals(IFile file, SourceFile sourceFile) {
        if (!file.exists()) {
            throw new IllegalArgumentException("file does not exist");
        }

        /* file name (without path) */
        if (!sourceFile.getFileName().equals(file.getName())) {
            return false;
        }

        /* extract content of file */
        String contentOfFile = null;
        try {
            contentOfFile = Eclipse.getContent(file);
        } catch (Exception e) {
            //TODO: is there a better way to handle files that are not synchronized (happened) or not local
            CodeCoverPlugin.getDefault().getLogger()
                    .warning("possible false" + " negative comparing '" + file.toString() + "' to match '"
                            + sourceFile.toString() + "', because content is unavailable:", e);
            return false;
        }

        if (!sourceFile.getContent().equals(contentOfFile)) {
            return false;
        }

        return true;
    }

    /**
     * Check if the given resource and sourceFile are identical. i.e.
     * instrumenting the resource with the current settings would produce the
     * same as instrumenting <code>sourceFile</code>.
     * 
     * @param resource
     *            the Eclipse resource
     * @param sourceFile
     *            the original source file to match
     * @return true, iff <code>resource</code> matches all attributes in
     *         <code>sourceFile</code>
     */
    public static boolean equals(IResource resource, SourceFile sourceFile) {
        IFile file;
        if (resource instanceof IFile) {
            /* shortcut */
            file = (IFile) resource;
        } else {
            file = (IFile) resource.getAdapter(IFile.class);
            if (file == null) {
                throw new IllegalArgumentException("resource is no file"); //$NON-NLS-1$
            }
        }
        return equals(file, sourceFile);
    }

    /**
     * Create a Location that represents the current selection of an editor.
     * 
     * @param editor
     * @param file
     * the file that is opened in <code>editor</code>
     * @return
     * the current selection of <code>editor</code>
     */
    @SuppressWarnings("nls")
    public static Location getSelection(ITextEditor editor, SourceFile file) {
        MASTBuilder builder = new MASTBuilder(Logger.NULL);

        /* determine cursor position and selection */
        int offset = -1;
        int length = -1;
        ISelection s = editor.getSelectionProvider().getSelection();
        ITextSelection selection;
        if (s instanceof ITextSelection) {
            selection = (ITextSelection) s;
            offset = selection.getOffset();
            length = selection.getLength();
        } else {
            throw new IllegalArgumentException("editor does not return a" + " proper ITextSelection: " + s);
        }

        /* offset and length are set to the selection of editor */

        return builder.createLocation(file, offset, offset + length);
    }

    /**
     * Find the corresponding CodeCover MAST HierarchyLevel to the given
     * Eclipse Java Element.
     * 
     * @param code
     *   root of code (MAST) to search
     * @param element
     *   search key
     * @return
     *   the HierarchyLevel of <i>element</i>, null if not found
     */
    public static HierarchyLevel findSource(HierarchyLevel code, IJavaElement element) {
        HierarchyLevel result = null; //null until element is found

        /* check input */
        if (code == null) {
            throw new IllegalArgumentException("code is null"); //$NON-NLS-1$
        }
        if (element == null) {
            throw new IllegalArgumentException("element is null"); //$NON-NLS-1$
        }

        /* get corresponding ICompilationUnit */
        ICompilationUnit compilationUnit;
        if (element.getElementType() == IJavaElement.COMPILATION_UNIT) {
            compilationUnit = (ICompilationUnit) element;
        } else {
            compilationUnit = (ICompilationUnit) element.getAncestor(IJavaElement.COMPILATION_UNIT);
        }

        if (compilationUnit != null) {

            /* Extract fully qualified class name with its package */
            String fileName = compilationUnit.getElementName();
            String className = fileName.split("\\.")[0]; //$NON-NLS-1$
            IPackageFragment pkgF = (IPackageFragment) compilationUnit.getAncestor(IJavaElement.PACKAGE_FRAGMENT);
            if (pkgF != null) {
                String path[];
                if (pkgF.getElementName().equals("")) { //$NON-NLS-1$
                    path = new String[] { className };
                } else {
                    className = pkgF.getElementName() + "." + className; //$NON-NLS-1$
                    path = className.split("\\."); //$NON-NLS-1$
                }

                /* find HierarchyLevel for the class by name */
                HierarchyLevel current = code;
                boolean found = true;

                for (int i = 0; i < path.length && found; ++i) {
                    found = false;

                    /* find next HierarchyLevel in path */
                    for (HierarchyLevel l : current.getChildren()) {
                        if (l.getName().equals(path[i])) {
                            current = l;
                            found = true;
                            break;
                        }
                    }
                }
                if (found) {

                    /* the whole path was successfully traversed */
                    result = current;
                }
            }
        }

        return result;
    }

    /**
     * Tools to work with MAST.
     * 
     * @version 1.0 ($Id$)
     * @author Johannes Langauf
     */
    public static class MAST {

        /**
         * Get a <code>Location</code> representing <code>hLev</code> in the
         * code. Does not have to contain all code of <code>hLev</code>.
         * 
         * @param hLev
         * a HierarchyLevel with a none empty header
         * 
         * @return a Location to represent <code>hLev</code>
         * @see HierarchyLevel#getHeader()
         */
        public static Location getHighlightLocation(HierarchyLevel hLev) {
            List<Location> locList = hLev.getHeader().getLocations();
            Location target;
            if (locList.size() > 0) {
                target = locList.get(0);
            } else {
                throw new IllegalArgumentException("hLev has no header Location."); //$NON-NLS-1$
            }

            return target;
        }

        /**
         * Get the top level class of a java hierarchy level as defined in the
         * JLS:
         * http://java.sun.com/docs/books/jls/third_edition/html/classes.html#246201
         * 
         * @param hLev
         * a top level class or its descendant
         * @param tsc
         * the java test session container that contains hLev
         * @return
         * the top level class that contains hLev or hLev, if it's the top level
         * class
         */
        public static HierarchyLevel getTopLevelClass(HierarchyLevel hLev, TestSessionContainer tsc) {
            HierarchyLevel parentPackage = hLev;
            HierarchyLevel topLevelClass = null;

            while (!parentPackage.getType().getInternalName().equals("package") //$NON-NLS-1$
                    && !parentPackage.getType().getInternalName().equals("default package")) { //$NON-NLS-1$
                topLevelClass = parentPackage;
                parentPackage = tsc.getParentOfHierarchyLevel(parentPackage);
            }

            return topLevelClass;
        }

        /**
         * Get the package of hLev.
         * 
         * @param hLev
         * a class or package
         * @param tsc
         * the java test session container that contains hLev
         * @return
         * the package of hLev, hLev itself if it's a package
         */
        public static HierarchyLevel getPackage(HierarchyLevel hLev, TestSessionContainer tsc) {
            //same as above, just return the parentPackage
            HierarchyLevel parentPackage = hLev;
            @SuppressWarnings("unused")
            HierarchyLevel topLevelClass = null;

            while (!parentPackage.getType().getInternalName().equals("package") //$NON-NLS-1$
                    && !parentPackage.getType().getInternalName().equals("default package")) { //$NON-NLS-1$
                topLevelClass = parentPackage;
                parentPackage = tsc.getParentOfHierarchyLevel(parentPackage);
            }

            return parentPackage;
        }

        /**
         * Return the fully qualified name of a compilation unit in a Java TSC.
         * Types of Hierarchy Levels can be found in
         * <code>HierarchyLevelTypeProvider</code>.
         * 
         * @param element
         * the compilation unit, valid types are class, interface, enum, &at;interface,  
         * @param tsc
         * the test session container containing <code>element</code>
         * 
         * @return the fully qualified name of <code>element</code>
         * @see org.codecover.instrumentation.java15.HierarchyLevelTypeProvider
         */
        @SuppressWarnings("nls")
        public static String getFQName(HierarchyLevel element, TestSessionContainer tsc) {
            if (element == null) {
                throw new IllegalArgumentException("HierarchyLevel is null");
            }
            String internalName = element.getType().getInternalName();
            if (!internalName.equals("class") && !internalName.equals("interface") && !internalName.equals("enum")
                    && !internalName.equals("@interface")) {
                throw new IllegalArgumentException("HierarchyLevel is no Compilation unit. Type: " + internalName);
            }
            if (tsc == null) {
                throw new IllegalArgumentException("tsc is null");
            }

            /* extract name of innermost level */
            String fqName = element.getName();
            element = tsc.getParentOfHierarchyLevel(element);

            /* add names of parent levels, not including the unnamed top level */
            while (!element.getType().getInternalName().equals("default package")) {
                fqName = element.getName() + "." + fqName;
                element = tsc.getParentOfHierarchyLevel(element);
            }
            return fqName;
        }
    }

    /**
     * Tools to work with Eclipse Java model.
     * 
     * @version 1.0 ($Id$)
     * @author Johanne Langauf
     */
    public static class Eclipse {

        /**
         * Read a whole file into a String.
         * 
         * @param file
         * the synchronized, existing, readable and local file
         * 
         * @return
         * the contents of the file
         * 
         * @see IResource#isSynchronized(int)
         * @see IResource#isLocal(int)
         */
        public static String getContent(IFile file) {
            String contentOfFile = null;
            try {
                InputStream inStream = file.getContents();
                Charset charsetOfFile = Charset.forName(file.getCharset());
                contentOfFile = FileTool.getContentFromStream(inStream, charsetOfFile);
            } catch (CoreException e) {
                boolean isSynchronized = false;
                boolean isLocal = false;
                try {
                    isSynchronized = file.isSynchronized(IResource.DEPTH_ZERO);
                    isLocal = file.isLocal(IResource.DEPTH_ZERO);
                } catch (Exception ex) {
                    CodeCoverPlugin.getDefault().getLogger().fatal("something is totally broken", ex); //$NON-NLS-1$
                }
                if (!isSynchronized) {
                    // There seems to be no way to handle this without
                    // bothering the user. Like the editor does. Asking
                    // the user if he want's to synchronize a file he hasn'
                    // t even seen like the editor does is stupid so we let
                    // callers take care of what they want.
                    throw new IllegalArgumentException("file is not in sync"); //$NON-NLS-1$
                }
                if (!isLocal) {
                    //TODO: Can this happen? - if so handle gracefully.
                    CodeCoverPlugin.getDefault().getLogger().error("Unexpected not local resource.  Please " //$NON-NLS-1$
                            + "file a bug with the whole message.", e); //$NON-NLS-1$
                    throw new IllegalArgumentException("file is not local"); //$NON-NLS-1$
                }

            } catch (IOException e) {
                CodeCoverPlugin.getDefault().getLogger().debug("Can't read: " //$NON-NLS-1$
                        + file.getFullPath().toOSString(), e);
                throw new IllegalArgumentException("File not readable."); //$NON-NLS-1$
            }
            return contentOfFile;
        }

        /**
         * Find a type by its name.
         * 
         * @param fQName
         * fully qualified name of the type
         * @return
         * list of matching compilation units
         */
        public static Set<ICompilationUnit> findCompilationUnit(String fQName) {
            final Set<ICompilationUnit> result = new HashSet<ICompilationUnit>(1);

            SearchPattern pattern = SearchPattern.createPattern(fQName, IJavaSearchConstants.TYPE,
                    IJavaSearchConstants.DECLARATIONS,
                    SearchPattern.R_CASE_SENSITIVE | SearchPattern.R_EXACT_MATCH);

            IJavaSearchScope scope = SearchEngine.createWorkspaceScope();
            //to narrow search, e.g. on one project you could use this:
            //           IJavaProject p = null;
            //           IJavaElement project[] = {p};
            //           IJavaSearchScope scope;
            //           scope = SearchEngine.createJavaSearchScope(project);

            SearchRequestor requestor = new SearchRequestor() {
                @Override
                public void acceptSearchMatch(SearchMatch match) throws CoreException {
                    if (match.getAccuracy() == SearchMatch.A_ACCURATE) {
                        addMatch(match.getResource());
                        addMatch(match.getElement());
                    }
                }

                private void addMatch(Object match) {
                    ICompilationUnit cu = null;
                    if (match instanceof SourceType) {
                        SourceType sf = (SourceType) match;
                        cu = sf.getCompilationUnit();
                    }
                    if (cu == null && match instanceof IJavaElement) {

                        IJavaElement adaptable = (IJavaElement) match;

                        cu = (ICompilationUnit) adaptable.getAdapter(ICompilationUnit.class);
                    }
                    if (cu != null) {
                        synchronized (result) {
                            result.add(cu);
                        }
                    }
                }
            };
            SearchEngine searchEngine = new SearchEngine();

            try {
                searchEngine.search(pattern, new SearchParticipant[] { SearchEngine.getDefaultSearchParticipant() },
                        scope, requestor, null);
            } catch (CoreException e) {
                CodeCoverPlugin.getDefault().getLogger().warning("Ignoring failed search for: " + fQName, e); //$NON-NLS-1$
            }

            return result;
        }

        /**
         * Open a Java-Element in an <code>ITextEditor</code>. Don't focus it.
         * 
         * @param member
         * the element to open
         * 
         * @return the editor showing <code>member</code>, null if member can't
         *  be opened
         */
        public static ITextEditor openMember(IMember member) {
            ICompilationUnit cu = member.getCompilationUnit();
            return openMember(cu);
        }

        /**
         * Open a Java-Element in an <code>ITextEditor</code>. Don't focus it.
         * 
         * @param cu
         * the element to open
         * 
         * @return the editor showing <code>cu</code>, null if member can't
         *  be opened
         */
        public static ITextEditor openMember(ICompilationUnit cu) {
            ITextEditor editor;
            try {
                IEditorPart ed = JavaUI.openInEditor(cu, false, false);
                editor = (ITextEditor) ed.getAdapter(ITextEditor.class);
            } catch (PartInitException e) {
                /* the editor could not be initialized or no workbench page is
                 * active */
                return null;
            } catch (JavaModelException e) {
                /* cu does not exist or an exception occurs while accessing its
                 * underlying resource */
                return null;
            }

            return editor;
        }
    }
}