EnhancedSpinnerListFormatter.java Source code

Java tutorial

Introduction

Here is the source code for EnhancedSpinnerListFormatter.java

Source

import java.text.ParseException;
import java.util.Arrays;
import java.util.List;

import javax.swing.JFormattedTextField;
import javax.swing.SpinnerListModel;
import javax.swing.text.AttributeSet;
import javax.swing.text.BadLocationException;
import javax.swing.text.DocumentFilter;

/**
 * Copyright 2007 Brandon Goodin
 *
 * 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.
 */

/**
 * ListFormatter provides completion while text is being input
 * into the JFormattedTextField.Completion is only done if the
 * user is inserting text at the end of the document.Completion
 * is done by way of the SpinnerListModel method findNextMatch.
 * This is largely a copy of the SpinnerListFormatter found in
 * JDK 1.5 sources. A new version was written because the JDK
 * version was not extensible and did not allow for the ability
 * to ehance the stringToValue.
 *
 * The stringToValue was enhanced to iterate through the model
 * list and compare the String values of the list objects and
 * the string value passed to the formatter. The first equal
 * will return. So, if multiple objects have the same toString
 * value, only the first will be returned.
 */
public class EnhancedSpinnerListFormatter extends JFormattedTextField.AbstractFormatter {
    private DocumentFilter filter;
    private EnhancedSpinnerListModel model;

    public EnhancedSpinnerListFormatter(EnhancedSpinnerListModel model) {
        this.model = model;
    }

    public String valueToString(Object value) throws ParseException {
        if (value == null) {
            return "";
        }
        return value.toString();
    }

    public Object stringToValue(String string) throws ParseException {

        for (Object item : model.getList()) {
            if (item.toString().equals(string))
                return item;
        }
        return null;
    }

    protected DocumentFilter getDocumentFilter() {
        if (filter == null) {
            filter = new Filter();
        }
        return filter;
    }

    private class Filter extends DocumentFilter {
        public void replace(FilterBypass fb, int offset, int length, String string, AttributeSet attrs)
                throws BadLocationException {
            if (string != null && (offset + length) == fb.getDocument().getLength()) {
                Object next = model.findNextMatch(fb.getDocument().getText(0, offset) + string);
                String value = (next != null) ? next.toString() : null;

                if (value != null) {
                    fb.remove(0, offset + length);
                    fb.insertString(0, value, null);
                    getFormattedTextField().select(offset + string.length(), value.length());
                    return;
                }
            }
            super.replace(fb, offset, length, string, attrs);
        }

        public void insertString(FilterBypass fb, int offset, String string, AttributeSet attr)
                throws BadLocationException {
            replace(fb, offset, 0, string, attr);
        }
    }
}

/**
 * Copyright 2007 Brandon Goodin
 *
 * 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.
 */

/**
 * This is a pretty much a direct copy of the SpinnerListModel from JDK 1.5 sources.
 * It was copied out in order to expose methods that needed to be publicly available
 * in order to allow for completion to work properly with a list of complex objects
 * that present their human readable value from a toString(). The findNextMatch
 * method was exposed as public.
 */
class EnhancedSpinnerListModel extends SpinnerListModel {

    private List list;
    private int index;

    /**
     * Constructs a <code>SpinnerModel</code> whose sequence of
     * values is defined by the specified <code>List</code>.
     * The initial value (<i>current element</i>)
     * of the model will be <code>values.get(0)</code>.
     * If <code>values</code> is <code>null</code> or has zero
     * size, an <code>IllegalArugmentException</code> is thrown.
     *
     * @param values the sequence this model represents
     * @throws IllegalArugmentException if <code>values</code> is
     *                                  <code>null</code> or zero size
     */
    @SuppressWarnings({ "JavadocReference" })
    public EnhancedSpinnerListModel(List<?> values) {
        if (values == null || values.size() == 0) {
            throw new IllegalArgumentException("SpinnerListModel(List) expects non-null non-empty List");
        }
        this.list = values;
        this.index = 0;
    }

    /**
     * Constructs a <code>SpinnerModel</code> whose sequence of values
     * is defined by the specified array.  The initial value of the model
     * will be <code>values[0]</code>.  If <code>values</code> is
     * <code>null</code> or has zero length, an
     * <code>IllegalArugmentException</code> is thrown.
     *
     * @param values the sequence this model represents
     * @throws IllegalArugmentException if <code>values</code> is
     *                                  <code>null</code> or zero length
     */
    @SuppressWarnings({ "JavadocReference" })
    public EnhancedSpinnerListModel(Object[] values) {
        if (values == null || values.length == 0) {
            throw new IllegalArgumentException("SpinnerListModel(Object[]) expects non-null non-empty Object[]");
        }
        this.list = Arrays.asList(values);
        this.index = 0;
    }

    /**
     * Constructs an effectively empty <code>SpinnerListModel</code>.
     * The model's list will contain a single
     * <code>"empty"</code> string element.
     */
    public EnhancedSpinnerListModel() {
        this(new Object[] { "empty" });
    }

    /**
     * Returns the <code>List</code> that defines the sequence for this model.
     *
     * @return the value of the <code>list</code> property
     * @see #setList
     */
    public List<?> getList() {
        return list;
    }

    /**
     * Changes the list that defines this sequence and resets the index
     * of the models <code>value</code> to zero.  Note that <code>list</code>
     * is not copied, the model just stores a reference to it.
     * <p/>
     * This method fires a <code>ChangeEvent</code> if <code>list</code> is
     * not equal to the current list.
     *
     * @param list the sequence that this model represents
     * @throws IllegalArgumentException if <code>list</code> is
     *                                  <code>null</code> or zero length
     * @see #getList
     */
    public void setList(List<?> list) {
        if ((list == null) || (list.size() == 0)) {
            throw new IllegalArgumentException("invalid list");
        }
        if (!list.equals(this.list)) {
            this.list = list;
            index = 0;
            fireStateChanged();
        }
    }

    /**
     * Returns the current element of the sequence.
     *
     * @return the <code>value</code> property
     * @see javax.swing.SpinnerModel#getValue
     * @see #setValue
     */
    public Object getValue() {
        return list.get(index);
    }

    /**
     * Changes the current element of the sequence and notifies
     * <code>ChangeListeners</code>.  If the specified
     * value is not equal to an element of the underlying sequence
     * then an <code>IllegalArgumentException</code> is thrown.
     * In the following example the <code>setValue</code> call
     * would cause an exception to be thrown:
     * <pre>
     * String[] values = {"one", "two", "free", "four"};
     * SpinnerModel model = new SpinnerListModel(values);
     * model.setValue("TWO");
     * </pre>
     *
     * @param elt the sequence element that will be model's current value
     * @throws IllegalArgumentException if the specified value isn't allowed
     * @see javax.swing.SpinnerModel#setValue
     * @see #getValue
     */
    public void setValue(Object elt) {
        int index = list.indexOf(elt);
        if (index == -1) {
            throw new IllegalArgumentException("invalid sequence element");
        } else if (index != this.index) {
            this.index = index;
            fireStateChanged();
        }
    }

    /**
     * Returns the next legal value of the underlying sequence or
     * <code>null</code> if value is already the last element.
     *
     * @return the next legal value of the underlying sequence or
     *         <code>null</code> if value is already the last element
     * @see javax.swing.SpinnerModel#getNextValue
     * @see #getPreviousValue
     */
    public Object getNextValue() {
        return (index >= (list.size() - 1)) ? null : list.get(index + 1);
    }

    /**
     * Returns the previous element of the underlying sequence or
     * <code>null</code> if value is already the first element.
     *
     * @return the previous element of the underlying sequence or
     *         <code>null</code> if value is already the first element
     * @see javax.swing.SpinnerModel#getPreviousValue
     * @see #getNextValue
     */
    public Object getPreviousValue() {
        return (index <= 0) ? null : list.get(index - 1);
    }

    /**
     * Returns the next object that starts with <code>substring</code>.
     *
     * @param substring the string to be matched
     * @return the match
     */
    public Object findNextMatch(String substring) {
        int max = list.size();

        if (max == 0) {
            return null;
        }
        int counter = index;

        do {
            Object value = list.get(counter);
            String string = value.toString();

            if (string != null && string.startsWith(substring)) {
                return value;
            }
            counter = (counter + 1) % max;
        } while (counter != index);
        return null;
    }

}