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