/* * Copyright 2000-2014 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.pom.xml.impl; import com.intellij.lang.ASTNode; import com.intellij.pom.PomModel; import com.intellij.pom.PomModelAspect; import com.intellij.pom.event.PomModelEvent; import com.intellij.pom.tree.TreeAspect; import com.intellij.pom.tree.events.ChangeInfo; import com.intellij.pom.tree.events.ReplaceChangeInfo; import com.intellij.pom.tree.events.TreeChange; import com.intellij.pom.tree.events.TreeChangeEvent; import com.intellij.pom.tree.events.impl.ChangeInfoImpl; import com.intellij.pom.tree.events.impl.TreeChangeImpl; import com.intellij.pom.xml.XmlAspect; import com.intellij.pom.xml.impl.events.*; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiFile; import com.intellij.psi.TokenType; import com.intellij.psi.XmlElementVisitor; import com.intellij.psi.impl.source.tree.FileElement; import com.intellij.psi.tree.IElementType; import com.intellij.psi.xml.*; import com.intellij.util.CharTable; import java.util.Collections; public class XmlAspectImpl implements XmlAspect { private final PomModel myModel; private final TreeAspect myTreeAspect; public XmlAspectImpl(PomModel model, TreeAspect aspect) { myModel = model; myTreeAspect = aspect; myModel.registerAspect(XmlAspect.class, this, Collections.singleton((PomModelAspect)myTreeAspect)); } @Override public void update(PomModelEvent event) { if (!event.getChangedAspects().contains(myTreeAspect)) return; final TreeChangeEvent changeSet = (TreeChangeEvent)event.getChangeSet(myTreeAspect); if (changeSet == null) return; final ASTNode rootElement = changeSet.getRootElement(); final PsiFile file = (PsiFile)rootElement.getPsi(); if (!(file instanceof XmlFile)) return; final XmlAspectChangeSetImpl xmlChangeSet = event.registerChangeSetIfAbsent(this, new XmlAspectChangeSetImpl(myModel)); xmlChangeSet.addChangedFile((XmlFile)file); final ASTNode[] changedElements = changeSet.getChangedElements(); final CharTable table = ((FileElement)changeSet.getRootElement()).getCharTable(); for (ASTNode changedElement : changedElements) { TreeChange changesByElement = changeSet.getChangesByElement(changedElement); PsiElement psiElement = null; while (changedElement != null && (psiElement = changedElement.getPsi()) == null) { final ASTNode parent = changedElement.getTreeParent(); final ChangeInfoImpl changeInfo = ChangeInfoImpl.create(ChangeInfo.CONTENTS_CHANGED, changedElement); changeInfo.compactChange(changesByElement); changesByElement = new TreeChangeImpl(parent); changesByElement.addChange(changedElement, changeInfo); changedElement = parent; } if (changedElement == null) continue; final TreeChange finalChangedElement = changesByElement; psiElement.accept(new XmlElementVisitor() { TreeChange myChange = finalChangedElement; @Override public void visitElement(PsiElement element) { final ASTNode child = element.getNode(); final ASTNode treeParent = child.getTreeParent(); if (treeParent == null) return; final PsiElement parent = treeParent.getPsi(); final ChangeInfoImpl changeInfo = ChangeInfoImpl.create(ChangeInfo.CONTENTS_CHANGED, child); changeInfo.compactChange(myChange); myChange = new TreeChangeImpl(treeParent); myChange.addChange(child, changeInfo); parent.accept(this); } @Override public void visitXmlAttribute(XmlAttribute attribute) { final ASTNode[] affectedChildren = myChange.getAffectedChildren(); String oldName = null; String oldValue = null; for (final ASTNode treeElement : affectedChildren) { final ChangeInfo changeByChild = myChange.getChangeByChild(treeElement); final int changeType = changeByChild.getChangeType(); if (treeElement.getElementType() == XmlTokenType.XML_NAME) { if (changeType == ChangeInfo.REMOVED) { oldName = treeElement.getText(); } else if (changeType == ChangeInfo.REPLACE) { oldName = ((ReplaceChangeInfo)changeByChild).getReplaced().getText(); } } if (treeElement.getElementType() == XmlElementType.XML_ATTRIBUTE_VALUE) { if (changeType == ChangeInfo.REMOVED) { oldValue = treeElement.getText(); } else if (changeType == ChangeInfo.REPLACE) { oldValue = ((ReplaceChangeInfo)changeByChild).getReplaced().getText(); } } } if (oldName != null && !oldName.equals(attribute.getName())) { xmlChangeSet.add(new XmlAttributeSetImpl(attribute.getParent(), oldName, null)); xmlChangeSet.add(new XmlAttributeSetImpl(attribute.getParent(), attribute.getName(), attribute.getValue())); } else if (oldValue != null) { xmlChangeSet.add(new XmlAttributeSetImpl(attribute.getParent(), attribute.getName(), attribute.getValue())); } else { xmlChangeSet.add(new XmlElementChangedImpl(attribute)); } } @Override public void visitXmlTag(XmlTag tag) { ASTNode[] affectedChildren = shortenChange(myChange.getAffectedChildren(), changeSet); for (final ASTNode treeElement : affectedChildren) { /*final IElementType type = treeElement.getElementType(); if (type == ElementType.WHITE_SPACE) continue; if (type == ElementType.XML_NAME) { if (myChange.getChangeByChild(treeElement).getChangeType() == ChangeInfo.REPLACE) { continue; } }*/ if (!(treeElement.getPsi() instanceof XmlTagChild)) { visitElement(tag); return; } } for (final ASTNode treeElement : affectedChildren) { final ChangeInfo changeByChild = myChange.getChangeByChild(treeElement); final int changeType = changeByChild.getChangeType(); final IElementType type = treeElement.getElementType(); if (type == TokenType.WHITE_SPACE) continue; /* if (type == ElementType.XML_NAME) { final XmlToken xmlToken = (XmlToken)((ReplaceChangeInfo)changeByChild).getReplaced(); xmlChangeSet.add(new XmlTagNameChangedImpl(tag, xmlToken.getText())); continue; } */ final PsiElement element = treeElement.getPsi(); switch (changeType) { case ChangeInfo.ADD: xmlChangeSet.add(new XmlTagChildAddImpl(tag, (XmlTagChild)element)); break; case ChangeInfo.REMOVED: treeElement.putUserData(CharTable.CHAR_TABLE_KEY, table); xmlChangeSet.add(new XmlTagChildRemovedImpl(tag, (XmlTagChild)element)); break; case ChangeInfo.CONTENTS_CHANGED: xmlChangeSet.add(new XmlTagChildChangedImpl(tag, (XmlTagChild)element)); break; case ChangeInfo.REPLACE: final PsiElement psi = ((ReplaceChangeInfo)changeByChild).getReplaced().getPsi(); if (psi instanceof XmlTagChild) { final XmlTagChild replaced = (XmlTagChild)psi; replaced.putUserData(CharTable.CHAR_TABLE_KEY, table); xmlChangeSet.add(new XmlTagChildRemovedImpl(tag, replaced)); xmlChangeSet.add(new XmlTagChildAddImpl(tag, (XmlTagChild)element)); } break; } } } @Override public void visitXmlDocument(XmlDocument document) { xmlChangeSet.clear(); xmlChangeSet.add(new XmlDocumentChangedImpl(document)); } @Override public void visitFile(PsiFile file) { final XmlDocument document = ((XmlFile)file).getDocument(); if (document != null) { xmlChangeSet.clear(); xmlChangeSet.add(new XmlDocumentChangedImpl(document)); } } }); } } private ASTNode[] shortenChange(ASTNode[] affectedChildren, TreeChangeEvent event) { // TODO return affectedChildren; } }