001    /*
002     * Copyright 2010-2013 JetBrains s.r.o.
003     *
004     * Licensed under the Apache License, Version 2.0 (the "License");
005     * you may not use this file except in compliance with the License.
006     * You may obtain a copy of the License at
007     *
008     * http://www.apache.org/licenses/LICENSE-2.0
009     *
010     * Unless required by applicable law or agreed to in writing, software
011     * distributed under the License is distributed on an "AS IS" BASIS,
012     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013     * See the License for the specific language governing permissions and
014     * limitations under the License.
015     */
016    
017    package org.jetbrains.jet.asJava;
018    
019    import com.intellij.openapi.diagnostic.Logger;
020    import com.intellij.psi.*;
021    import com.intellij.psi.impl.PsiModificationTrackerImpl;
022    import com.intellij.psi.impl.PsiTreeChangeEventImpl;
023    import com.intellij.psi.impl.PsiTreeChangePreprocessor;
024    import com.intellij.psi.util.PsiModificationTracker;
025    import org.jetbrains.jet.lang.psi.JetBlockExpression;
026    import org.jetbrains.jet.lang.psi.JetClass;
027    import org.jetbrains.jet.lang.psi.JetFile;
028    
029    public class JetCodeBlockModificationListener implements PsiTreeChangePreprocessor {
030        private static final Logger LOG = Logger.getInstance("#org.jetbrains.jet.asJava.JetCodeBlockModificationListener");
031        
032        private final PsiModificationTrackerImpl myModificationTracker;
033    
034        public JetCodeBlockModificationListener(PsiModificationTracker modificationTracker) {
035            myModificationTracker = (PsiModificationTrackerImpl) modificationTracker;
036        }
037    
038        @Override
039        public void treeChanged(PsiTreeChangeEventImpl event) {
040            if (!(event.getFile() instanceof JetFile)) return;
041            switch (event.getCode()) {
042                case BEFORE_CHILDREN_CHANGE:
043                case BEFORE_PROPERTY_CHANGE:
044                case BEFORE_CHILD_MOVEMENT:
045                case BEFORE_CHILD_REPLACEMENT:
046                case BEFORE_CHILD_ADDITION:
047                case BEFORE_CHILD_REMOVAL:
048                    break;
049    
050                case CHILD_ADDED:
051                case CHILD_REMOVED:
052                case CHILD_REPLACED:
053                    processChange(event.getParent(), event.getOldChild(), event.getChild());
054                    break;
055    
056                case CHILDREN_CHANGED:
057                    // general childrenChanged() event after each change
058                    if (!event.isGenericChildrenChange()) {
059                        processChange(event.getParent(), event.getParent(), null);
060                    }
061                    break;
062    
063                case CHILD_MOVED:
064                case PROPERTY_CHANGED:
065                    myModificationTracker.incCounter();
066                    break;
067    
068                default:
069                    LOG.error("Unknown code:" + event.getCode());
070                    break;
071            }
072        }
073    
074        private void processChange(PsiElement parent, PsiElement child1, PsiElement child2) {
075            try {
076                if (!isInsideCodeBlock(parent)) {
077                    if (parent != null && parent.getContainingFile() instanceof JetFile) {
078                        myModificationTracker.incCounter();
079                    }
080                    else {
081                        myModificationTracker.incOutOfCodeBlockModificationCounter();
082                    }
083                    return;
084                }
085    
086                if (containsClassesInside(child1) || child2 != child1 && containsClassesInside(child2)) {
087                    myModificationTracker.incCounter();
088                }
089            } catch (PsiInvalidElementAccessException e) {
090                myModificationTracker.incCounter(); // Shall not happen actually, just a pre-release paranoia
091            }
092        }
093    
094        private static boolean containsClassesInside(PsiElement element) {
095            if (element == null) return false;
096            if (element instanceof PsiClass) return true;
097    
098            PsiElement child = element.getFirstChild();
099            while (child != null) {
100                if (containsClassesInside(child)) return true;
101                child = child.getNextSibling();
102            }
103    
104            return false;
105        }
106    
107        private static boolean isInsideCodeBlock(PsiElement element) {
108            if (element instanceof PsiFileSystemItem) {
109                return false;
110            }
111    
112            if (element == null || element.getParent() == null) return true;
113    
114            PsiElement parent = element;
115            while (true) {
116                if (parent instanceof PsiFile || parent instanceof PsiDirectory || parent == null) {
117                    return false;
118                }
119                if (parent instanceof JetClass) return false; // anonymous or local class
120                if (parent instanceof JetBlockExpression) {
121                    return true;
122                }
123                parent = parent.getParent();
124            }
125        }
126    }