com.intellij.plugins.haxe.ide.hierarchy.HaxeHierarchyUtils.java Source code

Java tutorial

Introduction

Here is the source code for com.intellij.plugins.haxe.ide.hierarchy.HaxeHierarchyUtils.java

Source

/*
 * Copyright 2000-2013 JetBrains s.r.o.
 * Copyright 2014-2014 AS3Boyan
 * Copyright 2014-2014 Elias Ku
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.intellij.plugins.haxe.ide.hierarchy;

import com.intellij.codeInsight.TargetElementUtil;
import com.intellij.codeInsight.TargetElementUtilBase;
import com.intellij.openapi.actionSystem.CommonDataKeys;
import com.intellij.openapi.actionSystem.DataContext;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.project.Project;
import com.intellij.plugins.haxe.lang.psi.*;
import com.intellij.plugins.haxe.lang.psi.impl.AbstractHaxePsiClass;
import com.intellij.plugins.haxe.lang.psi.impl.AnonymousHaxeTypeImpl;
import com.intellij.psi.*;
import com.intellij.psi.impl.source.resolve.reference.impl.PsiMultiReference;
import com.intellij.psi.impl.source.tree.LeafPsiElement;
import com.intellij.psi.util.MethodSignatureUtil;
import org.apache.commons.lang.NotImplementedException;
import org.apache.log4j.Level;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.util.ArrayList;

/**
 * Created by ebishton on 9/4/14.
 *
 * A set of utility functions that support the HierarchyProviders.
 */
public class HaxeHierarchyUtils {
    private static final Logger LOG = Logger.getInstance("#com.intellij.ide.hierarchy.HaxeHierarchyUtils");

    static {
        LOG.setLevel(Level.DEBUG);
    }

    private HaxeHierarchyUtils() {
        throw new NotImplementedException("Static use only.");
    }

    /**
     * Given a PSI id element, find out if it -- or one of its parents --
     * references a class, and, if so, returns the PSI element for the class.
     *
     * @param id A PSI element for an identifier (e.g. variable name).
     * @return A PSI class element, or null if not found.
     */
    @Nullable
    public static HaxeClass findReferencedClassForId(@NotNull LeafPsiElement id) {
        if (null == id) {
            return null;
        }

        PsiReference found = id.findReferenceAt(0);
        PsiElement resolved = null;
        if (found instanceof PsiMultiReference) {
            for (PsiReference ref : ((PsiMultiReference) found).getReferences()) {
                PsiElement target = ref.resolve();
                if (null != target && target instanceof PsiClass) {
                    resolved = target;
                    break;
                }
            }
        } else {
            resolved = found.resolve();
        }

        if (LOG.isDebugEnabled()) {
            LOG.debug("findReferencedClassForID found " + resolved);
        }

        return ((resolved instanceof HaxeClass) ? ((HaxeClass) resolved) : null);
    }

    /**
     * Retrieve the list of classes implemented in the given File.
     *
     * @param psiRoot - File to search.
     * @return An array of found classes, or an empty array if none.
     */
    public static HaxeClass[] getClassList(@NotNull HaxeFile psiRoot) {

        ArrayList<HaxeClass> classes = new ArrayList<HaxeClass>();
        for (PsiElement child : psiRoot.getChildren()) {
            if (child instanceof HaxeClass) {
                classes.add((HaxeClass) child);
            }
        }
        HaxeClass[] return_type = {};
        return (classes.toArray(return_type));
    }

    /**
     * Get the PSI element for the class containing the currently focused
     * element.  Anonymous classes can be excluded if desired.
     *
     * @param context - editing context
     * @param allowAnonymous - flag to allow anonymous classes or not.
     * @return The PSI element representing the containing class.
     */
    @Nullable
    public static AbstractHaxePsiClass getContainingClass(@NotNull DataContext context, boolean allowAnonymous) {
        if (LOG.isDebugEnabled()) {
            LOG.debug("getContainingClass " + context);
        }

        final Project project = CommonDataKeys.PROJECT.getData(context);
        if (project == null) {
            if (LOG.isDebugEnabled()) {
                LOG.debug("No project");
            }
            return null;
        }

        final Editor editor = CommonDataKeys.EDITOR.getData(context);
        if (LOG.isDebugEnabled()) {
            LOG.debug("editor " + editor);
        }
        if (editor != null) {
            final PsiFile file = PsiDocumentManager.getInstance(project).getPsiFile(editor.getDocument());
            if (file == null) {
                if (LOG.isDebugEnabled()) {
                    LOG.debug("No file found.");
                }
                return null;
            }

            final PsiElement targetElement = TargetElementUtilBase.findTargetElement(editor,
                    TargetElementUtilBase.ELEMENT_NAME_ACCEPTED | TargetElementUtilBase.REFERENCED_ELEMENT_ACCEPTED
                            | TargetElementUtilBase.LOOKUP_ITEM_ACCEPTED);
            if (LOG.isDebugEnabled()) {
                LOG.debug("target element " + targetElement);
            }
            if (targetElement instanceof AbstractHaxePsiClass) {
                return (AbstractHaxePsiClass) targetElement;
            }

            // Haven't found it yet, walk the PSI tree toward the root.
            final int offset = editor.getCaretModel().getOffset();
            PsiElement element = file.findElementAt(offset);
            while (element != null) {
                if (LOG.isDebugEnabled()) {
                    LOG.debug("context element " + element);
                }
                if (element instanceof HaxeFile) {
                    // If we get to the file node, then we're outside of a class definition.
                    // No need to look further.
                    return null;
                }
                if (element instanceof AbstractHaxePsiClass) {
                    // Keep looking if we don't allow anonymous classes.
                    if (allowAnonymous || !(element instanceof AnonymousHaxeTypeImpl)) {
                        return (AbstractHaxePsiClass) element;
                    }
                }
                element = element.getParent();
            }

            return null;
        } else {
            final PsiElement element = CommonDataKeys.PSI_ELEMENT.getData(context);
            return element instanceof AbstractHaxePsiClass ? (AbstractHaxePsiClass) element : null;
        }
    }

    /**
     * Retrieve the PSI element for the file containing the given
     * context (focus element).
     *
     * @param context - editing context
     * @return The PSI node representing the file element.
     */
    @Nullable
    public static HaxeFile getContainingFile(@NotNull DataContext context) {
        if (LOG.isDebugEnabled()) {
            LOG.debug("getContainingFile " + context);
        }

        // XXX: EMB: Can we just ask for the node at offset 0??
        PsiElement element = getPsiElement(context);
        while (element != null) {
            if (element instanceof HaxeFile) {
                return (HaxeFile) element;
            }
            element = element.getParent();
        }
        return null;
    }

    /**
     * Retrieve the PSI element for the given context (focal point).
     * Returns the leaf-node element at the exact position in the PSI.
     * This does NOT attempt to locate a higher-order PSI element as
     * {@link TargetElementUtilBase#findTargetElement} would.
     *
     * @param context - editing context
     * @return The PSI element at the caret position.
     */
    @Nullable
    public static PsiElement getPsiElement(@NotNull DataContext context) {
        if (LOG.isDebugEnabled()) {
            LOG.debug("getPsiElement " + context);
        }

        PsiElement element = null;

        final Project project = CommonDataKeys.PROJECT.getData(context);
        if (project == null) {
            if (LOG.isDebugEnabled()) {
                LOG.debug("No project");
            }
            return null;
        }

        final Editor editor = CommonDataKeys.EDITOR.getData(context);
        if (LOG.isDebugEnabled()) {
            LOG.debug("editor " + editor);
        }
        if (editor != null) {
            final PsiFile file = PsiDocumentManager.getInstance(project).getPsiFile(editor.getDocument());
            if (file == null) {
                if (LOG.isDebugEnabled()) {
                    LOG.debug("No file found.");
                }
                return null;
            }

            final int offset = editor.getCaretModel().getOffset();
            element = file.findElementAt(offset);
        } else {
            element = CommonDataKeys.PSI_ELEMENT.getData(context);
        }
        return element;
    }

    @Nullable
    public static PsiElement getReferencedElement(@NotNull DataContext context) {
        PsiElement element = null;

        final Editor editor = CommonDataKeys.EDITOR.getData(context);
        if (editor != null) {
            element = TargetElementUtil.findTargetElement(editor,
                    TargetElementUtil.REFERENCED_ELEMENT_ACCEPTED | TargetElementUtil.ELEMENT_NAME_ACCEPTED);
        }

        return element;
    }

    /**
     * Determine if there is a method that is the target of the current
     * action, and, if so, return it.
     *
     * @param context Editor context.
     * @return The PSI method if the current context points at a method,
     *         null otherwise.
     */
    @Nullable
    public static HaxeMethod getTargetMethod(@NotNull DataContext context) {

        if (LOG.isDebugEnabled()) {
            LOG.debug("getTargetMethod " + context);
        }

        final Project project = CommonDataKeys.PROJECT.getData(context);
        if (null == project) {
            if (LOG.isDebugEnabled()) {
                LOG.debug("No project");
            }
            return null;
        }

        final Editor editor = CommonDataKeys.EDITOR.getData(context);

        if (null == editor) {
            if (LOG.isDebugEnabled()) {
                LOG.debug("No editor");
            }

            final PsiElement element = CommonDataKeys.PSI_ELEMENT.getData(context);
            return element instanceof HaxeMethod ? (HaxeMethod) element : null;
        }

        if (LOG.isDebugEnabled()) {
            LOG.debug("editor " + editor);
        }

        final PsiFile file = PsiDocumentManager.getInstance(project).getPsiFile(editor.getDocument());
        if (file == null) {
            if (LOG.isDebugEnabled()) {
                LOG.debug("No file found.");
            }
            return null;
        }

        final PsiElement targetElement = TargetElementUtilBase.findTargetElement(editor,
                TargetElementUtilBase.ELEMENT_NAME_ACCEPTED | TargetElementUtilBase.REFERENCED_ELEMENT_ACCEPTED
                        | TargetElementUtilBase.LOOKUP_ITEM_ACCEPTED);
        if (LOG.isDebugEnabled()) {
            LOG.debug("target element " + targetElement);
        }

        if (targetElement instanceof HaxeMethod) {
            LOG.debug("target element " + targetElement);
            return ((HaxeMethod) targetElement);
        }

        // Haven't found it yet, walk the PSI tree toward the root.
        final int offset = editor.getCaretModel().getOffset();
        PsiElement element = file.findElementAt(offset);
        while (element != null) {
            if (LOG.isDebugEnabled()) {
                LOG.debug("context element " + element);
            }
            if (element instanceof HaxeFile) {
                // If we get to the file node, then we're outside of a class definition.
                // No need to look further.
                return null;
            }
            if (element instanceof HaxeMethod) {
                return ((HaxeMethod) element);
            }
            element = element.getParent();
        }

        return null;
    }

    /**
     * Determine the class (PSI element), if any, that is referenced by the
     * given reference expression.
     *
     * @param element A PSI reference expression.
     * @return The associated class, if any.  null if not found.
     */
    @Nullable
    public static HaxeClass resolveClassReference(@NotNull HaxeReference element) {
        HaxeClassResolveResult result = element.resolveHaxeClass();
        HaxeClass pclass = result == null ? null : result.getHaxeClass();
        return pclass;
    }

    // Lifted from MethodHierarchyUtil, which needed the cannotBeOverriding helper
    // to be overridden.
    /**
     * Locates a potentially overridden method in a sub-class or an
     * intermediate parent -- up to the class containing the base method.
     *
     * Note: This CANNOT be used to find a method in a super-class of
     * baseMethod's class. Only classes that are sub-classes of baseMethod are
     * considered.
     *
     * @param baseMethod The method that may be overridden
     * @param aClass The sub-class to start inspecting at.
     * @param checkBases Whether to continue to further base classes.
     * @return The PsiMethod in the given class or the closest superclass.
     */
    public static PsiMethod findBaseMethodInClass(final PsiMethod baseMethod, final PsiClass aClass,
            final boolean checkBases) {
        if (baseMethod == null)
            return null; // base method is invalid
        if (cannotBeOverriding(baseMethod))
            return null;
        return MethodSignatureUtil.findMethodBySuperMethod(aClass, baseMethod, checkBases);
    }

    /**
     * Figure out if a method can override a lower one.
     * @param method The method to test.
     * @return true if the method can override one in a superclass, false if not.
     */
    public static boolean cannotBeOverriding(final PsiMethod method) {
        // Note that in Haxe, a private method can override another private
        // method, and so can a public method (but private can't override public).
        final PsiClass parentClass = method.getContainingClass();
        return parentClass == null || method.isConstructor() || method.hasModifierProperty(PsiModifier.STATIC);
    }

} // END class HaxeHierarchyUtils