package info.freelibrary.maven; import static info.freelibrary.util.Constants.FREELIB_UTIL_MESSAGES; import java.io.File; import java.io.FileInputStream; import java.io.FileWriter; import java.io.IOException; import java.util.Arrays; import java.util.Iterator; import java.util.List; import java.util.Properties; import org.apache.maven.plugin.AbstractMojo; import org.apache.maven.plugin.MojoExecutionException; import org.apache.maven.plugin.MojoFailureException; 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.project.MavenProject; import org.codehaus.plexus.util.StringUtils; import org.jboss.forge.roaster.Roaster; import org.jboss.forge.roaster.model.source.FieldSource; import org.jboss.forge.roaster.model.source.JavaInterfaceSource; import info.freelibrary.util.IOUtils; import info.freelibrary.util.Logger; import info.freelibrary.util.LoggerFactory; import info.freelibrary.util.MessageCodes; /** * I18nCodesMojo is a Maven mojo that can generate a <code>MessageCodes</code> class from which I18N message codes can * be referenced. The codes are then used to retrieve textual messages from resource bundles. The benefit of this is the * code can be generic, but the actual text from the pre-configured message file will be displayed in the IDE. * <p> * To manually run the plugin: `mvn info.freelibrary:freelib-utils:0.7.2-SNAPSHOT:generate-codes * -DmessageFiles=src/main/resources/freelib-utils_messages.xml` (supplying whatever version and message file is * appropriate). Usually, though, the plugin would just be configured to run with the process-sources Maven lifecycle. * </p> * * @author <a href="mailto:ksclarke@ksclarke.io">Kevin S. Clarke</a> */ @Mojo(name = "generate-codes", defaultPhase = LifecyclePhase.PROCESS_SOURCES) public class I18nCodesMojo extends AbstractMojo { private static final String MESSAGE_CLASS_NAME = "message-class-name"; private static final Logger LOGGER = LoggerFactory.getLogger(I18nCodesMojo.class, FREELIB_UTIL_MESSAGES); /** * The Maven project directory. */ @Parameter(defaultValue = "${project}") protected MavenProject myProject; @Parameter(alias = "messageFiles", property = "messageFiles") private List<String> myPropertyFiles; @Parameter(alias = "generatedSourcesDirectory", property = "generatedSourcesDirectory", defaultValue = "${project.basedir}/src/main/generated") private File myGeneratedSrcDir; @Override public void execute() throws MojoExecutionException, MojoFailureException { if (myPropertyFiles != null) { final Iterator<?> iterator = myPropertyFiles.iterator(); final Properties properties = new Properties(); while (iterator.hasNext()) { FileInputStream inStream = null; try { inStream = new FileInputStream((String) iterator.next()); properties.loadFromXML(inStream); final String fullClassName = properties.getProperty(MESSAGE_CLASS_NAME); final String srcFolderName = myGeneratedSrcDir == null ? myProject.getBuild().getSourceDirectory() : myGeneratedSrcDir.getAbsolutePath(); if (fullClassName != null) { final Iterator<String> messageIterator = properties.stringPropertyNames().iterator(); final String[] nameParts = fullClassName.split("\\."); final int classNameIndex = nameParts.length - 1; final String className = nameParts[classNameIndex]; final String[] packageParts = Arrays.copyOfRange(nameParts, 0, classNameIndex); final String packageName = StringUtils.join(packageParts, "."); final JavaInterfaceSource java = Roaster.create(JavaInterfaceSource.class); final File packageDirectory = new File(srcFolderName + File.separatorChar + packageName.replace( '.', File.separatorChar)); // Make sure the package directory already exists if (!packageDirectory.exists() && !packageDirectory.mkdirs()) { throw new MojoExecutionException(LOGGER.getMessage(MessageCodes.MVN_003, packageDirectory, className)); } // Cycle through all the entries in the supplied messages file, creating fields while (messageIterator.hasNext()) { final String key = messageIterator.next(); // Create a field in our new message codes class for the message if (!key.equals(MESSAGE_CLASS_NAME)) { final String normalizedKey = key.replaceAll("[\\.-]", "_"); final String value = properties.getProperty(key); final FieldSource<JavaInterfaceSource> field = java.addField(); field.setName(normalizedKey).setStringInitializer(key); field.setType("String").setPublic().setStatic(true).setFinal(true); field.getJavaDoc().setFullText("Message: " + value); } } // Create our new message codes class in the requested package directory final File javaFile = new File(packageDirectory, className + ".java"); final FileWriter javaWriter = new FileWriter(javaFile); if (LOGGER.isDebugEnabled()) { LOGGER.debug("Writing MessageCodes Java source file: {}", javaFile); } // Let's tell Checkstyle to ignore the generated code (if it's so configured) java.getJavaDoc().setFullText("BEGIN GENERATED CODE"); // Name our Java file and add a constructor java.setPackage(packageName).setName(className); // Lastly, write our generated Java class out to the file system javaWriter.write(java.toString()); javaWriter.close(); } else if (LOGGER.isWarnEnabled()) { LOGGER.warn(MessageCodes.MVN_002, MESSAGE_CLASS_NAME); } } catch (final IOException details) { if (LOGGER.isErrorEnabled()) { LOGGER.error(details.getMessage(), details); } IOUtils.closeQuietly(inStream); } } } else if (LOGGER.isWarnEnabled()) { LOGGER.warn(MessageCodes.MVN_001); } } }