/* * Copyright 2013 Blazebit. * * 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.blazebit.message.apt; import com.blazebit.i18n.LocaleUtils; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.io.PrintStream; import java.io.Writer; import java.net.URI; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.Locale; import java.util.Map; import java.util.Properties; import java.util.Set; import java.util.TreeSet; import javax.annotation.processing.AbstractProcessor; import javax.annotation.processing.Filer; import javax.annotation.processing.RoundEnvironment; import javax.annotation.processing.SupportedAnnotationTypes; import javax.annotation.processing.SupportedSourceVersion; import javax.lang.model.SourceVersion; import javax.lang.model.element.Element; import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.TypeElement; import javax.tools.Diagnostic; import javax.tools.Diagnostic.Kind; import javax.tools.FileObject; import javax.tools.JavaFileObject; import javax.tools.StandardLocation; import org.apache.deltaspike.core.api.message.MessageBundle; /** * * @author Christian */ @SupportedAnnotationTypes("org.apache.deltaspike.core.api.message.annotation.MessageBundle") @SupportedSourceVersion(SourceVersion.RELEASE_6) public abstract class MessageBundleEnumProcessor extends AbstractProcessor { private static final Comparator<Locale> LOCALE_COMPARATOR = new Comparator<Locale>() { @Override public int compare(Locale o1, Locale o2) { return o1.toString().compareTo(o2.toString()); } }; @Override public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { if (annotations.isEmpty()) { return true; } try { Filer filer = processingEnv.getFiler(); Map<String, MessageBundleInfo> messageBundles = new HashMap<String, MessageBundleInfo>(); for (Element e : roundEnv .getElementsAnnotatedWith(MessageBundle.class)) { if (e instanceof TypeElement) { TypeElement messageBundleElement = (TypeElement) e; String messageBundle = messageBundleElement .getQualifiedName().toString(); String messageBundleSimpleName = messageBundleElement.getSimpleName().toString(); String messageBundleEnumName = messageBundle + "Enum"; Set<String> messages = new HashSet<String>(); for (Element messageBundleChild : messageBundleElement .getEnclosedElements()) { if (messageBundleChild instanceof ExecutableElement) { messages.add(getKey((ExecutableElement) messageBundleChild)); } } if (!messages.isEmpty()) { String baseName = messageBundle.replaceAll("\\.", "/"); FileObject javaFileObject; try { javaFileObject = filer.getResource( StandardLocation.SOURCE_PATH, "", baseName + ".java"); } catch(FileNotFoundException ex) { throw new IllegalArgumentException("Could not find the source file '" + baseName + ".java' that actually triggered the enum generation process", ex); } long lastModified = javaFileObject.getLastModified(); Set<Locale> locales = new TreeSet<Locale>(LOCALE_COMPARATOR); URI baseUri = null; // Here we assume that the resources are present in the // source output location try { // Normally there is an english properties file try { baseUri = filer.getResource( StandardLocation.CLASS_PATH, "", baseName + "_en.properties").toUri(); } catch (FileNotFoundException ex1) { try { baseUri = filer.getResource( StandardLocation.CLASS_OUTPUT, "", baseName + "_en.properties").toUri(); } catch (FileNotFoundException ex2) { baseUri = filer.getResource( StandardLocation.SOURCE_PATH, "", baseName + "_en.properties").toUri(); } } } catch (FileNotFoundException ex1) { // Otherwise we have to go through all available // locales for (Locale l : Locale.getAvailableLocales()) { String path; try { path = baseName + "_" + l.getLanguage() + ".properties"; baseUri = filer.getResource( StandardLocation.CLASS_PATH, "", path).toUri(); break; } catch (FileNotFoundException ex2) { try { path = baseName + "_" + l.getLanguage() + ".properties"; baseUri = filer.getResource( StandardLocation.CLASS_OUTPUT, "", path).toUri(); break; } catch (FileNotFoundException ex3) { try { path = baseName + "_" + l.getLanguage() + ".properties"; baseUri = filer.getResource( StandardLocation.SOURCE_PATH, "", path).toUri(); break; } catch (FileNotFoundException ex4) { // Nothing we can do about it } } } if (l.getCountry() != null && !l.getCountry().isEmpty()) { try { path = baseName + "_" + l.getLanguage() + "_" + l.getCountry() + ".properties"; baseUri = filer.getResource( StandardLocation.CLASS_PATH, "", path).toUri(); break; } catch (FileNotFoundException ex2) { try { path = baseName + "_" + l.getLanguage() + "_" + l.getCountry() + ".properties"; baseUri = filer.getResource( StandardLocation.CLASS_OUTPUT, "", path).toUri(); break; } catch (FileNotFoundException ex3) { try { path = baseName + "_" + l.getLanguage() + "_" + l.getCountry() + ".properties"; baseUri = filer.getResource( StandardLocation.SOURCE_PATH, "", path).toUri(); break; } catch (FileNotFoundException ex4) { // Nothing we can do about it } } } } } } if (baseUri == null) { throw new IllegalArgumentException( "No properties files could be found for '" + messageBundle + "'"); } File baseDir = new File(baseUri).getParentFile(); if(!baseDir.exists() || !baseDir.isDirectory()) { throw new IllegalArgumentException("The base directory for the properties files could not be found!"); } for (File propertiesFile : baseDir.listFiles()) { // Only check properties files that belong to the message bundle if(propertiesFile.getName().startsWith(messageBundleSimpleName + "_") || propertiesFile.getName().equals(messageBundleSimpleName + ".properties")) { checkPropertiesFile(propertiesFile, messages); String name = propertiesFile.getName(); int index = name.indexOf('_'); int dotIndex = name.lastIndexOf('.'); if (index > -1 && dotIndex > -1) { locales.add(LocaleUtils.getLocale(name .substring(index + 1, dotIndex))); } } } MessageBundleInfo info = new MessageBundleInfo( baseName, lastModified, locales, messages); messageBundles.put(messageBundleEnumName, info); } } } for (Map.Entry<String, MessageBundleInfo> entry : messageBundles .entrySet()) { String className = entry.getKey(); JavaFileObject jfo = filer.createSourceFile(className); if (jfo.getLastModified() == entry.getValue().getLastModified()) { // Skip unchanged files continue; } Writer writer = null; try { writer = jfo.openWriter(); generateClass(className, entry.getValue(), writer); } finally { if (writer != null) { writer.close(); } } File generatedFile = new File(jfo.toUri()); generatedFile.setLastModified(entry.getValue() .getLastModified()); } } catch (Exception ex) { ByteArrayOutputStream baos = new ByteArrayOutputStream(); ex.printStackTrace(new PrintStream(baos)); String message = baos.toString(); processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "Could not generate enums\n" + message); return false; } return true; } protected void checkPropertiesFile(File propertiesFile, Collection<String> messages) { // Check if the messages from the list all exist in the file InputStream is = null; try { Properties props = new Properties(); is = new FileInputStream(propertiesFile); props.load(is); for (String message : messages) { if (!props.containsKey(message)) { processingEnv.getMessager().printMessage( Kind.ERROR, "Properties file '" + propertiesFile.getName() + "' is missing the property '" + message + "'"); } } } catch (Exception ex) { throw new RuntimeException("Could not check properties file '" + propertiesFile.getName() + "'", ex); } finally { if (is != null) { try { is.close(); } catch (IOException e) { // Ignore } } } } protected String getKey(ExecutableElement messageElement) { return messageElement.getSimpleName().toString(); // Alternative implementation that converts camel case names to upper // case enum like names // String key = messageElement.getSimpleName().toString(); // StringBuilder enumKeySb = new StringBuilder(); // char[] keyChars = key.toCharArray(); // // enumKeySb.append(Character.toUpperCase(keyChars[0])); // // for (int i = 1; i < keyChars.length; i++) { // if (Character.isUpperCase(keyChars[i])) { // enumKeySb.append('_').append(keyChars[i]); // } else { // enumKeySb.append(Character.toUpperCase(keyChars[i])); // } // } // // return enumKeySb.toString(); } protected void generateClass(String className, MessageBundleInfo info, Writer writer) throws Exception { String packageName = className.substring(0, className.lastIndexOf('.')); String simpleName = className.substring(packageName.length() + 1); Collection<String> imports = getImports(className); Collection<String> keys = info.getMessages(); writer.append("package ").append(packageName).append(";\n\n"); if (!imports.isEmpty()) { for (String importElement : imports) { writer.append("import ").append(importElement).append(";\n"); } writer.append("\n"); } writer.append("public enum ").append(simpleName).append(" {\n\n"); if (!keys.isEmpty()) { Iterator<String> keysIter = keys.iterator(); writer.append("\t").append(keysIter.next()); while (keysIter.hasNext()) { writer.append(",\n\t").append(keysIter.next()); } writer.append(";\n\n"); } generateBody(className, writer); writer.append("}\n"); } protected Collection<String> getImports(String enumClassName) { return Collections.emptyList(); } protected void generateBody(String enumClassName, Writer writer) { // Default Noop } }