All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.nuiton.util.beans.BeanMonitor Maven / Gradle / Ivy

There is a newer version: 3.1
Show newest version
/*
 * #%L
 * Nuiton Utils
 * %%
 * Copyright (C) 2004 - 2010 CodeLutin
 * %%
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as 
 * published by the Free Software Foundation, either version 3 of the 
 * License, or (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Lesser Public License for more details.
 * 
 * You should have received a copy of the GNU General Lesser Public 
 * License along with this program.  If not, see
 * .
 * #L%
 */
package org.nuiton.util.beans;

import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;

/**
 * A monitor of beans.
 *
 * You indicates which properties to monitor (via the constructor).
 *
 * Then attach a bean to monitor via the method {@link #setBean(Object)}.
 *
 * The method {@link #clearModified()} reset the states about modified
 * properties.
 *
 * The method {@link #wasModified()} tells if there was any modification on
 * monitored properties for the attached bean since the last call to
 * {@link #clearModified()} (or {@link #setBean(Object)}.
 *
 * the method {@link #getModifiedProperties()} gives you the names of monitored
 * properties for the attached bean since the last call to
 * {@link #clearModified()} (or {@link #setBean(Object)}.
 *
 * @author Tony Chemit - [email protected]
 * @since 1.4.1
 */
public class BeanMonitor {

    /** Logger */
    private static final Log log = LogFactory.getLog(BeanMonitor.class);

    /** Names of properties to watch or not on attached bean. */
    protected final List propertyNames;

    /** If true, then listen to the properties which are not in the propertyNames */
    protected boolean excludeMode;

    /** Names of monitored and modified properties for the attached bean. */
    protected final Map propertyDiffs;

    /**
     * The {@link PropertyChangeListener} which listen modification on bean
     * only on monitored properties.
     */
    protected final PropertyChangeListener listener;

    /** The bean to monitor. */
    protected Object bean;

    /**
     * Constructor of monitor with property names to monitor.
     *
     * @param propertyNames the names of properties to monitor
     */
    public BeanMonitor(String... propertyNames) {
        this(false, propertyNames);
    }

    public BeanMonitor(boolean exclude, String... propertyNames) {
        this.excludeMode = exclude;
        this.propertyNames = new ArrayList(Arrays.asList(propertyNames));
        propertyDiffs = new LinkedHashMap();

        listener = new PropertyChangeListener() {
            @Override
            public void propertyChange(PropertyChangeEvent evt) {
                String propertyName = evt.getPropertyName();
                if (excludeMode && BeanMonitor.this.propertyNames.contains(propertyName)
                        || !excludeMode && !BeanMonitor.this.propertyNames.contains(propertyName)) {
                    return;
                }

                // the property is monitored

                Object newValue = evt.getNewValue();
                Object oldValue = evt.getOldValue();

                PropertyDiff propertyDiff;

                if (propertyDiffs.containsKey(propertyName)) {

                    // property was already modified
                    propertyDiff = propertyDiffs.get(propertyName);

                    // change the target value
                    propertyDiff.setTargetValue(newValue);

                    // check if value did not come back to original
                    Object originalValue = propertyDiff.getSourceValue();

                    if (ObjectUtils.equals(originalValue, newValue)) {

                        // coming back to original value
                        // the property is no more modified
                        propertyDiffs.remove(propertyName);
                    }
                } else {

                    // check old value and newvalue are real not equals
                    if (ObjectUtils.notEqual(newValue, oldValue)) {
                        propertyDiff = new PropertyDiff(
                                propertyName,
                                oldValue,
                                propertyName,
                                newValue,
                                null
                        );

                        // add it to modified properties
                        propertyDiffs.put(propertyName, propertyDiff);
                    }

                }
            }
        };
    }

    /**
     * Obtains the monitored {@link #bean}.
     *
     * @return the attached bean, or {@code null} if none attached.
     */
    public Object getBean() {
        return bean;
    }

    /**
     * Tells if any of the monitored properties were modified on the attached
     * bean since last call to {@link #clearModified()} or
     * {@link #setBean(Object)}.
     *
     * @return {@code true} if there were a modified monitored property on
     *         the attached bean, {@code false} otherwise.
     */
    public boolean wasModified() {
        return !propertyDiffs.isEmpty();
    }

    /**
     * Obtains the names of monitored properties which has been touched for the
     * attached bean since last call to {@link #clearModified()} or
     * {@link #setBean(Object)}.
     *
     * @return the array of property names to monitor.
     */
    public String[] getModifiedProperties() {
        Set propertyNames = propertyDiffs.keySet();
        return propertyNames.toArray(new String[propertyNames.size()]);
    }

    /**
     * Obtains the original values for all modified properties.
     *
     * @return the dictionnary of original values for modified properties
     */
    public Map getOriginalValues() {

        // always expose a copy of map
        Map map = new TreeMap();
        for (PropertyDiff propertyDiff : propertyDiffs.values()) {
            map.put(propertyDiff.getSourceProperty(),
                    propertyDiff.getSourceValue());
        }
        return map;
    }

    /**
     * Obtains the property diffs since the bean is monitoried.
     *
     * @return the property diffs since the bean is monitoried.
     * @since 2.4
     */
    public PropertyDiff[] getPropertyDiffs() {
        Collection values = propertyDiffs.values();
        return values.toArray(new PropertyDiff[values.size()]);
    }

    /**
     * Sets the {@code bean} to monitor.
     *
     * As a side effect, it will attach the {@link #listener} to new bean
     * (if not null) and remove it from the previous bean attached (if not null).
     *
     * As a second side effect, it will always clean the modified states,
     * using the method {@link #clearModified()}.
     *
     * @param bean the new bean to monitor
     */
    public void setBean(Object bean) {
        Object oldBean = this.bean;
        this.bean = bean;

        // while removing a bean must clean modified states
        clearModified();

        if (oldBean != null) {

            // dettach listener from old bean
            try {
                BeanUtil.removePropertyChangeListener(listener, oldBean);
            } catch (Exception eee) {
                log.error(String.format("Could remove the PropertychangeListener %1$s from object %2$s for following reason \\: %3$s",
                                        listener, oldBean, eee.getMessage()));
            }
        }
        if (bean != null) {

            // attach listener to new bean
            try {
                BeanUtil.addPropertyChangeListener(listener, bean);
            } catch (Exception eee) {
                log.error(String.format("Could not add the PropertychangeListener %1$s on object %2$s for following reason \\: %3$s",
                                        listener, bean, eee.getMessage()));
            }
        }
    }

    /** To clear the modified states. */
    public void clearModified() {
        propertyDiffs.clear();
    }

    /**
     * To change the list of properties to watch.
     *
     * Note: As a side-effect, we call a
     * {@link #clearModified()} method after having changed the list of
     * properties to watch.
     *
     * @param properties the list of properties to watch on the bean.
     * @since 3.0
     */
    public void setProperties(String... properties) {
        propertyNames.clear();
        propertyNames.addAll(Arrays.asList(properties));
        clearModified();
    }

    public boolean isExcludeMode() {
        return excludeMode;
    }

    public void setExcludeMode(boolean excludeMode) {
        this.excludeMode = excludeMode;
        clearModified();
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy