com.helger.meta.tools.codeingstyleguide.MainCheckCodingStyleguide.java Source code

Java tutorial

Introduction

Here is the source code for com.helger.meta.tools.codeingstyleguide.MainCheckCodingStyleguide.java

Source

/**
 * Copyright (C) 2014-2015 Philip Helger (www.helger.com)
 * philip[at]helger[dot]com
 *
 * 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 com.helger.meta.tools.codeingstyleguide;

import java.io.File;
import java.io.IOException;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;

import javax.annotation.Nonnull;

import org.objectweb.asm.Opcodes;
import org.objectweb.asm.tree.ClassNode;
import org.objectweb.asm.tree.FieldNode;
import org.objectweb.asm.tree.MethodNode;

import com.helger.commons.annotations.CodingStyleguideUnaware;
import com.helger.commons.annotations.Nonempty;
import com.helger.commons.annotations.OverrideOnDemand;
import com.helger.commons.io.file.FileUtils;
import com.helger.commons.io.file.iterate.FileSystemRecursiveIterator;
import com.helger.commons.lang.CGStringHelper;
import com.helger.commons.state.EContinue;
import com.helger.commons.string.StringHelper;
import com.helger.commons.string.StringParser;
import com.helger.meta.AbstractProjectMain;
import com.helger.meta.asm.ASMUtils;
import com.helger.meta.project.EProject;
import com.helger.meta.project.EProjectType;
import com.helger.meta.project.IProject;
import com.helger.meta.project.ProjectList;

public final class MainCheckCodingStyleguide extends AbstractProjectMain {
    private static final Locale LOCALE_SYSTEM = Locale.US;

    private static void _checkClass(@Nonnull final IProject eProject, @Nonnull final ClassNode cn) {
        final String sClassLocalName = CGStringHelper.getClassLocalName(CGStringHelper.getClassFromPath(cn.name));
        final boolean bIsSpecialCase = sClassLocalName.equals("package-info");
        if (bIsSpecialCase)
            return;

        String sInnerClassLocalName = StringHelper.getFromLastExcl(sClassLocalName, '$');
        if (sInnerClassLocalName == null)
            sInnerClassLocalName = sClassLocalName;
        else if (StringParser.isUnsignedInt(sInnerClassLocalName)) {
            // It's an anonymous inner class - use the full name
            sInnerClassLocalName = sClassLocalName;
        }

        final String sPrefix = "[" + sClassLocalName + "] ";
        final boolean bClassIsAbstract = Modifier.isAbstract(cn.access);
        final boolean bClassIsAnnotation = (cn.access & Opcodes.ACC_ANNOTATION) != 0;
        final boolean bClassIsEnum = (cn.access & Opcodes.ACC_ENUM) != 0;
        final boolean bClassIsInterface = Modifier.isInterface(cn.access);

        if (bClassIsInterface) {
            if (bClassIsAnnotation) {
                // TODO
            } else {
                if (sInnerClassLocalName.startsWith("I")) {
                    if (sInnerClassLocalName.length() > 1 && !Character.isUpperCase(sInnerClassLocalName.charAt(1)))
                        _warn(eProject, sPrefix + "Interface names should have an upper case second letter");
                } else if (!sInnerClassLocalName.startsWith("I") && !sClassLocalName.endsWith("MBean"))
                    _warn(eProject, sPrefix + "Interface names should start with an uppercase 'I'");
            }
        } else {
            if (bClassIsEnum) {
                if (!sInnerClassLocalName.startsWith("E"))
                    _warn(eProject, sPrefix + "enum classes should start with 'E'");
            } else {
                if (bClassIsAbstract) {
                    if (!sInnerClassLocalName.startsWith("Abstract") && !sInnerClassLocalName.contains("Singleton"))
                        _warn(eProject, sPrefix + "Abstract classes should start with 'Abstract'");
                }
            }
        }
    }

    private static void _checkMethods(@Nonnull final IProject eProject, @Nonnull final ClassNode cn) {
        final String sClassLocalName = CGStringHelper.getClassLocalName(CGStringHelper.getClassFromPath(cn.name));
        final boolean bIsSpecialCase = false;
        if (bIsSpecialCase)
            return;

        final boolean bClassIsAbstract = Modifier.isAbstract(cn.access);
        final boolean bClassIsEnum = (cn.access & Opcodes.ACC_ENUM) != 0;
        final boolean bClassIsFinal = Modifier.isFinal(cn.access);
        final boolean bClassIsInterface = Modifier.isInterface(cn.access);

        final List<MethodNode> aAllCtors = new ArrayList<>();
        for (final Object oMethod : cn.methods) {
            final MethodNode mn = (MethodNode) oMethod;

            if (ASMUtils.containsAnnotation(mn, CodingStyleguideUnaware.class))
                continue;

            final String sPrefix = "[" + sClassLocalName + "::" + mn.name + "] ";

            final boolean bIsConstructor = mn.name.equals("<init>");
            final boolean bIsPrivate = Modifier.isPrivate(mn.access);
            final boolean bIsFinal = Modifier.isFinal(mn.access);

            if (bIsConstructor)
                aAllCtors.add(mn);

            if (bIsPrivate) {
                if (!bIsConstructor && !mn.name.startsWith("_") && !mn.name.equals("readObject")
                        && !mn.name.equals("writeObject") && !mn.name.equals("readResolve")
                        && !mn.name.startsWith("lambda$"))
                    _warn(eProject, sPrefix + "Privat methods should start with an underscore");
            }

            if (bIsFinal) {
                if (bClassIsFinal)
                    _warn(eProject, sPrefix + "final method in final class");

                if (ASMUtils.containsAnnotation(mn, OverrideOnDemand.class))
                    _warn(eProject, sPrefix + "final method uses @OverrideOnDemand annotation");
            } else {
                if (bClassIsFinal && ASMUtils.containsAnnotation(mn, OverrideOnDemand.class))
                    _warn(eProject, sPrefix + "final class uses @OverrideOnDemand annotation");
            }
        }

        if (bClassIsAbstract && !bClassIsInterface && !bClassIsEnum) {
            boolean bAnyNonPrivateCtor = false;
            for (final MethodNode aCtor : aAllCtors)
                if (!Modifier.isPrivate(aCtor.access)) {
                    bAnyNonPrivateCtor = true;
                    break;
                }
            if (!bAnyNonPrivateCtor)
                _warn(eProject, "[" + sClassLocalName + "] The abstract class contains only private constructors!");
        }
    }

    private static void _checkVariables(@Nonnull final IProject eProject, @Nonnull final ClassNode cn) {
        final String sClassLocalName = CGStringHelper.getClassLocalName(CGStringHelper.getClassFromPath(cn.name));
        final boolean bIsSpecialCase = (eProject.getProjectType() == EProjectType.MAVEN_PLUGIN
                && sClassLocalName.endsWith("Mojo"));
        if (bIsSpecialCase)
            return;

        final String sPrefix = "[" + sClassLocalName + "] ";
        final boolean bClassIsFinal = Modifier.isFinal(cn.access);

        for (final Object oField : cn.fields) {
            final FieldNode fn = (FieldNode) oField;

            if (ASMUtils.containsAnnotation(fn, CodingStyleguideUnaware.class))
                continue;

            final boolean bIsStatic = Modifier.isStatic(fn.access);
            final boolean bIsFinal = Modifier.isFinal(fn.access);
            final boolean bIsPrivate = Modifier.isPrivate(fn.access);

            if (bIsStatic) {
                // Internal variable names
                if (fn.name.startsWith("$SWITCH_TABLE$") || fn.name.equals("$assertionsDisabled")
                        || fn.name.startsWith("$SwitchMap$"))
                    continue;

                if (bIsFinal) {
                    if (!fn.name.startsWith("s_") && !fn.name.equals(fn.name.toUpperCase(LOCALE_SYSTEM))
                            && !fn.name.equals("serialVersionUID"))
                        _warn(eProject, sPrefix + "Static final member name '" + fn.name
                                + "' does not match naming conventions");
                } else {
                    if (!fn.name.startsWith("s_"))
                        _warn(eProject,
                                sPrefix + "Static member name '" + fn.name + "' does not match naming conventions");

                    if (!bIsPrivate)
                        _warn(eProject, sPrefix + "Static member '" + fn.name + "' is not private");
                }
            } else {
                if (fn.name.startsWith("this$") || fn.name.startsWith("val$"))
                    continue;

                if (!fn.name.startsWith("m_"))
                    _warn(eProject, sPrefix + "Instance member name '" + fn.name + "' does not match");

                if (bClassIsFinal && !bIsPrivate)
                    _warn(eProject, sPrefix + "Instance member '" + fn.name + "' is not private");
            }
        }
    }

    @Nonnull
    private static EContinue _doScanClass(@Nonnull final IProject aProject, @Nonnull final String sPackageName,
            @Nonnull @Nonempty final String sClassLocalName) {
        if (aProject == EProject.ERECHNUNG_WS_CLIENT
                && (sPackageName.equals("at.gv.brz.eproc.erb.ws.documentupload._20121205")
                        || sPackageName.equals("at.gv.brz.eproc.erb.ws.invoicedelivery._201306")
                        || sPackageName.equals("at.gv.brz.eproc.erb.ws.invoicedeliverycallback._201305")
                        || sPackageName.equals("at.gv.brz.schema.eproc.invoice_uploadstatus_1_0")))
            return EContinue.BREAK;

        if (aProject == EProject.JCODEMODEL)
            return EContinue.BREAK;

        if (aProject == EProject.PEPPOL_PRACTICAL && sPackageName.equals("com.helger.peppol.wsclient"))
            return EContinue.BREAK;

        if (aProject == EProject.PH_CSS
                && (sClassLocalName.equals("CharStream") || sClassLocalName.equals("ParseException")
                        || sClassLocalName.startsWith("ParserCSS21") || sClassLocalName.startsWith("ParserCSS30")
                        || sClassLocalName.startsWith("ParserCSSCharsetDetector") || sClassLocalName.equals("Token")
                        || sClassLocalName.equals("TokenMgrError") || sClassLocalName.startsWith("JJTParser")
                        || sClassLocalName.equals("Node")
                        || (sClassLocalName.startsWith("Parser") && sClassLocalName.endsWith("Constants"))
                        || sClassLocalName.equals("SimpleNode")))
            return EContinue.BREAK;

        if (aProject == EProject.PH_EBINTERFACE && (sPackageName.startsWith("com.helger.ebinterface.v30")
                || sPackageName.startsWith("com.helger.ebinterface.v40")
                || sPackageName.startsWith("com.helger.ebinterface.v41")
                || sPackageName.equals("com.helger.ebinterface.xmldsig")))
            return EContinue.BREAK;

        if (aProject == EProject.PH_GENERICODE
                && (sPackageName.equals("com.helger.cva.v10") || sPackageName.equals("com.helger.genericode.v04")
                        || sPackageName.equals("com.helger.genericode.v10")))
            return EContinue.BREAK;

        if (aProject == EProject.PH_JSON && (sClassLocalName.equals("CharStream")
                || sClassLocalName.equals("ParseException") || sClassLocalName.startsWith("ParserJson")
                || sClassLocalName.equals("Token") || sClassLocalName.equals("TokenMgrError")))
            return EContinue.BREAK;

        if (aProject == EProject.PH_SBDH && sPackageName.equals("org.unece.cefact.namespaces.sbdh"))
            return EContinue.BREAK;

        if (aProject == EProject.PH_SCHEMATRON && sPackageName.equals("org.oclc.purl.dsdl.svrl"))
            return EContinue.BREAK;

        if (aProject == EProject.PH_UBL20 && (sPackageName.startsWith("oasis.names.specification.ubl.schema.xsd.")
                || sPackageName.startsWith("un.unece.uncefact.codelist.specification.")
                || sPackageName.startsWith("un.unece.uncefact.data.specification.")))
            return EContinue.BREAK;

        if (aProject == EProject.PH_UBL21 && (sPackageName.startsWith("oasis.names.specification.ubl.schema.xsd.")
                || sPackageName.startsWith("org.etsi.uri.") || sPackageName.equals("org.w3._2000._09.xmldsig")
                || sPackageName.equals("un.unece.uncefact.data.specification.corecomponenttypeschemamodule._21")))
            return EContinue.BREAK;

        return EContinue.CONTINUE;
    }

    private static void _scanProject(@Nonnull final IProject aProject) throws IOException {
        if (false)
            s_aLogger.info("  " + aProject.getProjectName());
        final File aTargetDir = FileUtils.getCanonicalFile(new File(aProject.getBaseDir(), "target/classes"));

        // Find all class files
        for (final File aClassFile : new FileSystemRecursiveIterator(aTargetDir))
            if (aClassFile.isFile() && aClassFile.getName().endsWith(".class")) {
                // Interpret byte code
                final ClassNode cn = ASMUtils.readClassFile(aClassFile);

                // Ignore classes explicitly marked as unaware
                if (ASMUtils.containsAnnotation(cn, CodingStyleguideUnaware.class))
                    continue;

                final String sClassName = CGStringHelper.getClassFromPath(cn.name);
                final String sPackageName = CGStringHelper.getClassPackageName(sClassName);
                final String sClassLocalName = CGStringHelper.getClassLocalName(sClassName);

                // Special generated classes
                if (_doScanClass(aProject, sPackageName, sClassLocalName).isBreak())
                    continue;

                _checkClass(aProject, cn);
                _checkVariables(aProject, cn);
                _checkMethods(aProject, cn);
            }
    }

    public static void main(final String[] args) throws IOException {
        s_aLogger.info("Start checking coding style guide in .class files!");
        for (final IProject eProject : ProjectList.getAllProjects())
            if (eProject.getProjectType().hasJavaCode() && eProject != EProject.PH_JAVACC_MAVEN_PLUGIN
                    && !eProject.isDeprecated())
                _scanProject(eProject);
        s_aLogger.info("Done - " + getWarnCount() + " warning(s) for " + ProjectList.size() + " projects");
    }
}