/* * 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.pom.tree.events.impl; import com.intellij.lang.ASTNode; import com.intellij.openapi.diagnostic.Logger; import com.intellij.pom.PomModelAspect; import com.intellij.pom.event.PomChangeSet; import com.intellij.pom.tree.events.ChangeInfo; import com.intellij.pom.tree.events.TreeChange; import com.intellij.pom.tree.events.TreeChangeEvent; import com.intellij.psi.impl.source.tree.CompositeElement; import com.intellij.psi.impl.source.tree.FileElement; import com.intellij.psi.impl.source.tree.TreeElement; import com.intellij.util.CharTable; import gnu.trove.THashMap; import gnu.trove.TObjectIntHashMap; import org.jetbrains.annotations.NotNull; import java.util.*; /** * @author ik */ public class TreeChangeEventImpl implements TreeChangeEvent{ private static final Logger LOG = Logger.getInstance("#com.intellij.pom.tree.events.impl.TreeChangeEventImpl"); private final Map<ASTNode, TreeChange> myChangedElements = new THashMap<ASTNode, TreeChange>(); private List<ASTNode> myChangedInOrder; private final List<Set<ASTNode>> myOfEqualDepth = new ArrayList<Set<ASTNode>>(10); private final PomModelAspect myAspect; private final FileElement myFileElement; public TreeChangeEventImpl(@NotNull PomModelAspect aspect, @NotNull FileElement treeElement) { myAspect = aspect; myFileElement = treeElement; } @Override @NotNull public FileElement getRootElement() { return myFileElement; } @Override @NotNull public ASTNode[] getChangedElements() { if (myChangedInOrder == null) { myChangedInOrder = new ArrayList<ASTNode>(myChangedElements.keySet()); Collections.sort(myChangedInOrder, new Comparator<ASTNode>() { final Map<ASTNode, int[]> routeMap = new THashMap<ASTNode, int[]>(myChangedElements.size()); final TObjectIntHashMap<ASTNode> nodeIndex = new TObjectIntHashMap<ASTNode>(myChangedElements.size()); @Override public int compare(ASTNode o1, ASTNode o2) { int[] route = routeMap.get(o1); if (route == null) routeMap.put(o1, route = getRoute(o1, nodeIndex)); int[] route2 = routeMap.get(o2); if (route2 == null) routeMap.put(o2, route2 = getRoute(o2, nodeIndex)); return compareRoutes(route, route2); } }); } return myChangedInOrder.toArray(new ASTNode[myChangedInOrder.size()]); } @Override public TreeChange getChangesByElement(@NotNull ASTNode element) { LOG.assertTrue(isAncestor(element, myFileElement), element); return myChangedElements.get(element); } private static boolean isAncestor(ASTNode thisElement, FileElement fileElement) { TreeElement element; for (element = (TreeElement)thisElement; element.getTreeParent() != null; element = element.getTreeParent()) { } return element == fileElement; } @Override public void addElementaryChange(@NotNull ASTNode element, @NotNull ChangeInfo change) { LOG.assertTrue(isAncestor(element, myFileElement), element); final ASTNode parent = element.getTreeParent(); if (parent == null) return; ASTNode currentParent = parent; ASTNode prevParent = element; int depth = 0; while (currentParent != null) { if (myChangedElements.containsKey(currentParent)) { final TreeChange changesByElement = getChangesByElement(currentParent); final boolean currentParentHasChange = changesByElement.getChangeByChild(prevParent) != null; if (currentParentHasChange && prevParent != element) return; if (prevParent != element) { final ChangeInfo newChange = ChangeInfoImpl.create(ChangeInfo.CONTENTS_CHANGED, prevParent); if (change.getChangeType() != ChangeInfo.REMOVED) { ((ChangeInfoImpl)newChange).processElementaryChange(change, element); } change = newChange; } processElementaryChange(currentParent, prevParent, change, -1); return; } depth++; prevParent = currentParent; currentParent = currentParent.getTreeParent(); } compactChanges(parent, depth - 1); processElementaryChange(parent, element, change, depth - 1); } private static int getDepth(ASTNode element) { int depth = 0; while((element = element.getTreeParent()) != null) depth++; return depth; } @Override public void clear() { myChangedInOrder = null; myChangedElements.clear(); myOfEqualDepth.clear(); } private void processElementaryChange(ASTNode parent, ASTNode element, ChangeInfo change, int depth) { TreeChange treeChange = myChangedElements.get(parent); if (treeChange == null) { treeChange = new TreeChangeImpl(parent); myChangedElements.put(parent, treeChange); final int index = depth >= 0 ? depth : getDepth(parent); addToEqualsDepthList(index, parent); } treeChange.addChange(element, change); if (change.getChangeType() == ChangeInfo.REMOVED) { element.putUserData(CharTable.CHAR_TABLE_KEY, myFileElement.getCharTable()); } if (treeChange.isEmpty()) removeAssociatedChanges(parent, depth); } private void addToEqualsDepthList(final int depth, final ASTNode parent) { Set<ASTNode> treeElements = depth < myOfEqualDepth.size() ? myOfEqualDepth.get(depth) : null; if(treeElements == null){ treeElements = new HashSet<ASTNode>(); while (depth > myOfEqualDepth.size()) { myOfEqualDepth.add(new HashSet<ASTNode>()); } myOfEqualDepth.add(depth, treeElements); } treeElements.add(parent); } private void compactChanges(ASTNode parent, int depth) { int currentDepth = myOfEqualDepth.size(); while(--currentDepth > depth){ final Set<ASTNode> treeElements = myOfEqualDepth.get(currentDepth); if(treeElements == null) continue; final Iterator<ASTNode> iterator = treeElements.iterator(); while (iterator.hasNext()) { boolean isUnderCompacted = false; final TreeElement treeElement = (TreeElement)iterator.next(); ASTNode currentParent = treeElement; while(currentParent != null){ if(currentParent == parent){ isUnderCompacted = true; break; } currentParent = currentParent.getTreeParent(); } if(isUnderCompacted){ final ChangeInfoImpl compactedChange = ChangeInfoImpl.create(ChangeInfo.CONTENTS_CHANGED, treeElement); compactedChange.compactChange(getChangesByElement(treeElement)); iterator.remove(); removeAssociatedChanges(treeElement, currentDepth); final CompositeElement treeParent = treeElement.getTreeParent(); final TreeChange changesByElement = getChangesByElement(treeParent); if (changesByElement != null) { final ChangeInfoImpl changeByChild = (ChangeInfoImpl)changesByElement.getChangeByChild(treeElement); if (changeByChild != null) { changeByChild.setOldLength(compactedChange.getOldLength()); } else { changesByElement.addChange(treeElement, compactedChange); } } else { processElementaryChange(treeParent, treeElement, compactedChange, currentDepth - 1); } } } } } private void removeAssociatedChanges(ASTNode treeElement, int depth) { if(myChangedElements.remove(treeElement) != null) { if (depth < 0) depth = getDepth(treeElement); if (depth < myOfEqualDepth.size()) { myOfEqualDepth.get(depth < 0 ? getDepth(treeElement) : depth).remove(treeElement); } } } private static int[] getRoute(ASTNode node, TObjectIntHashMap<ASTNode> index){ final List<ASTNode> parents = new ArrayList<ASTNode>(20); while(node != null){ ASTNode nodeTreeParent = node.getTreeParent(); if (nodeTreeParent == null) break; if (!index.contains(node)) { ASTNode current = nodeTreeParent.getFirstChildNode(); int rootIndex = 0; while(current != null){ index.put(current, rootIndex); current = current.getTreeNext(); rootIndex++; } } parents.add(node); node = nodeTreeParent; } final int[] root = new int[parents.size()]; for(int i = 0; i < root.length; i++){ final ASTNode parent = parents.get(root.length - i - 1); root[i] = index.get(parent); } return root; } private static int compareRoutes(int[] root1, int[] root2){ final int depth = Math.min(root1.length, root2.length); for(int i = 0; i < depth; i++){ if(root1[i] == root2[i]) continue; if(root1[i] > root2[i]){ return 1; } else if(root2[i] > root1[i]){ return -1; } } if(root1.length == root2.length) return 0; if(root1.length < root2.length) return 1; return -1; } @Override @NotNull public PomModelAspect getAspect() { return myAspect; } @Override public void merge(@NotNull PomChangeSet blocked) { if(!(blocked instanceof TreeChangeEventImpl)) return; final TreeChangeEventImpl blockedTreeChange = (TreeChangeEventImpl)blocked; final Map<ASTNode, TreeChange> changedElements = new HashMap<ASTNode, TreeChange>(blockedTreeChange.myChangedElements); {// merging conflicting changes final Iterator<Map.Entry<ASTNode, TreeChange>> iterator = changedElements.entrySet().iterator(); while (iterator.hasNext()) { final Map.Entry<ASTNode, TreeChange> entry = iterator.next(); final ASTNode changed = entry.getKey(); LOG.assertTrue(isAncestor(changed, myFileElement), changed); final TreeChange treeChange = myChangedElements.get(changed); if(treeChange != null){ iterator.remove(); treeChange.add(entry.getValue()); } } } int depth = 0; { final Iterator<Map.Entry<ASTNode, TreeChange>> iterator = changedElements.entrySet().iterator(); while (iterator.hasNext()) { final Map.Entry<ASTNode, TreeChange> entry = iterator.next(); final ASTNode changed = entry.getKey(); TreeElement prevParent = (TreeElement)changed; CompositeElement currentParent = (CompositeElement)changed.getTreeParent(); while(currentParent != null){ if(myChangedElements.containsKey(currentParent)){ final ChangeInfoImpl newChange = ChangeInfoImpl.create(ChangeInfo.CONTENTS_CHANGED, prevParent); final int newLength = ((TreeElement)changed).getNotCachedLength(); final int oldLength = entry.getValue().getOldLength(); newChange.setOldLength(prevParent.getNotCachedLength() - newLength + oldLength); processElementaryChange(currentParent, prevParent, newChange, -1); iterator.remove(); break; } depth++; prevParent = currentParent; currentParent = currentParent.getTreeParent(); } } } { for (final Map.Entry<ASTNode, TreeChange> entry : changedElements.entrySet()) { final ASTNode changed = entry.getKey(); myChangedElements.put(changed, entry.getValue()); addToEqualsDepthList(depth, changed); compactChanges(changed, depth); } } } public String toString(){ final StringBuilder buffer = new StringBuilder(); for (final Map.Entry<ASTNode, TreeChange> entry : myChangedElements.entrySet()) { buffer.append(entry.getKey().getElementType().toString()); buffer.append(": "); buffer.append(entry.getValue().toString()); buffer.append("\n"); } return buffer.toString(); } }