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

src.main.java.com.mebigfatguy.fbcontrib.utils.SignatureUtils Maven / Gradle / Ivy

Go to download

An auxiliary findbugs.sourceforge.net plugin for java bug detectors that fall outside the narrow scope of detectors to be packaged with the product itself.

There is a newer version: 7.6.8
Show newest version
/*
 * fb-contrib - Auxiliary detectors for Java programs
 * Copyright (C) 2005-2018 Dave Brosius
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */
package com.mebigfatguy.fbcontrib.utils;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Pattern;

import javax.annotation.Nullable;

import org.apache.bcel.Constants;
import org.apache.bcel.classfile.JavaClass;
import org.apache.bcel.classfile.Method;
import org.apache.bcel.generic.Type;

import edu.umd.cs.findbugs.ba.generic.GenericSignatureParser;
import edu.umd.cs.findbugs.internalAnnotations.DottedClassName;
import edu.umd.cs.findbugs.internalAnnotations.SlashedClassName;

/**
 * a collection of static methods for parsing signatures to find information out about them
 */
public final class SignatureUtils {

    public static final Set PRIMITIVE_TYPES = UnmodifiableSet.create(Values.SIG_PRIMITIVE_BYTE, Values.SIG_PRIMITIVE_SHORT, Values.SIG_PRIMITIVE_INT,
            Values.SIG_PRIMITIVE_LONG, Values.SIG_PRIMITIVE_CHAR, Values.SIG_PRIMITIVE_FLOAT, Values.SIG_PRIMITIVE_DOUBLE, Values.SIG_PRIMITIVE_BOOLEAN,
            Values.SIG_VOID, "", null);

    private static final Set TWO_SLOT_TYPES = UnmodifiableSet.create(Values.SIG_PRIMITIVE_LONG, Values.SIG_PRIMITIVE_DOUBLE);

    private static final Pattern CLASS_COMPONENT_DELIMITER = Pattern.compile("\\$");
    private static final Pattern ANONYMOUS_COMPONENT = Pattern.compile("^[1-9][0-9]{0,9}$");
    private static final String ECLIPSE_WEIRD_SIG_CHARS = "!+";

    private static final Map PRIMITIVE_NAME_TO_SIG = new HashMap<>();
    static {
        PRIMITIVE_NAME_TO_SIG.put("boolean", "B");
        PRIMITIVE_NAME_TO_SIG.put("short", "S");
        PRIMITIVE_NAME_TO_SIG.put("int", "I");
        PRIMITIVE_NAME_TO_SIG.put("long", "J");
        PRIMITIVE_NAME_TO_SIG.put("char", "C");
        PRIMITIVE_NAME_TO_SIG.put("float", "F");
        PRIMITIVE_NAME_TO_SIG.put("double", "D");
        PRIMITIVE_NAME_TO_SIG.put("boolean", "Z");
    }

    /**
     * private to reinforce the helper status of the class
     */
    private SignatureUtils() {
    }

    public static boolean isInheritedMethod(JavaClass cls, String methodName, String signature) throws ClassNotFoundException {
        JavaClass[] infs = cls.getAllInterfaces();
        if (findInheritedMethod(infs, methodName, signature) != null) {
            return true;
        }

        JavaClass[] supers = cls.getSuperClasses();
        for (int i = 0; i < supers.length; i++) {
            if (Values.DOTTED_JAVA_LANG_OBJECT.equals(supers[i].getClassName())) {
                supers[i] = null;
            }
        }
        return findInheritedMethod(supers, methodName, signature) != null;
    }

    /**
     * parses the package name from a fully qualified class name
     *
     * @param className
     *            the class in question
     *
     * @return the package of the class
     */
    public static String getPackageName(final String className) {
        int dotPos = className.lastIndexOf('.');
        if (dotPos < 0) {
            return "";
        }
        return className.substring(0, dotPos);
    }

    /**
     * returns whether or not the two packages have the same first 'depth' parts, if they exist
     *
     * @param packName1
     *            the first package to check
     * @param packName2
     *            the second package to check
     * @param depth
     *            the number of package parts to check
     *
     * @return if they are similar
     */
    public static boolean similarPackages(String packName1, String packName2, int depth) {
        if (depth == 0) {
            return true;
        }

        String dottedPackName1 = packName1.replace('/', '.');
        String dottedPackName2 = packName2.replace('/', '.');

        int dot1 = dottedPackName1.indexOf('.');
        int dot2 = dottedPackName2.indexOf('.');
        if (dot1 < 0) {
            return (dot2 < 0);
        } else if (dot2 < 0) {
            return false;
        }

        String s1 = dottedPackName1.substring(0, dot1);
        String s2 = dottedPackName2.substring(0, dot2);

        if (!s1.equals(s2)) {
            return false;
        }

        return similarPackages(dottedPackName1.substring(dot1 + 1), dottedPackName2.substring(dot2 + 1), depth - 1);
    }

    /**
     * converts a primitive type code to a signature
     *
     * @param typeCode
     *            the raw JVM type value
     * @return the signature of the type
     */
    public static String getTypeCodeSignature(int typeCode) {
        switch (typeCode) {
            case Constants.T_BOOLEAN:
                return Values.SIG_PRIMITIVE_BOOLEAN;

            case Constants.T_CHAR:
                return Values.SIG_PRIMITIVE_CHAR;

            case Constants.T_FLOAT:
                return Values.SIG_PRIMITIVE_FLOAT;

            case Constants.T_DOUBLE:
                return Values.SIG_PRIMITIVE_DOUBLE;

            case Constants.T_BYTE:
                return Values.SIG_PRIMITIVE_BYTE;

            case Constants.T_SHORT:
                return Values.SIG_PRIMITIVE_SHORT;

            case Constants.T_INT:
                return Values.SIG_PRIMITIVE_INT;

            case Constants.T_LONG:
                return Values.SIG_PRIMITIVE_LONG;
        }

        return Values.SIG_JAVA_LANG_OBJECT;
    }

    @Nullable
    private static JavaClass findInheritedMethod(JavaClass[] classes, String methodName, String signature) {
        for (JavaClass cls : classes) {
            if (cls != null) {
                Method[] methods = cls.getMethods();
                for (Method m : methods) {
                    if (!m.isPrivate() && m.getName().equals(methodName) && m.getSignature().equals(signature)) {
                        return cls;
                    }
                }
            }
        }
        return null;
    }

    /**
     * returns a Map that represents the type of the parameter in slot x
     *
     * @param methodIsStatic
     *            if the method is static, causes where to start counting from, slot 0 or 1
     * @param methodSignature
     *            the signature of the method to parse
     * @return a map of parameter types (expect empty slots when doubles/longs are used
     */
    public static Map getParameterSlotAndSignatures(boolean methodIsStatic, String methodSignature) {

        int start = methodSignature.indexOf('(') + 1;
        int limit = methodSignature.lastIndexOf(')');

        if ((limit - start) == 0) {
            return Collections.emptyMap();
        }

        Map slotIndexToParms = new LinkedHashMap<>();
        int slot = methodIsStatic ? 0 : 1;
        int sigStart = start;
        for (int i = start; i < limit; i++) {
            if (!methodSignature.startsWith(Values.SIG_ARRAY_PREFIX, i)) {
                String parmSignature = null;
                if (methodSignature.startsWith(Values.SIG_QUALIFIED_CLASS_PREFIX, i)) {
                    int semiPos = methodSignature.indexOf(Values.SIG_QUALIFIED_CLASS_SUFFIX_CHAR, i + 1);
                    parmSignature = methodSignature.substring(sigStart, semiPos + 1);
                    slotIndexToParms.put(Integer.valueOf(slot), parmSignature);
                    i = semiPos;
                } else if (isWonkyEclipseSignature(methodSignature, i)) {
                    sigStart++;
                    continue;
                } else {
                    parmSignature = methodSignature.substring(sigStart, i + 1);
                    slotIndexToParms.put(Integer.valueOf(slot), parmSignature);
                }
                sigStart = i + 1;
                slot += getSignatureSize(parmSignature);
            }
        }

        return slotIndexToParms;
    }

    /**
     * returns a List of parameter signatures
     *
     * @param methodSignature
     *            the signature of the method to parse
     * @return a list of parameter signatures
     */
    public static List getParameterSignatures(String methodSignature) {

        int start = methodSignature.indexOf('(') + 1;
        int limit = methodSignature.lastIndexOf(')');

        if ((limit - start) == 0) {
            return Collections.emptyList();
        }

        List parmSignatures = new ArrayList<>();
        int sigStart = start;
        for (int i = start; i < limit; i++) {
            if (!methodSignature.startsWith(Values.SIG_ARRAY_PREFIX, i)) {
                if (methodSignature.startsWith(Values.SIG_QUALIFIED_CLASS_PREFIX, i)) {
                    int semiPos = methodSignature.indexOf(Values.SIG_QUALIFIED_CLASS_SUFFIX_CHAR, i + 1);
                    parmSignatures.add(methodSignature.substring(sigStart, semiPos + 1));
                    i = semiPos;
                } else if (!isWonkyEclipseSignature(methodSignature, i)) {
                    parmSignatures.add(methodSignature.substring(sigStart, i + 1));
                }
                sigStart = i + 1;
            }
        }

        return parmSignatures;
    }

    /**
     * gets the return type signature from a method signature
     *
     * @param methodSig
     *            the signature of the method
     *
     * @return the signature of the return type, or ? if a bogus method signature is given
     *
     */
    public static String getReturnSignature(String methodSig) {
        int parenPos = methodSig.indexOf(')');
        if (parenPos < 0) {
            return "?";
        }

        return methodSig.substring(parenPos + 1);
    }

    /**
     * returns the number of parameters in this method signature
     *
     * @param methodSignature
     *            the method signature to parse
     * @return the number of parameters
     */
    public static int getNumParameters(String methodSignature) {
        int start = methodSignature.indexOf('(') + 1;
        int limit = methodSignature.lastIndexOf(')');

        if ((limit - start) == 0) {
            return 0;
        }

        int numParms = 0;
        for (int i = start; i < limit; i++) {
            if (!methodSignature.startsWith(Values.SIG_ARRAY_PREFIX, i)) {
                if (methodSignature.startsWith(Values.SIG_QUALIFIED_CLASS_PREFIX, i)) {
                    i = methodSignature.indexOf(Values.SIG_QUALIFIED_CLASS_SUFFIX_CHAR, i + 1);
                } else if (isWonkyEclipseSignature(methodSignature, i)) {
                    continue;
                }
                numParms++;
            }
        }

        return numParms;
    }

    /**
     * returns the first open register slot after parameters
     *
     * @param m
     *            the method for which you want the parameters
     * @return the first available register slot
     */
    public static int getFirstRegisterSlot(Method m) {
        Type[] parms = m.getArgumentTypes();

        int first = m.isStatic() ? 0 : 1;
        for (Type t : parms) {
            first += getSignatureSize(t.getSignature());
        }

        return first;
    }

    public static boolean compareGenericSignature(String genericSignature, String regularSignature) {
        Type[] regParms = Type.getArgumentTypes(regularSignature);

        GenericSignatureParser genParser = new GenericSignatureParser(genericSignature);
        Iterator genIt = genParser.parameterSignatureIterator();

        for (Type regParm : regParms) {
            if (!genIt.hasNext()) {
                return false;
            }

            String genSig = genIt.next();
            int bracketPos = genSig.indexOf('<');
            if (bracketPos >= 0) {
                genSig = genSig.substring(0, bracketPos) + Values.SIG_QUALIFIED_CLASS_SUFFIX_CHAR;
            }

            if (!regParm.getSignature().equals(genSig) && !genSig.startsWith(Values.SIG_GENERIC_TEMPLATE)) {
                return false;
            }
        }

        if (genIt.hasNext()) {
            return false;
        }

        Type regReturnParms = Type.getReturnType(regularSignature);
        String genReturnSig = genParser.getReturnTypeSignature();
        int bracketPos = genReturnSig.indexOf('<');
        if (bracketPos >= 0) {
            genReturnSig = genReturnSig.substring(0, bracketPos) + Values.SIG_QUALIFIED_CLASS_SUFFIX_CHAR;
        }

        return regReturnParms.getSignature().equals(genReturnSig) || genReturnSig.startsWith(Values.SIG_GENERIC_TEMPLATE);
    }

    public static int getSignatureSize(String signature) {
        return (TWO_SLOT_TYPES.contains(signature)) ? 2 : 1;
    }

    /**
     * converts a signature, like Ljava/lang/String; into a dotted class name.
     *
     * @param signature
     *            a class signature, must not be null
     *
     * @return the dotted class name
     */
    public static @DottedClassName String stripSignature(String signature) {
        return trimSignature(signature).replace('/', '.');
    }

    /**
     * converts a signature, like Ljava/lang/String; into a slashed class name.
     *
     * @param signature
     *            the class signature
     *
     * @return the slashed class name
     */
    public static @SlashedClassName String trimSignature(String signature) {
        if ((signature != null) && signature.startsWith(Values.SIG_QUALIFIED_CLASS_PREFIX) && signature.endsWith(Values.SIG_QUALIFIED_CLASS_SUFFIX)) {
            return signature.substring(1, signature.length() - 1);
        }

        return signature;
    }

    /**
     * returns a slashed or dotted class name into a signature, like java/lang/String -- Ljava/lang/String; Primitives and arrays are accepted.
     *
     * @param className
     *            the class name to convert
     * @return the signature format of the class
     */
    public static String classToSignature(String className) {
        if (className.startsWith(Values.SIG_ARRAY_PREFIX)) {
            // convert the classname inside the array
            return Values.SIG_ARRAY_PREFIX + classToSignature(className.substring(Values.SIG_ARRAY_PREFIX.length()));
        } else if (PRIMITIVE_TYPES.contains(className) || className.endsWith(Values.SIG_QUALIFIED_CLASS_SUFFIX)) {
            return className.replace('.', '/');
        } else {
            String primitive = PRIMITIVE_NAME_TO_SIG.get(className);
            if (primitive != null) {
                return primitive;
            }

            return Values.SIG_QUALIFIED_CLASS_PREFIX + className.replace('.', '/') + Values.SIG_QUALIFIED_CLASS_SUFFIX_CHAR;
        }
    }

    /**
     * Converts a type name into an array signature. Accepts slashed or dotted classnames, or type signatures.
     *
     * @param typeName
     *            the class name to generate an array signature from
     *
     * @return the array signature
     */
    public static String toArraySignature(String typeName) {
        String sig = classToSignature(typeName);
        if ((sig == null) || (sig.length() == 0)) {
            return sig;
        }
        return Values.SIG_ARRAY_PREFIX + sig;
    }

    /**
     * @param className
     *            the name of the class
     *
     * @return the class name, discarding any anonymous component
     */
    public static String getNonAnonymousPortion(String className) {
        String[] components = CLASS_COMPONENT_DELIMITER.split(className);
        StringBuilder buffer = new StringBuilder(className.length()).append(components[0]);
        for (int i = 1; (i < components.length) && !ANONYMOUS_COMPONENT.matcher(components[i]).matches(); i++) {
            buffer.append(Values.INNER_CLASS_SEPARATOR).append(components[i]);
        }
        return buffer.toString();
    }

    public static boolean isPlainStringConvertableClass(String className) {
        return Values.SLASHED_JAVA_LANG_STRINGBUILDER.equals(className) || Values.SLASHED_JAVA_LANG_STRINGBUFFER.equals(className)
                || Values.SLASHED_JAVA_UTIL_UUID.equals(className);
    }

    /**
     * Eclipse makes weird class signatures.
     *
     * @param sig
     *            the signature in type table
     * @param startIndex
     *            the index into the signature where the wonkyness begins
     *
     * @return if this signature has eclipse meta chars
     */
    private static boolean isWonkyEclipseSignature(String sig, int startIndex) {
        return (sig.length() > startIndex) && (ECLIPSE_WEIRD_SIG_CHARS.indexOf(sig.charAt(startIndex)) >= 0);
    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy