org.springframework.richclient.core.LabelInfo.java Source code

Java tutorial

Introduction

Here is the source code for org.springframework.richclient.core.LabelInfo.java

Source

/*
 * Copyright 2002-2004 the original author or authors.
 *
 * 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 org.springframework.richclient.core;

import javax.swing.AbstractButton;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JPanel;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.core.style.ToStringCreator;
import org.springframework.richclient.util.Assert;
import org.springframework.util.StringUtils;

/**
 * An immutable parameter object consisting of the text, mnemonic character and mnemonic character
 * index that may be associated with a labeled component. This class also acts as a factory for
 * creating instances of itself based on a string descriptor that adheres to some simple syntax
 * rules as described in the javadoc for the {@link #valueOf(String)} method.
 *
 * <p>
 * The syntax used for the label info descriptor is just the text to be displayed by the label with
 * an ampersand (&) optionally inserted before the character that is to be used as a
 * mnemonic for the label.
 * </p>
 *
 * <p>
 * Example: To create a label with the text {@code My Label} and the capital L as a mnemonic,
 * use the following descriptor:
 * </p>
 *
 * <pre>
 *     <code>My &Label</code>
 * </pre>
 *
 * <p>
 * A backslash character (\) can be used to escape ampersand characters that are to be displayed as
 * part of the label's text. For example:
 * </p>
 *
 * <pre>
 *     <code>Save \& Run</code>
 * </pre>
 *
 * <p>
 * Only one non-escaped backslash can appear in the label descriptor. Attempting to specify more
 * than one mnemonic character will result in an exception being thrown.
 * TODO finish comment regarding backslash chars in props file
 * Note that for label descriptors provided in properties files, an extra backslash will be required
 * to avoid the single backslash being interpreted as a special character.
 * </p>
 *
 * @author Keith Donald
 * @author Peter De Bruycker
 * @author Kevin Stembridge
 */
public final class LabelInfo {

    private static final Log logger = LogFactory.getLog(LabelInfo.class);

    private static final LabelInfo BLANK_LABEL_INFO = new LabelInfo("");

    private static final int DEFAULT_MNEMONIC = 0;

    private static final int DEFAULT_MNEMONIC_INDEX = -1;

    private final String text;

    private final int mnemonic;

    private final int mnemonicIndex;

    /**
     * Creates a new {@code LabelInfo} instance by parsing the given label descriptor to determine
     * the label's text and mnemonic character. The syntax rules for the descriptor are as follows:
     *
     * <ul>
     * <li>The descriptor may be null or an empty string, in which case, an instance with no text
     * or mnemonic will be returned.</li>
     * <li>The mnemonic character is indicated by a preceding ampersand (&).</li>
     * <li>A backslash character (\) can be used to escape ampersand characters that are to be
     * displayed as part of the label's text.</li>
     * <li>A double backslash (a backslash escaped by a backslash) indicates that a single backslash
     * is to appear in the label's text.</li>
     * <li>Only one non-escaped ampersand can appear in the descriptor.</li>
     * <li>A space character cannot be specified as the mnemonic character.</li>
     * </ul>
     *
     * @param labelDescriptor The label descriptor. The text may be null or empty, in which case a
     * blank {@code LabelInfo} instance will be returned.
     *
     * @return A {@code LabelInfo} instance that is described by the given descriptor.
     * Never returns null.
     *
     * @throws IllegalArgumentException if {@code labelDescriptor} violates any of the syntax rules
     * described above.
     */
    public static LabelInfo valueOf(final String labelDescriptor) {

        if (logger.isDebugEnabled()) {
            logger.debug("Creating a new LabelInfo from label descriptor [" + labelDescriptor + "]");
        }

        if (!StringUtils.hasText(labelDescriptor)) {
            return BLANK_LABEL_INFO;
        }

        StringBuffer labelText = new StringBuffer();
        char mnemonicChar = '\0';
        int mnemonicCharIndex = DEFAULT_MNEMONIC_INDEX;
        char currentChar;

        for (int i = 0; i < labelDescriptor.length();) {
            currentChar = labelDescriptor.charAt(i);
            int nextCharIndex = i + 1;

            if (currentChar == '\\') {
                //confirm that the next char is a valid escaped char, add the next char to the
                //stringbuffer then skip ahead 2 chars.
                checkForValidEscapedCharacter(nextCharIndex, labelDescriptor);
                labelText.append(labelDescriptor.charAt(nextCharIndex));
                i++;
                i++;
            } else if (currentChar == '&') {
                //we've found a mnemonic indicator, so...

                //confirm that we haven't already found one, ...
                if (mnemonicChar != '\0') {
                    throw new IllegalArgumentException("The label descriptor [" + labelDescriptor
                            + "] can only contain one non-escaped ampersand.");
                }

                //...that it isn't the last character, ...
                if (nextCharIndex >= labelDescriptor.length()) {
                    throw new IllegalArgumentException("The label descriptor [" + labelDescriptor
                            + "] cannot have a non-escaped ampersand as its last character.");
                }

                //...and that the character that it prefixes is a valid mnemonic character.
                mnemonicChar = labelDescriptor.charAt(nextCharIndex);
                checkForValidMnemonicChar(mnemonicChar, labelDescriptor);

                //...add it to the stringbuffer and set the mnemonic index to the position of
                //the newly added char, then skip ahead 2 characters
                labelText.append(mnemonicChar);
                mnemonicCharIndex = labelText.length() - 1;
                i++;
                i++;

            } else {
                labelText.append(currentChar);
                i++;
            }

        }

        // mnemonics work with VK_XXX (see KeyEvent) and only uppercase letters are used as event
        return new LabelInfo(labelText.toString(), Character.toUpperCase(mnemonicChar), mnemonicCharIndex);

    }

    /**
     * Confirms that the character at the specified index within the given label descriptor is
     * a valid 'escapable' character. i.e. either an ampersand or backslash.
     *
     * @param index The position within the label descriptor of the character to be checked.
     * @param labelDescriptor The label descriptor.
     *
     * @throws NullPointerException if {@code labelDescriptor} is null.
     * @throws IllegalArgumentException if the given {@code index} position is beyond the length
     * of the string or if the character at that position is not an ampersand or backslash.
     */
    private static void checkForValidEscapedCharacter(int index, String labelDescriptor) {

        if (index >= labelDescriptor.length()) {
            throw new IllegalArgumentException(
                    "The label descriptor contains an invalid escape sequence. Backslash "
                            + "characters (\\) must be followed by either an ampersand (&) or another "
                            + "backslash.");
        }

        char escapedChar = labelDescriptor.charAt(index);

        if (escapedChar != '&' && escapedChar != '\\') {
            throw new IllegalArgumentException("The label descriptor [" + labelDescriptor
                    + "] contains an invalid escape sequence. Backslash "
                    + "characters (\\) must be followed by either an ampersand (&) or another " + "backslash.");
        }

    }

    /**
     * Confirms that the given character is allowed to be used as a mnemonic. Currently, only
     * spaces are disallowed.
     *
     * @param mnemonicChar The mnemonic character.
     * @param labelDescriptor The label descriptor.
     */
    private static void checkForValidMnemonicChar(char mnemonicChar, String labelDescriptor) {

        if (mnemonicChar == ' ') {
            throw new IllegalArgumentException(
                    "The mnemonic character cannot be a space. [" + labelDescriptor + "]");
        }

    }

    /**
     * Creates a new {@code LabelInfo} with the given text and no specified mnemonic.
     *
     * @param text The text to be displayed by the label. This may be an empty string but
     * cannot be null.
     *
     * @throws IllegalArgumentException if {@code text} is null.
     */
    public LabelInfo(String text) {
        this(text, DEFAULT_MNEMONIC, DEFAULT_MNEMONIC_INDEX);
    }

    /**
     * Creates a new {@code LabelInfo} with the given text and mnemonic character.
     *
     * @param text The text to be displayed by the label. This may be an empty string but cannot
     * be null.
     * @param mnemonic The character from the label text that acts as a mnemonic.
     *
     * @throws IllegalArgumentException if {@code text} is null or if {@code mnemonic} is a
     * negative value.
     */
    public LabelInfo(String text, int mnemonic) {
        this(text, mnemonic, DEFAULT_MNEMONIC_INDEX);
    }

    /**
     * Creates a new {@code LabelInfo} with the given text, mnemonic character and mnemonic index.
     *
     * @param text The text to be displayed by the label. This may be an empty string but cannot
     * be null.
     * @param mnemonic The character from the label text that acts as a mnemonic.
     * @param mnemonicIndex The zero-based index of the mnemonic character within the label text.
     * If the specified label text is an empty string, this property will be ignored and set to -1.
     *
     * @throws IllegalArgumentException if {@code text} is null, if {@code mnemonic} is a negative
     * value, if {@code mnemonicIndex} is less than -1 or if {@code mnemonicIndex} is outside the
     * length of {@code text}.
     */
    public LabelInfo(String text, int mnemonic, int mnemonicIndex) {

        Assert.required(text, "text");
        Assert.isTrue(mnemonic >= 0, "mnemonic must be greater than or equal to 0");
        Assert.isTrue(mnemonicIndex >= -1, "mnemonicIndex must be greater than or equal to -1");

        Assert.isTrue(mnemonicIndex < text.length(),
                "The mnemonic index must be less than the text length; mnemonicIndex = " + mnemonicIndex
                        + ", text length = " + text.length());

        this.text = text;

        if (!StringUtils.hasText(text)) {
            mnemonicIndex = DEFAULT_MNEMONIC_INDEX;
        }

        if (logger.isDebugEnabled()) {
            logger.debug("Constructing a new LabelInfo instance with properties: text='" + text + "', mnemonic="
                    + mnemonic + ", mnemonicIndex=" + mnemonicIndex);
        }

        this.mnemonic = mnemonic;
        this.mnemonicIndex = mnemonicIndex;

    }

    /**
     * {@inheritDoc}
     */
    public int hashCode() {
        final int PRIME = 31;
        int result = 1;
        result = PRIME * result + this.mnemonic;
        result = PRIME * result + this.mnemonicIndex;
        result = PRIME * result + ((this.text == null) ? 0 : this.text.hashCode());
        return result;
    }

    /**
     * {@inheritDoc}
     */
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        final LabelInfo other = (LabelInfo) obj;
        if (this.mnemonic != other.mnemonic)
            return false;
        if (this.mnemonicIndex != other.mnemonicIndex)
            return false;
        if (this.text == null) {
            if (other.text != null)
                return false;
        } else if (!this.text.equals(other.text))
            return false;
        return true;
    }

    /**
     * Configures the given label with the parameters from this instance.
     *
     * @param label The label that is to be configured.
     * @throws IllegalArgumentException if {@code label} is null.
     */
    public void configureLabel(JLabel label) {

        Assert.required(label, "label");

        label.setText(this.text);
        label.setDisplayedMnemonic(getMnemonic());

        if (getMnemonicIndex() >= -1) {
            label.setDisplayedMnemonicIndex(getMnemonicIndex());
        }

    }

    /**
     * Configures the given label with the property values described by this instance and then sets
     * it as the label for the given component.
     *
     * @param label The label to be configured.
     * @param component The component that the label is 'for'.
     *
     * @throws IllegalArgumentException if either argument is null.
     *
     * @see JLabel#setLabelFor(java.awt.Component)
     */
    public void configureLabelFor(JLabel label, JComponent component) {

        Assert.required(label, "label");
        Assert.required(component, "component");

        configureLabel(label);

        if (!(component instanceof JPanel)) {
            String labelText = label.getText();

            if (!labelText.endsWith(":")) {

                if (logger.isDebugEnabled()) {
                    logger.debug("Appending colon to text field label text '" + this.text + "'");
                }

                label.setText(labelText + ":");
            }

        }

        label.setLabelFor(component);

    }

    /**
     * Configures the given button with the properties held in this instance. Note that this
     * instance doesn't hold any keystroke accelerator information.
     *
     * @param button The button to be configured.
     *
     * @throws IllegalArgumentException if {@code button} is null.
     */
    public void configureButton(AbstractButton button) {
        Assert.notNull(button);
        button.setText(this.text);
        button.setMnemonic(getMnemonic());
        button.setDisplayedMnemonicIndex(getMnemonicIndex());
    }

    /**
     * Returns the text to be displayed by the label.
     * @return The label text, possibly an empty string but never null.
     */
    public String getText() {
        return this.text;
    }

    /**
     * Returns the character that is to be treated as the mnemonic character for the label.
     *
     * @return The mnemonic character.
     */
    public int getMnemonic() {
        return this.mnemonic;
    }

    /**
     * Returns the index within the label text of the mnemonic character.
     * @return The index of the mnemonic character, or -1 if no mnemonic index is specified.
     */
    public int getMnemonicIndex() {
        return this.mnemonicIndex;
    }

    /**
     * {@inheritDoc}
     */
    public String toString() {
        return new ToStringCreator(this).append("text", this.text).append("mnemonic", this.mnemonic)
                .append("mnemonicIndex", this.mnemonicIndex).toString();
    }

}