blob: 6fa2c5e3f403f226baa2b54b2d135ab2a6bbd7d7 [file] [log] [blame]
/*
* Copyright 2000-2014 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.plugins.groovy.lang.psi.impl.auxiliary.modifiers;
import com.intellij.lang.ASTNode;
import com.intellij.psi.*;
import com.intellij.psi.stubs.IStubElementType;
import com.intellij.psi.tree.IElementType;
import com.intellij.psi.util.CachedValueProvider;
import com.intellij.psi.util.CachedValuesManager;
import com.intellij.psi.util.PsiModificationTracker;
import com.intellij.util.ArrayFactory;
import com.intellij.util.IncorrectOperationException;
import com.intellij.util.containers.ContainerUtil;
import gnu.trove.TObjectIntHashMap;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.plugins.groovy.config.GroovyConfigUtils;
import org.jetbrains.plugins.groovy.lang.lexer.GroovyTokenTypes;
import org.jetbrains.plugins.groovy.lang.lexer.TokenSets;
import org.jetbrains.plugins.groovy.lang.parser.GroovyElementTypes;
import org.jetbrains.plugins.groovy.lang.psi.GroovyElementVisitor;
import org.jetbrains.plugins.groovy.lang.psi.GroovyPsiElementFactory;
import org.jetbrains.plugins.groovy.lang.psi.api.auxiliary.modifiers.GrModifier;
import org.jetbrains.plugins.groovy.lang.psi.api.auxiliary.modifiers.GrModifierFlags;
import org.jetbrains.plugins.groovy.lang.psi.api.auxiliary.modifiers.GrModifierList;
import org.jetbrains.plugins.groovy.lang.psi.api.auxiliary.modifiers.annotation.GrAnnotation;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.GrField;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.GrVariable;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.GrVariableDeclaration;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.params.GrParameter;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.GrTypeDefinition;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.GrTypeDefinitionBody;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.members.GrEnumConstant;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.members.GrMethod;
import org.jetbrains.plugins.groovy.lang.psi.api.toplevel.packaging.GrPackageDefinition;
import org.jetbrains.plugins.groovy.lang.psi.impl.GrStubElementBase;
import org.jetbrains.plugins.groovy.lang.psi.impl.PsiImplUtil;
import org.jetbrains.plugins.groovy.lang.psi.stubs.GrModifierListStub;
import java.util.ArrayList;
import java.util.Map;
/**
* @autor: Dmitry.Krasilschikov
* @date: 18.03.2007
*/
@SuppressWarnings({"StaticFieldReferencedViaSubclass"})
public class GrModifierListImpl extends GrStubElementBase<GrModifierListStub> implements GrModifierList, StubBasedPsiElement<GrModifierListStub> {
public static final TObjectIntHashMap<String> NAME_TO_MODIFIER_FLAG_MAP = new TObjectIntHashMap<String>();
public static final Map<String, IElementType> NAME_TO_MODIFIER_ELEMENT_TYPE = ContainerUtil.newHashMap();
private static final ArrayFactory<GrAnnotation> ARRAY_FACTORY = new ArrayFactory<GrAnnotation>() {
@NotNull
@Override
public GrAnnotation[] create(int count) {
return new GrAnnotation[count];
}
};
private static final TObjectIntHashMap<String> PRIORITY = new TObjectIntHashMap<String>(16);
static {
NAME_TO_MODIFIER_FLAG_MAP.put(GrModifier.PUBLIC, GrModifierFlags.PUBLIC_MASK);
NAME_TO_MODIFIER_FLAG_MAP.put(GrModifier.PROTECTED, GrModifierFlags.PROTECTED_MASK);
NAME_TO_MODIFIER_FLAG_MAP.put(GrModifier.PRIVATE, GrModifierFlags.PRIVATE_MASK);
NAME_TO_MODIFIER_FLAG_MAP.put(GrModifier.PACKAGE_LOCAL, GrModifierFlags.PACKAGE_LOCAL_MASK);
NAME_TO_MODIFIER_FLAG_MAP.put(GrModifier.STATIC, GrModifierFlags.STATIC_MASK);
NAME_TO_MODIFIER_FLAG_MAP.put(GrModifier.ABSTRACT, GrModifierFlags.ABSTRACT_MASK);
NAME_TO_MODIFIER_FLAG_MAP.put(GrModifier.FINAL, GrModifierFlags.FINAL_MASK);
NAME_TO_MODIFIER_FLAG_MAP.put(GrModifier.NATIVE, GrModifierFlags.NATIVE_MASK);
NAME_TO_MODIFIER_FLAG_MAP.put(GrModifier.SYNCHRONIZED, GrModifierFlags.SYNCHRONIZED_MASK);
NAME_TO_MODIFIER_FLAG_MAP.put(GrModifier.STRICTFP, GrModifierFlags.STRICTFP_MASK);
NAME_TO_MODIFIER_FLAG_MAP.put(GrModifier.TRANSIENT, GrModifierFlags.TRANSIENT_MASK);
NAME_TO_MODIFIER_FLAG_MAP.put(GrModifier.VOLATILE, GrModifierFlags.VOLATILE_MASK);
NAME_TO_MODIFIER_FLAG_MAP.put(GrModifier.DEF, GrModifierFlags.DEF_MASK);
PRIORITY.put(GrModifier.PUBLIC, 0);
PRIORITY.put(GrModifier.PROTECTED, 0);
PRIORITY.put(GrModifier.PRIVATE, 0);
PRIORITY.put(GrModifier.PACKAGE_LOCAL, 0);
PRIORITY.put(GrModifier.STATIC, 1);
PRIORITY.put(GrModifier.ABSTRACT, 1);
PRIORITY.put(GrModifier.FINAL, 2);
PRIORITY.put(GrModifier.NATIVE, 3);
PRIORITY.put(GrModifier.SYNCHRONIZED, 3);
PRIORITY.put(GrModifier.STRICTFP, 3);
PRIORITY.put(GrModifier.TRANSIENT, 3);
PRIORITY.put(GrModifier.VOLATILE, 3);
PRIORITY.put(GrModifier.DEF, 4);
NAME_TO_MODIFIER_ELEMENT_TYPE.put(GrModifier.PUBLIC, GroovyTokenTypes.kPUBLIC);
NAME_TO_MODIFIER_ELEMENT_TYPE.put(GrModifier.ABSTRACT, GroovyTokenTypes.kABSTRACT);
NAME_TO_MODIFIER_ELEMENT_TYPE.put(GrModifier.NATIVE, GroovyTokenTypes.kNATIVE);
NAME_TO_MODIFIER_ELEMENT_TYPE.put(GrModifier.PRIVATE, GroovyTokenTypes.kPRIVATE);
NAME_TO_MODIFIER_ELEMENT_TYPE.put(GrModifier.PROTECTED, GroovyTokenTypes.kPROTECTED);
NAME_TO_MODIFIER_ELEMENT_TYPE.put(GrModifier.SYNCHRONIZED, GroovyTokenTypes.kSYNCHRONIZED);
NAME_TO_MODIFIER_ELEMENT_TYPE.put(GrModifier.STRICTFP, GroovyTokenTypes.kSTRICTFP);
NAME_TO_MODIFIER_ELEMENT_TYPE.put(GrModifier.STATIC, GroovyTokenTypes.kSTATIC);
NAME_TO_MODIFIER_ELEMENT_TYPE.put(GrModifier.FINAL, GroovyTokenTypes.kFINAL);
NAME_TO_MODIFIER_ELEMENT_TYPE.put(GrModifier.TRANSIENT, GroovyTokenTypes.kTRANSIENT);
NAME_TO_MODIFIER_ELEMENT_TYPE.put(GrModifier.NATIVE, GroovyTokenTypes.kNATIVE);
NAME_TO_MODIFIER_ELEMENT_TYPE.put(GrModifier.DEF, GroovyTokenTypes.kDEF);
NAME_TO_MODIFIER_ELEMENT_TYPE.put(GrModifier.VOLATILE, GroovyTokenTypes.kVOLATILE);
}
private static final String[] VISIBILITY_MODIFIERS = {GrModifier.PUBLIC, GrModifier.PROTECTED, GrModifier.PRIVATE};
public GrModifierListImpl(@NotNull ASTNode node) {
super(node);
}
@Override
public PsiElement getParent() {
return getParentByStub();
}
public GrModifierListImpl(GrModifierListStub stub) {
this(stub, GroovyElementTypes.MODIFIERS);
}
public GrModifierListImpl(GrModifierListStub stub, IStubElementType nodeType) {
super(stub, nodeType);
}
@Override
public void accept(GroovyElementVisitor visitor) {
visitor.visitModifierList(this);
}
public String toString() {
return "Modifiers";
}
@Override
@NotNull
public PsiElement[] getModifiers() {
final ArrayList<PsiElement> result = new ArrayList<PsiElement>();
for (PsiElement cur = getFirstChild(); cur != null; cur = cur.getNextSibling()) {
if (cur instanceof GrAnnotation || TokenSets.MODIFIERS.contains(cur.getNode().getElementType())) {
result.add(cur);
}
}
return result.toArray(new PsiElement[result.size()]);
}
@Override
public boolean hasExplicitVisibilityModifiers() {
final GrModifierListStub stub = getStub();
if (stub != null) {
return (stub.getModifiersFlags() & (GrModifierFlags.PUBLIC_MASK | GrModifierFlags.PROTECTED_MASK | GrModifierFlags.PRIVATE_MASK)) != 0;
}
for (@GrModifier.GrModifierConstant String type : VISIBILITY_MODIFIERS) {
if (hasExplicitModifier(type)) return true;
}
return false;
}
public static boolean checkModifierProperty(@NotNull GrModifierList modifierList, @GrModifier.GrModifierConstant @NotNull String modifier) {
final PsiElement owner = modifierList.getParent();
if (owner instanceof GrVariableDeclaration && owner.getParent() instanceof GrTypeDefinitionBody) {
PsiElement pParent = owner.getParent().getParent();
if (!modifierList.hasExplicitVisibilityModifiers()) { //properties are backed by private fields
if (!(pParent instanceof GrTypeDefinition && isInterface((GrTypeDefinition)pParent))) {
if (modifier.equals(GrModifier.PRIVATE)) return true;
if (modifier.equals(GrModifier.PROTECTED)) return false;
if (modifier.equals(GrModifier.PUBLIC)) return false;
}
}
if (pParent instanceof GrTypeDefinition && isInterface((GrTypeDefinition)pParent)) {
if (modifier.equals(GrModifier.STATIC)) return true;
if (modifier.equals(GrModifier.FINAL)) return true;
}
if (pParent instanceof GrTypeDefinition) {
PsiModifierList pModifierList = ((GrTypeDefinition)pParent).getModifierList();
if (pModifierList != null && !modifierList.hasExplicitVisibilityModifiers() && PsiImplUtil.hasImmutableAnnotation(pModifierList)) {
if (modifier.equals(GrModifier.FINAL)) return true;
}
}
}
if (owner instanceof GrMethod && owner.getParent() instanceof GrTypeDefinitionBody) {
PsiElement parent = owner.getParent().getParent();
if (parent instanceof GrTypeDefinition && ((GrTypeDefinition)parent).isInterface()) {
if (GrModifier.ABSTRACT.equals(modifier)) return true;
if (!((GrTypeDefinition)parent).isTrait() && GrModifier.PUBLIC.equals(modifier)) return true;
}
}
if (modifierList.hasExplicitModifier(modifier)) {
return true;
}
if (modifier.equals(GrModifier.PUBLIC)) {
if (owner instanceof GrPackageDefinition) return false;
if (owner instanceof GrVariableDeclaration && !(owner.getParent() instanceof GrTypeDefinitionBody) || owner instanceof GrVariable) {
return false;
}
//groovy type definitions and methods are public by default
return !modifierList.hasExplicitModifier(GrModifier.PRIVATE) && !modifierList.hasExplicitModifier(GrModifier.PROTECTED);
}
if (owner instanceof GrTypeDefinition) {
final GrTypeDefinition clazz = (GrTypeDefinition)owner;
if (modifier.equals(GrModifier.STATIC)) {
final PsiClass containingClass = clazz.getContainingClass();
return containingClass != null && containingClass.isInterface();
}
if (modifier.equals(GrModifier.ABSTRACT)) {
if (clazz.isInterface()) return true;
if (clazz.isEnum() &&
GroovyConfigUtils.getInstance().isVersionAtLeast(modifierList, GroovyConfigUtils.GROOVY2_0)) {
for (GrMethod method : clazz.getCodeMethods()) {
if (method.hasModifierProperty(PsiModifier.ABSTRACT)) return true;
}
}
}
if (modifier.equals(GrModifier.FINAL)) {
if (clazz.isEnum()) {
final GrField[] fields = clazz.getFields();
for (GrField field : fields) {
if (field instanceof GrEnumConstant && ((GrEnumConstant)field).getInitializingClass() != null) {
return false;
}
}
return true;
}
}
}
return false;
}
private static boolean isInterface(GrTypeDefinition pParent) {
return pParent.isInterface() && !pParent.isTrait();
}
@Override
public boolean hasModifierProperty(@NotNull @NonNls String modifier) {
return checkModifierProperty(this, modifier);
}
@Override
public boolean hasExplicitModifier(@NotNull @NonNls String name) {
final GrModifierListStub stub = getStub();
if (stub != null) {
return hasMaskExplicitModifier(name, stub.getModifiersFlags());
}
return findChildByType(NAME_TO_MODIFIER_ELEMENT_TYPE.get(name)) != null;
}
public static boolean hasMaskExplicitModifier(String name, int mask) {
final int flag = NAME_TO_MODIFIER_FLAG_MAP.get(name);
return (mask & flag) != 0;
}
@Override
public void setModifierProperty(@NotNull @NonNls String name, boolean doSet) throws IncorrectOperationException {
if (hasModifierProperty(name) == doSet) return;
if (doSet) {
if (GrModifier.PRIVATE.equals(name) ||
GrModifier.PROTECTED.equals(name) ||
GrModifier.PUBLIC.equals(name) ||
GrModifier.PACKAGE_LOCAL.equals(name)) {
setModifierPropertyInternal(GrModifier.PUBLIC, false);
setModifierPropertyInternal(GrModifier.PROTECTED, false);
setModifierPropertyInternal(GrModifier.PRIVATE, false);
}
}
if (GrModifier.PACKAGE_LOCAL.equals(name) /*|| GrModifier.PUBLIC.equals(name)*/) {
if (getModifiers().length == 0) {
setModifierProperty(GrModifier.DEF, true);
}
}
else {
setModifierPropertyInternal(name, doSet);
}
}
@NotNull
@Override
public GrAnnotation[] getRawAnnotations() {
return getStubOrPsiChildren(GroovyElementTypes.ANNOTATION, ARRAY_FACTORY);
}
private void setModifierPropertyInternal(String name, boolean doSet) {
if (doSet) {
if (isEmptyModifierList()) {
final PsiElement nextSibling = getNextSibling();
if (nextSibling != null && !PsiImplUtil.isWhiteSpaceOrNls(nextSibling)) {
getNode().getTreeParent().addLeaf(TokenType.WHITE_SPACE, " ", nextSibling.getNode());
}
}
PsiElement modifier = GroovyPsiElementFactory.getInstance(getProject()).createModifierFromText(name);
PsiElement anchor = findAnchor(name);
addAfter(modifier, anchor);
}
else {
final PsiElement[] modifiers = findChildrenByType(TokenSets.MODIFIERS, PsiElement.class);
for (PsiElement modifier : modifiers) {
if (name.equals(modifier.getText())) {
deleteChildRange(modifier, modifier);
break;
}
}
if (isEmptyModifierList()) {
final PsiElement nextSibling = getNextSibling();
if (nextSibling != null && PsiImplUtil.isWhiteSpaceOrNls(nextSibling)) {
nextSibling.delete();
}
}
}
}
@Override
public ASTNode addInternal(ASTNode first, ASTNode last, ASTNode anchor, Boolean before) {
final ASTNode node = super.addInternal(first, last, anchor, before);
final PsiElement sibling = getNextSibling();
if (sibling != null && sibling.getText().contains("\n")) {
sibling.replace(GroovyPsiElementFactory.getInstance(getProject()).createWhiteSpace());
}
return node;
}
private boolean isEmptyModifierList() {
return getTextLength() == 0 || getModifiers().length == 0 && getRawAnnotations().length == 0;
}
@Nullable
private PsiElement findAnchor(String name) {
final int myPriority = PRIORITY.get(name);
PsiElement anchor = null;
for (PsiElement modifier : getModifiers()) {
final int otherPriority = PRIORITY.get(modifier.getText());
if (otherPriority <= myPriority) {
anchor = modifier;
}
else if (otherPriority > myPriority && anchor != null) {
break;
}
}
return anchor;
}
@Override
public void checkSetModifierProperty(@NotNull @NonNls String name, boolean value) throws IncorrectOperationException {
}
@Override
@NotNull
public GrAnnotation[] getAnnotations() {
return CachedValuesManager.getCachedValue(this, new CachedValueProvider<GrAnnotation[]>() {
@Nullable
@Override
public Result<GrAnnotation[]> compute() {
return Result.create(GrAnnotationCollector.getResolvedAnnotations(GrModifierListImpl.this), PsiModificationTracker.JAVA_STRUCTURE_MODIFICATION_COUNT);
}
});
}
@Override
@NotNull
public PsiAnnotation[] getApplicableAnnotations() {
//todo[medvedev]
return getAnnotations();
}
@Override
@Nullable
public PsiAnnotation findAnnotation(@NotNull @NonNls String qualifiedName) {
for (GrAnnotation annotation : getAnnotations()) {
if (qualifiedName.equals(annotation.getQualifiedName())) {
return annotation;
}
}
return null;
}
@Override
@NotNull
public GrAnnotation addAnnotation(@NotNull @NonNls String qualifiedName) {
final PsiClass psiClass = JavaPsiFacade.getInstance(getProject()).findClass(qualifiedName, getResolveScope());
final GroovyPsiElementFactory factory = GroovyPsiElementFactory.getInstance(getProject());
GrAnnotation annotation;
if (psiClass != null && psiClass.isAnnotationType()) {
annotation = (GrAnnotation)addAfter(factory.createModifierFromText("@xxx"), null);
annotation.getClassReference().bindToElement(psiClass);
}
else {
annotation = (GrAnnotation)addAfter(factory.createModifierFromText("@" + qualifiedName), null);
}
final PsiElement parent = getParent();
if (!(parent instanceof GrParameter)) {
final ASTNode node = annotation.getNode();
final ASTNode treeNext = node.getTreeNext();
if (treeNext != null) {
getNode().addLeaf(TokenType.WHITE_SPACE, "\n", treeNext);
}
else {
parent.getNode().addLeaf(TokenType.WHITE_SPACE, "\n", getNode().getTreeNext());
}
}
return annotation;
}
}