All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.netbeans.modules.java.hints.ClassStructure Maven / Gradle / Ivy

The newest version!
/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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.netbeans.modules.java.hints;

import com.sun.source.tree.BlockTree;
import com.sun.source.tree.ClassTree;
import com.sun.source.tree.CompilationUnitTree;
import com.sun.source.tree.MethodTree;
import com.sun.source.tree.ModifiersTree;
import com.sun.source.tree.Tree;
import com.sun.source.tree.Tree.Kind;
import com.sun.source.tree.VariableTree;
import com.sun.source.util.TreePath;
import com.sun.source.util.Trees;
import java.util.ArrayList;
import java.util.EnumSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.TypeElement;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror;
import org.netbeans.api.java.source.GeneratorUtilities;
import org.netbeans.api.java.source.TreeMaker;
import org.netbeans.api.java.source.TreePathHandle;
import org.netbeans.api.java.source.TreeUtilities;
import org.netbeans.api.java.source.WorkingCopy;
import org.netbeans.modules.java.editor.overridden.ComputeOverriding;
import org.netbeans.modules.java.editor.overridden.ElementDescription;
import org.netbeans.spi.editor.hints.ErrorDescription;
import org.netbeans.spi.java.hints.ErrorDescriptionFactory;
import org.netbeans.spi.java.hints.Hint;
import org.netbeans.spi.java.hints.Hint.Options;
import org.netbeans.spi.java.hints.HintContext;
import org.netbeans.spi.java.hints.JavaFix;
import org.netbeans.spi.java.hints.TriggerTreeKind;
import org.netbeans.spi.java.hints.support.FixFactory;
import org.openide.util.NbBundle;

/**
 *
 * @author Dusan Balek
 */
public class ClassStructure {

    @Hint(displayName = "#DN_org.netbeans.modules.java.hints.ClassStructure.finalClass", description = "#DESC_org.netbeans.modules.java.hints.ClassStructure.finalClass", category = "class_structure", enabled = false, suppressWarnings = "FinalClass") //NOI18N
    @TriggerTreeKind({Tree.Kind.ANNOTATION_TYPE, Tree.Kind.CLASS, Tree.Kind.ENUM, Tree.Kind.INTERFACE})
    public static ErrorDescription finalClass(HintContext context) {
        final ClassTree cls = (ClassTree) context.getPath().getLeaf();
        if (cls.getModifiers().getFlags().contains(Modifier.FINAL)) {
            return ErrorDescriptionFactory.forName(context, cls, NbBundle.getMessage(ClassStructure.class, "MSG_FinalClass", cls.getSimpleName()), //NOI18N
                    FixFactory.removeModifiersFix(context.getInfo(), TreePath.getPath(context.getPath(), cls.getModifiers()), EnumSet.of(Modifier.FINAL), NbBundle.getMessage(ClassStructure.class, "FIX_RemoveFinalFromClass", cls.getSimpleName()))); //NOI18N
        }
        return null;
    }

    @Hint(displayName = "#DN_org.netbeans.modules.java.hints.ClassStructure.finalMethod", description = "#DESC_org.netbeans.modules.java.hints.ClassStructure.finalMethod", category = "class_structure", enabled = false, suppressWarnings = {"FinalMethod"}) //NOI18N
    @TriggerTreeKind(Kind.METHOD)
    public static ErrorDescription finalMethod(HintContext context) {
        final MethodTree mth = (MethodTree) context.getPath().getLeaf();
        if (mth.getModifiers().getFlags().contains(Modifier.FINAL)) {
            return ErrorDescriptionFactory.forName(context, mth, NbBundle.getMessage(ClassStructure.class, "MSG_FinalMethod", mth.getName()), //NOI18N
                    FixFactory.removeModifiersFix(context.getInfo(), TreePath.getPath(context.getPath(), mth.getModifiers()), EnumSet.of(Modifier.FINAL), NbBundle.getMessage(ClassStructure.class, "FIX_RemoveFinalFromMethod", mth.getName()))); //NOI18N
        }
        return null;
    }

    @Hint(displayName = "#DN_org.netbeans.modules.java.hints.ClassStructure.finalPrivateMethod", description = "#DESC_org.netbeans.modules.java.hints.ClassStructure.finalPrivateMethod", category = "class_structure", suppressWarnings = {"FinalPrivateMethod"}) //NOI18N
    @TriggerTreeKind(Kind.METHOD)
    public static ErrorDescription finalPrivateMethod(HintContext context) {
        final MethodTree mth = (MethodTree) context.getPath().getLeaf();
        if (mth.getModifiers().getFlags().containsAll(EnumSet.of(Modifier.FINAL, Modifier.PRIVATE))) {
            return ErrorDescriptionFactory.forName(context, mth, NbBundle.getMessage(ClassStructure.class, "MSG_FinalPrivateMethod", mth.getName()), //NOI18N
                    FixFactory.removeModifiersFix(context.getInfo(), TreePath.getPath(context.getPath(), mth.getModifiers()), EnumSet.of(Modifier.FINAL), NbBundle.getMessage(ClassStructure.class, "FIX_RemoveFinalFromMethod", mth.getName()))); //NOI18N
        }
        return null;
    }

    @Hint(displayName = "#DN_org.netbeans.modules.java.hints.ClassStructure.finalMethodInFinalClass", description = "#DESC_org.netbeans.modules.java.hints.ClassStructure.finalMethodInFinalClass", category = "class_structure", enabled = false, suppressWarnings = {"FinalMethodInFinalClass"}) //NOI18N
    @TriggerTreeKind(Kind.METHOD)
    public static ErrorDescription finalMethodInFinalClass(HintContext context) {
        final MethodTree mth = (MethodTree) context.getPath().getLeaf();
        final Tree parent = context.getPath().getParentPath().getLeaf();
        if (TreeUtilities.CLASS_TREE_KINDS.contains(parent.getKind()) && mth.getModifiers().getFlags().contains(Modifier.FINAL) && ((ClassTree) parent).getModifiers().getFlags().contains(Modifier.FINAL)) {
            // see defect #236974; although redundant, if the method is annotated with @SafeVarargs, we must permit final modifier.
            ExecutableElement ee = (ExecutableElement)context.getInfo().getTrees().getElement(context.getPath());
            if (ee == null || ee.getAnnotation(SafeVarargs.class) == null) {
                return ErrorDescriptionFactory.forName(context, mth, NbBundle.getMessage(ClassStructure.class, "MSG_FinalMethodInFinalClass", mth.getName()), //NOI18N
                        FixFactory.removeModifiersFix(context.getInfo(), TreePath.getPath(context.getPath(), mth.getModifiers()), EnumSet.of(Modifier.FINAL), NbBundle.getMessage(ClassStructure.class, "FIX_RemoveFinalFromMethod", mth.getName()))); //NOI18N
            }
        }
        return null;
    }

    @Hint(displayName = "#DN_org.netbeans.modules.java.hints.ClassStructure.noopMethodInAbstractClass", description = "#DESC_org.netbeans.modules.java.hints.ClassStructure.noopMethodInAbstractClass", category = "class_structure", enabled = false, suppressWarnings = {"NoopMethodInAbstractClass"}, options=Options.QUERY) //NOI18N
    @TriggerTreeKind(Kind.METHOD)
    public static ErrorDescription noopMethodInAbstractClass(HintContext context) {
        final MethodTree mth = (MethodTree) context.getPath().getLeaf();
        final Tree parent = context.getPath().getParentPath().getLeaf();
        if (TreeUtilities.CLASS_TREE_KINDS.contains(parent.getKind()) && ((ClassTree) parent).getModifiers().getFlags().contains(Modifier.ABSTRACT)) {
            final BlockTree body = mth.getBody();
            if (body != null && body.getStatements().isEmpty()) {
                return ErrorDescriptionFactory.forName(context, mth, NbBundle.getMessage(ClassStructure.class, "MSG_NoopMethodInAbstractClass", mth.getName())); //NOI18N
            }
        }
        return null;
    }

    @Hint(displayName = "#DN_org.netbeans.modules.java.hints.ClassStructure.publicConstructorInNonPublicClass", description = "#DESC_org.netbeans.modules.java.hints.ClassStructure.publicConstructorInNonPublicClass", category = "class_structure", enabled = false, suppressWarnings = {"PublicConstructorInNonPublicClass"}) //NOI18N
    @TriggerTreeKind(Kind.METHOD)
    public static ErrorDescription publicConstructorInNonPublicClass(HintContext context) {
        final MethodTree mth = (MethodTree) context.getPath().getLeaf();
        final Tree parent = context.getPath().getParentPath().getLeaf();
        if (TreeUtilities.CLASS_TREE_KINDS.contains(parent.getKind()) && mth.getReturnType() == null && "".contentEquals(mth.getName()) && //NOI18N
                mth.getModifiers().getFlags().contains(Modifier.PUBLIC) && !((ClassTree) parent).getModifiers().getFlags().contains(Modifier.PUBLIC)) {
            return ErrorDescriptionFactory.forName(context, mth, NbBundle.getMessage(ClassStructure.class, "MSG_PublicConstructorInNonPublicClass", mth.getName()), //NOI18N
                    FixFactory.removeModifiersFix(context.getInfo(), TreePath.getPath(context.getPath(), mth.getModifiers()), EnumSet.of(Modifier.PUBLIC), NbBundle.getMessage(ClassStructure.class, "FIX_RemovePublicFromConstructor"))); //NOI18N
        }
        return null;
    }

    @Hint(displayName = "#DN_org.netbeans.modules.java.hints.ClassStructure.protectedMemberInFinalClass", description = "#DESC_org.netbeans.modules.java.hints.ClassStructure.protectedMemberInFinalClass", category = "class_structure", enabled = false, suppressWarnings = {"ProtectedMemberInFinalClass"}) //NOI18N
    @TriggerTreeKind({Kind.METHOD, Kind.VARIABLE})
    public static ErrorDescription protectedMemberInFinalClass(HintContext context) {
        final Tree tree = context.getPath().getLeaf();
        final Tree parent = context.getPath().getParentPath().getLeaf();
        if (TreeUtilities.CLASS_TREE_KINDS.contains(parent.getKind())) {
            if (tree.getKind() == Kind.METHOD) {
                final MethodTree mth = (MethodTree) tree;
                if (mth.getModifiers().getFlags().contains(Modifier.PROTECTED) && ((ClassTree) parent).getModifiers().getFlags().contains(Modifier.FINAL)) {
                    Element el = context.getInfo().getTrees().getElement(context.getPath());
                    if (el == null || el.getKind() != ElementKind.METHOD) {
                        return null;
                    }
                    List overrides = new LinkedList();
                    ComputeOverriding.detectOverrides(context.getInfo(), (TypeElement) el.getEnclosingElement(), (ExecutableElement) el, overrides);
                    for (ElementDescription ed : overrides) {
                        Element res = ed.getHandle().resolve(context.getInfo());
                        if (res == null) {
                            continue; //XXX: log
                        }
                        if (   res.getModifiers().contains(Modifier.PROTECTED)
                            || /*to prevent reports for broken sources:*/ res.getModifiers().contains(Modifier.PUBLIC)) {
                            return null;
                        }
                    }
                    return ErrorDescriptionFactory.forName(context, mth, NbBundle.getMessage(ClassStructure.class, "MSG_ProtectedMethodInFinalClass", mth.getName()), //NOI18N
                            FixFactory.removeModifiersFix(context.getInfo(), TreePath.getPath(context.getPath(), mth.getModifiers()), EnumSet.of(Modifier.PROTECTED), NbBundle.getMessage(ClassStructure.class, "FIX_RemoveProtectedFromMethod", mth.getName()))); //NOI18N
                }
            } else {
                final VariableTree var = (VariableTree) tree;
                if (var.getModifiers().getFlags().contains(Modifier.PROTECTED) && ((ClassTree) parent).getModifiers().getFlags().contains(Modifier.FINAL)) {
                    return ErrorDescriptionFactory.forName(context, var, NbBundle.getMessage(ClassStructure.class, "MSG_ProtectedFieldInFinalClass", var.getName()), //NOI18N
                            FixFactory.removeModifiersFix(context.getInfo(), TreePath.getPath(context.getPath(), var.getModifiers()), EnumSet.of(Modifier.PROTECTED), NbBundle.getMessage(ClassStructure.class, "FIX_RemoveProtectedFromField", var.getName()))); //NOI18N
                }
            }
        }
        return null;
    }

    @Hint(displayName = "#DN_org.netbeans.modules.java.hints.ClassStructure.markerInterface", description = "#DESC_org.netbeans.modules.java.hints.ClassStructure.markerInterface", category = "class_structure", enabled = false, suppressWarnings = {"MarkerInterface"}, options=Options.QUERY) //NOI18N
    @TriggerTreeKind({Tree.Kind.ANNOTATION_TYPE, Tree.Kind.CLASS, Tree.Kind.ENUM, Tree.Kind.INTERFACE})
    public static ErrorDescription markerInterface(HintContext context) {
        final ClassTree cls = (ClassTree) context.getPath().getLeaf();
        if (context.getInfo().getTreeUtilities().isInterface(cls) && cls.getMembers().isEmpty() && cls.getImplementsClause().size() < 2) {
            return ErrorDescriptionFactory.forName(context, cls, NbBundle.getMessage(ClassStructure.class, "MSG_MarkerInterface", cls.getSimpleName())); //NOI18N
        }
        return null;
    }

    @Hint(displayName = "#DN_org.netbeans.modules.java.hints.ClassStructure.classMayBeInterface", description = "#DESC_org.netbeans.modules.java.hints.ClassStructure.classMayBeInterface", category = "class_structure", enabled = false, suppressWarnings = {"ClassMayBeInterface"}, options=Options.NO_BATCH) //NOI18N
    @TriggerTreeKind({Tree.Kind.ANNOTATION_TYPE, Tree.Kind.CLASS, Tree.Kind.ENUM, Tree.Kind.INTERFACE})
    public static ErrorDescription classMayBeInterface(HintContext context) {
        final ClassTree cls = (ClassTree) context.getPath().getLeaf();
        final TreeUtilities treeUtilities = context.getInfo().getTreeUtilities();
        if (treeUtilities.isClass(cls) && testClassMayBeInterface(context.getInfo().getTrees(), treeUtilities, context.getPath())) {
            return ErrorDescriptionFactory.forName(context, cls, NbBundle.getMessage(ClassStructure.class, "MSG_ClassMayBeInterface", cls.getSimpleName()), //NOI18N
                    new ConvertClassToInterfaceFixImpl(TreePathHandle.create(context.getPath(), context.getInfo()), NbBundle.getMessage(ClassStructure.class, "FIX_ConvertClassToInterface", cls.getSimpleName())).toEditorFix()); //NOI18N
        }
        return null;
    }

    @Hint(displayName = "#DN_org.netbeans.modules.java.hints.ClassStructure.multipleTopLevelClassesInFile", description = "#DESC_org.netbeans.modules.java.hints.ClassStructure.multipleTopLevelClassesInFile", category = "class_structure", enabled = false, suppressWarnings = {"MultipleTopLevelClassesInFile"}, options=Options.QUERY) //NOI18N
    @TriggerTreeKind({Tree.Kind.ANNOTATION_TYPE, Tree.Kind.CLASS, Tree.Kind.ENUM, Tree.Kind.INTERFACE})
    public static ErrorDescription multipleTopLevelClassesInFile(HintContext context) {
        final ClassTree cls = (ClassTree) context.getPath().getLeaf();
        final Tree parent = context.getPath().getParentPath().getLeaf();
        if (parent.getKind() == Kind.COMPILATION_UNIT) {
            final List typeDecls = new ArrayList(((CompilationUnitTree) parent).getTypeDecls());
            for (Iterator it = typeDecls.iterator(); it.hasNext();) {
                if (it.next().getKind() == Kind.EMPTY_STATEMENT) it.remove();
            }
            if (typeDecls.size() > 1 && typeDecls.get(0) != cls) {
                return ErrorDescriptionFactory.forName(context, cls, NbBundle.getMessage(ClassStructure.class, "MSG_MultipleTopLevelClassesInFile")); //NOI18N
            }
        }
        return null;
    }

    private static boolean testClassMayBeInterface(Trees trees, TreeUtilities treeUtilities, TreePath path) {
        final ClassTree cls = (ClassTree) path.getLeaf();
        if (!treeUtilities.isClass(cls)) {
            return true;
        }
        final Element element = trees.getElement(path);
        if (element == null) return false; //see bug #221820
        final TypeMirror superclass = element.getKind().isClass() ? ((TypeElement) element).getSuperclass() : null;
        if (superclass == null || superclass.getKind() != TypeKind.DECLARED
                || !"java.lang.Object".contentEquals(((TypeElement) ((DeclaredType) superclass).asElement()).getQualifiedName())) { //NOI18N
            return false;
        }
        for (Tree member : cls.getMembers()) {
            TreePath memberPath = TreePath.getPath(path, member);
            if (!treeUtilities.isSynthetic(memberPath)) {
                switch (member.getKind()) {
                    case VARIABLE:
                        if (!((VariableTree) member).getModifiers().getFlags().containsAll(EnumSet.of(Modifier.PUBLIC, Modifier.STATIC, Modifier.FINAL))) {
                            return false;
                        }
                        break;
                    case METHOD:
                        if (!((MethodTree) member).getModifiers().getFlags().containsAll(EnumSet.of(Modifier.PUBLIC, Modifier.ABSTRACT))) {
                            return false;
                        }
                        break;
                    case ANNOTATION_TYPE:
                    case CLASS:
                    case ENUM:
                    case INTERFACE:
                        if (!testClassMayBeInterface(trees, treeUtilities, memberPath)) {
                            return false;
                        }
                        break;
                    default:
                        return false;
                }
            }
        }
        return true;
    }

    private static final class ConvertClassToInterfaceFixImpl extends JavaFix {

        private final String text;

        public ConvertClassToInterfaceFixImpl(TreePathHandle clsHandle, String text) {
            super(clsHandle);
            this.text = text;
        }

        @Override
        public String getText() {
            return text;
        }

        @Override
        protected void performRewrite(TransformationContext ctx) {
            WorkingCopy wc = ctx.getWorkingCopy();
            GeneratorUtilities gu = GeneratorUtilities.get(wc);
            TreePath path = ctx.getPath();
            final ClassTree cls = (ClassTree) path.getLeaf();
            gu.importComments(cls, wc.getCompilationUnit());
            final TreeMaker treeMaker = wc.getTreeMaker();
            ModifiersTree mods = cls.getModifiers();
            if (mods.getFlags().contains(Modifier.ABSTRACT)) {
                Set modifiers = EnumSet.copyOf(mods.getFlags());
                modifiers.remove(Modifier.ABSTRACT);
                ModifiersTree nmods = treeMaker.Modifiers(modifiers, mods.getAnnotations());
                gu.copyComments(mods, nmods, true);
                gu.copyComments(mods, nmods, false);
                mods = nmods;
            }
            Tree nue = treeMaker.Interface(mods, cls.getSimpleName(), cls.getTypeParameters(), cls.getImplementsClause(), cls.getMembers());
            gu.copyComments(cls, nue, true);
            gu.copyComments(cls, nue, false);
            wc.rewrite(path.getLeaf(), nue);
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy