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

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

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.AnnotationValue;
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;
    private final ExecutableElement formatArgTypesElement;

    public I18nFormatterTreeUtil(BaseTypeChecker checker) {
        this.checker = checker;
        this.processingEnv = checker.getProcessingEnvironment();
        this.formatArgTypesElement = TreeUtils.getMethod(
                org.checkerframework.checker.i18nformatter.qual.I18nFormat.class.getCanonicalName(), "value", 0,
                processingEnv);
    }

    /**
     * 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) {
        @SuppressWarnings("unchecked")
        List vals = (List) AnnotationUtils
                .getElementValuesWithDefaults(anno).get(formatArgTypesElement).getValue();

        I18nConversionCategory[] argTypes = new I18nConversionCategory[vals.size()];
        for (int i = 0; i < vals.size(); ++i) {
            VariableElement ve = (VariableElement) vals.get(i).getValue();
            argTypes[i] = I18nConversionCategory.valueOf(ve.getSimpleName().toString());
        }

        return argTypes;
    }

    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