Package com.kre8orz.i18n.processor

Source Code of com.kre8orz.i18n.processor.I18NProcessor

/* Copyright Tom Valine 2002,2014 All Rights Reserved. ****************************************************************/
package com.kre8orz.i18n.processor;

import com.kre8orz.i18n.annotation.*;
import com.kre8orz.i18n.processor.I18NVisitor.MsgInfo;
import java.io.*;
import java.nio.charset.Charset;
import java.util.*;
import javax.annotation.processing.*;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.*;
import javax.tools.Diagnostic.Kind;
import javax.tools.FileObject;
import javax.tools.JavaFileManager.Location;
import javax.tools.StandardLocation;

import static com.kre8orz.i18n.processor.I18NProcessorConstants.*;
import static com.kre8orz.i18n.processor.I18NProcessorMessages.*;
import static com.kre8orz.i18n.processor.I18NProcessorTemplates.*;

/**
* The annotation processor used to generate the resource bundles and catalog class from annotated message interfaces.
*
* <p>The following properties are available for processor customization. How these properties are passed to your
* compiler are vendor specific, however for Oracle Java compilers 1.7.x or higher, they are specified using <tt><span
* style="white-space:nowrap;">-A&lt;property_name&gt;[=&lt;property_value&gt;]</span></tt></p>
*
* <ul>
*   <li>com.kre8orz.i18n.processor.I18NProcessor.catalogClass - Specifies the fully qualified catalog class name. If
*     the word <b>null</b> is specified then the catalog class is not generated.</li>
*   <li>com.kre8orz.i18n.processor.I18NProcessor.obfuscate - Specifies the key phrase to use for obfuscation. If this
*     option is supplied then the generated bundle messages will be obfuscated. If this option is left empty or is
*     omitted then no obfuscation is performed.</li>
*   <li>
*     <p>com.kre8orz.i18n.processor.I18NProcessor.language - Specifies the logging/message output language. Currently
*     supported languages are:</p>
*
*     <ul>
*       <li>English (en_US) This is the default.</li>
*       <li>Spanish (es_ES)</li>
*     </ul>
*   </li>
* </ul>
*
* @author  Tom Valine (thomas.valine@gmail.com)
*/
public final class I18NProcessor implements Processor {

    //~ Static fields/initializers *************************************************************************************

    /* This is a helper collection that is used to store the valid annotation
     * types supported by the processor. */
    private static final Set<String> _TYPES;

    /* Instantiate and populate the type collection. */
    static {
        _TYPES = new HashSet<String>();
        _TYPES.add(I18NMessage.class.getName());
        _TYPES.add(I18NMessages.class.getName());
        _TYPES.add(I18NResourceBundle.class.getName());
    }

    //~ Instance fields ************************************************************************************************

    /* Helper class used to encapsulate messages generated by the processor. */
    private I18NProcessorMessages _i18n;

    /* The option processor and repository. */
    private I18NProcessorOptions _options;

    /* The processing environment to be used during processing.  This is set
     * by the calling code via the init(ProcessingEnvironment) method.  The field should be null at any time before that
     * and not null any time after
     * that.*/
    private ProcessingEnvironment _pe;

    /* The collection of file writers for the resource bundle property files.
     * This field should be null before init(ProcessingEnvironment) is called and should be initialized to an empty map
     * of bundle names to record writers.  The map is populated only after processing is completed and the annotation
     * information has been collected.  New writers are added on an as needed basis during bundle file generation.
     * Existing writers are obtained from this collection and re-used when a new key-value pair needs to be written to
     * an existing (yet incomplete) bundle file.  At no time after writers are created and added to this collection
     * should they be closed unless processing and file generation is complete and the
     * processor is exiting.*/
    private Map<String, PrintWriter> _resWrtr;

    /* The element visitor that analyzes annotated elements sent to the
     * processor by the calling code.  This is the class that is responsible for collecting the associated annotation
     * information that is uses to write the bundle files as well as the catalog class file.  This field should be null
     * until init(ProcessingEnvironment) is called at which point it is
     * populated with a new instance of the class. */
    private I18NVisitor _visitor;

    //~ Methods ********************************************************************************************************

    /**
     * Returns an empty list of completions. This processor does not support this feature.
     *
     * @param   element     The element being annotated.
     * @param   annotation  The (perhaps partial) annotation being applied to the element.
     * @param   member      The annotation member to return possible completions for.
     * @param   userText    Source code text to be completed.
     *
     * @return  An empty list of completions.
     *
     * @see     javax.annotation.processing.Processor
     */
    @Override
    public Iterable<? extends Completion> getCompletions(Element element, AnnotationMirror annotation,
        ExecutableElement member, String userText) {
        return Collections.unmodifiableList(new ArrayList<Completion>());
    }

    /**
     * Returns a list of supported annotations. This processor supports all annotations within the <tt>
     * com.kre8orz.i18n.annotation</tt> package.
     *
     * @return  A list of supported annotations.
     *
     * @see     javax.annotation.processing.Processor
     */
    @Override
    public Set<String> getSupportedAnnotationTypes() {
        return Collections.unmodifiableSet(_TYPES);
    }

    /**
     * Returns the list of supported options. This processor supports the following options:
     *
     * <ul>
     *   <li>com.kre8orz.i18n.processor.I18NProcessor.catalogClass - Specifies the fully qualified catalog class name.
     *     If not specified the catalog class will be named I18NCatalog and reside in the default package.</li>
     *   <li>com.kre8orz.i18n.processor.I18NProcessor.obfuscate - Specifies the key phrase to use for obfuscation. If
     *     this option is supplied then the generated bundle messages will be obfuscated. If this option is left empty
     *     or is omitted then no obfuscation is performed.</li>
     *   <li>com.kre8orz.i18n.processor.I18NProcessor.language - Specifies the logging/message output language.
     *     Currently supported languages are:<br/>
     *
     *     <ul>
     *       <li>English (en_US) This is the default.</li>
     *       <li>Spanish (es_ES)</li>
     *     </ul>
     *   </li>
     * </ul>
     *
     * @return  The list of supported options.
     *
     * @see     javax.annotation.processing.Processor
     */
    @Override
    public Set<String> getSupportedOptions() {
        return I18NProcessorOptions.getSupportedOptions();
    }

    /**
     * Returns the supported source version. This processor supports Java 1.6 or greater.
     *
     * @return  The supported source version.
     *
     * @see     javax.annotation.processing.Processor
     */
    @Override
    public SourceVersion getSupportedSourceVersion() {
        return SourceVersion.RELEASE_7;
    }

    /**
     * Initializes the annotation process.
     *
     * @param  pe  The processing environment.
     *
     * @see    javax.annotation.processing.Processor
     */
    @Override
    public void init(ProcessingEnvironment pe) {
        _pe = pe;
        _options = new I18NProcessorOptions(pe);
        _i18n = new I18NProcessorMessages(pe.getMessager(), _options.getLocale(), _options.getVerbosity());
        _visitor = new I18NVisitor(pe, _i18n);
        _resWrtr = new HashMap<String, PrintWriter>();
    }

    /**
     * Processes the current round of annotations.
     *
     * @param   st  The set of annotated element candidates.
     * @param   re  The environment of the current processing round.
     *
     * @return  True if the current set of element candidates were consumed by this processor.
     *
     * @see     javax.annotation.processing.Processor
     */
    @Override
    public boolean process(Set<? extends TypeElement> st, RoundEnvironment re) {
        if (re.processingOver()) {
            _doFinished(re);
        } else {
            _doGenerating(st, re);
        }
        return true;
    }

    /* Called when processing has completed. This method is responsible for
     * finalizing all the resource bundle property file writers as well as
     * generating the catalog if required. */
    private void _doFinished(RoundEnvironment re) {
        assert (_resWrtr != null) : "Writers are uninitialized." /*NOI18N*/;
        for (final PrintWriter writer : _resWrtr.values()) {
            writer.close();
        }
        _generateCatalog();
    }

    /* Apply the visitor to each annotated element in the current processing
     * round.  When all elements have been visited and their information collected the information is then used to write
     * the resource bundle
     * property file entries. */
    private void _doGenerating(Set<? extends TypeElement> st, RoundEnvironment re) {
        Class<I18NMessage> mClz = I18NMessage.class;

        for (final Element e : re.getElementsAnnotatedWith(mClz)) {
            e.accept(_visitor, null);
        }

        Class<I18NMessages> wClz = I18NMessages.class;

        for (final Element e : re.getElementsAnnotatedWith(wClz)) {
            e.accept(_visitor, null);
        }
        _generateBundles();
    }

    /* Write the resource bundle property file entries for the current list of
     * visited elements. */
    private void _generateBundles() {
        List<MsgInfo> msgInfo = _visitor.getMsgInfo();

        assert (msgInfo != null) : "Info may be empty but not null" /*NOI18N*/;

        I18NObfuscator obfuscator = _options.getObfuscator();

        for (final I18NVisitor.MsgInfo info : _visitor.getMsgInfo()) {
            String name = info.getBaseName();
            PrintWriter pw = _resWrtr.get(name);

            if (pw == null) {
                pw = _getResourceWriter(name, info.getPkg());
                _resWrtr.put(name, pw);
            }
            if (!info.getHelp().isEmpty()) {
                pw.println(info.getHelp());
            }

            String key = info.getKey();

            assert ((key != null) && !key.isEmpty()) : "Null key." /*NOI18N*/;

            String value = info.getValue(obfuscator != null);

            if (value == null) {
                value = EMPTY_STRING;
            }
            if (obfuscator != null) {
                value = obfuscator.obfuscate(value);
            }
            pw.println(key + "=" + value);
        } // end for
    }

    /* Generate the catalog if it hasn't been disabled.  The catalog is a
     * convenience class written out by the processor that may be used by
     * developers to simplify access to their generated bundles. */
    private void _generateCatalog() {
        if (!_options.isSuppressCatalog()) {
            String catName = _options.getCatalogName();
            int sepIdx = catName.lastIndexOf(DOT_SEPARATOR);
            String clz = (sepIdx >= 0) ? catName.substring(sepIdx + 1) : catName;
            String pkg = (sepIdx >= 0) ? catName.substring(0, sepIdx) : null;

            if (pkg != null) {
                pkg = formatTemplate(CATALOG_PKGENTRY, pkg);
            } else {
                pkg = EMPTY_STRING;
            }

            String now = new Date().toString();
            String data = _generateCatalogEntries();
            String catalog = formatTemplate(CATALOG_BODY, now, pkg, clz, data);
            PrintWriter catWrtr = _getSourceWriter(catName);

            catWrtr.append(catalog);
            catWrtr.flush();
            catWrtr.close();
        }
    }

    /* Helper method to iterate over all the collected data and generate the
     * text replacement for the catalog template entries placeholder. */
    private String _generateCatalogEntries() {
        Set<String> seen = new HashSet<String>();
        StringBuilder sb = new StringBuilder();

        for (final I18NVisitor.MsgInfo info : _visitor.getMsgInfo()) {
            String pckage = info.getPkg();
            String baseName = info.getBaseName();

            if (info.isLocalized()) {
                String locale = UNDERSCORE.concat(info.getLocale());

                baseName = baseName.replaceAll(locale, EMPTY_STRING);
            }

            String qName = pckage.concat(DOT_SEPARATOR).concat(baseName);

            if (qName.endsWith(PROPERTY_FILE_SUFFIX)) {
                int i = qName.length() - PROPERTY_FILE_SUFFIX.length();

                qName = qName.substring(0, i);
            }
            if (!seen.contains(qName)) {
                String path = qName.replaceAll(PKG_SEP_REGEX, FORWARD_SLASH);

                if (path.startsWith(FORWARD_SLASH)) {
                    path = path.substring(1);
                }

                String key = info.getCatalogKey();
                String entry = formatTemplate(CATALOG_MAPENTRY, key, path);

                sb.append(entry);
                seen.add(qName);
            }
        } // end for

        String data = sb.toString();

        return data;
    }

    /* Helper method that returns a resource file writer for the file having the
     * indicated name and package. */
    private PrintWriter _getResourceWriter(String name, String pkg) {
        Filer filer = _pe.getFiler();
        Element[] siblings = null;
        Location loc = StandardLocation.CLASS_OUTPUT;

        try {
            return _getWriter(filer.createResource(loc, pkg, name, siblings), ASCII_ENCODING);
        } catch (IOException ex) {
            String msg = _i18n.record(Kind.ERROR, CANNOT_OPEN_BUNDLE);

            throw new RuntimeException(msg, ex);
        }
    }

    /* Helper method that returns a resource file writer for the file having the
     * indicated fully qualified name. */
    private PrintWriter _getSourceWriter(String qName) {
        Filer filer = _pe.getFiler();
        Element[] elements = null;

        try {
            return _getWriter(filer.createSourceFile(qName, elements), UTF8_ENCODING);
        } catch (IOException ex) {
            String msg = _i18n.record(Kind.ERROR, CANNOT_OPEN_SOURCE);

            throw new RuntimeException(msg, ex);
        }
    }

    /* Helper method that obtains a record writer for a given file object. */
    private PrintWriter _getWriter(FileObject file, String encoding) throws IOException {
        OutputStream os = file.openOutputStream();
        OutputStreamWriter osw;

        try {
            osw = new OutputStreamWriter(os, encoding);
        } catch (UnsupportedEncodingException ex) {
            osw = new OutputStreamWriter(os, Charset.forName(encoding));
        }
        return new PrintWriter(osw);
    }
}
/* Copyright Tom Valine 2002,2014 All Rights Reserved. ****************************************************************/ 
TOP

Related Classes of com.kre8orz.i18n.processor.I18NProcessor

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.