MessagePropertyKey.java

/*
 * The contents of this file are subject to the terms of the Common Development and
 * Distribution License (the License). You may not use this file except in compliance with the
 * License.
 *
 * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
 * specific language governing permission and limitations under the License.
 *
 * When distributing Covered Software, include this CDDL Header Notice in each file and include
 * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
 * Header, with the fields enclosed by brackets [] replaced by your own identifying
 * information: "Portions Copyrighted [year] [name of copyright owner]".
 *
 *      Copyright 2008 Sun Microsystems, Inc.
 *      Portions copyright 2011 ForgeRock AS
 */

package org.forgerock.i18n.maven;

import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * A representation of a message property key contained in a message property
 * file. A key comprises of an upper-case name and an optional ordinal:
 * <ul>
 * <li>{@code NAME} is an upper-case string containing characters and the
 * underscore character for describing the purpose of the message.</li>
 * <li>{@code ORDINAL} is an integer that makes the message unique within the
 * property file.</li>
 * </ul>
 * Message property keys have the following string representation and are parsed
 * using the {@code valueOf(String)} method.
 *
 * <pre>
 * NAME[_ORDINAL]
 * </pre>
 *
 * If no ordinal is provided then it will default to {@code -1}. Ordinals should
 * be used for messages may be used for support purposes since they provide a
 * language independent means for identifying the message.
 */
final class MessagePropertyKey implements Comparable<MessagePropertyKey> {
    // Message property keys must contain an upper-case name, optionally
    // containing underscore characters, followed by an optional ordinal.
    private static final Pattern PATTERN = Pattern
            .compile("^([A-Z][A-Z0-9_]*?)(_([0-9]+))?$");

    /**
     * Parses a message property key from a string value.
     *
     * @param keyString
     *            The property key string.
     * @return The parsed message property key.
     * @throws IllegalArgumentException
     *             If the message property string had an invalid syntax.
     */
    static MessagePropertyKey valueOf(final String keyString) {
        final Matcher matcher = PATTERN.matcher(keyString);

        if (!matcher.matches()) {
            throw new IllegalArgumentException(
                    "Error processing "
                            + keyString
                            + ". The provided key string must be of the form NAME[_ORDINAL]");
        }

        if (matcher.group(3) == null) {
            // No ordinal.
            return new MessagePropertyKey(keyString, -1);
        } else {
            final String name = matcher.group(1);
            final int ordinal = Integer.parseInt(matcher.group(3));
            return new MessagePropertyKey(name, ordinal);
        }
    }

    // The message name.
    private final String name;

    // The ordinal will be -1 if none was specified.
    private final int ordinal;

    // Pattern for searching the key in source files.
    private final Pattern startRegex;
    private final Pattern midRegex;
    private final Pattern endRegex;

    /**
     * Creates a new message property key with the provided name and ordinal.
     *
     * @param name
     *            The name of the message key.
     * @param ordinal
     *            The ordinal of the message key, or {@code -1} if none was
     *            provided.
     */
    private MessagePropertyKey(final String name, final int ordinal) {
        this.name = name;
        this.ordinal = ordinal;
        this.startRegex = Pattern.compile(name + "[^A-Z0-9_].*");
        this.midRegex = Pattern.compile(".*[^A-Z0-9_]" + name + "[^A-Z0-9_].*");
        this.endRegex = Pattern.compile(".*[^A-Z0-9_]" + name);
    }

    /**
     * {@inheritDoc}
     */
    public int compareTo(final MessagePropertyKey k) {
        if (ordinal == k.ordinal) {
            return name.compareTo(k.name);
        } else {
            return ordinal - k.ordinal;
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public boolean equals(final Object obj) {
        if (this == obj) {
            return true;
        } else if (obj instanceof MessagePropertyKey) {
            final MessagePropertyKey k = (MessagePropertyKey) obj;
            return this.compareTo(k) == 0;
        } else {
            return false;
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public int hashCode() {
        return 31 * name.hashCode() + ordinal;
    }

    /**
     * Returns {@code true} if this message property key is present in the
     * provided line of text.
     *
     * @param line
     *            The line of text.
     * @return {@code true} if this message property key is present in the
     *         provided line of text.
     */
    boolean isPresent(final String line) {
        if (!line.contains(name)) { // Avoid regex if possible.
            return false;
        } else if (midRegex.matcher(line).matches()) { // Most likely regex.
            return true;
        } else if (endRegex.matcher(line).matches()) {
            return true;
        } else if (startRegex.matcher(line).matches()) {
            return true;
        } else {
            return line.equals(name); // Unlikely, but for completeness.
        }
    }

    /**
     * Returns the name of this message property key with the ordinal. This
     * method is equivalent to calling:
     *
     * <pre>
     * getName(true);
     * </pre>
     *
     * @return The name of this message property key.
     */
    @Override
    public String toString() {
        return getName(true);
    }

    /**
     * Returns the name of this message property key without the ordinal. This
     * method is equivalent to calling:
     *
     * <pre>
     * getName(false);
     * </pre>
     *
     * @return The name of this message property key.
     */
    String getName() {
        return getName(false);
    }

    /**
     * Returns the name of this message property key optionally including the
     * ordinal.
     *
     * @param includeOrdinal
     *            {@code true} if the ordinal should be appended to the key
     *            name.
     * @return The name of this message property key.
     */
    String getName(final boolean includeOrdinal) {
        if (!includeOrdinal || ordinal < 0) {
            return name;
        } else {
            final StringBuilder builder = new StringBuilder(name);
            builder.append("_");
            builder.append(ordinal);
            return builder.toString();
        }
    }

    /**
     * Returns the ordinal of this message property key.
     *
     * @return The ordinal of this message property key, or {@code -1} if it
     *         does not have an ordinal.
     */
    int getOrdinal() {
        return ordinal;
    }

}