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