/* * 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.*; import com.intellij.javascript.flex.mxml.MxmlJSClass; import com.intellij.javascript.flex.resolve.ActionScriptClassResolver; import com.intellij.lang.javascript.flex.ECMAScriptImportOptimizer; import com.intellij.lang.javascript.flex.FlexBundle; import com.intellij.lang.javascript.flex.ImportUtils; import com.intellij.lang.javascript.flex.XmlBackedJSClassImpl; import com.intellij.lang.javascript.flex.actions.newfile.NewFlexComponentAction; import com.intellij.lang.javascript.index.JSPackageIndex; import com.intellij.lang.javascript.index.JSPackageIndexInfo; import com.intellij.lang.javascript.psi.ecmal4.JSClass; import com.intellij.lang.javascript.psi.ecmal4.JSPackage; import com.intellij.lang.javascript.psi.ecmal4.JSQualifiedNamedElement; import com.intellij.lang.javascript.psi.ecmal4.JSReferenceList; import com.intellij.lang.javascript.psi.impl.JSPsiImplUtils; import com.intellij.lang.javascript.psi.resolve.JSInheritanceUtil; import com.intellij.lang.javascript.psi.resolve.JSResolveUtil; import com.intellij.lang.javascript.refactoring.FormatFixer; import com.intellij.lang.javascript.psi.util.JSProjectUtil; import com.intellij.lang.javascript.refactoring.util.JSRefactoringUtil; import com.intellij.openapi.project.Project; import com.intellij.openapi.ui.Messages; import com.intellij.openapi.util.ModificationTracker; import com.intellij.openapi.util.Pair; import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiManager; import com.intellij.psi.SmartPointerManager; import com.intellij.psi.SmartPsiElementPointer; import com.intellij.psi.search.GlobalSearchScope; import com.intellij.util.containers.ContainerUtil; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.*; import java.util.concurrent.Callable; /** * @author Konstantin Bulenkov * @author Kirill Safonov */ public class FlashUmlDataModel extends DiagramDataModel<Object> { private final Map<String, SmartPsiElementPointer<JSClass>> classesAddedByUser = new HashMap<>(); private final Map<String, SmartPsiElementPointer<JSClass>> classesRemovedByUser = new HashMap<>(); private final String initialPackage; private SmartPsiElementPointer<? extends PsiElement> myInitialElement; private final Set<String> packages = new HashSet<>(); private final Set<String> packagesRemovedByUser = new HashSet<>(); private final VirtualFile myEditorFile; private final SmartPointerManager spManager; public FlashUmlDataModel(final Project project, Object element, final VirtualFile file, DiagramProvider<Object> provider) { super(project, provider); myEditorFile = file; spManager = SmartPointerManager.getInstance(project); if (element instanceof JSClass) { initialPackage = null; myInitialElement = spManager.createSmartPsiElementPointer((JSClass)element); JSClass psiClass = (JSClass)element; classesAddedByUser.put(psiClass.getQualifiedName(), (SmartPsiElementPointer<JSClass>)myInitialElement); final Collection<JSClass> classes = JSInheritanceUtil.findAllParentsForClass(psiClass, true); for (JSClass aClass : classes) { classesAddedByUser.put(aClass.getQualifiedName(), spManager.createSmartPsiElementPointer(aClass)); } } else if (element instanceof String) { initialPackage = (String)element; final GlobalSearchScope searchScope = GlobalSearchScope.allScope(project); for (String aPackage : getSubPackages(initialPackage, searchScope)) { packages.add(aPackage); } for (JSClass jsClass : getClasses(initialPackage, searchScope)) { classesAddedByUser.put(jsClass.getQualifiedName(), spManager.createSmartPsiElementPointer(jsClass)); } } else { initialPackage = null; } } private static Collection<String> getSubPackages(final String packageName, final GlobalSearchScope searchScope) { final Collection<String> result = new HashSet<>(); JSPackageIndex.processElementsInScope(packageName, null, new JSPackageIndex.PackageElementsProcessor() { public boolean process(VirtualFile file, String name, JSPackageIndexInfo.Kind kind, boolean isPublic) { if (kind == JSPackageIndexInfo.Kind.PACKAGE) { result.add(StringUtil.getQualifiedName(packageName, name)); } return true; } }, searchScope, searchScope.getProject()); return result; } private static Collection<JSClass> getClasses(final String packageName, final GlobalSearchScope searchScope) { final Collection<JSClass> result = new HashSet<>(); JSPackageIndex.processElementsInScope(packageName, null, new JSPackageIndex.PackageElementsProcessor() { public boolean process(VirtualFile file, String name, JSPackageIndexInfo.Kind kind, boolean isPublic) { String qualifiedName = StringUtil.getQualifiedName(packageName, name); if (kind == JSPackageIndexInfo.Kind.CLASS || kind == JSPackageIndexInfo.Kind.INTERFACE) { PsiElement element = ActionScriptClassResolver.findClassByQNameStatic(qualifiedName, searchScope); if (element instanceof JSClass) { result.add((JSClass)element); } } return true; } }, searchScope, searchScope.getProject()); return result; } private final Collection<DiagramNode<Object>> myNodes = new HashSet<>(); private final Collection<DiagramEdge<Object>> myEdges = new HashSet<>(); private final Collection<DiagramEdge<Object>> myDependencyEdges = new HashSet<>(); private final Collection<DiagramNode<Object>> myNodesOld = new HashSet<>(); private final Collection<DiagramEdge<Object>> myEdgesOld = new HashSet<>(); private final Collection<DiagramEdge<Object>> myDependencyEdgesOld = new HashSet<>(); @NotNull public Collection<DiagramNode<Object>> getNodes() { return new ArrayList<>(myNodes); } @NotNull public Collection<DiagramEdge<Object>> getEdges() { if (myDependencyEdges.isEmpty()) { return myEdges; } else { Collection<DiagramEdge<Object>> allEdges = new HashSet<>(myEdges); allEdges.addAll(myDependencyEdges); return allEdges; } } @NotNull @NonNls public String getNodeName(final DiagramNode<Object> node) { Object element = getIdentifyingElement(node); if (element instanceof JSClass) { return "Class " + ((JSClass)element).getQualifiedName(); } else if (element instanceof String) { return "Package " + element; } return ""; } @Override public void removeNode(DiagramNode<Object> node) { removeElement(getIdentifyingElement(node)); } @Override public void removeEdge(DiagramEdge<Object> edge) { final Object source = edge.getSource().getIdentifyingElement(); final Object target = edge.getTarget().getIdentifyingElement(); final DiagramRelationshipInfo relationship = edge.getRelationship(); if (!(source instanceof JSClass) || !(target instanceof JSClass) || relationship == DiagramRelationshipInfo.NO_RELATIONSHIP) { return; } final JSClass fromClass = (JSClass)source; final JSClass toClass = (JSClass)target; if (JSProjectUtil.isInLibrary(fromClass)) { return; } if (fromClass instanceof XmlBackedJSClassImpl && !toClass.isInterface()) { Messages.showErrorDialog(fromClass.getProject(), FlexBundle.message("base.component.needed.message"), FlexBundle.message("remove.edge.title")); return; } if (Messages.showYesNoDialog(fromClass.getProject(), FlexBundle .message("remove.inheritance.link.prompt", fromClass.getQualifiedName(), toClass.getQualifiedName()), FlexBundle.message("remove.edge.title"), Messages.getQuestionIcon()) != Messages.YES) { return; } final Runnable runnable = () -> { JSReferenceList refList = !fromClass.isInterface() && toClass.isInterface() ? fromClass.getImplementsList() : fromClass.getExtendsList(); List<FormatFixer> formatters = new ArrayList<>(); JSRefactoringUtil.removeFromReferenceList(refList, toClass, formatters); if (!(fromClass instanceof XmlBackedJSClassImpl) && needsImport(fromClass, toClass)) { formatters.addAll(ECMAScriptImportOptimizer.executeNoFormat(fromClass.getContainingFile())); } FormatFixer.fixAll(formatters); }; DiagramAction .performCommand(getBuilder(), runnable, FlexBundle.message("remove.relationship.command.name"), null, fromClass.getContainingFile()); } private static boolean needsImport(JSClass context, JSClass referenced) { String packageName = StringUtil.getPackageName(referenced.getQualifiedName()); return !packageName.isEmpty() && !packageName.equals(StringUtil.getPackageName(context.getQualifiedName())); } public void refreshDataModel() { clearAll(); updateDataModel(); } @NotNull @Override public ModificationTracker getModificationTracker() { return PsiManager.getInstance(getProject()).getModificationTracker(); } private void clearAll() { clearAndBackup(myNodes, myNodesOld); clearAndBackup(myEdges, myEdgesOld); clearAndBackup(myDependencyEdges, myDependencyEdgesOld); } public void removeAllElements() { classesRemovedByUser.clear(); classesRemovedByUser.putAll(classesAddedByUser); classesAddedByUser.clear(); packagesRemovedByUser.clear(); packagesRemovedByUser.addAll(packages); packages.clear(); clearAll(); } private boolean isAllowedToShow(JSClass psiClass) { if (psiClass == null || !psiClass.isValid()) return false; final DiagramScopeManager<Object> scopeManager = getScopeManager(); if (scopeManager != null && !scopeManager.contains(psiClass)) return false; final PsiElement initialElement = getInitialElement(); if (isInsidePackages(psiClass)) return false; if (initialElement instanceof JSClass && equals(psiClass, (JSClass)initialElement)) return true; return true; } private static boolean equals(JSClass one, JSClass another) { return one != null && one.isValid() && another != null && another.isValid() && one.getQualifiedName() != null && one.getQualifiedName().equals(another.getQualifiedName()); } public synchronized void updateDataModel() { final Set<JSClass> classes = getAllClasses(); syncPackages(); final Set<JSClass> interfaces = new HashSet<>(); for (String psiPackage : packages) { if (FlashUmlElementManager.packageExists(getProject(), psiPackage, GlobalSearchScope.allScope(getProject()))) { myNodes.add(new FlashUmlPackageNode(psiPackage, getProvider())); } } for (JSClass psiClass : classes) { if (isAllowedToShow(psiClass)) { myNodes.add(new FlashUmlClassNode(psiClass, getProvider())); } if (psiClass.isInterface()) { interfaces.add(psiClass); } } for (JSClass psiClass : classes) { { DiagramNode<Object> source = findNode(psiClass); DiagramNode<Object> target = null; Collection<JSClass> processed = new ArrayList<>(); JSClass superClass = getSuperClass(psiClass, processed); while (target == null && superClass != null) { target = findNode(superClass); superClass = getSuperClass(superClass, processed); } if (source != null && target != null && source != target) { if (!((JSClass)getIdentifyingElement(source)).isInterface() || !JSResolveUtil.isObjectClass((JSClass)getIdentifyingElement(target))) { addEdge(source, target, psiClass.isInterface() ? FlashUmlRelationship.INTERFACE_GENERALIZATION : FlashUmlRelationship.GENERALIZATION); } } } for (JSClass inter : psiClass.getImplementedInterfaces()) { if (interfaces.contains(inter)) { DiagramNode<Object> source = findNode(psiClass); DiagramNode<Object> target = findNode(inter); if (source != null && target != null && source != target) { addEdge(source, target, FlashUmlRelationship.REALIZATION); } } } if (psiClass.isInterface()) { Set<JSClass> found = new HashSet<>(); findNearestInterfaces(psiClass, found); for (JSClass inter : found) { if (interfaces.contains(inter)) { DiagramNode<Object> source = findNode(psiClass); DiagramNode<Object> target = findNode(inter); if (source != null && target != null && source != target) { addEdge(source, target, FlashUmlRelationship.INTERFACE_GENERALIZATION); } } } } else { //Collect all realized interfaces Set<JSClass> inters = new HashSet<>(); ContainerUtil.addAll(inters, psiClass.getImplementedInterfaces()); Collection<JSClass> processed = new ArrayList<>(); JSClass cur = getSuperClass(psiClass, processed); while (cur != null) { if (findNode(cur) == null) { ContainerUtil.addAll(inters, cur.getImplementedInterfaces()); } else { break; } cur = getSuperClass(cur, processed); } ArrayList<JSClass> faces = new ArrayList<>(inters); while (!faces.isEmpty()) { JSClass inter = faces.get(0); if (findNode(inter) != null) { DiagramNode<Object> source = findNode(psiClass); DiagramNode<Object> target = findNode(inter); if (source != null && target != null && source != target) { addEdge(source, target, FlashUmlRelationship.REALIZATION); } faces.remove(inter); } else { faces.remove(inter); ContainerUtil.addAll(faces, inter.getImplementedInterfaces()); } } } } if (isShowDependencies()) { final EnumSet<FlashUmlDependenciesSettingsOption> options = FlashUmlDependenciesSettingsOption.getEnabled(); for (JSClass psiClass : classes) { showDependenciesFor(psiClass, options); } } //merge! mergeWithBackup(myNodes, myNodesOld); mergeWithBackup(myEdges, myEdgesOld); mergeWithBackup(myDependencyEdges, myDependencyEdgesOld); } private void showDependenciesFor(final JSClass clazz, final EnumSet<FlashUmlDependenciesSettingsOption> options) { DiagramNode<Object> mainNode = findNode(clazz); if (mainNode == null) return; FlashUmlDependencyProvider provider = new FlashUmlDependencyProvider(clazz); Collection<Pair<JSClass, FlashUmlRelationship>> list = provider.computeUsedClasses(); for (Pair<JSClass, FlashUmlRelationship> pair : list) { if (shouldShow(options, clazz, pair.first, pair.second)) { DiagramNode<Object> node = findNode(pair.first); if (node != null) { addDependencyEdge(mainNode, node, pair.second); } } } } private static boolean shouldShow(EnumSet<FlashUmlDependenciesSettingsOption> options, final JSClass from, final JSClass to, final FlashUmlRelationship relShip) { if (JSResolveUtil.isObjectClass(from) && JSResolveUtil.isObjectClass(to)) { return false; } if (!options.contains(FlashUmlDependenciesSettingsOption.SELF) && JSPsiImplUtils.isTheSameClass(from, to)) { return false; } if (!options.contains(FlashUmlDependenciesSettingsOption.ONE_TO_ONE) && relShip.getType() == FlashUmlRelationship.TYPE_ONE_TO_ONE) { return false; } if (!options.contains(FlashUmlDependenciesSettingsOption.ONE_TO_MANY) && relShip.getType() == FlashUmlRelationship.TYPE_ONE_TO_MANY) { return false; } if (!options.contains(FlashUmlDependenciesSettingsOption.USAGES) && relShip.getType() == FlashUmlRelationship.TYPE_DEPENDENCY) { return false; } if (!options.contains(FlashUmlDependenciesSettingsOption.CREATE) && relShip.getType() == FlashUmlRelationship.TYPE_CREATE) { return false; } return true; } @Nullable private static JSClass getSuperClass(JSClass psiClass, Collection<JSClass> processed) { JSClass[] superClasses = psiClass.getSuperClasses(); if (superClasses.length > 0 && !superClasses[0].isEquivalentTo(psiClass) && !JSPsiImplUtils.containsEquivalent(processed, superClasses[0])) { processed.add(superClasses[0]); return superClasses[0]; } return null; } private static <T> void clearAndBackup(Collection<T> target, Collection<T> backup) { backup.clear(); backup.addAll(target); target.clear(); } private static <T> void mergeWithBackup(Collection<T> target, Collection<T> backup) { for (T t : backup) { if (target.contains(t)) { target.remove(t); target.add(t); } } } private void syncPackages() { final GlobalSearchScope searchScope = GlobalSearchScope.allScope(getProject()); if (initialPackage == null || FlashUmlElementManager.packageExists(getProject(), initialPackage, searchScope)) return; final Set<String> psiPackages = new HashSet<>(); for (String sub : getSubPackages(initialPackage, searchScope)) { psiPackages.add(sub); } for (String fqn : packages) psiPackages.remove(fqn); for (String fqn : packagesRemovedByUser) psiPackages.remove(fqn); if (psiPackages.size() > 0) { packages.addAll(psiPackages); } } private static void findNearestInterfaces(final JSClass psiClass, final Set<JSClass> result) { for (JSClass anInterface : psiClass.getSuperClasses()) { if (result.contains(anInterface)) { continue; // don't check isEquivalent, equality check is enough for interfaces } result.add(anInterface); findNearestInterfaces(anInterface, result); } } private static boolean isGeneralizationEdgeAllowed(final JSClass psiClass) { return !psiClass.isInterface(); } private boolean isInsidePackages(JSClass psiClass) { return packages.contains(StringUtil.getPackageName(psiClass.getQualifiedName())); } public FlashUmlEdge addEdge(DiagramNode<Object> from, DiagramNode<Object> to, DiagramRelationshipInfo relationship) { return addEdge(from, to, relationship, myEdges); } public FlashUmlEdge addDependencyEdge(DiagramNode<Object> from, DiagramNode<Object> to, DiagramRelationshipInfo relationship) { return addEdge(from, to, relationship, myDependencyEdges); } private static FlashUmlEdge addEdge(DiagramNode<Object> from, DiagramNode<Object> to, DiagramRelationshipInfo relationship, Collection<DiagramEdge<Object>> storage) { for (DiagramEdge edge : storage) { if (edge.getSource() == from && edge.getTarget() == to && relationship.equals(edge.getRelationship())) return null; } FlashUmlEdge result = new FlashUmlEdge(from, to, relationship); storage.add(result); return result; } private Set<JSClass> getAllClasses() { Set<JSClass> classes = new HashSet<>(); for (SmartPsiElementPointer<JSClass> pointer : classesAddedByUser.values()) { classes.add(pointer.getElement()); } final GlobalSearchScope searchScope = GlobalSearchScope.allScope(getProject()); if (initialPackage != null && FlashUmlElementManager.packageExists(getProject(), initialPackage, searchScope)) { classes.addAll(getClasses(initialPackage, searchScope)); } for (String psiPackage : packages) { if (FlashUmlElementManager.packageExists(getProject(), psiPackage, searchScope)) { classes.addAll(getClasses(psiPackage, searchScope)); } } classes.remove(null); Set<JSClass> temp = new HashSet<>(); for (JSClass aClass : classes) { if (!aClass.isValid()) temp.add(aClass); } for (SmartPsiElementPointer<JSClass> cls : classesRemovedByUser.values()) { classes.remove(cls.getElement()); } classes.removeAll(temp); return classes; } @Nullable public DiagramNode<Object> findNode(Object object) { String objectFqn = getFqn(object); for (DiagramNode<Object> node : getNodes()) { final String fqn = getFqn(getIdentifyingElement(node)); if (fqn != null && fqn.equals(objectFqn)) { if (object instanceof JSClass && !(node instanceof FlashUmlClassNode)) continue; if (object instanceof String && !(node instanceof FlashUmlPackageNode)) continue; return node; } } //final SmartPsiElementPointer<JSPackage> ptr = packages.get(UmlUtils.getPackageName(psiElement)); return null; //ptr == null ? null : findNode(ptr.getElement()); } @Nullable private static String getFqn(Object element) { if (element instanceof JSQualifiedNamedElement) { String qName = ((JSQualifiedNamedElement)element).getQualifiedName(); return qName != null ? FlashUmlVfsResolver.fixVectorTypeName(qName) : null; } if (element instanceof String) { return (String)element; } return null; } public boolean contains(PsiElement psiElement) { return findNode(psiElement) != null; } public void dispose() { } public void removeElement(final Object element) { DiagramNode node = findNode(element); if (node == null) { classesAddedByUser.remove(getFqn(element)); return; } Collection<DiagramEdge> edgesToRemove = new ArrayList<>(); for (DiagramEdge edge : myEdges) { if (node.equals(edge.getTarget()) || node.equals(edge.getSource())) { edgesToRemove.add(edge); } } myEdges.removeAll(edgesToRemove); Collection<DiagramEdge> dependencyEdgesToRemove = new ArrayList<>(); for (DiagramEdge edge : myDependencyEdges) { if (node.equals(edge.getTarget()) || node.equals(edge.getSource())) { dependencyEdgesToRemove.add(edge); } } myDependencyEdges.removeAll(dependencyEdgesToRemove); myNodes.remove(node); if (element instanceof JSClass) { final JSClass psiClass = (JSClass)element; classesRemovedByUser.put(psiClass.getQualifiedName(), spManager.createSmartPsiElementPointer(psiClass)); classesAddedByUser.remove(psiClass.getQualifiedName()); } if (element instanceof String) { String p = (String)element; packages.remove(p); packagesRemovedByUser.add(p); Set<String> toDelete = new HashSet<>(); for (String key : classesAddedByUser.keySet()) { final SmartPsiElementPointer<JSClass> pointer = classesAddedByUser.get(key); final JSClass psiClass = pointer.getElement(); if (p.equals(StringUtil.getPackageName(psiClass.getQualifiedName()))) { toDelete.add(key); } } for (String key : toDelete) { classesAddedByUser.remove(key); } } } @Nullable public DiagramNode<Object> addElement(Object element) { if (findNode(element) != null) return null; if (element instanceof JSClass) { if (!isAllowedToShow((JSClass)element)) { return null; } JSClass psiClass = (JSClass)element; if (psiClass.getQualifiedName() == null) return null; final SmartPsiElementPointer<JSClass> pointer = spManager.createSmartPsiElementPointer(psiClass); final String fqn = psiClass.getQualifiedName(); classesAddedByUser.put(fqn, pointer); classesRemovedByUser.remove(fqn); setupScopeManager(psiClass, true); return new FlashUmlClassNode((JSClass)element, getProvider()); } else if (element instanceof String) { String aPackage = (String)element; packages.add(aPackage); packagesRemovedByUser.remove(aPackage); return new FlashUmlPackageNode(aPackage, getProvider()); } return null; } @Override public void expandNode(final DiagramNode<Object> node) { final Object element = node.getIdentifyingElement(); if (element instanceof String) { expandPackage((String)element); } } public void expandPackage(final String psiPackage) { packages.remove(psiPackage); packagesRemovedByUser.add(psiPackage); final GlobalSearchScope searchScope = GlobalSearchScope.allScope(getProject()); for (JSClass psiClass : getClasses(psiPackage, searchScope)) { addElement(psiClass); } for (String aPackage : getSubPackages(psiPackage, searchScope)) { addElement(aPackage); } } @Override public void collapseNode(final DiagramNode<Object> node) { Object element = node.getIdentifyingElement(); String fqn = getFqn(element); if (fqn == null) { return; } String parentPackage = StringUtil.getPackageName(fqn); if (parentPackage.isEmpty()) { return; } final String fqnStart = parentPackage + "."; final ArrayList<String> toRemove = new ArrayList<>(); for (String p : packages) { if (p.startsWith(fqnStart)) { toRemove.add(p); } } packages.removeAll(toRemove); toRemove.clear(); for (String s : classesAddedByUser.keySet()) { if (s.startsWith(fqnStart)) { toRemove.add(s); } } for (String s : toRemove) { classesAddedByUser.remove(s); } packages.add(parentPackage); packagesRemovedByUser.remove(parentPackage); } List<String> getAllClassesFQN() { List<String> fqns = new ArrayList<>(); for (DiagramNode node : getNodes()) { final Object identifyingElement = getIdentifyingElement(node); if (identifyingElement instanceof JSClass) { fqns.add(((JSClass)identifyingElement).getQualifiedName()); } } return fqns; } List<String> getAllPackagesFQN() { List<String> fqns = new ArrayList<>(); for (DiagramNode node : getNodes()) { final Object identifyingElement = getIdentifyingElement(node); if (identifyingElement instanceof JSPackage) { fqns.add(((JSPackage)identifyingElement).getQualifiedName()); } } return fqns; } @Nullable public PsiElement getInitialElement() { if (myInitialElement == null) return null; final PsiElement element = myInitialElement.getElement(); return element == null || !element.isValid() ? null : element; } public String getInitialPackage() { return initialPackage; } public boolean hasNotValid() { for (DiagramNode<Object> node : getNodes()) { if (!isValid(getIdentifyingElement(node))) { return true; } } return false; } private boolean isValid(Object element) { if (element instanceof PsiElement) return ((PsiElement)element).isValid(); return false; } public static String getMessage(final JSClass source, final JSClass target, final DiagramRelationshipInfo relationship) { if (relationship == FlashUmlRelationship.ANNOTATION) { return "Remove annotation from class"; //TODO: return UmlBundle.message("remove.annotation.from.class", target.getName(), source.getName()); } else { return "This will remove relationship between classes";//TODO: return UmlBundle.message("this.will.remove.relationship.link.between.classes", source.getQualifiedName()); } } public VirtualFile getFile() { return myEditorFile; } @Override public boolean hasElement(Object element) { return findNode(element) != null; } @Override public boolean isPsiListener() { return true; } @Nullable public static Object getIdentifyingElement(DiagramNode node) { if (node instanceof FlashUmlClassNode || node instanceof FlashUmlPackageNode) { return node.getIdentifyingElement(); } if (node instanceof DiagramNoteNode) { final DiagramNode delegate = ((DiagramNoteNode)node).getIdentifyingElement(); if (delegate != node) { return getIdentifyingElement(delegate); } } return null; } @Override @Nullable public DiagramEdge<Object> createEdge(@NotNull final DiagramNode<Object> from, @NotNull final DiagramNode<Object> to) { final JSClass fromClass = (JSClass)from.getIdentifyingElement(); final JSClass toClass = (JSClass)to.getIdentifyingElement(); if (fromClass.isEquivalentTo(toClass)) { return null; } if (toClass.isInterface()) { if (JSPsiImplUtils.containsEquivalent(fromClass.isInterface() ? fromClass.getSuperClasses() : fromClass.getImplementedInterfaces(), toClass)) { return null; } Callable<DiagramEdge<Object>> callable = () -> { String targetQName = toClass.getQualifiedName(); JSRefactoringUtil.addToSupersList(fromClass, targetQName, true); if (targetQName.contains(".") && !(fromClass instanceof XmlBackedJSClassImpl)) { List<FormatFixer> formatters = new ArrayList<>(); formatters.add(ImportUtils.insertImportStatements(fromClass, Collections.singletonList(targetQName))); formatters.addAll(ECMAScriptImportOptimizer.executeNoFormat(fromClass.getContainingFile())); FormatFixer.fixAll(formatters); } return addEdgeAndRefresh(from, to, fromClass.isInterface() ? FlashUmlRelationship.GENERALIZATION : FlashUmlRelationship.INTERFACE_GENERALIZATION); }; String commandName = FlexBundle .message(fromClass.isInterface() ? "create.extends.relationship.command.name" : "create.implements.relationship.command.name", fromClass.getQualifiedName(), toClass.getQualifiedName()); return DiagramAction.performCommand(getBuilder(), callable, commandName, null, fromClass.getContainingFile()); } else { if (fromClass.isInterface()) { return null; } else if (fromClass instanceof XmlBackedJSClassImpl) { JSClass[] superClasses = fromClass.getSuperClasses(); if (JSPsiImplUtils.containsEquivalent(superClasses, toClass)) { return null; } if (superClasses.length > 0) { // if base component is not resolved, replace it silently final JSClass currentParent = superClasses[0]; if (Messages.showYesNoDialog( FlexBundle.message("replace.base.component.prompt", currentParent.getQualifiedName(), toClass.getQualifiedName()), FlexBundle.message("create.edge.title"), Messages.getQuestionIcon()) == Messages.NO) { return null; } } Callable<DiagramEdge<Object>> callable = () -> { NewFlexComponentAction.setParentComponent((MxmlJSClass)fromClass, toClass.getQualifiedName()); return addEdgeAndRefresh(from, to, DiagramRelationships.GENERALIZATION); }; String commandName = FlexBundle.message("create.extends.relationship.command.name", fromClass.getQualifiedName(), toClass.getQualifiedName()); return DiagramAction.performCommand(getBuilder(), callable, commandName, null, fromClass.getContainingFile()); } else { final JSClass[] superClasses = fromClass.getSuperClasses(); if (JSPsiImplUtils.containsEquivalent(superClasses, toClass)) { return null; } if (superClasses.length > 0 && !JSResolveUtil.isObjectClass(superClasses[0])) { // if base class is not resolved, replace it silently final JSClass currentParent = superClasses[0]; if (Messages.showYesNoDialog( FlexBundle.message("replace.base.class.prompt", currentParent.getQualifiedName(), toClass.getQualifiedName()), FlexBundle.message("create.edge.title"), Messages.getQuestionIcon()) == Messages.NO) { return null; } } Callable<DiagramEdge<Object>> callable = () -> { List<FormatFixer> formatters = new ArrayList<>(); boolean optimize = false; if (superClasses.length > 0 && !JSResolveUtil.isObjectClass(superClasses[0])) { JSRefactoringUtil.removeFromReferenceList(fromClass.getExtendsList(), superClasses[0], formatters); optimize = needsImport(fromClass, superClasses[0]); } JSRefactoringUtil.addToSupersList(fromClass, toClass.getQualifiedName(), false); if (needsImport(fromClass, toClass)) { formatters.add(ImportUtils.insertImportStatements(fromClass, Collections.singletonList(toClass.getQualifiedName()))); optimize = true; } if (optimize) { formatters.addAll(ECMAScriptImportOptimizer.executeNoFormat(fromClass.getContainingFile())); } FormatFixer.fixAll(formatters); return addEdgeAndRefresh(from, to, DiagramRelationships.GENERALIZATION); }; String commandName = FlexBundle.message("create.extends.relationship.command.name", fromClass.getQualifiedName(), toClass.getQualifiedName()); return DiagramAction.performCommand(getBuilder(), callable, commandName, null, fromClass.getContainingFile()); } } } private DiagramEdge<Object> addEdgeAndRefresh(DiagramNode<Object> from, DiagramNode<Object> to, DiagramRelationshipInfo type) { FlashUmlEdge result = addEdge(from, to, type); final DiagramBuilder builder = getBuilder(); if (builder != null) { builder.update(true, false); } return result; } @Override public boolean isDependencyDiagramSupported() { return true; } }