net.gtaun.wl.lang.BeanMessageFormat.java Source code

Java tutorial

Introduction

Here is the source code for net.gtaun.wl.lang.BeanMessageFormat.java

Source

/*
 * Copyright (C) 2011 Peransin Nicolas. 
 * Use is subject to license terms. 
 */
package net.gtaun.wl.lang;

import java.lang.reflect.InvocationTargetException;
import java.text.ChoiceFormat;
import java.text.FieldPosition;
import java.text.Format;
import java.text.MessageFormat;
import java.text.ParsePosition;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.commons.beanutils.NestedNullException;
import org.apache.commons.beanutils.PropertyUtils;

/**
 * A message format which handles property navigation. 
 * 
 * @author Peransin Nicolas 
 */
public class BeanMessageFormat extends Format {

    private static final long serialVersionUID = 6479157306784022952L;

    MessageFormat inner;

    List<ArgumentMap> maps;

    static final Pattern indexPattern = Pattern.compile("(\\d+)(\\.(.*))?");

    /**
     * The root is used as foctory for inner format. 
     */
    BeanMessageFormat root = this;

    private BeanMessageFormat(String pattern, BeanMessageFormat ancestor) {
        root = ancestor;
        maps = root.maps;
        inner = root.createFormat(mapPattern(pattern));
        applyFormats(inner);
    }

    /**
     * Constructs a MessageFormat for the default locale and the 
     * specified pattern. 
     * The constructor first sets the locale, then parses the pattern and 
     * creates a list of subformats for the format elements contained in it. 
     * Patterns and their interpretation are specified in the 
     * <a href="#patterns">class description</a>. 
     * 
     * @param pattern the pattern for this message format 
     * @exception IllegalArgumentException if the pattern is invalid 
     */
    public BeanMessageFormat(String pattern) {
        applyPattern(pattern);
    }

    public void applyPattern(String pattern) {
        maps = new ArrayList<ArgumentMap>();
        inner = root.createFormat(mapPattern(pattern));
        applyFormats(inner);
    }

    protected MessageFormat createFormat(String pattern) {
        // Should/could use ExtendedMessageFormat from commons.apache.org 
        return new MessageFormat(pattern);
    }

    /**
     * Constructs a MessageFormat for the specified locale and 
     * pattern. 
     * The constructor first sets the locale, then parses the pattern and 
     * creates a list of subformats for the format elements contained in it. 
     * Patterns and their interpretation are specified in the 
     * <a href="#patterns">class description</a>. 
     * 
     * @param pattern the pattern for this message format 
     * @param locale the locale for this message format 
     * @exception IllegalArgumentException if the pattern is invalid 
     * @since 1.4 
     */
    public BeanMessageFormat(String pattern, Locale locale) {
        this(pattern);
        inner.setLocale(locale);
    }

    /**
     * Do something TODO. 
     * <p> 
     * Details of the function. 
     * </p> 
     */
    private void applyFormats(MessageFormat subFormat) {

        for (Format format : subFormat.getFormats()) {
            if (!(format instanceof ChoiceFormat)) {
                continue;
            }

            ChoiceFormat choice = (ChoiceFormat) format;
            String[] choiceFormats = (String[]) choice.getFormats();
            for (int i = 0; i < choiceFormats.length; i++) {
                String innerFormat = choiceFormats[i];
                if (innerFormat.contains("{")) {
                    BeanMessageFormat recursive = new BeanMessageFormat(innerFormat, root);
                    choiceFormats[i] = recursive.inner.toPattern();
                }
            }

            choice.setChoices(choice.getLimits(), choiceFormats);
        }
    }

    /**
     * Sets the locale to be used when creating or comparing subformats. 
     * This affects subsequent calls 
     * <ul> 
     * <li>to the {@link #applyPattern applyPattern} and methods if format elements specify a format type and therefore
     * have the subformats created in the <code>applyPattern</code> method, as 
     * well as 
     * <li>to the <code>format</code> and {@link #formatToCharacterIterator 
     * formatToCharacterIterator} methods if format elements do not specify a 
     * format type and therefore have the subformats created in the formatting 
     * methods. 
     * </ul> 
     * Subformats that have already been created are not affected. 
     * 
     * @param locale the locale to be used when creating or comparing subformats 
     */
    public void setLocale(Locale locale) {
        inner.setLocale(locale);
    }

    /**
     * Gets the locale that's used when creating or comparing subformats. 
     * 
     * @return the locale used when creating or comparing subformats 
     */
    public Locale getLocale() {
        return inner.getLocale();
    }

    /**
     * Creates a MessageFormat with the given pattern and uses it 
     * to format the given arguments.
     * 
     * @exception IllegalArgumentException if the pattern is invalid, 
     *            or if an argument in the <code>arguments</code> array 
     *            is not of the type expected by the format element(s) 
     *            that use it. 
     */
    public static String format(String pattern, Object... arguments) {
        return new BeanMessageFormat(pattern).format(arguments);
    }

    protected String mapPattern(String pattern) {
        StringBuilder[] parts = { new StringBuilder(pattern.length()), // pattern 
                new StringBuilder(), // index 
                new StringBuilder() // option 
        };

        int iPart = 0;
        boolean inQuote = false;
        int braceStack = 0;

        for (int i = 0; i < pattern.length(); ++i) {
            char ch = pattern.charAt(i);
            if (iPart == 0) {
                parts[iPart].append(ch);
                if (ch == '\'') {
                    if (i + 1 < pattern.length() && pattern.charAt(i + 1) == '\'') {

                        parts[0].append('\'');
                        ++i;
                    } else {
                        inQuote = !inQuote;
                    }
                } else if (ch == '{' && !inQuote) {
                    iPart = 1;
                }
            } else if (inQuote) { // just copy quotes in parts 
                parts[iPart].append(ch);

                if (ch == '\'') {
                    inQuote = false;
                }
            } else {
                switch (ch) {
                case ',':
                    if (iPart < parts.length - 1) {
                        iPart += 1;
                    }
                    parts[iPart].append(ch);
                    break;
                case '{':
                    ++braceStack;
                    parts[iPart].append(ch);
                    break;
                case '}':
                    if (braceStack == 0) { // back to main pattern 
                        iPart = 0;
                        int index = maps.size();
                        maps.add(createMap(parts[1].toString()));
                        parts[0].append(index);
                        parts[0].append(parts[2]);
                        parts[1].setLength(0);
                        parts[2].setLength(0);
                    } else {
                        --braceStack;
                    }
                    parts[iPart].append(ch);
                    break;
                case '\'':
                    inQuote = true;
                    // fall through, so we keep quotes in other parts 
                default:
                    parts[iPart].append(ch);
                    break;
                }
            }
        }
        if (braceStack == 0 && iPart != 0) {
            throw new IllegalArgumentException("Unmatched braces in the pattern.");
        }

        return parts[0].toString();
    }

    protected int readIndex(String expr) {
        Matcher m = indexPattern.matcher(expr);

        if (!m.find()) {
            throw new IllegalArgumentException("can't parse argument number " + expr);
        }
        expr = m.group(1);

        // get the argument number 
        int argumentNumber = Integer.parseInt(expr);
        if (argumentNumber < 0) {
            throw new IllegalArgumentException("negative argument number " + argumentNumber);
        }
        return argumentNumber;
    }

    /*
     * (non-Javadoc) 
     * 
     * @see java.text.Format#format(java.lang.Object, java.lang.StringBuffer, 
     * java.text.FieldPosition) 
     */
    @Override
    public StringBuffer format(Object obj, StringBuffer toAppendTo, FieldPosition pos) {
        Object[] values = (Object[]) obj;
        Object[] mappeds = new Object[maps.size()];

        for (int iMap = 0; iMap < mappeds.length; iMap++) {
            ArgumentMap map = maps.get(iMap);
            mappeds[iMap] = (map.index < values.length) ? map.map(values[map.index]) : null;
        }

        return inner.format(mappeds, toAppendTo, pos);
    }

    /*
     * (non-Javadoc) 
     * 
     * @see java.text.Format#parseObject(java.lang.String, 
     * java.text.ParsePosition) 
     */
    @Override
    public Object parseObject(String source, ParsePosition pos) {
        throw new UnsupportedOperationException();
    }

    protected ArgumentMap createMap(String expr) {
        Matcher m = indexPattern.matcher(expr);

        if (!m.find()) {
            throw new IllegalArgumentException("can't parse argument number " + expr);
        }

        // get the argument number 
        int argumentNumber = Integer.parseInt(m.group(1));
        if (argumentNumber < 0) {
            throw new IllegalArgumentException("negative argument number " + argumentNumber);
        }

        return new ArgumentMap(argumentNumber, m.group(3));
    }

    protected class ArgumentMap {

        protected int index = -1;

        protected String path = null;

        protected ArgumentMap(int i, String expr) {
            index = i;
            path = expr;
        }

        public Object map(Object object) {
            if (path == null) {
                return object;
            }

            if (object == null) {
                return null;
            }

            return BeanMessageFormat.this.root.map(object, path);
        }

    }

    /**
     * Interpret the property path of the object. 
     * <p> 
     * The default implementation use  
     * {@link org.apache.commons.beanutils.PropertyUtils} 
     * </p>. 
     * 
     * @param bean bean object
     * @param path string path
     * @return property value 
     * @throws IllegalArgumentException if the value cannot be interpreted 
     */
    protected Object map(Object bean, String path) throws IllegalArgumentException {
        try {
            return PropertyUtils.getProperty(bean, path);
        } catch (NestedNullException e) {
            return null;
        } catch (IllegalAccessException e) {
            throw new IllegalArgumentException(e);
        } catch (InvocationTargetException e) {
            throw new IllegalArgumentException(e);
        } catch (NoSuchMethodException e) {
            throw new IllegalArgumentException(e);
        }
    }

}