blob: 713b311970281acd986a0c3183e889793e330f83 [file] [log] [blame]
/*
* 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();
}
}