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

checker.src.org.checkerframework.checker.i18nformatter.I18nFormatterTreeUtil Maven / Gradle / Ivy

Go to download

The Checker Framework enhances Java’s type system to make it more powerful and useful. This lets software developers detect and prevent errors in their Java programs. The Checker Framework includes compiler plug-ins ("checkers") that find bugs or verify their absence. It also permits you to write your own compiler plug-ins.

There is a newer version: 3.42.0
Show newest version
package org.checkerframework.checker.i18nformatter;

/*>>>
import org.checkerframework.checker.compilermsgs.qual.CompilerMessageKey;
*/

import org.checkerframework.checker.formatter.FormatterTreeUtil.InvocationType;
import org.checkerframework.checker.formatter.FormatterTreeUtil.Result;
import org.checkerframework.checker.i18nformatter.qual.I18nChecksFormat;
import org.checkerframework.checker.i18nformatter.qual.I18nConversionCategory;
import org.checkerframework.checker.i18nformatter.qual.I18nFormat;
import org.checkerframework.checker.i18nformatter.qual.I18nFormatFor;
import org.checkerframework.checker.i18nformatter.qual.I18nInvalidFormat;
import org.checkerframework.checker.i18nformatter.qual.I18nMakeFormat;
import org.checkerframework.checker.i18nformatter.qual.I18nValidFormat;
import org.checkerframework.common.basetype.BaseTypeChecker;
import org.checkerframework.dataflow.analysis.FlowExpressions.Receiver;
import org.checkerframework.dataflow.cfg.node.ArrayCreationNode;
import org.checkerframework.dataflow.cfg.node.FieldAccessNode;
import org.checkerframework.dataflow.cfg.node.MethodInvocationNode;
import org.checkerframework.dataflow.cfg.node.Node;
import org.checkerframework.dataflow.cfg.node.StringLiteralNode;
import org.checkerframework.framework.type.AnnotatedTypeFactory;
import org.checkerframework.framework.type.AnnotatedTypeMirror;
import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedExecutableType;
import org.checkerframework.framework.util.AnnotationBuilder;
import org.checkerframework.framework.util.FlowExpressionParseUtil;
import org.checkerframework.framework.util.FlowExpressionParseUtil.FlowExpressionContext;
import org.checkerframework.framework.util.FlowExpressionParseUtil.FlowExpressionParseException;
import org.checkerframework.javacutil.AnnotationUtils;
import org.checkerframework.javacutil.TreeUtils;

import java.util.List;
import java.util.Map;

import javax.annotation.processing.ProcessingEnvironment;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.type.ArrayType;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.NullType;
import javax.lang.model.type.PrimitiveType;
import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.SimpleElementVisitor7;
import javax.lang.model.util.SimpleTypeVisitor7;

import com.sun.source.tree.ExpressionTree;
import com.sun.source.tree.MethodInvocationTree;
import com.sun.source.tree.Tree;
import com.sun.source.tree.TypeCastTree;
import com.sun.source.util.SimpleTreeVisitor;

/**
 * This class provides a collection of utilities to ease working with syntax
 * trees that have something to do with I18nFormatters.
 *
 * @checker_framework.manual #i18n-formatter-checker Internationalization
 *                           Format String Checker
 * @author Siwakorn Srisakaokul
 */
public class I18nFormatterTreeUtil {
    public final BaseTypeChecker checker;
    public final ProcessingEnvironment processingEnv;

    public I18nFormatterTreeUtil(BaseTypeChecker checker) {
        this.checker = checker;
        this.processingEnv = checker.getProcessingEnvironment();
    }

    /**
     * Describe the format annotation type
     *
     */
    public enum FormatType {
        I18NINVALID, I18NFORMAT, I18NFORMATFOR
    }

    /**
     * Takes an exception that describes an invalid formatter string and,
     * returns a syntax trees element that represents a
     * {@link I18nInvalidFormat} annotation with the exception's error message
     * as value.
     */
    public AnnotationMirror exceptionToInvalidFormatAnnotation(IllegalArgumentException ex) {
        AnnotationBuilder builder = new AnnotationBuilder(processingEnv, I18nInvalidFormat.class.getCanonicalName());
        builder.setValue("value", ex.getMessage());
        return builder.build();
    }

    /**
     * Takes a list of ConversionCategory elements, and returns a syntax tree
     * element that represents a {@link I18nFormat} annotation with the list as
     * value.
     */
    public AnnotationMirror categoriesToFormatAnnotation(I18nConversionCategory[] args) {
        AnnotationBuilder builder = new AnnotationBuilder(processingEnv, I18nFormat.class.getCanonicalName());
        builder.setValue("value", args);
        return builder.build();
    }

    /**
     * Takes a syntax tree element that represents a {@link I18nFormat}
     * annotation, and returns its value.
     */
    public I18nConversionCategory[] formatAnnotationToCategories(AnnotationMirror anno) {
    	List list =
                AnnotationUtils.getElementValueEnumArray(anno, "value", I18nConversionCategory.class, false);
        return list.toArray(new I18nConversionCategory[] {});
    }

    public boolean isHasFormatCall(MethodInvocationNode node, AnnotatedTypeFactory atypeFactory) {
        ExecutableElement method = node.getTarget().getMethod();
        AnnotationMirror anno = atypeFactory.getDeclAnnotation(method, I18nChecksFormat.class);
        return anno != null;
    }

    public boolean isIsFormatCall(MethodInvocationNode node, AnnotatedTypeFactory atypeFactory) {
        ExecutableElement method = node.getTarget().getMethod();
        AnnotationMirror anno = atypeFactory.getDeclAnnotation(method, I18nValidFormat.class);
        return anno != null;
    }

    public boolean isMakeFormatCall(MethodInvocationNode node, AnnotatedTypeFactory atypeFactory) {
        ExecutableElement method = node.getTarget().getMethod();
        AnnotationMirror anno = atypeFactory.getDeclAnnotation(method, I18nMakeFormat.class);
        return anno != null;
    }

    private static class ResultImpl implements Result {
        private final E value;
        public final ExpressionTree location;

        public ResultImpl(E value, ExpressionTree location) {
            this.value = value;
            this.location = location;
        }

        @Override
        public E value() {
            return value;
        }
    }

    /**
     * Reports an error. Takes a {@link Result} to report the location.
     */
    public final  void failure(Result res, /*@CompilerMessageKey*/ String msg, Object... args) {
        checker.report(org.checkerframework.framework.source.Result.failure(msg, args), ((ResultImpl) res).location);
    }

    /**
     * Reports an warning. Takes a {@link Result} to report the location.
     */
    public final  void warning(Result res, /*@CompilerMessageKey*/ String msg, Object... args) {
        checker.report(org.checkerframework.framework.source.Result.warning(msg, args), ((ResultImpl) res).location);
    }

    private I18nConversionCategory[] asFormatCallCategoriesLowLevel(MethodInvocationNode node) {
        Node vararg = node.getArgument(1);
        if (vararg instanceof ArrayCreationNode) {
            List convs = ((ArrayCreationNode) vararg).getInitializers();
            I18nConversionCategory[] res = new I18nConversionCategory[convs.size()];
            for (int i = 0; i < convs.size(); i++) {
                Node conv = convs.get(i);
                if (conv instanceof FieldAccessNode) {
                    if (typeMirrorToClass(((FieldAccessNode) conv).getType()) == I18nConversionCategory.class) {
                        res[i] = I18nConversionCategory.valueOf(((FieldAccessNode) conv).getFieldName());
                        continue; /* avoid returning null */
                    }
                }
                return null;
            }
            return res;
        }
        return null;
    }

    public Result getHasFormatCallCategories(MethodInvocationNode node) {
        return new ResultImpl(asFormatCallCategoriesLowLevel(node), node.getTree());
    }

    public Result makeFormatCallCategories(MethodInvocationNode node,
            I18nFormatterAnnotatedTypeFactory atypeFactory) {
        Map translations = atypeFactory.translations;
        Node firstParam = node.getArgument(0);
        Result ret = new ResultImpl(null, node.getTree());

        // Now only work with a literal string
        if (firstParam != null && (firstParam instanceof StringLiteralNode)) {
            String s = ((StringLiteralNode) firstParam).getValue();
            if (translations.containsKey(s)) {
                String value = translations.get(s);
                ret = new ResultImpl(I18nFormatUtil.formatParameterCategories(value),
                        node.getTree());
            }
        }
        return ret;
    }

    /**
     * Returns an I18nFormatCall instance, only if FormatFor is called. Otherwise, returns null.
     */
    public I18nFormatCall createFormatForCall(MethodInvocationTree tree, MethodInvocationNode node, I18nFormatterAnnotatedTypeFactory atypeFactory) {
        ExecutableElement method = TreeUtils.elementFromUse(tree);
        AnnotatedExecutableType methodAnno = atypeFactory.getAnnotatedType(method);
        for (AnnotatedTypeMirror paramType : methodAnno.getParameterTypes()) {
            // find @FormatFor
            if (paramType.getAnnotation(I18nFormatFor.class) != null) {
                return atypeFactory.treeUtil.new I18nFormatCall(tree, node, atypeFactory);
            }
        }
        return null;
    }

    /**
     * Represents a format method invocation in the syntax tree.
     *
     * An I18nFormatCall instance can only be instantiated by createFormatForCall method
     */
    public class I18nFormatCall {

        private final ExpressionTree tree;
        private ExpressionTree formatArg;
        private final AnnotatedTypeFactory atypeFactory;
        private List args;
        private String invalidMessage;

        private AnnotatedTypeMirror formatAnno;

        public I18nFormatCall(MethodInvocationTree tree, MethodInvocationNode node, AnnotatedTypeFactory atypeFactory) {
            this.tree = tree;
            this.atypeFactory = atypeFactory;
            List theargs = (tree).getArguments();
            this.args = null;
            ExecutableElement method = TreeUtils.elementFromUse(tree);
            AnnotatedExecutableType methodAnno = atypeFactory.getAnnotatedType(method);
            initialCheck(theargs, method, node, methodAnno);
        }

        @Override
        public String toString() {
            return this.tree.toString();
        }

        /**
         * This method checks the validity of the FormatFor.
         * If it is valid, this.args will be set to the correct parameter arguments.
         * Otherwise, it will be still null.
         */
        private void initialCheck(List theargs, ExecutableElement method,
                MethodInvocationNode node, AnnotatedExecutableType methodAnno) {
            int paramIndex = -1;
            Receiver paramArg = null;
            int i = 0;
            for (AnnotatedTypeMirror paramType : methodAnno.getParameterTypes()) {
                if (paramType.getAnnotation(I18nFormatFor.class) != null) {
                    this.formatArg = theargs.get(i);
                    this.formatAnno = atypeFactory.getAnnotatedType(formatArg);

                    if (!typeMirrorToClass(paramType.getUnderlyingType()).equals(String.class)) {
                        // Invalid FormatFor invocation
                        return ;
                    }
                    FlowExpressionContext flowExprContext = FlowExpressionParseUtil
                                .buildFlowExprContextForUse(node, checker.getContext());
                    String formatforArg = AnnotationUtils.getElementValue(paramType.getAnnotation(I18nFormatFor.class)
                            , "value", String.class, false);
                    if (flowExprContext != null) {
                        try {
                            paramArg = FlowExpressionParseUtil
                                .parse(formatforArg, flowExprContext, atypeFactory.getPath(tree));
                            paramIndex = flowExprContext.arguments.indexOf(paramArg);
                        } catch (FlowExpressionParseException e) {
                            // errors are reported at declaration site
                        }
                    }
                    break;
                }
                i++;
            }

            if (paramIndex != -1) {
                VariableElement param = method.getParameters().get(paramIndex);
                if (param.asType().getKind().equals(TypeKind.ARRAY)) {
                    this.args = theargs.subList(paramIndex, theargs.size());
                } else {
                    this.args = theargs.subList(paramIndex, paramIndex + 1);
                }
            }
        }

        public Result getFormatType() {
            FormatType type;
            if (isValidFormatForInvocation()) {
                if (formatAnno.hasAnnotation(I18nFormat.class)) {
                    type = FormatType.I18NFORMAT;
                } else if (formatAnno.hasAnnotation(I18nFormatFor.class)) {
                    type = FormatType.I18NFORMATFOR;
                } else {
                    type = FormatType.I18NINVALID;
                    invalidMessage = "(is a @I18nFormat annotation missing?)";
                    AnnotationMirror inv = formatAnno.getAnnotation(I18nInvalidFormat.class);
                    if (inv != null) {
                        invalidMessage = AnnotationUtils.getElementValue(inv, "value", String.class, true);
                    }
                }
            } else {
                // If the FormatFor is invalid, it's still I18nFormatFor type but invalid,
                // and we can't do anything else
                type = FormatType.I18NFORMATFOR;
            }
            return new ResultImpl(type, formatArg);
        }

        public final String getInvalidError() {
            return invalidMessage;
        }

        public boolean isValidFormatForInvocation() {
            return this.args != null;
        }

        /**
         * Returns the type of method invocation.
         *
         * @see InvocationType
         */
        public final Result getInvocationType() {
            InvocationType type = InvocationType.VARARG;

            if (args.size() == 1) {
                final ExpressionTree first = args.get(0);
                TypeMirror argType = atypeFactory.getAnnotatedType(first).getUnderlyingType();
                // figure out if argType is an array
                type = argType.accept(new SimpleTypeVisitor7>() {
                    @Override
                    protected InvocationType defaultAction(TypeMirror e, Class p) {
                        // not an array
                        return InvocationType.VARARG;
                    }

                    @Override
                    public InvocationType visitArray(ArrayType t, Class p) {
                        // it's an array, now figure out if it's a
                        // (Object[])null array
                        return first.accept(new SimpleTreeVisitor>() {
                            @Override
                            protected InvocationType defaultAction(Tree node, Class p) {
                                // just a normal array
                                return InvocationType.ARRAY;
                            }

                            @Override
                            public InvocationType visitTypeCast(TypeCastTree node, Class p) {
                                // it's a (Object[])null
                                return atypeFactory.getAnnotatedType(node.getExpression()).getUnderlyingType()
                                        .getKind() == TypeKind.NULL ? InvocationType.NULLARRAY : InvocationType.ARRAY;
                            }
                        }, p);
                    }

                    @Override
                    public InvocationType visitNull(NullType t, Class p) {
                        return InvocationType.NULLARRAY;
                    }
                }, Void.TYPE);
            }

            ExpressionTree loc;
            loc = ((MethodInvocationTree) tree).getMethodSelect();
            if (type != InvocationType.VARARG && args.size() > 0) {
                loc = args.get(0);
            }
            return new ResultImpl(type, loc);
        }

        public Result getInvalidInvocationType() {
            return new ResultImpl(FormatType.I18NFORMATFOR, formatArg);
        }

        /**
         * Returns the conversion category for every parameter.
         *
         * @see I18nConversionCategory
         */
        public final I18nConversionCategory[] getFormatCategories() {
            AnnotationMirror anno = formatAnno.getAnnotation(I18nFormat.class);
            return formatAnnotationToCategories(anno);
        }

        public final Result[] getParamTypes() {
            // One to make javac happy, the other to make Eclipse happy...
            @SuppressWarnings({"rawtypes", "unchecked"})
            Result[] res = new Result[args.size()];
            for (int i = 0; i < res.length; ++i) {
                ExpressionTree arg = args.get(i);
                TypeMirror argType = atypeFactory.getAnnotatedType(arg).getUnderlyingType();
                res[i] = new ResultImpl(argType, arg);
            }
            return res;
        }

        public boolean isValidParameter(I18nConversionCategory formatCat, TypeMirror paramType) {
            Class type = typeMirrorToClass(paramType);
            if (type == null) {
                // we did not recognize the parameter type
                return false;
            }
            for (Class c : formatCat.types) {
                if (c.isAssignableFrom(type)) {
                    return true;
                }
            }
            return false;
        }
    }

    private final Class typeMirrorToClass(final TypeMirror type) {
        return type.accept(new SimpleTypeVisitor7, Class>() {
            @Override
            public Class visitPrimitive(PrimitiveType t, Class v) {
                switch (t.getKind()) {
                case BOOLEAN:
                    return Boolean.class;
                case BYTE:
                    return Byte.class;
                case CHAR:
                    return Character.class;
                case SHORT:
                    return Short.class;
                case INT:
                    return Integer.class;
                case LONG:
                    return Long.class;
                case FLOAT:
                    return Float.class;
                case DOUBLE:
                    return Double.class;
                default:
                    return null;
                }
            }

            @Override
            public Class visitDeclared(DeclaredType dt, Class v) {
                return dt.asElement().accept(new SimpleElementVisitor7, Class>() {
                    @Override
                    public Class visitType(TypeElement e, Class v) {
                        try {
                            return Class.forName(e.getQualifiedName().toString());
                        } catch (ClassNotFoundException e1) {
                            return null; // the lookup should work for all the
                                         // classes we care about
                        }
                    }
                }, Void.TYPE);
            }
        }, Void.TYPE);
    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy