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.diagnostics.Errors;
029    import org.jetbrains.jet.lang.psi.JetClass;
030    import org.jetbrains.jet.lang.psi.JetEnumEntry;
031    import org.jetbrains.jet.lang.psi.JetModifierList;
032    import org.jetbrains.jet.lang.psi.JetModifierListOwner;
033    import org.jetbrains.jet.lexer.JetModifierKeywordToken;
034    
035    import java.util.Collection;
036    import java.util.Collections;
037    import java.util.Map;
038    
039    import static org.jetbrains.jet.lang.diagnostics.Errors.ILLEGAL_MODIFIER;
040    import static org.jetbrains.jet.lexer.JetTokens.*;
041    
042    public class ModifiersChecker {
043        private static final Collection<JetModifierKeywordToken> MODALITY_MODIFIERS =
044                Lists.newArrayList(ABSTRACT_KEYWORD, OPEN_KEYWORD, FINAL_KEYWORD, OVERRIDE_KEYWORD);
045    
046        private static final Collection<JetModifierKeywordToken> VISIBILITY_MODIFIERS =
047                Lists.newArrayList(PRIVATE_KEYWORD, PROTECTED_KEYWORD, PUBLIC_KEYWORD, INTERNAL_KEYWORD);
048    
049        public static void reportIllegalModifiers(
050                @Nullable JetModifierList modifierList,
051                @NotNull Collection<JetModifierKeywordToken> illegalModifiers,
052                @NotNull BindingTrace trace
053        ) {
054            if (modifierList == null) return;
055    
056            for (JetModifierKeywordToken modifierToken : illegalModifiers) {
057                if (modifierList.hasModifier(modifierToken)) {
058                    PsiElement modifierPsi = modifierList.getModifier(modifierToken);
059                    assert modifierPsi != null;
060                    trace.report(ILLEGAL_MODIFIER.on(modifierPsi, modifierToken));
061                }
062            }
063        }
064    
065        @NotNull
066        private final BindingTrace trace;
067    
068        public ModifiersChecker(@NotNull BindingTrace trace) {
069            this.trace = trace;
070        }
071    
072        public static ModifiersChecker create(@NotNull BindingTrace trace) {
073            return new ModifiersChecker(trace);
074        }
075    
076        public void checkModifiersForDeclaration(@NotNull JetModifierListOwner modifierListOwner, @NotNull DeclarationDescriptor descriptor) {
077            JetModifierList modifierList = modifierListOwner.getModifierList();
078            checkModalityModifiers(modifierList);
079            checkVisibilityModifiers(modifierList, descriptor);
080            checkInnerModifier(modifierListOwner, descriptor);
081        }
082    
083        public void checkModifiersForLocalDeclaration(@NotNull JetModifierListOwner modifierListOwner) {
084            checkIllegalModalityModifiers(modifierListOwner);
085            checkIllegalVisibilityModifiers(modifierListOwner);
086        }
087    
088        public void checkIllegalModalityModifiers(@NotNull JetModifierListOwner modifierListOwner) {
089            checkIllegalInThisContextModifiers(modifierListOwner.getModifierList(), MODALITY_MODIFIERS);
090        }
091    
092        public void checkIllegalVisibilityModifiers(@NotNull JetModifierListOwner modifierListOwner) {
093            checkIllegalInThisContextModifiers(modifierListOwner.getModifierList(), VISIBILITY_MODIFIERS);
094        }
095    
096        private void checkModalityModifiers(@Nullable JetModifierList modifierList) {
097            if (modifierList == null) return;
098            checkRedundantModifier(modifierList, Pair.create(OPEN_KEYWORD, ABSTRACT_KEYWORD), Pair.create(OPEN_KEYWORD, OVERRIDE_KEYWORD));
099    
100            checkCompatibility(modifierList, Lists.newArrayList(ABSTRACT_KEYWORD, OPEN_KEYWORD, FINAL_KEYWORD),
101                               Lists.newArrayList(ABSTRACT_KEYWORD, OPEN_KEYWORD));
102        }
103    
104        private void checkVisibilityModifiers(@Nullable JetModifierList modifierList, @NotNull DeclarationDescriptor descriptor) {
105            if (modifierList == null) return;
106    
107            DeclarationDescriptor containingDeclaration = descriptor.getContainingDeclaration();
108            if (containingDeclaration instanceof PackageFragmentDescriptor) {
109                if (modifierList.hasModifier(PROTECTED_KEYWORD)) {
110                    trace.report(Errors.PACKAGE_MEMBER_CANNOT_BE_PROTECTED.on(modifierList.getModifierNode(PROTECTED_KEYWORD).getPsi()));
111                }
112            }
113    
114            checkCompatibility(modifierList, VISIBILITY_MODIFIERS);
115        }
116    
117        private void checkInnerModifier(@NotNull JetModifierListOwner modifierListOwner, @NotNull DeclarationDescriptor descriptor) {
118            JetModifierList modifierList = modifierListOwner.getModifierList();
119    
120            if (modifierList != null && modifierList.hasModifier(INNER_KEYWORD)) {
121                if (isIllegalInner(descriptor)) {
122                    checkIllegalInThisContextModifiers(modifierList, Collections.singletonList(INNER_KEYWORD));
123                }
124            }
125            else {
126                if (modifierListOwner instanceof JetClass && !(modifierListOwner instanceof JetEnumEntry) && isIllegalNestedClass(descriptor)) {
127                    PsiElement name = ((JetClass) modifierListOwner).getNameIdentifier();
128                    if (name != null) {
129                        trace.report(Errors.NESTED_CLASS_NOT_ALLOWED.on(name));
130                    }
131                }
132            }
133        }
134    
135        private static boolean isIllegalInner(@NotNull DeclarationDescriptor descriptor) {
136            if (!(descriptor instanceof ClassDescriptor)) return true;
137            ClassDescriptor classDescriptor = (ClassDescriptor) descriptor;
138            if (classDescriptor.getKind() != ClassKind.CLASS) return true;
139            DeclarationDescriptor containingDeclaration = classDescriptor.getContainingDeclaration();
140            if (!(containingDeclaration instanceof ClassDescriptor)) return true;
141            return ((ClassDescriptor) containingDeclaration).getKind() == ClassKind.TRAIT;
142        }
143    
144        private static boolean isIllegalNestedClass(@NotNull DeclarationDescriptor descriptor) {
145            if (!(descriptor instanceof ClassDescriptor)) return false;
146            DeclarationDescriptor containingDeclaration = descriptor.getContainingDeclaration();
147            if (!(containingDeclaration instanceof ClassDescriptor)) return false;
148            ClassDescriptor containingClass = (ClassDescriptor) containingDeclaration;
149            return containingClass.isInner() || containingClass.getContainingDeclaration() instanceof FunctionDescriptor;
150        }
151    
152        private void checkCompatibility(@Nullable JetModifierList modifierList, Collection<JetModifierKeywordToken> availableModifiers, Collection<JetModifierKeywordToken>... availableCombinations) {
153            if (modifierList == null) return;
154            Collection<JetModifierKeywordToken> presentModifiers = Sets.newLinkedHashSet();
155            for (JetModifierKeywordToken modifier : availableModifiers) {
156                if (modifierList.hasModifier(modifier)) {
157                    presentModifiers.add(modifier);
158                }
159            }
160            if (presentModifiers.size() == 1) {
161                return;
162            }
163            for (Collection<JetModifierKeywordToken> combination : availableCombinations) {
164                if (presentModifiers.containsAll(combination) && combination.containsAll(presentModifiers)) {
165                    return;
166                }
167            }
168            for (JetModifierKeywordToken token : presentModifiers) {
169                trace.report(Errors.INCOMPATIBLE_MODIFIERS.on(modifierList.getModifierNode(token).getPsi(), presentModifiers));
170            }
171        }
172    
173        private void checkRedundantModifier(@NotNull JetModifierList modifierList, Pair<JetModifierKeywordToken, JetModifierKeywordToken>... redundantBundles) {
174            for (Pair<JetModifierKeywordToken, JetModifierKeywordToken> tokenPair : redundantBundles) {
175                JetModifierKeywordToken redundantModifier = tokenPair.getFirst();
176                JetModifierKeywordToken sufficientModifier = tokenPair.getSecond();
177                if (modifierList.hasModifier(redundantModifier) && modifierList.hasModifier(sufficientModifier)) {
178                    trace.report(Errors.REDUNDANT_MODIFIER.on(modifierList.getModifierNode(redundantModifier).getPsi(), redundantModifier, sufficientModifier));
179                }
180            }
181        }
182    
183        public void checkIllegalInThisContextModifiers(
184                @Nullable JetModifierList modifierList,
185                @NotNull Collection<JetModifierKeywordToken> illegalModifiers
186        ) {
187            reportIllegalModifiers(modifierList, illegalModifiers, trace);
188        }
189    
190        @NotNull
191        public static Map<JetModifierKeywordToken, ASTNode> getNodesCorrespondingToModifiers(@NotNull JetModifierList modifierList, @NotNull Collection<JetModifierKeywordToken> possibleModifiers) {
192            Map<JetModifierKeywordToken, ASTNode> nodes = Maps.newHashMap();
193            for (JetModifierKeywordToken modifier : possibleModifiers) {
194                if (modifierList.hasModifier(modifier)) {
195                    nodes.put(modifier, modifierList.getModifierNode(modifier));
196                }
197            }
198            return nodes;
199        }
200    
201        @NotNull
202        public static Modality resolveModalityFromModifiers(@NotNull JetModifierListOwner modifierListOwner, @NotNull Modality defaultModality) {
203            return resolveModalityFromModifiers(modifierListOwner.getModifierList(), defaultModality);
204        }
205    
206        public static Modality resolveModalityFromModifiers(@Nullable JetModifierList modifierList, @NotNull Modality defaultModality) {
207            if (modifierList == null) return defaultModality;
208            boolean hasAbstractModifier = modifierList.hasModifier(ABSTRACT_KEYWORD);
209            boolean hasOverrideModifier = modifierList.hasModifier(OVERRIDE_KEYWORD);
210    
211            if (modifierList.hasModifier(OPEN_KEYWORD)) {
212                if (hasAbstractModifier || defaultModality == Modality.ABSTRACT) {
213                    return Modality.ABSTRACT;
214                }
215                return Modality.OPEN;
216            }
217            if (hasAbstractModifier) {
218                return Modality.ABSTRACT;
219            }
220            boolean hasFinalModifier = modifierList.hasModifier(FINAL_KEYWORD);
221            if (hasOverrideModifier && !hasFinalModifier && !(defaultModality == Modality.ABSTRACT)) {
222                return Modality.OPEN;
223            }
224            if (hasFinalModifier) {
225                return Modality.FINAL;
226            }
227            return defaultModality;
228        }
229    
230        @NotNull
231        public static Visibility resolveVisibilityFromModifiers(@NotNull JetModifierListOwner modifierListOwner, @NotNull Visibility defaultVisibility) {
232            return resolveVisibilityFromModifiers(modifierListOwner.getModifierList(), defaultVisibility);
233        }
234    
235        public static Visibility resolveVisibilityFromModifiers(@Nullable JetModifierList modifierList, @NotNull Visibility defaultVisibility) {
236            if (modifierList == null) return defaultVisibility;
237            if (modifierList.hasModifier(PRIVATE_KEYWORD)) return Visibilities.PRIVATE;
238            if (modifierList.hasModifier(PUBLIC_KEYWORD)) return Visibilities.PUBLIC;
239            if (modifierList.hasModifier(PROTECTED_KEYWORD)) return Visibilities.PROTECTED;
240            if (modifierList.hasModifier(INTERNAL_KEYWORD)) return Visibilities.INTERNAL;
241            return defaultVisibility;
242        }
243    
244        public static boolean isInnerClass(@Nullable JetModifierList modifierList) {
245            return modifierList != null && modifierList.hasModifier(INNER_KEYWORD);
246        }
247    
248        @NotNull
249        public static Visibility getDefaultClassVisibility(@NotNull ClassDescriptor descriptor) {
250            ClassKind kind = descriptor.getKind();
251            if (kind == ClassKind.ENUM_ENTRY) {
252                return Visibilities.PUBLIC;
253            }
254            if (kind == ClassKind.CLASS_OBJECT) {
255                return ((ClassDescriptor) descriptor.getContainingDeclaration()).getVisibility();
256            }
257            return Visibilities.INTERNAL;
258        }
259    }