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