/*
* Copyright 2000-2009 JetBrains s.r.o.
*
* 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.lang.javascript.uml;
import com.intellij.diagram.AbstractDiagramElementManager;
import com.intellij.diagram.presentation.DiagramState;
import com.intellij.javascript.flex.resolve.FlexResolveHelper;
import com.intellij.lang.javascript.JSBundle;
import com.intellij.lang.javascript.JavaScriptSupportLoader;
import com.intellij.lang.javascript.psi.*;
import com.intellij.lang.javascript.psi.ecmal4.JSClass;
import com.intellij.lang.javascript.psi.ecmal4.JSPackageStatement;
import com.intellij.lang.javascript.psi.ecmal4.XmlBackedJSClass;
import com.intellij.lang.javascript.psi.ecmal4.XmlBackedJSClassFactory;
import com.intellij.lang.javascript.psi.ecmal4.impl.ActionScriptClassImpl;
import com.intellij.lang.javascript.psi.impl.JSFunctionImpl;
import com.intellij.lang.javascript.psi.impl.JSPsiImplUtils;
import com.intellij.lang.javascript.psi.resolve.JSResolveUtil;
import com.intellij.lang.javascript.psi.util.JSUtils;
import com.intellij.lang.javascript.ui.JSFormatUtil;
import com.intellij.openapi.actionSystem.CommonDataKeys;
import com.intellij.openapi.actionSystem.DataContext;
import com.intellij.openapi.actionSystem.LangDataKeys;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Iconable;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.*;
import com.intellij.psi.impl.ElementBase;
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.util.PsiUtilCore;
import com.intellij.psi.xml.XmlFile;
import com.intellij.ui.SimpleColoredText;
import com.intellij.ui.SimpleTextAttributes;
import com.intellij.util.PlatformIcons;
import com.intellij.util.containers.ContainerUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import java.awt.*;
import java.util.*;
import java.util.List;
public class FlashUmlElementManager extends AbstractDiagramElementManager<Object> {
private final FlashUmlProvider myUmlProvider;
private static final Logger LOG = Logger.getInstance("#com.intellij.lang.javascript.uml.FlashUmlElementManager");
public FlashUmlElementManager(FlashUmlProvider umlProvider) {
myUmlProvider = umlProvider;
}
public boolean isAcceptableAsNode(Object element) {
return isAcceptableAsNodeStatic(element);
}
public static boolean isAcceptableAsNodeStatic(Object element) {
if (element instanceof JSClass) {
return true;
}
else if (element instanceof JSPackageStatement) {
return true;
}
else if (element instanceof JSFile) {
return JSPsiImplUtils.findQualifiedElement((JSFile)element) instanceof JSClass;
}
else if (element instanceof PsiFile && JavaScriptSupportLoader.isFlexMxmFile((PsiFile)element)) {
return true;
}
else if (element instanceof PsiDirectory) {
PsiDirectory directory = (PsiDirectory)element;
String packageName = JSResolveUtil.getExpectedPackageNameFromFile(directory.getVirtualFile(), directory.getProject());
return packageName != null && packageExists(directory.getProject(), packageName, GlobalSearchScope.allScope(directory.getProject()));
}
return false;
}
public static boolean packageExists(final Project project, final String packageName, final GlobalSearchScope scope) {
return JSUtils.packageExists(packageName, scope) || FlexResolveHelper.mxmlPackageExists(packageName, project, scope);
}
public PsiElement findInDataContext(DataContext context) {
PsiElement element = CommonDataKeys.PSI_ELEMENT.getData(context);
if (isAcceptableAsNode(element)) {
return element;
}
// if caret stands on a member or whitespace, show diagram for the enclosing class
final Editor editor = LangDataKeys.EDITOR.getData(context);
if (editor != null) {
final PsiFile file = CommonDataKeys.PSI_FILE.getData(context);
if (file != null) {
PsiElement elementAtOffset = file.findElementAt(editor.getCaretModel().getOffset());
final PsiNamedElement enclosing = PsiTreeUtil.getParentOfType(elementAtOffset, JSClass.class, JSFile.class, XmlFile.class);
if (enclosing instanceof JSClass) {
element = enclosing;
}
else if (enclosing instanceof JSFile) {
final XmlBackedJSClass clazz = JSResolveUtil.getXmlBackedClass((JSFile)enclosing);
if (clazz != null) {
return clazz;
}
JSElement qualified = JSPsiImplUtils.findQualifiedElement((JSFile)enclosing);
if (qualified instanceof JSClass) {
return qualified;
}
}
if (enclosing instanceof XmlFile && JavaScriptSupportLoader.isFlexMxmFile((PsiFile)enclosing)) {
return XmlBackedJSClassFactory.getXmlBackedClass((XmlFile)enclosing);
}
}
}
// handle PsiPackage (invoked when 'View as packages' mode is selected)
Project project = CommonDataKeys.PROJECT.getData(context);
if (project != null) {
VirtualFile file = CommonDataKeys.VIRTUAL_FILE.getData(context);
if (file != null) {
PsiDirectory directory = PsiManager.getInstance(project).findDirectory(file);
if (directory != null && isAcceptableAsNode(directory)) {
return directory;
}
}
}
return element;
}
public PsiElement[] getNodeItems(Object parent) {
if (parent instanceof JSClass) {
final JSClass clazz = (JSClass)parent;
if (!clazz.isValid()) { return PsiElement.EMPTY_ARRAY; }
final List<PsiElement> elements = new ArrayList<>();
ContainerUtil.addAll(elements, clazz.getFields());
boolean isInterface = clazz.isInterface();
for (JSFunction method : clazz.getFunctions()) {
if (isInterface && method.getKind() == JSFunction.FunctionKind.CONSTRUCTOR) continue;
if (method.getKind() != JSFunction.FunctionKind.SETTER ||
clazz.findFunctionByNameAndKind(method.getName(), JSFunction.FunctionKind.GETTER) == null) {
elements.add(method);
}
}
// TODO: need to perform kind sorting
if (elements.isEmpty()) {
return PsiElement.EMPTY_ARRAY;
}
else if (!(clazz instanceof ActionScriptClassImpl) || clazz.getStub() == null) {
// this sort causes parsing in order to get ast node offset but
// when we have class on stub our fields / functions already in natural order
// TODO once we have stubs for xmlbackedclass we should update the code
Collections.sort(elements, (o1, o2) -> o1.getTextOffset() - o2.getTextOffset());
}
return PsiUtilCore.toPsiElementArray(elements);
}
return PsiElement.EMPTY_ARRAY;
}
public boolean canCollapse(Object element) {
return false;
}
public boolean isContainerFor(Object parent, Object child) {
//if (parent instanceof JSPackage && child instanceof JSQualifiedNamedElement) {
// JSQualifiedNamedElement psiQualifiedNamedElement = (JSQualifiedNamedElement)child;
// JSPackage psiPackage = (JSPackage)parent;
// return psiQualifiedNamedElement.getQualifiedName() != null &&
// psiQualifiedNamedElement.getQualifiedName().startsWith(psiPackage.getQualifiedName());
//}
return false;
}
public String getElementTitle(Object element) {
if (element instanceof JSNamedElement) {
return ((JSNamedElement)element).getName();
}
else if (element instanceof String) {
return JSFormatUtil.formatPackage((String)element);
}
else if (element instanceof JSFile) {
//noinspection ConstantConditions
return JSPsiImplUtils.findQualifiedElement((JSFile)element).getName();
}
else if (element instanceof XmlFile && JavaScriptSupportLoader.isFlexMxmFile((PsiFile)element)) {
//noinspection ConstantConditions
return ((XmlFile)element).getVirtualFile().getNameWithoutExtension();
}
else {
return null;
}
}
public SimpleColoredText getItemName(Object element, DiagramState presentation) {
if (element instanceof JSFunction) {
return getMethodPresentableName((JSFunction)element);
}
else if (element instanceof JSVariable) {
return getFieldPresentableName((JSVariable)element);
}
else if (element instanceof JSClass) {
return getClassPresentableName((JSClass)element);
}
else if (element instanceof String) {
String s = (String)element;
return decorate(getPackageDisplayName(s));
}
else if (element instanceof JSPackageStatement) {
return decorate(((JSPackageStatement)element).getQualifiedName());
}
else if (element instanceof JSFile) {
return decorate(getElementTitle(element));
}
else if (element instanceof XmlFile && JavaScriptSupportLoader.isFlexMxmFile((PsiFile)element)) {
return decorate(getElementTitle(element));
}
else if (element instanceof PsiDirectory) {
PsiDirectory directory = (PsiDirectory)element;
String qName = JSResolveUtil.getExpectedPackageNameFromFile(directory.getVirtualFile(), directory.getProject());
return decorate(getElementTitle(qName));
}
return null;
}
private static String getPackageDisplayName(String s) {
return s.length() > 0 ? s : JSBundle.message("top.level");
}
private SimpleColoredText decorate(String name) {
int style = SimpleTextAttributes.STYLE_BOLD;
final SimpleColoredText text = new SimpleColoredText();
text.append(name, new SimpleTextAttributes(style, getFGColor()));
return text;
}
private SimpleColoredText getClassPresentableName(JSClass clazz) {
int style = SimpleTextAttributes.STYLE_BOLD;
if (clazz.isDeprecated()) style |= SimpleTextAttributes.STYLE_STRIKEOUT;
if (!clazz.isPhysical()) style |= SimpleTextAttributes.STYLE_ITALIC;
final SimpleColoredText text = new SimpleColoredText();
String name = StringUtil.notNullize(clazz.getName());
text.append(FlashUmlVfsResolver.fixVectorTypeName(name), new SimpleTextAttributes(style, getFGColor()));
return text;
}
private SimpleColoredText getMethodPresentableName(JSFunction method) {
int style = SimpleTextAttributes.STYLE_PLAIN;
if (method.isDeprecated()) style |= SimpleTextAttributes.STYLE_STRIKEOUT;
if (!method.isPhysical()) style |= SimpleTextAttributes.STYLE_ITALIC;
final SimpleColoredText text = new SimpleColoredText();
text.append(getMethodText(method),
new SimpleTextAttributes(style, getFGColor()));
return text;
}
private SimpleColoredText getFieldPresentableName(@NotNull JSVariable field) {
int style = SimpleTextAttributes.STYLE_PLAIN;
if (field.isDeprecated()) style |= SimpleTextAttributes.STYLE_STRIKEOUT;
if (!field.isPhysical()) style |= SimpleTextAttributes.STYLE_ITALIC;
return new SimpleColoredText(getFieldText(field), new SimpleTextAttributes(style, getFGColor()));
}
public static String getMethodText(JSFunction method) {
return JSFormatUtil.formatMethod(method, JSFormatUtil.SHOW_NAME | JSFormatUtil.SHOW_PARAMETERS, JSFormatUtil.SHOW_TYPE);
}
public static String getFieldText(JSVariable field) {
return JSFormatUtil.formatField(field, JSFormatUtil.SHOW_NAME);
}
private Color getFGColor() {
return myUmlProvider.getColorManager().getNodeForegroundColor(false);
}
public SimpleColoredText getItemType(Object element) {
String text = getPresentableTypeStatic(element);
return text != null ? new SimpleColoredText(text, new SimpleTextAttributes(SimpleTextAttributes.STYLE_PLAIN, getFGColor())) : null;
}
@Nullable
public static String getPresentableTypeStatic(Object element) {
if (element instanceof JSFunction) {
return JSFormatUtil.formatMethod(((JSFunction)element), JSFormatUtil.SHOW_TYPE, 0);
}
else if (element instanceof JSVariable) {
return JSFormatUtil.formatField(((JSVariable)element), JSFormatUtil.SHOW_TYPE);
}
else {
return null;
}
}
public String getNodeTooltip(Object element) {
if (element instanceof JSClass) {
return "<html><b>" + JSFormatUtil.formatClass((JSClass)element, JSFormatUtil.SHOW_FQ_NAME) + "</b></html>";
}
return "<html><b>" + getPackageDisplayName((String)element) + "</b></html>";
}
@Override
public Icon getItemIcon(Object element, DiagramState presentation) {
return getNodeElementIconStatic(element);
}
public static Icon getNodeElementIconStatic(Object element) {
if (element instanceof JSFunction) {
JSFunction method = (JSFunction)element;
if (method.getKind() == JSFunction.FunctionKind.GETTER || method.getKind() == JSFunction.FunctionKind.SETTER) {
final Icon propertyIcon = JSFormatUtil.getPropertyIcon(method, true);
return ElementBase.buildRowIcon(propertyIcon, method.getAttributeList().getAccessType().getIcon());
}
else if (method.getKind() == JSFunction.FunctionKind.CONSTRUCTOR) {
return ElementBase.buildRowIcon(JSFunctionImpl.CONSTRUCTOR_ICON, method.getAttributeList().getAccessType().getIcon());
}
}
return element instanceof Iconable ? ((Iconable)element).getIcon(0) : PlatformIcons.ERROR_INTRODUCTION_ICON;
}
}