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

org.nuiton.i18n.plugin.GenerateI18nLabelsMojo Maven / Gradle / Ivy

Go to download

Maven plugin to deal with i18n stuff in a project, mainly base on the i18n api (but not only).

There is a newer version: 4.0-beta-28
Show newest version
package org.nuiton.i18n.plugin;

/*-
 * #%L
 * I18n :: Maven Plugin
 * %%
 * Copyright (C) 2007 - 2024 Code Lutin, Ultreia.io
 * %%
 * This program 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 3 of the
 * License, or (at your option) any later version.
 *
 * This program 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 General Lesser Public License for more details.
 *
 * You should have received a copy of the GNU General Lesser Public
 * License along with this program.  If not, see
 * .
 * #L%
 */

import com.google.common.collect.LinkedHashMultimap;
import io.ultreia.java4all.i18n.spi.bean.RegisterI18nLabel;
import io.ultreia.java4all.i18n.spi.bean.RegisterI18nLabels;
import io.ultreia.java4all.i18n.spi.builder.I18nKeySet;
import io.ultreia.java4all.i18n.spi.builder.I18nModule;
import io.ultreia.java4all.i18n.spi.type.TypeTranslators;
import io.ultreia.java4all.util.SortedProperties;
import org.apache.commons.lang3.tuple.Pair;
import org.apache.maven.plugins.annotations.LifecyclePhase;
import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.Parameter;
import org.apache.maven.plugins.annotations.ResolutionScope;
import org.reflections.Reflections;
import org.reflections.Store;
import org.reflections.util.ClasspathHelper;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.IOException;
import java.net.URLClassLoader;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.Collection;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * To generate i18n labels mapping from any classes with the {@link RegisterI18nLabel} annotation on it.
 * 

* Created on 08/08/2021. * * @author Tony Chemit - [email protected] * @since 4.0.0 */ @Mojo(name = "generate-i18n-labels", threadSafe = true, defaultPhase = LifecyclePhase.GENERATE_RESOURCES, requiresDependencyCollection = ResolutionScope.COMPILE_PLUS_RUNTIME, requiresDependencyResolution = ResolutionScope.COMPILE_PLUS_RUNTIME) public class GenerateI18nLabelsMojo extends I18nMojoSupport { /** * Build directory. */ @Parameter(property = "i18n.buildOutputDirectory", defaultValue = "${project.build.outputDirectory}") private File buildOutputDirectory; /** * The root directory where to generate java code. */ @Parameter(property = "i18n.javaOutputDirectory", defaultValue = "${basedir}/src/main/resources", required = true) private File resourceDirectory; /** * Strict mode to only keep in user i18n detected i18n keys and remove obsolete keys. *

* Note : By default not active. Use this with care since it can * delete keys. Moreover if this flag is activated, then all files will be parsed. */ @Parameter(property = "i18n.strictMode", defaultValue = "false") private boolean strictMode; /** * I18n prefix to add to any generated i18n keys. */ @Parameter(property = "i18n.i18nPrefix", required = true) private String i18nPrefix; /** * I18n modelName used to to add to any generated i18n keys. */ @Parameter(property = "i18n.targetFileName", defaultValue = "labels.properties", required = true) private String targetFileName; /** * Package where to seek for i18n definitions. */ @Parameter(property = "i18n.packageName", required = true) private String packageName; /** * Package where to seek for i18n definitions. */ @Parameter(property = "i18n.getterName", defaultValue = "labels", required = true) private String getterName; private I18nKeySet i18nKeysFile; private I18nModule i18nModule; @Override protected void doAction() throws Exception { if (strictMode) { getLog().info("Use i18n strict mode."); } i18nModule = I18nModule.forGetter(getProject().getProperties()); i18nKeysFile = i18nModule.getModuleKeySet(getterName); Path targetFile = resourceDirectory.toPath().resolve("META-INF").resolve("i18n").resolve(targetFileName); SortedProperties originalStore = loadStore(targetFile); SortedProperties store = new SortedProperties(); if (!strictMode) { store.putAll(originalStore); } getLog().info(String.format("Loaded %d i18n label key(s).", store.size())); URLClassLoader classLoader = initClassLoader(getProject(), buildOutputDirectory, true, false, true, true, true); if (isVerbose()) { getLog().info(String.format("Used classLoader with %d url(s).", classLoader.getURLs().length)); } Reflections reflections = new Reflections(ClasspathHelper.forPackage(packageName, classLoader)); reflections.getTypesAnnotatedWith(RegisterI18nLabels.class); reflections.getTypesAnnotatedWith(RegisterI18nLabel.class); Store reflectionsStore = reflections.getStore(); Set annotations = new LinkedHashSet<>(); Set typesAnnotated = reflectionsStore.get("TypesAnnotated").get(RegisterI18nLabels.class.getName()); for (String s : typesAnnotated) { Class aClass = classLoader.loadClass(s); RegisterI18nLabels annotation = aClass.getAnnotation(RegisterI18nLabels.class); annotations.addAll(Arrays.asList(annotation.value())); } Set types2Annotated = reflectionsStore.get("TypesAnnotated").get(RegisterI18nLabel.class.getName()); for (String s : types2Annotated) { Class aClass = classLoader.loadClass(s); RegisterI18nLabel annotation = aClass.getAnnotation(RegisterI18nLabel.class); annotations.add(annotation); } // Set annotations = reflections.getTypesAnnotatedWith(RegisterI18nLabels.class).stream().map(t -> t.getAnnotation(RegisterI18nLabels.class)).flatMap(l -> Stream.of(l.value())).filter(Objects::nonNull).collect(Collectors.toSet()); // annotations.addAll(reflections.getTypesAnnotatedWith(RegisterI18nLabel.class).stream().map(t -> t.getAnnotation(RegisterI18nLabel.class)).filter(Objects::nonNull).collect(Collectors.toSet())); getLog().info(String.format("Found %d i18n label(s) to register.", annotations.size())); // to compute for a type, his dependencies LinkedHashMultimap, Class> dependencies = LinkedHashMultimap.create(); annotations.forEach(k -> dependencies.put(k.target(), k.target())); LinkedHashMultimap, String> propertiesI18nLabels = LinkedHashMultimap.create(); LinkedHashMultimap, String> overridesI18nLabels = LinkedHashMultimap.create(); LinkedHashMultimap, String> offersI18nLabels = LinkedHashMultimap.create(); for (RegisterI18nLabel annotation : annotations) { Class aClass = annotation.target(); List properties = Arrays.asList(annotation.properties()); List offers = Arrays.asList(annotation.offers()); List overrides = Arrays.asList(annotation.overrides()); propertiesI18nLabels.putAll(aClass, properties); overridesI18nLabels.putAll(aClass, overrides); offersI18nLabels.putAll(aClass, offers); dependencies.keySet().forEach(k -> { if (k.isAssignableFrom(aClass)) { dependencies.put(aClass, k); } }); } List> dependenciesOrder = computeDependenciesOrder(dependencies); LinkedHashMultimap, Pair> i18nLabels = computeI18nLabels(dependenciesOrder, dependencies, propertiesI18nLabels, offersI18nLabels, overridesI18nLabels); getLog().info(String.format("Computed %d i18n key(s).", i18nLabels.values().size())); for (Map.Entry, Collection>> entry : i18nLabels.asMap().entrySet()) { String typeKey = getI18nTypeKey(entry.getKey()); for (Pair pair : entry.getValue()) { String property = pair.getKey(); String i18nKey = getI18nPropertyKey(i18nPrefix, typeKey, property); String realI18nKey = pair.getValue(); i18nKeysFile.addKey(realI18nKey); if (!Objects.equals(i18nKey, realI18nKey)) { // only store translations, the getter file has already registered the i18n key to keep store.put(i18nKey, realI18nKey); } } } flush(targetFile, originalStore, store); } private List> computeDependenciesOrder(LinkedHashMultimap, Class> dependencies) { // all types to process Set> todo = new LinkedHashSet<>(dependencies.keySet()); // remove self dependency todo.forEach(k -> dependencies.remove(k, k)); List> result = new LinkedList<>(); int round = 0; while (!todo.isEmpty()) { getLog().info(String.format("Start round %d with %d type(s).", round, todo.size())); Iterator> iterator = todo.iterator(); while (iterator.hasNext()) { Class k = iterator.next(); Set> classes = dependencies.get(k); if (classes == null || result.containsAll(classes)) { result.add(k); iterator.remove(); } } getLog().info(String.format("End round %d with %d type(s).", round, todo.size())); round++; } return result; } private LinkedHashMultimap, Pair> computeI18nLabels(List> dependenciesOrder, LinkedHashMultimap, Class> dependenciesMap, LinkedHashMultimap, String> propertiesI18nLabels, LinkedHashMultimap, String> offersI18nLabels, LinkedHashMultimap, String> overridesI18nLabels) { LinkedHashMultimap, Pair> result = LinkedHashMultimap.create(); for (Class aClass : dependenciesOrder) { String typeKey = getI18nTypeKey(aClass); Set> dependencies = dependenciesMap.get(aClass); boolean withDependencies = dependencies != null && !dependencies.isEmpty(); Set properties = propertiesI18nLabels.get(aClass); // We need to produce exactly one entry for allProperties Set allProperties = new LinkedHashSet<>(properties); if (withDependencies) { allProperties.addAll(propertiesI18nLabels.asMap().entrySet().stream().filter(e -> dependencies.contains(e.getKey())).flatMap(e -> e.getValue().stream()).collect(Collectors.toSet())); } Set offers = offersI18nLabels.get(aClass); // offers is always associated to this type registerDirectProperties(aClass, typeKey, offers, result); allProperties.removeAll(offers); Set overrides = overridesI18nLabels.get(aClass); // overrides is always associated to this type registerDirectProperties(aClass, typeKey, overrides, result); allProperties.removeAll(overrides); if (withDependencies) { // register every property offered by dependency registerDependencies(aClass, allProperties, dependencies, offersI18nLabels, result); // register every property registered already for a dependency registerDependencies(aClass, allProperties, dependencies, propertiesI18nLabels, result); } // the rest of properties are on this type registerDirectProperties(aClass, typeKey, allProperties, result); } return result; } private void registerDirectProperties(Class key, String typeKey, Set properties, LinkedHashMultimap, Pair> result) { for (String property : properties) { String i18nKey = getI18nPropertyKey(i18nPrefix, typeKey, property); result.put(key, Pair.of(property, i18nKey)); } } private void registerDependencies(Class aClass, Set properties, Set> dependencies, LinkedHashMultimap, String> map, LinkedHashMultimap, Pair> result) { map.asMap().entrySet().stream().filter(e -> dependencies.contains(e.getKey())).forEach(e -> { Class dependencyOffering = e.getKey(); Collection dependencyProperties = e.getValue(); String dependencyTypeKey = getI18nTypeKey(dependencyOffering); for (String property : dependencyProperties) { if (properties.contains(property)) { result.put(aClass, Pair.of(property, getI18nPropertyKey(i18nPrefix, dependencyTypeKey, property))); properties.remove(property); } } }); } public String getI18nTypeKey(Class type) { return TypeTranslators.getSimplifiedName(type); } public String getI18nPropertyKey(String i18nPrefix, String typeKey, String property) { return i18nPrefix + typeKey + "." + property; } protected SortedProperties loadStore(Path targetFile) { SortedProperties store = new SortedProperties(); if (Files.exists(targetFile)) { try (BufferedReader reader = Files.newBufferedReader(targetFile)) { store.load(reader); } catch (IOException e) { throw new IllegalStateException("Can't load properties from " + targetFile, e); } } return store; } private void flush(Path targetFile, SortedProperties originalStore, SortedProperties store) throws IOException { i18nModule.storeModuleKeySet(i18nKeysFile); if (Files.exists(targetFile) && originalStore.equals(store)) { getLog().info(String.format("No modification found at %s", targetFile)); return; } store(targetFile, store); } private void store(Path targetFile, SortedProperties store) { getLog().info(String.format("will store i18n labels mapping at %s", targetFile)); if (Files.notExists(targetFile.getParent())) { try { Files.createDirectories(targetFile.getParent()); } catch (IOException e) { throw new IllegalStateException("Can't create directories for " + targetFile, e); } } try (BufferedWriter writer = Files.newBufferedWriter(targetFile)) { store.store(writer, "Generated by " + getClass().getName()); } catch (IOException e) { throw new IllegalStateException("Can't store i18n labels mapping at " + targetFile, e); } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy