de.unentscheidbar.validation.swing.trigger.DocumentChangeTrigger.java Source code

Java tutorial

Introduction

Here is the source code for de.unentscheidbar.validation.swing.trigger.DocumentChangeTrigger.java

Source

/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * Copyright 1997-2009 Sun Microsystems, Inc. All rights reserved. Portions Copyrighted 2012 Daniel Huss.
 *
 * The contents of this file are subject to the terms of either the GNU General Public License Version 2 only ("GPL") or
 * the Common Development and Distribution License("CDDL") (collectively, the "License"). You may not use this file
 * except in compliance with the License. You can obtain a copy of the License at
 * http://www.netbeans.org/cddl-gplv2.html or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the specific language
 * governing permissions and limitations under the License. When distributing the software, include this License Header
 * Notice in each file and include the License file at nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this particular
 * file as subject to the "Classpath" exception as provided by Sun in the GPL Version 2 section of the License file that
 * accompanied this code. If applicable, add the following below the License Header, with the fields enclosed by
 * brackets [] replaced by your own identifying information: "Portions Copyrighted [year] [name of copyright owner]"
 *
 * Contributor(s):
 *
 * The Original Software is NetBeans. The Initial Developer of the Original Software is Sun Microsystems, Inc. Portions
 * Copyright 1997-2006 Sun Microsystems, Inc. All Rights Reserved.
 *
 * If you wish your version of this file to be governed by only the CDDL or only the GPL Version 2, indicate your
 * decision by adding "[Contributor] elects to include this software in this distribution under the [CDDL or GPL Version
 * 2] license." If you do not indicate a single choice of license, a recipient has the option to distribute your version
 * of this file under either the CDDL, the GPL Version 2 or to extend the choice of license to its licensees as provided
 * above. However, if you add GPL Version 2 code and therefore, elected the GPL Version 2 license, then the option
 * applies only if the new code is made subject to such option by the copyright holder.
 */
package de.unentscheidbar.validation.swing.trigger;

import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.Objects;

import javax.annotation.Nullable;
import javax.swing.JComponent;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.text.Document;
import javax.swing.text.JTextComponent;

import org.apache.commons.lang3.ClassUtils;

import com.google.common.base.Equivalence.Wrapper;
import com.google.common.collect.LinkedHashMultimap;
import com.google.common.collect.SetMultimap;

import de.unentscheidbar.validation.internal.Beans;
import de.unentscheidbar.validation.swing.ImmutableFunction;

final class DocumentChangeTrigger extends AbstractCoalescingTrigger<JComponent>
        implements DocumentListener, PropertyChangeListener {

    private static final class DocumentGetter extends ImmutableFunction<JComponent, Document> {

        static final DocumentGetter INSTANCE = new DocumentGetter();

        @Override
        @Nullable
        public Document apply(@Nullable JComponent input) {

            if (input == null)
                return null;
            if (input instanceof JTextComponent) {
                return ((JTextComponent) input).getDocument();
            } else {
                return Beans.propertyValue(input, DOCUMENT_PROPERTY);
            }
        }

    }

    public static final Trigger<JComponent> INSTANCE = new DocumentChangeTrigger();

    static final String DOCUMENT_PROPERTY = "document";

    /* Knows which document belongs to which components */
    private final SetMultimap<Wrapper<Document>, Wrapper<JComponent>> documentMap = LinkedHashMultimap.create();

    @Override
    public void changedUpdate(DocumentEvent e) {

        notify(e);
    }

    @Override
    public void insertUpdate(DocumentEvent e) {

        notify(e);
    }

    @Override
    public void propertyChange(PropertyChangeEvent evt) {

        if (evt.getSource() instanceof JComponent) {
            JComponent src = (JComponent) evt.getSource();
            forgetDocument((Document) evt.getOldValue(), src);
            observeDocument(src);
        } else {
            /* JComponent correctly reports itself as the event source, so this should never happen */
            throw new IllegalStateException("Received a PropertyChangeEvent with unexpected source type: "
                    + ClassUtils.getSimpleName(evt.getSource(), "NULL"));
        }
    }

    @Override
    public void registerSelf(JComponent component) {

        Objects.requireNonNull(component, "component");
        component.addPropertyChangeListener(DOCUMENT_PROPERTY, this);
        observeDocument(component);
    }

    @Override
    public void removeUpdate(DocumentEvent e) {

        notify(e);
    }

    @Override
    public void unregisterSelf(JComponent component) {

        forgetDocument(DocumentGetter.INSTANCE.apply(component), component);
    }

    private void forgetDocument(Document doc, JComponent component) {

        if (doc == null) {
            return;
        }
        Wrapper<Document> docIdentity = Beans.identity(doc);
        documentMap.remove(docIdentity, Beans.identity(component));
        if (!documentMap.containsKey(docIdentity)) {
            doc.removeDocumentListener(this);
        }
    }

    private void notify(DocumentEvent e) {

        /* Multiple components sharing a document instance is rare but we've got it covered */
        for (Wrapper<JComponent> component : documentMap.get(Beans.identity(e.getDocument()))) {
            fireEvent(component.get());
        }
    }

    private void observeDocument(JComponent component) {

        Document doc = DocumentGetter.INSTANCE.apply(component);
        Wrapper<Document> docIdentity = Beans.identity(doc);
        Wrapper<JComponent> componentIdentity = Beans.identity(component);

        /* Don't listen more than once to each document */
        documentMap.remove(docIdentity, componentIdentity);
        if (!documentMap.containsKey(docIdentity)) {
            doc.removeDocumentListener(this);
            doc.addDocumentListener(this);
        }
        documentMap.put(docIdentity, componentIdentity);
    }

    public static boolean supports(Object classOrInstance) {

        if (classOrInstance instanceof Class<?>) {
            return supportsClass((Class<?>) classOrInstance);
        } else {
            return supportsInstance(classOrInstance);
        }
    }

    private static boolean supportsInstance(Object instance) {

        return (instance instanceof JTextComponent) || supportsClass(instance.getClass());
    }

    private static boolean supportsClass(Class<?> clazz) {

        return ClassUtils.isAssignable(clazz, JTextComponent.class)
                || Beans.hasReadableProperty(clazz, DOCUMENT_PROPERTY, Document.class);
    }

}