⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 beanproperty.java

📁 java属性邦定的(JSR-295)的一个实现
💻 JAVA
📖 第 1 页 / 共 3 页
字号:
/*
 * Copyright (C) 2006-2007 Sun Microsystems, Inc. All rights reserved. Use is
 * subject to license terms.
 */

/*
 *   TO DO LIST:
 *
 *   - Re-think use of PropertyResolutionException.
 *     Many of the cases should be AssertionErrors, because they shouldn't happen.
 *     For the others, we should either use an Error subclass to indicate they're
 *     unrecoverable, or we need to try to leave the object in a consistent state.
 *     This is very difficult in methods like updateCachedSources where an
 *     exception can occur at any time while processing the chain.
 *
 *   - Do testing with applets/security managers.
 *
 *   - Introspector/reflection doesn't work for non-public classes. EL handles this
 *     by trying to find a version of the method in a public superclass/interface.
 *     Looking at the code for Introspector (also used by EL), I got the idea that
 *     it already does something like this. Investigate why EL handles this in an
 *     extra step, and decide what we need to do in this class.
 *
 *   - Add option to turn on validation. For now it's hard-coded to be off.
 */

package org.jdesktop.beansbinding;

import java.beans.*;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.*;
import org.jdesktop.observablecollections.ObservableMap;
import org.jdesktop.observablecollections.ObservableMapListener;
import static org.jdesktop.beansbinding.PropertyStateEvent.UNREADABLE;
import org.jdesktop.beansbinding.ext.BeanAdapterFactory;

/**
 * An implementation of {@code Property} that uses a simple dot-separated path
 * syntax to address Java Beans properties of source objects. For example, to
 * create a property representing the {@code firstName} property of an obect:
 * <p>
 * <pre><code>
 *    BeanProperty.create("firstName");
 *</code></pre>
 * <p>
 * Or to create a property representing the {@code firstName} property of
 * an object's {@code mother} property:
 * <p>
 * <pre><code>
 *    BeanProperty.create("mother.firstName");
 * </code></pre>
 * <p>
 * An instance of {@code BeanProperty} is immutable and can be used with
 * different source objects. When a {@code PropertyStateListener} is added to
 * a {@code BeanProperty} for a given source object, the {@code BeanProperty}
 * starts listening to all objects along the path (based on that source object)
 * for change notification, and reflects any changes by notifying the
 * listener associated with the property for that source object. So, in the second
 * example above, if a {@code PropertyStateListener} is added to the property
 * for an object {@code Duke}, the {@code PropertyStateListener} is notified
 * when either {@code Duke's} mother changes (if the new mother's name is
 * different), or {@code Duke's mother's firstName} changes.
 * <p>
 * It is very important that any bean properties addressed via a {@code BeanProperty}
 * follow the Java Beans specification, including firing property change notification;
 * otherwise, {@code BeanProperty} cannot respond to change. As some beans outside
 * of your control may not follow the Java Beans specification, {@code BeanProperty}
 * always checks the {@link org.jdesktop.beansbinding.ext.BeanAdapterFactory} to
 * see if a delegate provider has been registered to provide a delegate bean to take
 * the place of an object for a given property. See the
 * <a href="ext/package-summary.html">ext package level</a> documentation for more
 * details.
 * <p>
 * When there are no {@code PropertyStateListeners} installed on a {@code BeanProperty}
 * for a given source, all {@code Property} methods act by traversing the entire
 * path from the source to the end point, thereby always providing "live" information.
 * On the contrary, when there are {@code PropertyStateListeners} installed, the beans
 * along the path (including the final value) are cached, and only updated upon
 * notification of change from a bean. Again, this makes it very important that any
 * bean property that could change along the path fires property change notification.
 * <p>
 * <a name="READABILITY"><b>Readability</b></a> of a {@code BeanProperty} for a given source is defined as follows:
 * <i>A {@code BeanProperty} is readable for a given source if and only if
 * a) each bean in the path, starting with the source, defines a Java Beans getter
 * method for the the property to be read on it AND b) each bean in the path,
 * starting with the source and ending with the bean on which we read the final
 * property, is {@code non-null}. The final value being {@code null} does not
 * affect the readability.</i>
 * <p>
 * So, in the second example given earlier, the {@code BeanProperty} is readable for (@code Duke} when all
 * of the following are true: {@code Duke} defines a Java Beans getter for
 * {@code mother}, {@code Duke's mother} defines a Java Beans getter for
 * {@code firstName}, {@code Duke} is {@code non-null}, {@code Duke's mother}
 * is {@code non-null}. The {@code BeanProperty} is therefore unreadable when
 * any of the following is true: {@code Duke} does not define a Java Beans
 * getter for {@code mother}, {@code Duke's mother} does not define a Java
 * Beans getter for {@code firstName}, {@code Duke} is {@code null},
 * {@code Duke's mother} is {@code null}.
 * <p>
 * <a name="WRITEABILITY"><b>Writeability</b></a> of a {@code BeanProperty} for a given source is defined as follows:
 * <i>A {@code BeanProperty} is writeable for a given source if and only if
 * a) each bean in the path, starting with the source and ending with the bean on
 * which we set the final property, defines a Java Beans getter method for the
 * property to be read on it AND b) the bean on which we set the final property
 * defines a Java Beans setter for the property to be set on it AND c) each bean
 * in the path, starting with the source and ending with the bean on which we
 * set the final property, is {@code non-null}. The final value being {@code null}
 * does not affect the writeability.</i>
 * <p>
 * So, in the second example given earlier, the {@code BeanProperty} is writeable for {@code Duke} when all
 * of the following are true: {@code Duke} defines a Java Beans getter for
 * {@code mother}, {@code Duke's mother} defines a Java Beans setter for
 * {@code firstName}, {@code Duke} is {@code non-null}, {@code Duke's mother}
 * is {@code non-null}. The {@code BeanProperty} is therefore unreadable when
 * any of the following is true: {@code Duke} does not define a Java Beans
 * getter for {@code mother}, {@code Duke's mother} does not define a Java
 * Beans setter for {@code firstName}, {@code Duke} is {@code null},
 * {@code Duke's mother} is {@code null}.
 * <p>
 * In addition to working on Java Beans properties, any object in the path
 * can be an instance of {@code Map}. In this case, the {@code Map's get}
 * method is used with the property name as the getter, and the
 * {@code Map's put} method is used with the property name as the setter.
 * {@code BeanProperty} can only respond to changes in {@code Maps}
 * if they are instances of {@link org.jdesktop.observablecollections.ObservableMap}.
 * <p>
 * Some methods in this class document that they can throw
 * {@code PropertyResolutionException} if an exception occurs while trying
 * to resolve the path. The throwing of this exception represents an abnormal
 * condition and if listeners are installed for the given source object,
 * leaves the {@code BeanProperty} in an inconsistent state for that source object.
 * A {@code BeanProperty} should not be used again for that same source object
 * after such an exception without first removing all listeners associated with
 * the {@code BeanProperty} for that source object.
 *
 * @param <S> the type of source object that this {@code BeanProperty} operates on
 * @param <V> the type of value that this {@code BeanProperty} represents
 *
 * @author Shannon Hickey
 * @author Scott Violet
 */
public final class BeanProperty<S, V> extends PropertyHelper<S, V> {

    private Property<S, ?> baseProperty;
    private final PropertyPath path;
    private IdentityHashMap<S, SourceEntry> map = new IdentityHashMap<S, SourceEntry>();
    private static final Object NOREAD = new Object();

    private final class SourceEntry implements PropertyChangeListener,
                                               ObservableMapListener,
                                               PropertyStateListener {

        private S source;
        private Object cachedBean;
        private Object[] cache;
        private Object cachedValue;
        private Object cachedWriter;
        private boolean ignoreChange;

        private SourceEntry(S source) {
            this.source = source;
            cache = new Object[path.length()];
            cache[0] = NOREAD;

            if (baseProperty != null) {
                baseProperty.addPropertyStateListener(source, this);
            }

            updateCachedBean();
            updateCachedSources(0);
            updateCachedValue();
            updateCachedWriter();
        }

        private void cleanup() {
            for (int i = 0; i < path.length(); i++) {
                unregisterListener(cache[i], path.get(i), this);
            }

            if (baseProperty != null) {
                baseProperty.removePropertyStateListener(source, this);
            }

            cachedBean = null;
            cache = null;
            cachedValue = null;
            cachedWriter = null;
        }

        private boolean cachedIsReadable() {
            return cachedValue != NOREAD;
        }

        private boolean cachedIsWriteable() {
            return cachedWriter != null;
        }

        private int getSourceIndex(Object object) {
            for (int i = 0; i < cache.length; i++) {
                if (cache[i] == object) {
                    return i;
                }
            }

            if (object instanceof Map) {
                return -1;
            }

            for (int i = 0; i < cache.length; i++) {
                if (cache[i] != null) {
                    Object adapter = getAdapter(cache[i], path.get(i));
                    if (adapter == object) {
                        return i;
                    }
                }
            }

            return -1;
        }

        private void updateCachedBean() {
            cachedBean = getBeanFromSource(source);
        }
        
        private void updateCachedSources(int index) {
            boolean loggedYet = false;
            
            Object src;
            
            if (index == 0) {
                src = cachedBean;
                
                if (cache[0] != src) {
                    unregisterListener(cache[0], path.get(0), this);
                    
                    cache[0] = src;
                    
                    if (src == null) {
                        loggedYet = true;
                        log("updateCachedSources()", "source is null");
                    } else {
                        registerListener(src, path.get(0), this);
                    }
                }
                
                index++;
            }
            
            for (int i = index; i < path.length(); i++) {
                Object old = cache[i];
                src = getProperty(cache[i - 1], path.get(i - 1));
                
                if (src != old) {
                    unregisterListener(old, path.get(i), this);
                    
                    cache[i] = src;
                    
                    if (src == null) {
                        if (!loggedYet) {
                            loggedYet = true;
                            log("updateCachedSources()", "missing source");
                        }
                    } else if (src == NOREAD) {
                        if (!loggedYet) {
                            loggedYet = true;
                            log("updateCachedSources()", "missing read method");
                        }
                    } else {
                        registerListener(src, path.get(i), this);
                    }
                }
            }
        }

        // -1 already used to mean validate all
        // 0... means something in the path changed
        private void validateCache(int ignore) {

/* In the future, this debugging code can be enabled via a flag */
            
/*
            for (int i = 0; i < path.length() - 1; i++) {
                if (i == ignore - 1) {
                    continue;
                }
                
                Object src = cache[i];
                
                if (src == NOREAD) {
                    return;
                }
                
                Object next = getProperty(src, path.get(i));
                
                if (!match(next, cache[i + 1])) {
                    log("validateCache()", "concurrent modification");
                }
            }
            
            if (path.length() != ignore) {
                Object next = getProperty(cache[path.length() - 1], path.getLast());
                if (!match(cachedValue, next)) {
                    log("validateCache()", "concurrent modification");
                }
                
                Object src = cache[path.length() - 1];
                Object writer;
                if (src == null || src == NOREAD) {
                    writer = null;
                } else {
                    writer = getWriter(cache[path.length() - 1], path.getLast());
                }
                
                if (cachedWriter != writer && (cachedWriter == null || !cachedWriter.equals(writer))) {
                    log("validateCache()", "concurrent modification");
                }
            }
 */
        }
        
        private void updateCachedWriter() {
            Object src = cache[path.length() - 1];
            if (src == null || src == NOREAD) {
                cachedWriter = null;
            } else {
                cachedWriter = getWriter(src, path.getLast());
                if (cachedWriter == null) {
                    log("updateCachedWriter()", "missing write method");
                }
            }
        }
        
        private void updateCachedValue() {
            Object src = cache[path.length() - 1];
            if (src == null || src == NOREAD) {
                cachedValue = NOREAD;
            } else {
                cachedValue = getProperty(cache[path.length() - 1], path.getLast());
                if (cachedValue == NOREAD) {
                    log("updateCachedValue()", "missing read method");

⌨️ 快捷键说明

复制代码 Ctrl + C
搜索代码 Ctrl + F
全屏模式 F11
切换主题 Ctrl + Shift + D
显示快捷键 ?
增大字号 Ctrl + =
减小字号 Ctrl + -