001    /*
002     * Copyright 2010-2014 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.Lists;
020    import com.google.common.collect.Maps;
021    import com.google.common.collect.Sets;
022    import com.intellij.lang.ASTNode;
023    import com.intellij.openapi.util.Pair;
024    import com.intellij.psi.PsiElement;
025    import org.jetbrains.annotations.NotNull;
026    import org.jetbrains.annotations.Nullable;
027    import org.jetbrains.jet.lang.descriptors.*;
028    import org.jetbrains.jet.lang.descriptors.annotations.AnnotationDescriptor;
029    import org.jetbrains.jet.lang.diagnostics.Errors;
030    import org.jetbrains.jet.lang.psi.*;
031    import org.jetbrains.jet.lang.resolve.constants.CompileTimeConstant;
032    import org.jetbrains.jet.lang.resolve.constants.StringValue;
033    import org.jetbrains.jet.lang.resolve.name.FqName;
034    import org.jetbrains.jet.lang.resolve.name.Name;
035    import org.jetbrains.jet.lexer.JetModifierKeywordToken;
036    
037    import java.util.Arrays;
038    import java.util.Collection;
039    import java.util.Collections;
040    import java.util.Map;
041    
042    import static org.jetbrains.jet.lang.diagnostics.Errors.*;
043    import static org.jetbrains.jet.lexer.JetTokens.*;
044    
045    public class ModifiersChecker {
046        private static final Collection<JetModifierKeywordToken> MODALITY_MODIFIERS =
047                Lists.newArrayList(ABSTRACT_KEYWORD, OPEN_KEYWORD, FINAL_KEYWORD, OVERRIDE_KEYWORD);
048    
049        private static final Collection<JetModifierKeywordToken> VISIBILITY_MODIFIERS =
050                Lists.newArrayList(PRIVATE_KEYWORD, PROTECTED_KEYWORD, PUBLIC_KEYWORD, INTERNAL_KEYWORD);
051    
052        public static void reportIllegalModifiers(
053                @Nullable JetModifierList modifierList,
054                @NotNull Collection<JetModifierKeywordToken> illegalModifiers,
055                @NotNull BindingTrace trace
056        ) {
057            if (modifierList == null) return;
058    
059            for (JetModifierKeywordToken modifierToken : illegalModifiers) {
060                if (modifierList.hasModifier(modifierToken)) {
061                    PsiElement modifierPsi = modifierList.getModifier(modifierToken);
062                    assert modifierPsi != null;
063                    trace.report(ILLEGAL_MODIFIER.on(modifierPsi, modifierToken));
064                }
065            }
066        }
067    
068        @NotNull
069        private final BindingTrace trace;
070        @NotNull
071        private final AdditionalCheckerProvider additionalCheckerProvider;
072    
073        public ModifiersChecker(@NotNull BindingTrace trace, @NotNull AdditionalCheckerProvider additionalCheckerProvider) {
074            this.trace = trace;
075            this.additionalCheckerProvider = additionalCheckerProvider;
076        }
077    
078        public static ModifiersChecker create(@NotNull BindingTrace trace, @NotNull AdditionalCheckerProvider provider) {
079            return new ModifiersChecker(trace, provider);
080        }
081    
082        public void checkModifiersForDeclaration(@NotNull JetDeclaration modifierListOwner, @NotNull MemberDescriptor descriptor) {
083            if (modifierListOwner instanceof JetEnumEntry) {
084                checkIllegalInThisContextModifiers(modifierListOwner, Arrays.asList(MODIFIER_KEYWORDS_ARRAY));
085            }
086            else {
087                checkInnerModifier(modifierListOwner, descriptor);
088                checkModalityModifiers(modifierListOwner);
089                checkVisibilityModifiers(modifierListOwner, descriptor);
090            }
091            checkPlatformNameApplicability(descriptor);
092            runAnnotationCheckers(modifierListOwner, descriptor);
093        }
094    
095        public void checkModifiersForLocalDeclaration(@NotNull JetDeclaration modifierListOwner, @NotNull DeclarationDescriptor descriptor) {
096            checkIllegalModalityModifiers(modifierListOwner);
097            checkIllegalVisibilityModifiers(modifierListOwner);
098            checkPlatformNameApplicability(descriptor);
099            runAnnotationCheckers(modifierListOwner, descriptor);
100        }
101    
102        public void checkIllegalModalityModifiers(@NotNull JetModifierListOwner modifierListOwner) {
103            checkIllegalInThisContextModifiers(modifierListOwner, MODALITY_MODIFIERS);
104        }
105    
106        public void checkIllegalVisibilityModifiers(@NotNull JetModifierListOwner modifierListOwner) {
107            checkIllegalInThisContextModifiers(modifierListOwner, VISIBILITY_MODIFIERS);
108        }
109    
110        private void checkModalityModifiers(@NotNull JetModifierListOwner modifierListOwner) {
111            JetModifierList modifierList = modifierListOwner.getModifierList();
112            if (modifierList == null) return;
113    
114            checkRedundantModifier(modifierList, Pair.create(OPEN_KEYWORD, ABSTRACT_KEYWORD), Pair.create(OPEN_KEYWORD, OVERRIDE_KEYWORD));
115    
116            checkCompatibility(modifierList, Arrays.asList(ABSTRACT_KEYWORD, OPEN_KEYWORD, FINAL_KEYWORD),
117                               Arrays.asList(ABSTRACT_KEYWORD, OPEN_KEYWORD));
118    
119            if (modifierListOwner.getParent() instanceof JetClassObject || modifierListOwner instanceof JetObjectDeclaration) {
120                checkIllegalModalityModifiers(modifierListOwner);
121            }
122            else if (modifierListOwner instanceof JetClassOrObject) {
123                checkIllegalInThisContextModifiers(modifierListOwner, Collections.singletonList(OVERRIDE_KEYWORD));
124            }
125        }
126    
127        private void checkVisibilityModifiers(@NotNull JetModifierListOwner modifierListOwner, @NotNull DeclarationDescriptor descriptor) {
128            JetModifierList modifierList = modifierListOwner.getModifierList();
129            if (modifierList == null) return;
130    
131            DeclarationDescriptor containingDeclaration = descriptor.getContainingDeclaration();
132            if (containingDeclaration instanceof PackageFragmentDescriptor) {
133                if (modifierList.hasModifier(PROTECTED_KEYWORD)) {
134                    trace.report(Errors.PACKAGE_MEMBER_CANNOT_BE_PROTECTED.on(modifierListOwner));
135                }
136            }
137    
138            checkCompatibility(modifierList, VISIBILITY_MODIFIERS);
139        }
140    
141        private void checkInnerModifier(@NotNull JetModifierListOwner modifierListOwner, @NotNull DeclarationDescriptor descriptor) {
142            if (modifierListOwner.hasModifier(INNER_KEYWORD)) {
143                if (isIllegalInner(descriptor)) {
144                    checkIllegalInThisContextModifiers(modifierListOwner, Collections.singletonList(INNER_KEYWORD));
145                }
146                return;
147            }
148            if (modifierListOwner instanceof JetClass && !(modifierListOwner instanceof JetEnumEntry)) {
149                JetClass aClass = (JetClass) modifierListOwner;
150                boolean localEnumError = aClass.isLocal() && aClass.isEnum();
151                if (!localEnumError && isIllegalNestedClass(descriptor)) {
152                    trace.report(NESTED_CLASS_NOT_ALLOWED.on(aClass));
153                }
154            }
155        }
156    
157        public static boolean isIllegalInner(@NotNull DeclarationDescriptor descriptor) {
158            if (!(descriptor instanceof ClassDescriptor)) return true;
159            ClassDescriptor classDescriptor = (ClassDescriptor) descriptor;
160            if (classDescriptor.getKind() != ClassKind.CLASS) return true;
161            DeclarationDescriptor containingDeclaration = classDescriptor.getContainingDeclaration();
162            if (!(containingDeclaration instanceof ClassDescriptor)) return true;
163            return ((ClassDescriptor) containingDeclaration).getKind() == ClassKind.TRAIT;
164        }
165    
166        private static boolean isIllegalNestedClass(@NotNull DeclarationDescriptor descriptor) {
167            if (!(descriptor instanceof ClassDescriptor)) return false;
168            DeclarationDescriptor containingDeclaration = descriptor.getContainingDeclaration();
169            if (!(containingDeclaration instanceof ClassDescriptor)) return false;
170            ClassDescriptor containingClass = (ClassDescriptor) containingDeclaration;
171            return containingClass.isInner() || containingClass.getContainingDeclaration() instanceof FunctionDescriptor;
172        }
173    
174        private void checkPlatformNameApplicability(@NotNull DeclarationDescriptor descriptor) {
175            if (descriptor instanceof PropertyDescriptor) {
176                PropertyDescriptor propertyDescriptor = (PropertyDescriptor) descriptor;
177                if (propertyDescriptor.getGetter() != null) {
178                    checkPlatformNameApplicability(propertyDescriptor.getGetter());
179                }
180                if (propertyDescriptor.getSetter() != null) {
181                    checkPlatformNameApplicability(propertyDescriptor.getSetter());
182                }
183            }
184    
185            AnnotationDescriptor annotation = descriptor.getAnnotations().findAnnotation(new FqName("kotlin.platform.platformName"));
186            if (annotation == null) return;
187    
188            JetAnnotationEntry annotationEntry = trace.get(BindingContext.ANNOTATION_DESCRIPTOR_TO_PSI_ELEMENT, annotation);
189            if (annotationEntry == null) return;
190    
191            if (!DescriptorUtils.isTopLevelDeclaration(descriptor) || !(descriptor instanceof FunctionDescriptor)) {
192                trace.report(INAPPLICABLE_ANNOTATION.on(annotationEntry));
193            }
194    
195            Collection<CompileTimeConstant<?>> values = annotation.getAllValueArguments().values();
196            if (!values.isEmpty()) {
197                CompileTimeConstant<?> name = values.iterator().next();
198                if (name instanceof StringValue) {
199                    String value = ((StringValue) name).getValue();
200                    if (value == null || !Name.isValidIdentifier(value)) {
201                        trace.report(ILLEGAL_PLATFORM_NAME.on(annotationEntry, String.valueOf(value)));
202                    }
203                }
204            }
205        }
206    
207        private void checkCompatibility(@Nullable JetModifierList modifierList, Collection<JetModifierKeywordToken> availableModifiers, Collection<JetModifierKeywordToken>... availableCombinations) {
208            if (modifierList == null) return;
209            Collection<JetModifierKeywordToken> presentModifiers = Sets.newLinkedHashSet();
210            for (JetModifierKeywordToken modifier : availableModifiers) {
211                if (modifierList.hasModifier(modifier)) {
212                    presentModifiers.add(modifier);
213                }
214            }
215            if (presentModifiers.size() == 1) {
216                return;
217            }
218            for (Collection<JetModifierKeywordToken> combination : availableCombinations) {
219                if (presentModifiers.containsAll(combination) && combination.containsAll(presentModifiers)) {
220                    return;
221                }
222            }
223            for (JetModifierKeywordToken token : presentModifiers) {
224                trace.report(Errors.INCOMPATIBLE_MODIFIERS.on(modifierList.getModifierNode(token).getPsi(), presentModifiers));
225            }
226        }
227    
228        private void checkRedundantModifier(@NotNull JetModifierList modifierList, Pair<JetModifierKeywordToken, JetModifierKeywordToken>... redundantBundles) {
229            for (Pair<JetModifierKeywordToken, JetModifierKeywordToken> tokenPair : redundantBundles) {
230                JetModifierKeywordToken redundantModifier = tokenPair.getFirst();
231                JetModifierKeywordToken sufficientModifier = tokenPair.getSecond();
232                if (modifierList.hasModifier(redundantModifier) && modifierList.hasModifier(sufficientModifier)) {
233                    trace.report(Errors.REDUNDANT_MODIFIER.on(modifierList.getModifierNode(redundantModifier).getPsi(), redundantModifier, sufficientModifier));
234                }
235            }
236        }
237    
238        public void checkIllegalInThisContextModifiers(
239                @NotNull JetModifierListOwner modifierListOwner,
240                @NotNull Collection<JetModifierKeywordToken> illegalModifiers
241        ) {
242            reportIllegalModifiers(modifierListOwner.getModifierList(), illegalModifiers, trace);
243        }
244    
245        @NotNull
246        public static Map<JetModifierKeywordToken, ASTNode> getNodesCorrespondingToModifiers(@NotNull JetModifierList modifierList, @NotNull Collection<JetModifierKeywordToken> possibleModifiers) {
247            Map<JetModifierKeywordToken, ASTNode> nodes = Maps.newHashMap();
248            for (JetModifierKeywordToken modifier : possibleModifiers) {
249                if (modifierList.hasModifier(modifier)) {
250                    nodes.put(modifier, modifierList.getModifierNode(modifier));
251                }
252            }
253            return nodes;
254        }
255    
256        @NotNull
257        public static Modality resolveModalityFromModifiers(@NotNull JetModifierListOwner modifierListOwner, @NotNull Modality defaultModality) {
258            return resolveModalityFromModifiers(modifierListOwner.getModifierList(), defaultModality);
259        }
260    
261        public static Modality resolveModalityFromModifiers(@Nullable JetModifierList modifierList, @NotNull Modality defaultModality) {
262            if (modifierList == null) return defaultModality;
263            boolean hasAbstractModifier = modifierList.hasModifier(ABSTRACT_KEYWORD);
264            boolean hasOverrideModifier = modifierList.hasModifier(OVERRIDE_KEYWORD);
265    
266            if (modifierList.hasModifier(OPEN_KEYWORD)) {
267                if (hasAbstractModifier || defaultModality == Modality.ABSTRACT) {
268                    return Modality.ABSTRACT;
269                }
270                return Modality.OPEN;
271            }
272            if (hasAbstractModifier) {
273                return Modality.ABSTRACT;
274            }
275            boolean hasFinalModifier = modifierList.hasModifier(FINAL_KEYWORD);
276            if (hasOverrideModifier && !hasFinalModifier && !(defaultModality == Modality.ABSTRACT)) {
277                return Modality.OPEN;
278            }
279            if (hasFinalModifier) {
280                return Modality.FINAL;
281            }
282            return defaultModality;
283        }
284    
285        @NotNull
286        public static Visibility resolveVisibilityFromModifiers(@NotNull JetModifierListOwner modifierListOwner, @NotNull Visibility defaultVisibility) {
287            return resolveVisibilityFromModifiers(modifierListOwner.getModifierList(), defaultVisibility);
288        }
289    
290        public static Visibility resolveVisibilityFromModifiers(@Nullable JetModifierList modifierList, @NotNull Visibility defaultVisibility) {
291            if (modifierList == null) return defaultVisibility;
292            if (modifierList.hasModifier(PRIVATE_KEYWORD)) return Visibilities.PRIVATE;
293            if (modifierList.hasModifier(PUBLIC_KEYWORD)) return Visibilities.PUBLIC;
294            if (modifierList.hasModifier(PROTECTED_KEYWORD)) return Visibilities.PROTECTED;
295            if (modifierList.hasModifier(INTERNAL_KEYWORD)) return Visibilities.INTERNAL;
296            return defaultVisibility;
297        }
298    
299        public static boolean isInnerClass(@Nullable JetModifierList modifierList) {
300            return modifierList != null && modifierList.hasModifier(INNER_KEYWORD);
301        }
302    
303        @NotNull
304        public static Visibility getDefaultClassVisibility(@NotNull ClassDescriptor descriptor) {
305            ClassKind kind = descriptor.getKind();
306            if (kind == ClassKind.ENUM_ENTRY) {
307                return Visibilities.PUBLIC;
308            }
309            if (kind == ClassKind.CLASS_OBJECT) {
310                return ((ClassDescriptor) descriptor.getContainingDeclaration()).getVisibility();
311            }
312            return Visibilities.INTERNAL;
313        }
314    
315        private void runAnnotationCheckers(@NotNull JetDeclaration declaration, @NotNull DeclarationDescriptor descriptor) {
316            for (AnnotationChecker checker : additionalCheckerProvider.getAnnotationCheckers()) {
317                checker.check(declaration, descriptor, trace);
318            }
319        }
320    }