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