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

com.github.robtimus.maven.plugins.i18n.I18N Maven / Gradle / Ivy

/*
 * I18N.java
 * Copyright 2017 Rob Spoor
 *
 * 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.github.robtimus.maven.plugins.i18n;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.nio.charset.Charset;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import freemarker.template.Configuration;
import freemarker.template.Template;
import freemarker.template.TemplateException;
import freemarker.template.TemplateExceptionHandler;

/**
 * A wrapper class for I18N content and all related classes.
 * This allows classes {@link Node} and {@link FTLHelper} to be included in FreeMarker, which only supports methods and properties of public classes,
 * while still keeping them package private.
 *
 * @author Rob Spoor
 */
final class I18N {

    @SuppressWarnings("nls")
    private I18N() {
        throw new IllegalStateException("cannot create instances of " + getClass().getName());
    }

    public static final class Node {

        private final String name;
        private final String path;
        private final Map children;
        private boolean isLeaf;
        private String value;

        Node(String name, String path, boolean isLeaf, String value) {
            this.name = name;
            this.path = path;
            this.children = new LinkedHashMap<>();
            this.isLeaf = isLeaf;
            this.value = value;
        }

        public String name() {
            return name;
        }

        public String path() {
            return path;
        }

        public Collection children() {
            return children.values();
        }

        public boolean isLeaf() {
            return isLeaf;
        }

        public String getValue() {
            return value;
        }

        void addChild(Node child) {
            children.put(child.name, child);
        }

        void setLeaf(String value) {
            isLeaf = true;
            this.value = value;
        }
    }

    static final class Parser {

        private static final String ROOT_PATH = ""; //$NON-NLS-1$

        Node parse(Map properties) {
            Node root = new Node(ROOT_PATH, ROOT_PATH, false, null);

            Map nodes = new HashMap<>();
            nodes.put(ROOT_PATH, root);

            for (Map.Entry entry : properties.entrySet()) {
                String key = entry.getKey();
                String value = entry.getValue();
                addNodeIfNotExists(key, nodes, true, value);
            }
            return root;
        }

        private void addNodeIfNotExists(String path, Map nodes, boolean isLeaf, String value) {
            Node existing = nodes.get(path);
            if (existing == null) {
                String parentPath;
                String name;

                int index = path.lastIndexOf('.');
                if (index == -1) {
                    parentPath = ROOT_PATH;
                    name = path;
                } else {
                    parentPath = path.substring(0, index);
                    name = path.substring(index + 1);
                }

                Node node = new Node(name, path, isLeaf, value);
                nodes.put(path, node);

                addNodeIfNotExists(parentPath, nodes, false, null);
                Node parent = nodes.get(parentPath);
                parent.addChild(node);

            } else if (isLeaf) {
                existing.setLeaf(value);
            }
        }
    }

    static final class Writer {

        private final Configuration configuration;

        private final Charset encoding;

        private final boolean publicVisibility;
        private final License license;
        private final boolean useMessageFormat;
        private final Set suppressWarnings;

        @SuppressWarnings("nls")
        Writer(Charset encoding, boolean publicVisibility, License license, boolean useMessageFormat, Set suppressWarnings) {
            configuration = new Configuration(Configuration.VERSION_2_3_30);
            configuration.setClassForTemplateLoading(getClass(), "templates");
            configuration.setDefaultEncoding(encoding.name());
            configuration.setTemplateExceptionHandler(TemplateExceptionHandler.RETHROW_HANDLER);
            configuration.setLogTemplateExceptions(false);
            configuration.setWrapUncheckedExceptions(false);
            configuration.setFallbackOnNullLoopVariable(false);

            this.encoding = encoding;

            this.publicVisibility = publicVisibility;
            this.license = license;
            this.useMessageFormat = useMessageFormat;
            this.suppressWarnings = suppressWarnings;
        }

        @SuppressWarnings("nls")
        void write(Node i18n, String bundleName, String i18nClassName, File outputDir) throws IOException {
            String packageName;
            String simpleClassName;

            int index = i18nClassName.lastIndexOf('.');
            if (index == -1) {
                packageName = null;
                simpleClassName = i18nClassName;
            } else {
                packageName = i18nClassName.substring(0, index);
                simpleClassName = i18nClassName.substring(index + 1);
            }

            Map root = new HashMap<>();
            root.put("visibility", publicVisibility ? "public " : "");
            root.put("packageName", packageName);
            root.put("simpleClassName", simpleClassName);
            root.put("bundleName", bundleName);

            root.put("useMessageFormat", useMessageFormat);
            root.put("argumentTypesFinder", useMessageFormat ? new MessageFormatArgumentTypesFinder() : new StringFormatArgumentTypesFinder());

            root.put("suppressWarnings", suppressWarnings);

            root.put("i18n", i18n);
            root.put("helper", new FTLHelper());

            File packageDir = packageName == null ? outputDir : new File(outputDir, packageName.replace('.', '/'));
            packageDir.mkdirs();

            String i18nClassFileName = simpleClassName + ".java";
            File i18nClassFile = new File(packageDir, i18nClassFileName);
            root.put("licenseText", getLicenseText(i18nClassFileName));
            write("I18N.ftl", root, i18nClassFile);
        }

        String getLicenseText(String fileName) {
            if (license == null || license.getText() == null || license.getText().isEmpty()) {
                return null;
            }

            String copyrightYear = getLicenseCopyrightYear();
            String copyrightHolder = getLicenseCopyrightHolder();

            return license.getText()
                    .replace("${fileName}", fileName) //$NON-NLS-1$
                    .replace("${copyrightYear}", copyrightYear) //$NON-NLS-1$
                    .replace("${copyrightHolder}", copyrightHolder) //$NON-NLS-1$
                    ;
        }

        String getLicenseCopyrightYear() {
            assert license != null;

            return license.getCopyrightYear() != null ? license.getCopyrightYear() : String.format("%tY", new Date()); //$NON-NLS-1$
        }

        String getLicenseCopyrightHolder() {
            assert license != null;

            return license.getCopyrightHolder() != null ? license.getCopyrightHolder() : System.getProperty("user.name"); //$NON-NLS-1$
        }

        private void write(String templateName, Map root, File file) throws IOException {
            Template template = configuration.getTemplate(templateName, encoding.name());
            try (java.io.Writer writer = new OutputStreamWriter(new FileOutputStream(file), encoding)) {
                template.process(root, writer);
            } catch (TemplateException e) {
                throw new IOException(e);
            }
        }
    }

    public interface ArgumentTypesFinder {

        List findArgumentTypes(String formatOrPattern);

        final class ArgumentTypes {

            // OBJECT is effectively immutable, as it will always only contain one type : Object.class
            static final ArgumentTypes OBJECT = new ArgumentTypes(Object.class);

            private final Set> types = new LinkedHashSet<>();

            ArgumentTypes(Class... types) {
                this(Arrays.asList(types));
            }

            ArgumentTypes(Collection> types) {
                this.types.addAll(types);
            }

            public Set> getTypes() {
                return Collections.unmodifiableSet(types);
            }

            void retainTypes(ArgumentTypes types) {
                retainTypes(types.types);
            }

            void retainTypes(Collection> types) {
                this.types.retainAll(types);
                if (this.types.isEmpty()) {
                    this.types.add(Object.class);
                }
            }
        }
    }

    public static final class FTLHelper {

        @SuppressWarnings("nls")
        static final Collection KEYWORDS = Collections.unmodifiableCollection(Arrays.asList(
                "abstract", "assert",
                "boolean", "break", "byte",
                "case", "catch", "char", "class", "const", "continue",
                "default", "do", "double",
                "else", "enum", "extends",
                "final", "finally", "float", "for",
                "goto",
                "if", "implements", "import", "instanceof", "int", "interface",
                "long",
                "native", "new",
                "package", "private", "protected", "public",
                "return",
                "short", "static", "strictfp", "super", "switch", "synchronized",
                "this", "throw", "throws", "transient", "try",
                "void", "volatile",
                "while"
        ));
        @SuppressWarnings("nls")
        static final Collection LITERALS = Collections.unmodifiableCollection(Arrays.asList(
                "true", "false", "null"
        ));

        public String varName(Node node) {
            String name = node.name();
            if (!canUseName(name)) {
                name = '_' + name;
            }
            return name;
        }

        public String className(Node node, Collection classNames) {
            String className = className(node);
            while (classNames.contains(className)) {
                className += '_';
            }
            return className;
        }

        private String className(Node node) {
            String name = node.name();
            // always add a trailing _ to support classes for names that are already capitalised
            name = Character.toUpperCase(name.charAt(0)) + name.substring(1) + '_';
            if (!canUseName(node.name())) {
                name = '_' + name;
            }
            return name;
        }

        private boolean canUseName(String name) {
            return Character.isJavaIdentifierStart(name.charAt(0)) && !KEYWORDS.contains(name) && !LITERALS.contains(name);
        }

        public String trimRight(String s) {
            int end = s.length();
            while (end > 0 && Character.isWhitespace(s.charAt(end - 1))) {
                end--;
            }
            return s.substring(0, end);
        }

        public List splitLines(String text) {
            return text == null ? Collections.emptyList() : Arrays.asList(text.split("\r?\n")); //$NON-NLS-1$
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy