summaryrefslogtreecommitdiff
path: root/java/java-impl/src/com/intellij/psi/impl/JavaCodeBlockModificationListener.java
blob: 314f55c2f7fedca04e231edef75711a4243b99fe (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
/*
 * Copyright 2000-2011 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.psi.impl;

import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.roots.ProjectFileIndex;
import com.intellij.psi.*;
import com.intellij.psi.impl.source.jsp.jspXml.JspDirective;
import com.intellij.psi.util.PsiModificationTracker;
import com.intellij.psi.xml.XmlFile;
import org.jetbrains.annotations.NotNull;

public class JavaCodeBlockModificationListener implements PsiTreeChangePreprocessor {
  private static final Logger LOG = Logger.getInstance("#com.intellij.psi.impl.JavaCodeBlockModificationListener");

  private final PsiModificationTrackerImpl myModificationTracker;

  public JavaCodeBlockModificationListener(final PsiModificationTracker modificationTracker) {
    myModificationTracker = (PsiModificationTrackerImpl) modificationTracker;
  }

  @Override
  public void treeChanged(@NotNull final PsiTreeChangeEventImpl event) {
    switch (event.getCode()) {
      case BEFORE_CHILDREN_CHANGE:
      case BEFORE_PROPERTY_CHANGE:
      case BEFORE_CHILD_MOVEMENT:
      case BEFORE_CHILD_REPLACEMENT:
      case BEFORE_CHILD_ADDITION:
      case BEFORE_CHILD_REMOVAL:
        break;

      case CHILD_ADDED:
      case CHILD_REMOVED:
      case CHILD_REPLACED:
        processChange(event.getParent(), event.getOldChild(), event.getChild());
        break;

      case CHILDREN_CHANGED:
        // general childrenChanged() event after each change
        if (!event.isGenericChange()) {
          processChange(event.getParent(), event.getParent(), null);
        }
        break;

      case CHILD_MOVED:
      case PROPERTY_CHANGED:
        myModificationTracker.incCounter();
        break;

      default:
        LOG.error("Unknown code:" + event.getCode());
        break;
    }
  }

  private void processChange(final PsiElement parent, final PsiElement child1, final PsiElement child2) {
    try {
      if (!isInsideCodeBlock(parent)) {
        if (parent != null && isClassOwner(parent.getContainingFile()) ||
            isClassOwner(child1) || isClassOwner(child2) || isSourceDir(parent) ||
            (parent != null && isClassOwner(parent.getParent()))) {
          myModificationTracker.incCounter();
        }
        else {
          myModificationTracker.incOutOfCodeBlockModificationCounter();
        }
        return;
      }

      if (containsClassesInside(child1) || child2 != child1 && containsClassesInside(child2)) {
        myModificationTracker.incCounter();
      }
    }
    catch (PsiInvalidElementAccessException ignored) {
      myModificationTracker.incCounter(); // Shall not happen actually, just a pre-release paranoia
    }
  }

  private static boolean isSourceDir(PsiElement element) {
    return element instanceof PsiDirectory &&
           ProjectFileIndex.SERVICE.getInstance(element.getProject()).isInSource(((PsiDirectory)element).getVirtualFile());
  }

  private static boolean isClassOwner(final PsiElement element) {
    return element instanceof PsiClassOwner && !(element instanceof XmlFile) || element instanceof JspDirective;
  }

  private static boolean containsClassesInside(final PsiElement element) {
    if (element == null) return false;
    if (element instanceof PsiClass) return true;

    PsiElement child = element.getFirstChild();
    while (child != null) {
      if (containsClassesInside(child)) return true;
      child = child.getNextSibling();
    }

    return false;
  }

  private static boolean isInsideCodeBlock(PsiElement element) {
    if (element instanceof PsiFileSystemItem) {
      return false;
    }

    if (element == null || element.getParent() == null) return true;

    PsiElement parent = element;
    while (true) {
      if (parent instanceof PsiFile || parent instanceof PsiDirectory || parent == null) {
        return false;
      }
      if (parent instanceof PsiClass) return false; // anonymous or local class
      if (parent instanceof PsiModifiableCodeBlock) {
        if (!((PsiModifiableCodeBlock)parent).shouldChangeModificationCount(element)) {
          return true;
        }
      }
      parent = parent.getParent();
    }
  }
}