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