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.lang.resolve;
018    
019    import com.google.common.collect.ImmutableSet;
020    import com.intellij.openapi.diagnostic.Logger;
021    import com.intellij.openapi.util.Condition;
022    import com.intellij.openapi.util.ModificationTracker;
023    import com.intellij.psi.PsiElement;
024    import com.intellij.psi.PsiFile;
025    import com.intellij.util.containers.ConcurrentWeakValueHashMap;
026    import com.intellij.util.containers.ContainerUtil;
027    import com.intellij.util.containers.FilteringIterator;
028    import org.jetbrains.annotations.NotNull;
029    import org.jetbrains.annotations.TestOnly;
030    import org.jetbrains.jet.lang.descriptors.annotations.AnnotationDescriptor;
031    import org.jetbrains.jet.lang.diagnostics.Diagnostic;
032    import org.jetbrains.jet.lang.diagnostics.DiagnosticFactory;
033    import org.jetbrains.jet.lang.diagnostics.Errors;
034    import org.jetbrains.jet.lang.diagnostics.Severity;
035    import org.jetbrains.jet.lang.psi.JetAnnotated;
036    import org.jetbrains.jet.lang.psi.JetAnnotationEntry;
037    import org.jetbrains.jet.lang.psi.JetStubbedPsiUtil;
038    import org.jetbrains.jet.lang.psi.JetFile;
039    import org.jetbrains.jet.lang.psi.codeFragmentUtil.CodeFragmentUtilPackage;
040    import org.jetbrains.jet.lang.resolve.constants.ArrayValue;
041    import org.jetbrains.jet.lang.resolve.constants.CompileTimeConstant;
042    import org.jetbrains.jet.lang.resolve.constants.StringValue;
043    import org.jetbrains.jet.lang.types.lang.KotlinBuiltIns;
044    
045    import java.util.Collection;
046    import java.util.Iterator;
047    import java.util.Map;
048    import java.util.Set;
049    
050    public class DiagnosticsWithSuppression implements Diagnostics {
051    
052        private static final Logger LOG = Logger.getInstance(DiagnosticsWithSuppression.class);
053    
054        private final BindingContext context;
055        private final Collection<Diagnostic> diagnostics;
056    
057        // The cache is weak: we're OK with losing it
058        private final Map<JetAnnotated, Suppressor> suppressors = new ConcurrentWeakValueHashMap<JetAnnotated, Suppressor>();
059    
060        private final Condition<Diagnostic> filter = new Condition<Diagnostic>() {
061            @Override
062            public boolean value(Diagnostic diagnostic) {
063                return !isSuppressed(diagnostic);
064            }
065        };
066        private final DiagnosticsElementsCache elementsCache = new DiagnosticsElementsCache(this);
067    
068        public DiagnosticsWithSuppression(@NotNull BindingContext context, @NotNull Collection<Diagnostic> diagnostics) {
069            this.context = context;
070            this.diagnostics = diagnostics;
071        }
072    
073        @NotNull
074        @Override
075        public Diagnostics noSuppression() {
076            return new SimpleDiagnostics(diagnostics);
077        }
078    
079        @NotNull
080        @Override
081        public Iterator<Diagnostic> iterator() {
082            return new FilteringIterator<Diagnostic, Diagnostic>(diagnostics.iterator(), filter);
083        }
084    
085        @NotNull
086        @Override
087        public Collection<Diagnostic> all() {
088            return ContainerUtil.filter(diagnostics, filter);
089        }
090    
091        @NotNull
092        @Override
093        public Collection<Diagnostic> forElement(@NotNull PsiElement psiElement) {
094            return elementsCache.getDiagnostics(psiElement);
095        }
096    
097        @Override
098        public boolean isEmpty() {
099            return all().isEmpty();
100        }
101    
102        private boolean isSuppressed(@NotNull Diagnostic diagnostic) {
103            PsiElement element = diagnostic.getPsiElement();
104    
105            if (isSuppressedForDebugger(diagnostic, element)) return true;
106    
107            JetAnnotated annotated = JetStubbedPsiUtil.getPsiOrStubParent(element, JetAnnotated.class, false);
108            if (annotated == null) return false;
109    
110            return isSuppressedByAnnotated(diagnostic, annotated, 0);
111        }
112    
113        private static boolean isSuppressedForDebugger(@NotNull Diagnostic diagnostic, @NotNull PsiElement element) {
114            PsiFile containingFile = element.getContainingFile();
115            if (containingFile instanceof JetFile && CodeFragmentUtilPackage.getSkipVisibilityCheck((JetFile) containingFile)) {
116                DiagnosticFactory<?> diagnosticFactory = diagnostic.getFactory();
117                return diagnosticFactory == Errors.INVISIBLE_MEMBER ||
118                       diagnosticFactory == Errors.INVISIBLE_REFERENCE ||
119                       diagnosticFactory == Errors.INVISIBLE_SETTER;
120            }
121            return false;
122        }
123    
124        /*
125           The cache is optimized for the case where no warnings are suppressed (most frequent one)
126    
127           trait Root {
128             suppress("X")
129             trait A {
130               trait B {
131                 suppress("Y")
132                 trait C {
133                   fun foo() = warning
134                 }
135               }
136             }
137           }
138    
139           Nothing is suppressed at foo, so we look above. While looking above we went up to the root (once) and propagated
140           all the suppressors down, so now we have:
141    
142              foo  - suppress(Y) from C
143              C    - suppress(Y) from C
144              B    - suppress(X) from A
145              A    - suppress(X) from A
146              Root - suppress() from Root
147    
148           Next time we look up anything under foo, we try the Y-suppressor and then immediately the X-suppressor, then to the empty
149           suppressor at the root. All the intermediate empty nodes are skipped, because every suppressor remembers its definition point.
150    
151           This way we need no more lookups than the number of suppress() annotations from here to the root.
152         */
153        private boolean isSuppressedByAnnotated(@NotNull Diagnostic diagnostic, @NotNull JetAnnotated annotated, int debugDepth) {
154            if (LOG.isDebugEnabled()) {
155                LOG.debug("Annotated: ", annotated.getName());
156                LOG.debug("Depth: ", debugDepth);
157                LOG.debug("Cache size: ", suppressors.size(), "\n");
158            }
159    
160            Suppressor suppressor = getOrCreateSuppressor(annotated);
161            if (suppressor.isSuppressed(diagnostic)) return true;
162    
163            JetAnnotated annotatedAbove = JetStubbedPsiUtil.getPsiOrStubParent(suppressor.getAnnotatedElement(), JetAnnotated.class, true);
164            if (annotatedAbove == null) return false;
165    
166            boolean suppressed = isSuppressedByAnnotated(diagnostic, annotatedAbove, debugDepth + 1);
167            Suppressor suppressorAbove = suppressors.get(annotatedAbove);
168            if (suppressorAbove != null && suppressorAbove.dominates(suppressor)) {
169                suppressors.put(annotated, suppressorAbove);
170            }
171    
172            return suppressed;
173        }
174    
175        @NotNull
176        private Suppressor getOrCreateSuppressor(@NotNull JetAnnotated annotated) {
177            Suppressor suppressor = suppressors.get(annotated);
178            if (suppressor == null) {
179                Set<String> strings = getSuppressingStrings(annotated);
180                if (strings.isEmpty()) {
181                    suppressor = new EmptySuppressor(annotated);
182                }
183                else if (strings.size() == 1) {
184                    suppressor = new SingularSuppressor(annotated, strings.iterator().next());
185                }
186                else {
187                    suppressor = new MultiSuppressor(annotated, strings);
188                }
189                suppressors.put(annotated, suppressor);
190            }
191            return suppressor;
192        }
193    
194        private Set<String> getSuppressingStrings(@NotNull JetAnnotated annotated) {
195            ImmutableSet.Builder<String> builder = ImmutableSet.builder();
196            for (JetAnnotationEntry annotationEntry : annotated.getAnnotationEntries()) {
197                AnnotationDescriptor annotationDescriptor = context.get(BindingContext.ANNOTATION, annotationEntry);
198                if (annotationDescriptor == null) continue;
199    
200                if (!KotlinBuiltIns.getInstance().isSuppressAnnotation(annotationDescriptor)) continue;
201    
202                // We only add strings and skip other values to facilitate recovery in presence of erroneous code
203                for (CompileTimeConstant<?> arrayValue : annotationDescriptor.getAllValueArguments().values()) {
204                    if ((arrayValue instanceof ArrayValue)) {
205                        for (CompileTimeConstant<?> value : ((ArrayValue) arrayValue).getValue()) {
206                            if (value instanceof StringValue) {
207                                builder.add(String.valueOf(((StringValue) value).getValue()).toLowerCase());
208                            }
209                        }
210                    }
211                }
212            }
213            return builder.build();
214        }
215    
216        public static boolean isSuppressedByStrings(@NotNull Diagnostic diagnostic, @NotNull Set<String> strings) {
217            if (strings.contains("warnings") && diagnostic.getSeverity() == Severity.WARNING) return true;
218    
219            return strings.contains(diagnostic.getFactory().getName().toLowerCase());
220        }
221    
222        @NotNull
223        @Override
224        public ModificationTracker getModificationTracker() {
225            throw new IllegalStateException("Trying to obtain modification tracker for readonly DiagnosticsWithSuppression.");
226        }
227    
228        private static abstract class Suppressor {
229            private final JetAnnotated annotated;
230    
231            protected Suppressor(@NotNull JetAnnotated annotated) {
232                this.annotated = annotated;
233            }
234    
235            @NotNull
236            public JetAnnotated getAnnotatedElement() {
237                return annotated;
238            }
239    
240            public abstract boolean isSuppressed(@NotNull Diagnostic diagnostic);
241    
242            // true is \forall x. other.isSuppressed(x) -> this.isSuppressed(x)
243            public abstract boolean dominates(@NotNull Suppressor other);
244        }
245    
246        private static class EmptySuppressor extends Suppressor {
247    
248            private EmptySuppressor(@NotNull JetAnnotated annotated) {
249                super(annotated);
250            }
251    
252            @Override
253            public boolean isSuppressed(@NotNull Diagnostic diagnostic) {
254                return false;
255            }
256    
257            @Override
258            public boolean dominates(@NotNull Suppressor other) {
259                return other instanceof EmptySuppressor;
260            }
261        }
262    
263        private static class SingularSuppressor extends Suppressor {
264            private final String string;
265    
266            private SingularSuppressor(@NotNull JetAnnotated annotated, @NotNull String string) {
267                super(annotated);
268                this.string = string;
269            }
270    
271            @Override
272            public boolean isSuppressed(@NotNull Diagnostic diagnostic) {
273                return isSuppressedByStrings(diagnostic, ImmutableSet.of(string));
274            }
275    
276            @Override
277            public boolean dominates(@NotNull Suppressor other) {
278                return other instanceof EmptySuppressor
279                       || (other instanceof SingularSuppressor && ((SingularSuppressor) other).string.equals(string));
280            }
281        }
282    
283        private static class MultiSuppressor extends Suppressor {
284            private final Set<String> strings;
285    
286            private MultiSuppressor(@NotNull JetAnnotated annotated, @NotNull Set<String> strings) {
287                super(annotated);
288                this.strings = strings;
289            }
290    
291            @Override
292            public boolean isSuppressed(@NotNull Diagnostic diagnostic) {
293                return isSuppressedByStrings(diagnostic, strings);
294            }
295    
296            @Override
297            public boolean dominates(@NotNull Suppressor other) {
298                // it's too costly to check set inclusion
299                return other instanceof EmptySuppressor;
300            }
301        }
302    
303        @TestOnly
304        @NotNull
305        public Collection<Diagnostic> getDiagnostics() {
306            return diagnostics;
307        }
308    }