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

checker.src.org.checkerframework.checker.i18nformatter.I18nFormatterAnnotatedTypeFactory 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 java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.lang.annotation.Annotation;
import java.util.Collections;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.Properties;
import java.util.ResourceBundle;
import java.util.Set;

import javax.lang.model.element.AnnotationMirror;

import org.checkerframework.checker.i18nformatter.qual.I18nConversionCategory;
import org.checkerframework.checker.i18nformatter.qual.I18nFormat;
import org.checkerframework.checker.i18nformatter.qual.I18nFormatBottom;
import org.checkerframework.checker.i18nformatter.qual.I18nFormatFor;
import org.checkerframework.checker.i18nformatter.qual.I18nInvalidFormat;
import org.checkerframework.checker.i18nformatter.qual.I18nUnknownFormat;
import org.checkerframework.common.basetype.BaseTypeChecker;
import org.checkerframework.framework.flow.CFStore;
import org.checkerframework.framework.flow.CFValue;
import org.checkerframework.framework.type.AnnotatedTypeFactory;
import org.checkerframework.framework.type.AnnotatedTypeMirror;
import org.checkerframework.framework.type.GenericAnnotatedTypeFactory;
import org.checkerframework.framework.type.treeannotator.ListTreeAnnotator;
import org.checkerframework.framework.type.QualifierHierarchy;
import org.checkerframework.framework.type.treeannotator.TreeAnnotator;
import org.checkerframework.framework.util.GraphQualifierHierarchy;
import org.checkerframework.framework.util.MultiGraphQualifierHierarchy.MultiGraphFactory;
import org.checkerframework.javacutil.AnnotationUtils;

import com.sun.source.tree.LiteralTree;
import com.sun.source.tree.Tree;

/**
 * Adds {@link I18nFormat} to the type of tree, if it is a {@code String} or
 * {@code char} literal that represents a satisfiable format. The annotation's
 * value is set to be a list of appropriate {@link I18nConversionCategory}
 * values for every parameter of the format.
 *
 * It also creates a map from the provided translation file if exists. This map
 * will be used to get the corresponding value of a key when
 * {@link java.util.ResourceBundle#getString} method is invoked.
 *
 * @checker_framework.manual #i18n-formatter-checker Internationalization
 *                           Format String Checker
 * @author Siwakorn Srisakaokul
 */
public class I18nFormatterAnnotatedTypeFactory extends
        GenericAnnotatedTypeFactory {

    protected final AnnotationMirror I18NUNKNOWNFORMAT;
    protected final AnnotationMirror I18NFORMAT;
    protected final AnnotationMirror I18NINVALIDFORMAT;
    protected final AnnotationMirror I18NFORMATBOTTOM;
    protected final AnnotationMirror I18NFORMATFOR;

    public final Map translations;

    protected final I18nFormatterTreeUtil treeUtil;

    public I18nFormatterAnnotatedTypeFactory(BaseTypeChecker checker) {
        super(checker);

        I18NUNKNOWNFORMAT = AnnotationUtils.fromClass(elements, I18nUnknownFormat.class);
        I18NFORMAT = AnnotationUtils.fromClass(elements, I18nFormat.class);
        I18NINVALIDFORMAT = AnnotationUtils.fromClass(elements, I18nInvalidFormat.class);
        I18NFORMATBOTTOM = AnnotationUtils.fromClass(elements, I18nFormatBottom.class);
        I18NFORMATFOR = AnnotationUtils.fromClass(elements, I18nFormatFor.class);

        this.translations = Collections.unmodifiableMap(buildLookup());

        this.treeUtil = new I18nFormatterTreeUtil(checker);
        this.postInit();
    }

    @Override
    protected Set> createSupportedTypeQualifiers() {
        return getBundledTypeQualifiersWithoutPolyAll(
                I18nUnknownFormat.class, I18nFormatBottom.class);
    }

    private Map buildLookup() {
        Map result = new HashMap();

        if (checker.hasOption("propfiles")) {
            String names = checker.getOption("propfiles");
            String[] namesArr = names.split(":");

            if (namesArr == null) {
                System.err.println("Couldn't parse the properties files: <" + names + ">");
            } else {
                for (String name : namesArr) {
                    try {
                        Properties prop = new Properties();

                        InputStream in = null;

                        ClassLoader cl = this.getClass().getClassLoader();
                        if (cl == null) {
                            // the class loader is null if the system class
                            // loader was
                            // used
                            cl = ClassLoader.getSystemClassLoader();
                        }
                        in = cl.getResourceAsStream(name);

                        if (in == null) {
                            // if the classloader didn't manage to load the
                            // file, try
                            // whether a FileInputStream works. For absolute
                            // paths this
                            // might help.
                            try {
                                in = new FileInputStream(name);
                            } catch (FileNotFoundException e) {
                                // ignore
                            }
                        }

                        if (in == null) {
                            System.err.println("Couldn't find the properties file: " + name);
                            // report(Result.failure("propertykeychecker.filenotfound",
                            // name), null);
                            // return Collections.emptySet();
                            continue;
                        }

                        prop.load(in);

                        for (String key : prop.stringPropertyNames()) {
                            result.put(key, prop.getProperty(key));
                        }
                    } catch (Exception e) {
                        // TODO: is there a nicer way to report messages, that
                        // are not
                        // connected to an AST node?
                        // One cannot use report, because it needs a node.
                        System.err.println("Exception in PropertyKeyChecker.keysOfPropertyFile: " + e);
                        e.printStackTrace();
                    }
                }
            }
        }

        if (checker.hasOption("bundlenames")) {
            String bundleNames = checker.getOption("bundlenames");
            String[] namesArr = bundleNames.split(":");

            if (namesArr == null) {
                System.err.println("Couldn't parse the resource bundles: <" + bundleNames + ">");
            } else {
                for (String bundleName : namesArr) {
                    ResourceBundle bundle = ResourceBundle.getBundle(bundleName);
                    if (bundle == null) {
                        System.err.println("Couldn't find the resource bundle: <" + bundleName + "> for locale <"
                                + Locale.getDefault() + ">");
                        continue;
                    }

                    for (String key : bundle.keySet()) {
                        result.put(key, bundle.getString(key));
                    }
                }
            }
        }

        return result;
    }

    @Override
    public QualifierHierarchy createQualifierHierarchy(MultiGraphFactory factory) {
        return new I18nFormatterQualifierHierarchy(factory);
    }

    @Override
    public TreeAnnotator createTreeAnnotator() {
        return new ListTreeAnnotator(
                super.createTreeAnnotator(),
                new I18nFormatterTreeAnnotator(this)
        );
    }

    private class I18nFormatterTreeAnnotator extends TreeAnnotator {
        public I18nFormatterTreeAnnotator(AnnotatedTypeFactory atypeFactory) {
            super(atypeFactory);
        }

        @Override
        public Void visitLiteral(LiteralTree tree, AnnotatedTypeMirror type) {
            if (!type.isAnnotatedInHierarchy(I18NFORMAT)) {
                String format = null;
                if (tree.getKind() == Tree.Kind.STRING_LITERAL) {
                    format = (String) tree.getValue();
                } else if (tree.getKind() == Tree.Kind.CHAR_LITERAL) {
                    format = Character.toString((Character) tree.getValue());
                }
                if (format != null) {
                    AnnotationMirror anno;
                    try {
                        I18nConversionCategory[] cs = I18nFormatUtil.formatParameterCategories(format);
                        anno = I18nFormatterAnnotatedTypeFactory.this.treeUtil.categoriesToFormatAnnotation(cs);
                    } catch (IllegalArgumentException e) {
                        anno = I18nFormatterAnnotatedTypeFactory.this.treeUtil.exceptionToInvalidFormatAnnotation(e);
                    }
                    type.addAnnotation(anno);
                }
            }

            return super.visitLiteral(tree, type);
        }
    }

    class I18nFormatterQualifierHierarchy extends GraphQualifierHierarchy {

        public I18nFormatterQualifierHierarchy(MultiGraphFactory f) {
            super(f, I18NFORMATBOTTOM);
        }

        @Override
        public boolean isSubtype(AnnotationMirror rhs, AnnotationMirror lhs) {
            if (AnnotationUtils.areSameIgnoringValues(rhs, I18NFORMAT)
                    && AnnotationUtils.areSameIgnoringValues(lhs, I18NFORMAT)) {

                I18nConversionCategory[] rhsArgTypes = treeUtil.formatAnnotationToCategories(rhs);
                I18nConversionCategory[] lhsArgTypes = treeUtil.formatAnnotationToCategories(lhs);

                if (rhsArgTypes.length > lhsArgTypes.length) {
                    return false;
                }

                for (int i = 0; i < rhsArgTypes.length; ++i) {
                    if (!I18nConversionCategory.isSubsetOf(lhsArgTypes[i], rhsArgTypes[i])) {
                        return false;
                    }
                }
                return true;
            }

            if (AnnotationUtils.areSameIgnoringValues(lhs, I18NINVALIDFORMAT)
                    && AnnotationUtils.areSameIgnoringValues(rhs, I18NINVALIDFORMAT)) {
                return (AnnotationUtils.getElementValue(rhs, "value", String.class, true)).equals(AnnotationUtils
                        .getElementValue(lhs, "value", String.class, true));
            }

            if (AnnotationUtils.areSameIgnoringValues(lhs, I18NFORMAT)) {
                lhs = I18NFORMAT;
            }
            if (AnnotationUtils.areSameIgnoringValues(rhs, I18NFORMAT)) {
                rhs = I18NFORMAT;
            }
            if (AnnotationUtils.areSameIgnoringValues(lhs, I18NINVALIDFORMAT)) {
                lhs = I18NINVALIDFORMAT;
            }
            if (AnnotationUtils.areSameIgnoringValues(rhs, I18NINVALIDFORMAT)) {
                rhs = I18NINVALIDFORMAT;
            }
            if (AnnotationUtils.areSameIgnoringValues(lhs, I18NFORMATFOR)) {
                lhs = I18NFORMATFOR;
            }
            if (AnnotationUtils.areSameIgnoringValues(rhs, I18NFORMATFOR)) {
                rhs = I18NFORMATFOR;
            }

            return super.isSubtype(rhs, lhs);
        }

        @Override
        public AnnotationMirror greatestLowerBound(AnnotationMirror anno1,
                AnnotationMirror anno2) {
            if (AnnotationUtils.areSameIgnoringValues(anno1, I18NUNKNOWNFORMAT)) {
                return anno2;
            }
            if (AnnotationUtils.areSameIgnoringValues(anno2, I18NUNKNOWNFORMAT)) {
                return anno1;
            }
            if (AnnotationUtils.areSameIgnoringValues(anno1, I18NFORMAT) &&
                AnnotationUtils.areSameIgnoringValues(anno2, I18NFORMAT)) {
                I18nConversionCategory[] anno1ArgTypes =
                        treeUtil.formatAnnotationToCategories(anno1);
                I18nConversionCategory[] anno2ArgTypes =
                        treeUtil.formatAnnotationToCategories(anno2);

                // From the manual:
                // It is legal to use a format string with fewer format specifiers
                // than required, but a warning is issued.
                int length = anno1ArgTypes.length;
                if (anno2ArgTypes.length < length) {
                	length = anno2ArgTypes.length;
                }

                I18nConversionCategory[] anno3ArgTypes =
                        new I18nConversionCategory[length];

                for (int i = 0; i < length; ++i) {
                    anno3ArgTypes[i] = I18nConversionCategory.union(anno1ArgTypes[i], anno2ArgTypes[i]);
                }
                return treeUtil.categoriesToFormatAnnotation(anno3ArgTypes);
            }
            if (AnnotationUtils.areSameIgnoringValues(anno1, I18NINVALIDFORMAT) &&
                AnnotationUtils.areSameIgnoringValues(anno2, I18NINVALIDFORMAT)) {
                if (AnnotationUtils.areSame(anno1, anno2)) {
                    return anno1;
                }
                return I18NINVALIDFORMAT;
            }
            // All @I18nFormatFor annotations are unrelated by subtyping.
            if (AnnotationUtils.areSameIgnoringValues(anno1, I18NFORMATFOR) &&
                AnnotationUtils.areSame(anno1, anno2)) {
                return anno1;
            }

            return I18NFORMATBOTTOM;
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy