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
017package org.jetbrains.jet.lang.resolve;
018
019import com.google.common.collect.Lists;
020import com.google.common.collect.Maps;
021import com.google.common.collect.Sets;
022import com.intellij.lang.ASTNode;
023import com.intellij.openapi.util.Pair;
024import com.intellij.psi.PsiElement;
025import org.jetbrains.annotations.NotNull;
026import org.jetbrains.annotations.Nullable;
027import org.jetbrains.jet.lang.descriptors.*;
028import org.jetbrains.jet.lang.diagnostics.Errors;
029import org.jetbrains.jet.lang.psi.JetClass;
030import org.jetbrains.jet.lang.psi.JetModifierList;
031import org.jetbrains.jet.lang.psi.JetModifierListOwner;
032import org.jetbrains.jet.lexer.JetKeywordToken;
033import org.jetbrains.jet.lexer.JetToken;
034
035import java.util.Collection;
036import java.util.Collections;
037import java.util.Map;
038
039import static org.jetbrains.jet.lexer.JetTokens.*;
040
041public 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        return Visibilities.INTERNAL;
245    }
246
247    @NotNull
248    public static Visibility getDefaultVisibilityForObjectPropertyDescriptor(@NotNull ClassDescriptor objectClassDescriptor) {
249        if (objectClassDescriptor.getKind() == ClassKind.ENUM_ENTRY) {
250            return Visibilities.PUBLIC;
251        }
252        return Visibilities.INTERNAL;
253    }
254}