- 浏览: 135184 次
- 性别:
- 来自: 福建省莆田市
文章分类
最新评论
-
houruiming:
tks for your info which helps m ...
setcontent和setcontentobject用的是同一片内存 -
turingfellow:
in.tftpd -l -s /home/tmp -u ro ...
commands -
turingfellow:
LINUX下的网络设置 ifconfig ,routeLINU ...
commands -
turingfellow:
安装 linux loopbackyum install um ...
commands
Listening for bound property changes of a JavaBean is simple enough, and determining which bean fired the event is as straightforward as calling PropertyChangeEvent.getSource(). But what to do if the JavaBean in question has complex property values, each of which can have its own bound properties? This article examines a method for representing targeted events—those that may represent changes in objects other than the source. It also introduces an adapter that allows type-aware property change events using Java 5.0 generics. Examples are provided using the Guise™ Internet application framework.
Typed Property Change Events
When Sun added the JavaBeans component architecture, Java acquired a standard way to conceptualize properties of objects that were richer than mere data fields. Besides the ability to perform custom actions when properties are read and/or written, using so-called getter and setter methods, respectively, Java allowed third-party observer objects to listen for property changes and perform their own actions in response. Such properties which notify listeners of changes are referred to as bound properties. The foundational classes of this architecture are found in the java.beans package.
The two fundamental classes that implement bound properties in JavaBeans are PropertyChangeListener and PropertyChangeEvent. Sun provided the PropertyChangeSupport class to assist developers in creating classes with bound properties, obviating the need for a class to keep track of its own property change listeners and to manually fire property change events to those listeners. Using these classes, a simple Automobile class could implement an engine bound property, assuming that an Engine class exists. (Note that we require that an engine always be non-null to simplify the logic. Without this requirement, the code would need extra checks for null throughout.)
import java.beans.*;
public class Automobile
{
protected final PropertyChangeSupport propertyChangeSupport;
private Engine engine;
/**Default constructor with a default engine.*/
public Automobile()
{
propertyChangeSupport=new PropertyChangeSupport(this);
engine=new Engine();
}
/**@return The car's current engine.*/
public Engine getEngine() {return engine;}
/**Sets the car's engine.
@param newEngine The new engine.
@exception NullPointerException if the engine is
<code>null</code>.
*/
public void setEngine(final Engine newEngine)
{
if(newEngine==null)
{
throw new NullPointerException("No engine provided.");
}
if(!engine.equals(newEngine))
{
final Engine oldEngine= engine;
engine=newEngine;
propertyChangeSupport.firePropertyChange("engine",
oldEngine,
newEngine);
}
}
/**Add a property change listener for a specific property.
@param propertyName The name of the property to listen on.
@param listener The <code>PropertyChangeListener</code>
to be added.
*/
public void addPropertyChangeListener(final String propertyName,
final PropertyChangeListener listener)
{
propertyChangeSupport.addPropertyChangeListener(propertyName,
listener);
}
/**Remove a property change listener for a specific property.
@param propertyName The name of the property that was listened on.
@param listener The <code>PropertyChangeListener</code>
to be removed
*/
public void removePropertyChangeListener(final String propertyName,
final PropertyChangeListener listener)
{
propertyChangeSupport.removePropertyChangeListener(propertyName,
listener);
}
}Post a comment
Email Article
Print Article
Share Articles Digg del.icio.us Slashdot DZone Reddit StumbleUpon Facebook FriendFeed Furl Newsvine Google LinkedIn MySpace Technorati Twitter Windows Live YahooBuzz An interested class could listen for the car's engine changing by adding a property change listener, usually an anonymous inner class:
final Automobile automobile=new Automobile();
automobile.addPropertyChangeListener("engine",
new PropertyChangeEvent()
{
public void propertyChange(
final PropertyChangeEvent propertyChangeEvent)
{
final Engine newEngine=
(Engine)propertyChangeEvent.getNewValue();
if(newEngine.getSize()>8)
{
Systen.out.println("That's a big engine.");
}
}
});That's easy enough. But the old and new values of PropertyChangeEvent are always expressed as Object, forcing us to cast those values, even when we know ahead of time what type to expect. When we listen for the "engine" property, we expect the value to be an Engine. Wouldn't it be nice if PropertyChangeEvent used generics, so that its values were automatically returned as the expected types?
Unfortunately, PropertyChangeEvent was created long before generics were added to the Java language in version 5.0, but using another Java feature, covariance, it's possible to create an adapter class that provides us with typed values while providing backwards-compatibility with PropertyChangeEvent. This part of the task is surprisingly easy; here is the basic structure of a class, com.garretwilson.beans.GenericPropertyChangeEvent<V>, that does just that:
/**A property value change event is a Java Beans property change
event retrofitted to use generics to cast to proper value type.
@param <V> The type of property value.
@author Garret Wilson
*/
public class GenericPropertyChangeEvent<V>
extends PropertyChangeEvent
{
public GenericPropertyChangeEvent(final Object source,
final String propertyName, final V oldValue, V newValue)
{
super(source, propertyName, oldValue, newValue);
}
@SuppressWarnings("unchecked")
public GenericPropertyChangeEvent(final PropertyChangeEvent
propertyChangeEvent)
{
this(propertyChangeEvent.getSource(),
propertyChangeEvent.getPropertyName(),
(V)propertyChangeEvent.getOldValue(),
(V)propertyChangeEvent.getNewValue());
setPropagationId(propertyChangeEvent.getPropagationId());
}
@SuppressWarnings("unchecked")
public V getOldValue()
{
return (V)super.getOldValue();
}
@SuppressWarnings("unchecked")
public V getNewValue()
{
return (V)super.getNewValue();
}
}So far the main functionality of the class is to cast the old and new values to the generic type, V, before returning them. Because Java doesn't keep track of generics at runtime, the real return types of the getOldValue() and getNewValue() methods after erasure are Object, anyway. The cast to the generic type isn't actually performed here at runtime, so the @SuppressWarnings("unchecked") annotation is needed to prevent the compiler from alerting us to this fact. Whatever code uses these methods will perform the appropriate cast, however, providing equivalence to the property change listener code we had earlier, except without the need to code casts by hand. We've even provided a copy constructor to allow creating generic property change events from standard non-generic property change events.
There's a problem, however: the Automobile class fires a normal PropertyChangeEvent, not a GenericPropertyChangeEvent<V>. We could change the Automobile class to throw a GenericPropertyChangeEvent<V>, which is compatible with PropertyChangeEvent, but then we'd have to cast the event inside our PropertyChangeListener, which defeats the purpose of this exercise. It looks like we'll have to create a custom GenericPropertyChangeListener<V> as well:
/**A Java Beans property change listener retrofitted
to use generics to cast to proper value type.
@param <V> The type of property value.
@author Garret Wilson
*/
public interface GenericPropertyChangeListener<V>
extends PropertyChangeListener
{
public void propertyChange(final GenericPropertyChangeEvent<V>
genericPropertyChangeEvent);
}Well, that was certainly easy enough. But more problems arise. First, the java.beans.PropertyChangeSupport class keeps track of PropertyChangeListeners, not GenericPropertyChangeListeners, and therefore will call the PropertyChangeListener.propertyChange() method rather than the generic version. We could scrap PropertyChangeSupport and keep track of the listeners ourselves, but there bigger problem: our Automobile class would no longer be compatible with the standard Java bound property contract, which requires registration of PropertyChangeListeners and firing of property change events to the non-generics-aware version of propertyChange(). Third party tools would not be able to work with our JavaBean.
The trick is to use a special base listener class that is compatible with both PropertyChangeListener and GenericPropertyChangeListener<V> and that converts the PropertyChangeEvent to a GenericPropertyChangeEvent<V> as needed. That class, com.garretwilson.beans.AbstractGenericPropertyChangeListener, is presented below:
/**A Java Beans property change listener
retrofitted to use generics to cast to proper value type.
@param <V> The type of property value.
@author Garret Wilson
*/
public abstract class AbstractGenericPropertyChangeListener<V>
implements GenericPropertyChangeListener<V>
{
/**Called when a bound property is changed.
This non-generics version calls the generic version,
creating a new event if necessary.
No checks are made at compile time to ensure the given event
actually supports the given generic type.
@param propertyChangeEvent An event object describing
the event source, the property that has changed,
and its old and new values.
@see GenericPropertyChangeListener#propertyChange
(GenericPropertyChangeEvent)
*/
@SuppressWarnings("unchecked")
public final void propertyChange(final PropertyChangeEvent
propertyChangeEvent)
{
propertyChange((GenericPropertyChangeEvent<V>)
getGenericPropertyChangeEvent(propertyChangeEvent));
}
/**Converts a property change event to a generics-aware
property value change event.
@param propertyChangeEvent An event object describing
the event source, the property that has changed,
and its old and new values.
@return A generics-aware property change event,
either cast from the provided object
or created from the provided object's values as appropriate.
*/
@SuppressWarnings("unchecked")
public static <T> GenericPropertyChangeEvent<T>
getGenericPropertyChangeEvent(final PropertyChangeEvent
propertyChangeEvent)
{
if(propertyChangeEvent instanceof GenericPropertyChangeEvent)
{
return (GenericPropertyChangeEvent<T>)propertyChangeEvent;
}
else //if the event is a normal property change event
{
return new GenericPropertyChangeEvent<T>(
propertyChangeEvent); //create a copy of the event
}
}
}Fulfilling the contract of PropertyChangeListener, this class overrides propertyChange(PropertyChangeEvent), but then converts the event to a GenericPropertyChangeEvent<T> and passes it to the generic method version, propertyChange(GenericPropertyChangeEvent<T>). The conversion in the utility method <T> GenericPropertyChangeEvent<T> getGenericPropertyChangeEvent(final PropertyChangeEvent propertyChangeEvent) is efficient: if the event is already a GenericPropertyChangeEvent<T>, there's no need to create a new event. Otherwise, the method creates a new GenericPropertyChangeEvent<T> from the plain PropertyChangeEvent using the copy constructor we created above.
To use this efficiency, we'll need to modify the Automobile class to fire GenericPropertyChangeEvents. (The class would work just fine without this, but would require the unnecessary creation of new objects from generics-aware listeners.) It turns out that PropertyChangeSupport.firePropertyChange(String, Object, Object) just creates a PropertyChangeEvent and sends that to PropertyChangeSupport.firePropertyChange(PropertyChangeEvent). The Automobile class can create its own GenericPropertyChangeEvent<Engine> and fire that object when needed:
/**Sets the car's engine.
@param newEngine The new engine.
@exception NullPointerException if the engine
is <code>null</code>.
*/
public void setEngine(final Engine newEngine)
{
if(newEngine==null)
{
throw new NullPointerException("No engine provided.");
}
if(!engine.equals(newEngine))
{
final Engine oldEngine= engine;
engine=newEngine;
final PropertyChangeEvent propertyChangeEvent=
new GenericPropertyChangeEvent<Engine>(this,
"engine",
oldEngine,
newEngine);
propertyChangeSupport.firePropertyChange(propertyChangeEvent);
}
}All the pieces are now in place. We can now listen to the car's engine changing using our generics-aware classes with no casts:
final Automobile automobile=new Automobile();
automobile.addPropertyChangeListener("engine",
new AbstractGenericPropertyChangeEvent<Engine>()
{
public void propertyChange(
final GenericPropertyChangeEvent<Engine>
propertyChangeEvent)
{
final Engine newEngine=propertyChangeEvent.getNewValue();
if(newEngine.getSize()>8)
{
Systen.out.println("That's a big engine.");
}
}
});Furthermore, non-generics-aware PropertyChangeListener instances can still register with an Automobile instance, and they will function as normal. Even better, we can use the AbstractGenericPropertyChangeEvent<V> registration pattern to register with any JavaBean, whether or not it is generics-aware. Our code will perform casts implicitly, obviating the need for us to perform this tedious and error-prone process ourselves. This pattern additionally imposes consistency on our code—if we copy part of this routine to another property change listener that expects a type other than Engine, our code won't compile, whereas an erroneous hand-coded cast to Engine in a non-generics-aware property change listener would have gone unnoticed until runtime.
Targeted Property Change Events
Our current scheme works well for changing automobile engines, and for objects with simple properties the standard JavaBeans bound property paradigm gets the job done. But for complex properties—those properties the values of which have their own properties that can change—this paradigm has it limitations. What if some outside object want to listen for changes in the engine size as well, for example?
One solution would be for the external object to register a property change listener directly with the Automobile's engine property value. This quickly becomes cumbersome, because the value of the engine property can change. The external class must therefore also register a property change listener with the Automobile instance, listening for the "engine" property to change, and then unregister its event listener from the old engine and register it with the new one. Remembering to do this (not to mention documenting that this must be done) becomes unmanageable. Wouldn't it be better if we could do this once in the Automobile class, and propagate any property changes in the Engine to the property change listeners of Automobile?
This is a valid approach, but it leaves one detail unresolved: How does a PropertyChangeListener determine whether a property change was performed on the Automobile or on its contained Engine? We could have the Automobile forward the Engine property change events unchanged, so that PropertyChangeEvent.getSource() would equal the Engine instance as appropriate, but if one property change listener is listening to several Automobiles it prevents them from using PropertyChangeEvent.getSource() to find which Automobile's Engine changed properties. Besides, the contract of PropertyChangeEvent.getSource() (as stated by the API of java.util.EventObject) is that "All Events are constructed with a reference to the object, the "source", that is logically deemed to be the object upon which the Event in question initially occurred upon." While this is slightly ambiguous in our case as to whether "initially occurred upon" refers to the object that changed its properties or the object that fired the event, it seems pragmatically useful for a PropertyChangeListener to expect PropertyChangeEvent.getSource() to refer to the object with which the listener registered. This would favor the "object that fired the event as source" interpretation.
So there needs to be a separate method of PropertyChangeEvent that indicates the object the property of which changed, regardless of which object is firing the event. This value would stay the same regardless of how many times the event was propagated. Indeed it is possible the authors of Java had something similar in mind for the future, for the API of java.beans.PropertyChangeEvent.getPropagationId() states:
Post a comment
Email Article
Print Article
Share Articles Digg del.icio.us Slashdot DZone Reddit StumbleUpon Facebook FriendFeed Furl Newsvine Google LinkedIn MySpace Technorati Twitter Windows Live YahooBuzz The "propagationId" field is reserved for future use. In Beans 1.0 the sole requirement is that if a listener catches a PropertyChangeEvent and then fires a PropertyChangeEvent of its own, then it should make sure that it propagates the propagationId field from its incoming event to its outgoing event.
That sounds a lot like the functionality we want, but with no more information on semantics, and with no conventions relating to this field in widespread use, it seems foolhardy to hijack this field for own purposes. If future versions of Java were to use the propagationId field for some other purpose, it could cause serious backwards-compatibility issues with our code. It again seems that the safest route is to add in our own extensions to the JavaBeans component architecture.
We can start by creating an interface that can be mixed in with any event and that indicates the target of the event—the object to which the action was directed that caused the event, whether or not that object actually fired the event object in question. This is done in com.garretwilson.event.TargetedEvent, which could be used with any type of event, such as a property change event or an action event:
/**An interface for an event that knows its target,
or the object to which the event applies
(which may be different than the object that fired the event).
@author Garret Wilson
*/
public interface TargetedEvent
{
/**Returns the object to which the event applies.
This may be a different than <dfn>source</dfn>,
which is the object that generated this event instance.
@return The target of the event, or <code>null</code>
if the event target is not known.
*/
public Object getTarget();
}We can now make GenericPropertyChangeEvent<V>, which we created earlier, compatible with our new targeted event framework. We'll add new copy constructors that will allow new events to be created with different sources while still maintaining the same target. We'll also upgrade the old source-only constructors to automatically set the target to the same value as the source, for those instances in which there is no need to maintain a separate target and the class is being used traditionally:
/**A property value change event is a Java Beans
property change event retrofitted to use generics
to cast to proper value type.
This event is also <dfn>targeted</dfn>,
specifying an event target which may or may not
be the same as the source object firing this event.
@param <V> The type of property value.
@author Garret Wilson
*/
public class GenericPropertyChangeEvent<V>
extends PropertyChangeEvent implements TargetedEvent
{
/**The target of the event, or <code>null</code>
if the event target is not known.*/
private final Object target;
/**Returns the object to which the event applies.
This may be a different than <dfn>source</dfn>,
which is the object that generated this event instance.
@return The target of the event.
*/
public Object getTarget() {return target;}
/**Source and property name constructor with old and new values.
The target will be set to be the same as the given source.
@param source The bean that fired the event.
@param propertyName The programmatic name of the property
that was changed.
@param oldValue The old value of the property,
or <code>null</code> if no old value is not available.
@param newValue The new value of the property,
or <code>null</code> if the new value is not available.
*/
public GenericPropertyChangeEvent(final Object source,
final String propertyName, final V oldValue, V newValue)
{
this(source, source, propertyName, oldValue, newValue);
}
/**Source, target, and property name constructor
with old and new values.
@param source The bean that fired the event.
@param target The target of the event.
@param propertyName The programmatic name of the property
that was changed.
@param oldValue The old value of the property,
or <code>null</code> if no old value is not available.
@param newValue The new value of the property,
or <code>null</code> if the new value is not available.
*/
public GenericPropertyChangeEvent(final Object source,
final Object target, final String propertyName,
final V oldValue, V newValue)
{
super(source, propertyName, oldValue, newValue);
this.target= target;
}
/**Property change event copy constructor.
@param propertyChangeEvent A property change event the values
of which will later cast to this class' generic type.
*/
public GenericPropertyChangeEvent(final PropertyChangeEvent
propertyChangeEvent)
{
this(propertyChangeEvent.getSource(), propertyChangeEvent);
}
/**Property change event copy constructor
that specifies a different source.
If the property change event is a {@link TargetedEvent},
the target is copied from that event;
otherwise, the given source will be used as the target.
@param source The object on which the event initially occurred.
@param propertyChangeEvent A property change event the values
of which will later cast to this class' generic type.
*/
@SuppressWarnings("unchecked")
public GenericPropertyChangeEvent(final Object source,
final PropertyChangeEvent propertyChangeEvent)
{
this(source, propertyChangeEvent instanceof TargetedEvent
? ((TargetedEvent)propertyChangeEvent).getTarget() : source,
propertyChangeEvent.getPropertyName(),
(V)propertyChangeEvent.getOldValue(),
(V)propertyChangeEvent.getNewValue());
setPropagationId(propertyChangeEvent.getPropagationId());
}
/**@return The old value of the property,
or <code>null</code> if the old value is not available.*/
@SuppressWarnings("unchecked")
public V getOldValue()
{
return (V)super.getOldValue();
}
/**@return The new value of the property,
or <code>null</code> if the new value is not available.*/
@SuppressWarnings("unchecked")
public V getNewValue()
{
return (V)super.getNewValue();
}
}The Automobile class can now repeat any property changes of the contained Engine object by creating a new event object with a new source, but keeping the same target. This is best done using the GenericPropertyChangeEvent<V> copy constructor above that takes a different source as one of its arguments. The new Automobile class is shown below:
public class Automobile
{
protected final PropertyChangeSupport propertyChangeSupport;
protected final PropertyChangeListener repeatPropertyChangeListener=
new PropertyChangeListener()
{
public void propertyChange(final PropertyChangeEvent
propertyChangeEvent)
{
final PropertyChangeEvent repeatPropertyChangeEvent=
new GenericPropertyChangeEvent<Object>(
Automobile.this, propertyChangeEvent);
propertyChangeSupport.firePropertyChange(
repeatPropertyChangeEvent);
}
};
private Engine engine;
/**Default constructor with a default engine.*/
public Automobile()
{
propertyChangeSupport=new PropertyChangeSupport(this);
engine=new Engine();
engine.addPropertyChangeListener(repeatPropertyChangeListener);
addPropertyChangeListener("engine",
new AbstractGenericPropertyChangeEvent<Engine>()
{
public void propertyChange(
final GenericPropertyChangeEvent<Engine>
propertyChangeEvent)
{
propertyChangeEvent.getOldValue().
removePropertyChangeListener(repeatPropertyChangeListener);
propertyChangeEvent.getNewValue().
addPropertyChangeListener(repeatPropertyChangeListener);
}
});
}
/**@return The car's current engine.*/
public Engine getEngine() {return engine;}
/**Sets the car's engine.
@param newEngine The new engine.
@exception NullPointerException if the engine
is <code>null</code>.
*/
public void setEngine(final Engine newEngine)
{
if(newEngine==null)
{
throw new NullPointerException("No engine provided.");
}
if(!engine.equals(newEngine))
{
final Engine oldEngine= engine;
engine=newEngine;
final PropertyChangeEvent propertyChangeEvent=
new GenericPropertyChangeEvent<Engine>(this,
"engine",
oldEngine,
newEngine);
propertyChangeSupport.firePropertyChange(propertyChangeEvent);
}
}
/**Add a property change listener for a specific property.
@param propertyName The name of the property to listen on.
@param listener The <code>PropertyChangeListener</code>
to be added.
*/
public void addPropertyChangeListener(final String propertyName,
final PropertyChangeListener listener)
{
propertyChangeSupport.addPropertyChangeListener(propertyName,
listener);
}
/**Remove a property change listener for a specific property.
@param propertyName The name of the property that was listened on.
@param listener The <code>PropertyChangeListener</code>
to be removed
*/
public void removePropertyChangeListener(final String propertyName,
final PropertyChangeListener listener)
{
propertyChangeSupport.removePropertyChangeListener(propertyName,
listener);
}
}There are three important parts to this property change repeater pattern. The first is the repeater PropertyChangeListener, which can be registered with any class. It takes whatever property change that has occurred and refires the event after creating a new event with an updated source of the object firing the event. (It is important to note that Automobile.this is used to represent the Automobile instance as the source rather than the anonymous inner class.) Secondly, the repeater is registered with the Engine instance as soon as it is created. Thirdly, because the engine property can change, a separate listener is registered with the Automobile instance itself for notification of when the engine property value changes. When this happens, the repeater is unregistered from the old Engine and then registered with the new Engine.
Now an external class can be notified of property changes of the Engine property of the Automobile by registering with the Automobile either for all property change events or only for specific Engine-specific properties. If there is ever a question of whether a property of Automobile or Engine has changed, this may be resolved by examining TargetedEvent.getTarget() if the PropertyChangeEvent in question implements TargetedEvent.
The concept of a targeted event is especially useful when an object can have an arbitrary number of contained objects over an arbitrary number of hierarchical levels. As an example, consider the TreeControl component in the com.guiseframework.component package of the Guise™ Internet application framework. A TreeControl contains a root TreeNodeModel<V> which itself can contain an arbitrary number of TreeNodeModel<V> children, each of which in turn can contain other tree nodes, and so on. A common use case is to listen for the selection by the user of one of the nodes in a tree.
An application simply cannot practically register and unregister a property change listeners with each TreeNodeModel<V> as it is added or removed from the tree. The Guise™ TreeControl therefore uses the typed, targeted property change architecture described above to propagate property changes of each TreeNodeModel<V> up the tree hierarchy until it is finally repeated from the TreeControl. Any property change event fired from the control will properly indicate the TreeControl as its source, and indicate the tree node with the changed property as its target. An application can therefore listen directly to the TreeControl to be notified when a tree node is selected, for example:
final TreeControl treeControl=new TreeControl();
treeControl.addPropertyChangeListener(
TreeNodeModel.SELECTED_PROPERTY,
new AbstractGenericPropertyChangeListener<Boolean>()
{
public void propertyChange(
final GenericPropertyChangeEvent<Boolean>
propertyChangeEvent)
{
if(propertyChangeEvent.getTarget() instanceof TreeNodeModel)
{
final TreeNodeModel<?> treeNodeModel=
(TreeNodeModel<?>)propertyChangeEvent.getTarget();
if(Boolean.TRUE.equals(propertyChangeEvent.getNewValue()))
{
//the tree node was just selected
}
}
}
});The application listens for the TreeNodeModel.SELECTED_PROPERTY changing by registering one property change listener with the TreeControl instance. Once the property changes, the listener verifies that a tree node is indeed the object with the changed selection status. If the new selected status is true, the application may decide to perform some special action resulting from the selection change, perhaps based upon the specific tree node target as reported in propertyChangeEvent.getTarget().
Summary
This JavaBean extension architecture of typed, targeted property change events not only is simple and powerful, it also is interoperable with the existing JavaBeans component architecture. Through the use of generics it obviates the need for tedious manual casting of values while imposing consistency and internal type checking within a property change listener. By introducing the concept of an event target, the framework allows facile propagation of property change events to a single listening point without losing the identification of the original object the property of which was changed. A developer can experiment with the examples presented here using the free development version of the Guise™ framework library, available at http://www.guiseframework.com/.
Typed Property Change Events
When Sun added the JavaBeans component architecture, Java acquired a standard way to conceptualize properties of objects that were richer than mere data fields. Besides the ability to perform custom actions when properties are read and/or written, using so-called getter and setter methods, respectively, Java allowed third-party observer objects to listen for property changes and perform their own actions in response. Such properties which notify listeners of changes are referred to as bound properties. The foundational classes of this architecture are found in the java.beans package.
The two fundamental classes that implement bound properties in JavaBeans are PropertyChangeListener and PropertyChangeEvent. Sun provided the PropertyChangeSupport class to assist developers in creating classes with bound properties, obviating the need for a class to keep track of its own property change listeners and to manually fire property change events to those listeners. Using these classes, a simple Automobile class could implement an engine bound property, assuming that an Engine class exists. (Note that we require that an engine always be non-null to simplify the logic. Without this requirement, the code would need extra checks for null throughout.)
import java.beans.*;
public class Automobile
{
protected final PropertyChangeSupport propertyChangeSupport;
private Engine engine;
/**Default constructor with a default engine.*/
public Automobile()
{
propertyChangeSupport=new PropertyChangeSupport(this);
engine=new Engine();
}
/**@return The car's current engine.*/
public Engine getEngine() {return engine;}
/**Sets the car's engine.
@param newEngine The new engine.
@exception NullPointerException if the engine is
<code>null</code>.
*/
public void setEngine(final Engine newEngine)
{
if(newEngine==null)
{
throw new NullPointerException("No engine provided.");
}
if(!engine.equals(newEngine))
{
final Engine oldEngine= engine;
engine=newEngine;
propertyChangeSupport.firePropertyChange("engine",
oldEngine,
newEngine);
}
}
/**Add a property change listener for a specific property.
@param propertyName The name of the property to listen on.
@param listener The <code>PropertyChangeListener</code>
to be added.
*/
public void addPropertyChangeListener(final String propertyName,
final PropertyChangeListener listener)
{
propertyChangeSupport.addPropertyChangeListener(propertyName,
listener);
}
/**Remove a property change listener for a specific property.
@param propertyName The name of the property that was listened on.
@param listener The <code>PropertyChangeListener</code>
to be removed
*/
public void removePropertyChangeListener(final String propertyName,
final PropertyChangeListener listener)
{
propertyChangeSupport.removePropertyChangeListener(propertyName,
listener);
}
}Post a comment
Email Article
Print Article
Share Articles Digg del.icio.us Slashdot DZone Reddit StumbleUpon Facebook FriendFeed Furl Newsvine Google LinkedIn MySpace Technorati Twitter Windows Live YahooBuzz An interested class could listen for the car's engine changing by adding a property change listener, usually an anonymous inner class:
final Automobile automobile=new Automobile();
automobile.addPropertyChangeListener("engine",
new PropertyChangeEvent()
{
public void propertyChange(
final PropertyChangeEvent propertyChangeEvent)
{
final Engine newEngine=
(Engine)propertyChangeEvent.getNewValue();
if(newEngine.getSize()>8)
{
Systen.out.println("That's a big engine.");
}
}
});That's easy enough. But the old and new values of PropertyChangeEvent are always expressed as Object, forcing us to cast those values, even when we know ahead of time what type to expect. When we listen for the "engine" property, we expect the value to be an Engine. Wouldn't it be nice if PropertyChangeEvent used generics, so that its values were automatically returned as the expected types?
Unfortunately, PropertyChangeEvent was created long before generics were added to the Java language in version 5.0, but using another Java feature, covariance, it's possible to create an adapter class that provides us with typed values while providing backwards-compatibility with PropertyChangeEvent. This part of the task is surprisingly easy; here is the basic structure of a class, com.garretwilson.beans.GenericPropertyChangeEvent<V>, that does just that:
/**A property value change event is a Java Beans property change
event retrofitted to use generics to cast to proper value type.
@param <V> The type of property value.
@author Garret Wilson
*/
public class GenericPropertyChangeEvent<V>
extends PropertyChangeEvent
{
public GenericPropertyChangeEvent(final Object source,
final String propertyName, final V oldValue, V newValue)
{
super(source, propertyName, oldValue, newValue);
}
@SuppressWarnings("unchecked")
public GenericPropertyChangeEvent(final PropertyChangeEvent
propertyChangeEvent)
{
this(propertyChangeEvent.getSource(),
propertyChangeEvent.getPropertyName(),
(V)propertyChangeEvent.getOldValue(),
(V)propertyChangeEvent.getNewValue());
setPropagationId(propertyChangeEvent.getPropagationId());
}
@SuppressWarnings("unchecked")
public V getOldValue()
{
return (V)super.getOldValue();
}
@SuppressWarnings("unchecked")
public V getNewValue()
{
return (V)super.getNewValue();
}
}So far the main functionality of the class is to cast the old and new values to the generic type, V, before returning them. Because Java doesn't keep track of generics at runtime, the real return types of the getOldValue() and getNewValue() methods after erasure are Object, anyway. The cast to the generic type isn't actually performed here at runtime, so the @SuppressWarnings("unchecked") annotation is needed to prevent the compiler from alerting us to this fact. Whatever code uses these methods will perform the appropriate cast, however, providing equivalence to the property change listener code we had earlier, except without the need to code casts by hand. We've even provided a copy constructor to allow creating generic property change events from standard non-generic property change events.
There's a problem, however: the Automobile class fires a normal PropertyChangeEvent, not a GenericPropertyChangeEvent<V>. We could change the Automobile class to throw a GenericPropertyChangeEvent<V>, which is compatible with PropertyChangeEvent, but then we'd have to cast the event inside our PropertyChangeListener, which defeats the purpose of this exercise. It looks like we'll have to create a custom GenericPropertyChangeListener<V> as well:
/**A Java Beans property change listener retrofitted
to use generics to cast to proper value type.
@param <V> The type of property value.
@author Garret Wilson
*/
public interface GenericPropertyChangeListener<V>
extends PropertyChangeListener
{
public void propertyChange(final GenericPropertyChangeEvent<V>
genericPropertyChangeEvent);
}Well, that was certainly easy enough. But more problems arise. First, the java.beans.PropertyChangeSupport class keeps track of PropertyChangeListeners, not GenericPropertyChangeListeners, and therefore will call the PropertyChangeListener.propertyChange() method rather than the generic version. We could scrap PropertyChangeSupport and keep track of the listeners ourselves, but there bigger problem: our Automobile class would no longer be compatible with the standard Java bound property contract, which requires registration of PropertyChangeListeners and firing of property change events to the non-generics-aware version of propertyChange(). Third party tools would not be able to work with our JavaBean.
The trick is to use a special base listener class that is compatible with both PropertyChangeListener and GenericPropertyChangeListener<V> and that converts the PropertyChangeEvent to a GenericPropertyChangeEvent<V> as needed. That class, com.garretwilson.beans.AbstractGenericPropertyChangeListener, is presented below:
/**A Java Beans property change listener
retrofitted to use generics to cast to proper value type.
@param <V> The type of property value.
@author Garret Wilson
*/
public abstract class AbstractGenericPropertyChangeListener<V>
implements GenericPropertyChangeListener<V>
{
/**Called when a bound property is changed.
This non-generics version calls the generic version,
creating a new event if necessary.
No checks are made at compile time to ensure the given event
actually supports the given generic type.
@param propertyChangeEvent An event object describing
the event source, the property that has changed,
and its old and new values.
@see GenericPropertyChangeListener#propertyChange
(GenericPropertyChangeEvent)
*/
@SuppressWarnings("unchecked")
public final void propertyChange(final PropertyChangeEvent
propertyChangeEvent)
{
propertyChange((GenericPropertyChangeEvent<V>)
getGenericPropertyChangeEvent(propertyChangeEvent));
}
/**Converts a property change event to a generics-aware
property value change event.
@param propertyChangeEvent An event object describing
the event source, the property that has changed,
and its old and new values.
@return A generics-aware property change event,
either cast from the provided object
or created from the provided object's values as appropriate.
*/
@SuppressWarnings("unchecked")
public static <T> GenericPropertyChangeEvent<T>
getGenericPropertyChangeEvent(final PropertyChangeEvent
propertyChangeEvent)
{
if(propertyChangeEvent instanceof GenericPropertyChangeEvent)
{
return (GenericPropertyChangeEvent<T>)propertyChangeEvent;
}
else //if the event is a normal property change event
{
return new GenericPropertyChangeEvent<T>(
propertyChangeEvent); //create a copy of the event
}
}
}Fulfilling the contract of PropertyChangeListener, this class overrides propertyChange(PropertyChangeEvent), but then converts the event to a GenericPropertyChangeEvent<T> and passes it to the generic method version, propertyChange(GenericPropertyChangeEvent<T>). The conversion in the utility method <T> GenericPropertyChangeEvent<T> getGenericPropertyChangeEvent(final PropertyChangeEvent propertyChangeEvent) is efficient: if the event is already a GenericPropertyChangeEvent<T>, there's no need to create a new event. Otherwise, the method creates a new GenericPropertyChangeEvent<T> from the plain PropertyChangeEvent using the copy constructor we created above.
To use this efficiency, we'll need to modify the Automobile class to fire GenericPropertyChangeEvents. (The class would work just fine without this, but would require the unnecessary creation of new objects from generics-aware listeners.) It turns out that PropertyChangeSupport.firePropertyChange(String, Object, Object) just creates a PropertyChangeEvent and sends that to PropertyChangeSupport.firePropertyChange(PropertyChangeEvent). The Automobile class can create its own GenericPropertyChangeEvent<Engine> and fire that object when needed:
/**Sets the car's engine.
@param newEngine The new engine.
@exception NullPointerException if the engine
is <code>null</code>.
*/
public void setEngine(final Engine newEngine)
{
if(newEngine==null)
{
throw new NullPointerException("No engine provided.");
}
if(!engine.equals(newEngine))
{
final Engine oldEngine= engine;
engine=newEngine;
final PropertyChangeEvent propertyChangeEvent=
new GenericPropertyChangeEvent<Engine>(this,
"engine",
oldEngine,
newEngine);
propertyChangeSupport.firePropertyChange(propertyChangeEvent);
}
}All the pieces are now in place. We can now listen to the car's engine changing using our generics-aware classes with no casts:
final Automobile automobile=new Automobile();
automobile.addPropertyChangeListener("engine",
new AbstractGenericPropertyChangeEvent<Engine>()
{
public void propertyChange(
final GenericPropertyChangeEvent<Engine>
propertyChangeEvent)
{
final Engine newEngine=propertyChangeEvent.getNewValue();
if(newEngine.getSize()>8)
{
Systen.out.println("That's a big engine.");
}
}
});Furthermore, non-generics-aware PropertyChangeListener instances can still register with an Automobile instance, and they will function as normal. Even better, we can use the AbstractGenericPropertyChangeEvent<V> registration pattern to register with any JavaBean, whether or not it is generics-aware. Our code will perform casts implicitly, obviating the need for us to perform this tedious and error-prone process ourselves. This pattern additionally imposes consistency on our code—if we copy part of this routine to another property change listener that expects a type other than Engine, our code won't compile, whereas an erroneous hand-coded cast to Engine in a non-generics-aware property change listener would have gone unnoticed until runtime.
Targeted Property Change Events
Our current scheme works well for changing automobile engines, and for objects with simple properties the standard JavaBeans bound property paradigm gets the job done. But for complex properties—those properties the values of which have their own properties that can change—this paradigm has it limitations. What if some outside object want to listen for changes in the engine size as well, for example?
One solution would be for the external object to register a property change listener directly with the Automobile's engine property value. This quickly becomes cumbersome, because the value of the engine property can change. The external class must therefore also register a property change listener with the Automobile instance, listening for the "engine" property to change, and then unregister its event listener from the old engine and register it with the new one. Remembering to do this (not to mention documenting that this must be done) becomes unmanageable. Wouldn't it be better if we could do this once in the Automobile class, and propagate any property changes in the Engine to the property change listeners of Automobile?
This is a valid approach, but it leaves one detail unresolved: How does a PropertyChangeListener determine whether a property change was performed on the Automobile or on its contained Engine? We could have the Automobile forward the Engine property change events unchanged, so that PropertyChangeEvent.getSource() would equal the Engine instance as appropriate, but if one property change listener is listening to several Automobiles it prevents them from using PropertyChangeEvent.getSource() to find which Automobile's Engine changed properties. Besides, the contract of PropertyChangeEvent.getSource() (as stated by the API of java.util.EventObject) is that "All Events are constructed with a reference to the object, the "source", that is logically deemed to be the object upon which the Event in question initially occurred upon." While this is slightly ambiguous in our case as to whether "initially occurred upon" refers to the object that changed its properties or the object that fired the event, it seems pragmatically useful for a PropertyChangeListener to expect PropertyChangeEvent.getSource() to refer to the object with which the listener registered. This would favor the "object that fired the event as source" interpretation.
So there needs to be a separate method of PropertyChangeEvent that indicates the object the property of which changed, regardless of which object is firing the event. This value would stay the same regardless of how many times the event was propagated. Indeed it is possible the authors of Java had something similar in mind for the future, for the API of java.beans.PropertyChangeEvent.getPropagationId() states:
Post a comment
Email Article
Print Article
Share Articles Digg del.icio.us Slashdot DZone Reddit StumbleUpon Facebook FriendFeed Furl Newsvine Google LinkedIn MySpace Technorati Twitter Windows Live YahooBuzz The "propagationId" field is reserved for future use. In Beans 1.0 the sole requirement is that if a listener catches a PropertyChangeEvent and then fires a PropertyChangeEvent of its own, then it should make sure that it propagates the propagationId field from its incoming event to its outgoing event.
That sounds a lot like the functionality we want, but with no more information on semantics, and with no conventions relating to this field in widespread use, it seems foolhardy to hijack this field for own purposes. If future versions of Java were to use the propagationId field for some other purpose, it could cause serious backwards-compatibility issues with our code. It again seems that the safest route is to add in our own extensions to the JavaBeans component architecture.
We can start by creating an interface that can be mixed in with any event and that indicates the target of the event—the object to which the action was directed that caused the event, whether or not that object actually fired the event object in question. This is done in com.garretwilson.event.TargetedEvent, which could be used with any type of event, such as a property change event or an action event:
/**An interface for an event that knows its target,
or the object to which the event applies
(which may be different than the object that fired the event).
@author Garret Wilson
*/
public interface TargetedEvent
{
/**Returns the object to which the event applies.
This may be a different than <dfn>source</dfn>,
which is the object that generated this event instance.
@return The target of the event, or <code>null</code>
if the event target is not known.
*/
public Object getTarget();
}We can now make GenericPropertyChangeEvent<V>, which we created earlier, compatible with our new targeted event framework. We'll add new copy constructors that will allow new events to be created with different sources while still maintaining the same target. We'll also upgrade the old source-only constructors to automatically set the target to the same value as the source, for those instances in which there is no need to maintain a separate target and the class is being used traditionally:
/**A property value change event is a Java Beans
property change event retrofitted to use generics
to cast to proper value type.
This event is also <dfn>targeted</dfn>,
specifying an event target which may or may not
be the same as the source object firing this event.
@param <V> The type of property value.
@author Garret Wilson
*/
public class GenericPropertyChangeEvent<V>
extends PropertyChangeEvent implements TargetedEvent
{
/**The target of the event, or <code>null</code>
if the event target is not known.*/
private final Object target;
/**Returns the object to which the event applies.
This may be a different than <dfn>source</dfn>,
which is the object that generated this event instance.
@return The target of the event.
*/
public Object getTarget() {return target;}
/**Source and property name constructor with old and new values.
The target will be set to be the same as the given source.
@param source The bean that fired the event.
@param propertyName The programmatic name of the property
that was changed.
@param oldValue The old value of the property,
or <code>null</code> if no old value is not available.
@param newValue The new value of the property,
or <code>null</code> if the new value is not available.
*/
public GenericPropertyChangeEvent(final Object source,
final String propertyName, final V oldValue, V newValue)
{
this(source, source, propertyName, oldValue, newValue);
}
/**Source, target, and property name constructor
with old and new values.
@param source The bean that fired the event.
@param target The target of the event.
@param propertyName The programmatic name of the property
that was changed.
@param oldValue The old value of the property,
or <code>null</code> if no old value is not available.
@param newValue The new value of the property,
or <code>null</code> if the new value is not available.
*/
public GenericPropertyChangeEvent(final Object source,
final Object target, final String propertyName,
final V oldValue, V newValue)
{
super(source, propertyName, oldValue, newValue);
this.target= target;
}
/**Property change event copy constructor.
@param propertyChangeEvent A property change event the values
of which will later cast to this class' generic type.
*/
public GenericPropertyChangeEvent(final PropertyChangeEvent
propertyChangeEvent)
{
this(propertyChangeEvent.getSource(), propertyChangeEvent);
}
/**Property change event copy constructor
that specifies a different source.
If the property change event is a {@link TargetedEvent},
the target is copied from that event;
otherwise, the given source will be used as the target.
@param source The object on which the event initially occurred.
@param propertyChangeEvent A property change event the values
of which will later cast to this class' generic type.
*/
@SuppressWarnings("unchecked")
public GenericPropertyChangeEvent(final Object source,
final PropertyChangeEvent propertyChangeEvent)
{
this(source, propertyChangeEvent instanceof TargetedEvent
? ((TargetedEvent)propertyChangeEvent).getTarget() : source,
propertyChangeEvent.getPropertyName(),
(V)propertyChangeEvent.getOldValue(),
(V)propertyChangeEvent.getNewValue());
setPropagationId(propertyChangeEvent.getPropagationId());
}
/**@return The old value of the property,
or <code>null</code> if the old value is not available.*/
@SuppressWarnings("unchecked")
public V getOldValue()
{
return (V)super.getOldValue();
}
/**@return The new value of the property,
or <code>null</code> if the new value is not available.*/
@SuppressWarnings("unchecked")
public V getNewValue()
{
return (V)super.getNewValue();
}
}The Automobile class can now repeat any property changes of the contained Engine object by creating a new event object with a new source, but keeping the same target. This is best done using the GenericPropertyChangeEvent<V> copy constructor above that takes a different source as one of its arguments. The new Automobile class is shown below:
public class Automobile
{
protected final PropertyChangeSupport propertyChangeSupport;
protected final PropertyChangeListener repeatPropertyChangeListener=
new PropertyChangeListener()
{
public void propertyChange(final PropertyChangeEvent
propertyChangeEvent)
{
final PropertyChangeEvent repeatPropertyChangeEvent=
new GenericPropertyChangeEvent<Object>(
Automobile.this, propertyChangeEvent);
propertyChangeSupport.firePropertyChange(
repeatPropertyChangeEvent);
}
};
private Engine engine;
/**Default constructor with a default engine.*/
public Automobile()
{
propertyChangeSupport=new PropertyChangeSupport(this);
engine=new Engine();
engine.addPropertyChangeListener(repeatPropertyChangeListener);
addPropertyChangeListener("engine",
new AbstractGenericPropertyChangeEvent<Engine>()
{
public void propertyChange(
final GenericPropertyChangeEvent<Engine>
propertyChangeEvent)
{
propertyChangeEvent.getOldValue().
removePropertyChangeListener(repeatPropertyChangeListener);
propertyChangeEvent.getNewValue().
addPropertyChangeListener(repeatPropertyChangeListener);
}
});
}
/**@return The car's current engine.*/
public Engine getEngine() {return engine;}
/**Sets the car's engine.
@param newEngine The new engine.
@exception NullPointerException if the engine
is <code>null</code>.
*/
public void setEngine(final Engine newEngine)
{
if(newEngine==null)
{
throw new NullPointerException("No engine provided.");
}
if(!engine.equals(newEngine))
{
final Engine oldEngine= engine;
engine=newEngine;
final PropertyChangeEvent propertyChangeEvent=
new GenericPropertyChangeEvent<Engine>(this,
"engine",
oldEngine,
newEngine);
propertyChangeSupport.firePropertyChange(propertyChangeEvent);
}
}
/**Add a property change listener for a specific property.
@param propertyName The name of the property to listen on.
@param listener The <code>PropertyChangeListener</code>
to be added.
*/
public void addPropertyChangeListener(final String propertyName,
final PropertyChangeListener listener)
{
propertyChangeSupport.addPropertyChangeListener(propertyName,
listener);
}
/**Remove a property change listener for a specific property.
@param propertyName The name of the property that was listened on.
@param listener The <code>PropertyChangeListener</code>
to be removed
*/
public void removePropertyChangeListener(final String propertyName,
final PropertyChangeListener listener)
{
propertyChangeSupport.removePropertyChangeListener(propertyName,
listener);
}
}There are three important parts to this property change repeater pattern. The first is the repeater PropertyChangeListener, which can be registered with any class. It takes whatever property change that has occurred and refires the event after creating a new event with an updated source of the object firing the event. (It is important to note that Automobile.this is used to represent the Automobile instance as the source rather than the anonymous inner class.) Secondly, the repeater is registered with the Engine instance as soon as it is created. Thirdly, because the engine property can change, a separate listener is registered with the Automobile instance itself for notification of when the engine property value changes. When this happens, the repeater is unregistered from the old Engine and then registered with the new Engine.
Now an external class can be notified of property changes of the Engine property of the Automobile by registering with the Automobile either for all property change events or only for specific Engine-specific properties. If there is ever a question of whether a property of Automobile or Engine has changed, this may be resolved by examining TargetedEvent.getTarget() if the PropertyChangeEvent in question implements TargetedEvent.
The concept of a targeted event is especially useful when an object can have an arbitrary number of contained objects over an arbitrary number of hierarchical levels. As an example, consider the TreeControl component in the com.guiseframework.component package of the Guise™ Internet application framework. A TreeControl contains a root TreeNodeModel<V> which itself can contain an arbitrary number of TreeNodeModel<V> children, each of which in turn can contain other tree nodes, and so on. A common use case is to listen for the selection by the user of one of the nodes in a tree.
An application simply cannot practically register and unregister a property change listeners with each TreeNodeModel<V> as it is added or removed from the tree. The Guise™ TreeControl therefore uses the typed, targeted property change architecture described above to propagate property changes of each TreeNodeModel<V> up the tree hierarchy until it is finally repeated from the TreeControl. Any property change event fired from the control will properly indicate the TreeControl as its source, and indicate the tree node with the changed property as its target. An application can therefore listen directly to the TreeControl to be notified when a tree node is selected, for example:
final TreeControl treeControl=new TreeControl();
treeControl.addPropertyChangeListener(
TreeNodeModel.SELECTED_PROPERTY,
new AbstractGenericPropertyChangeListener<Boolean>()
{
public void propertyChange(
final GenericPropertyChangeEvent<Boolean>
propertyChangeEvent)
{
if(propertyChangeEvent.getTarget() instanceof TreeNodeModel)
{
final TreeNodeModel<?> treeNodeModel=
(TreeNodeModel<?>)propertyChangeEvent.getTarget();
if(Boolean.TRUE.equals(propertyChangeEvent.getNewValue()))
{
//the tree node was just selected
}
}
}
});The application listens for the TreeNodeModel.SELECTED_PROPERTY changing by registering one property change listener with the TreeControl instance. Once the property changes, the listener verifies that a tree node is indeed the object with the changed selection status. If the new selected status is true, the application may decide to perform some special action resulting from the selection change, perhaps based upon the specific tree node target as reported in propertyChangeEvent.getTarget().
Summary
This JavaBean extension architecture of typed, targeted property change events not only is simple and powerful, it also is interoperable with the existing JavaBeans component architecture. Through the use of generics it obviates the need for tedious manual casting of values while imposing consistency and internal type checking within a property change listener. By introducing the concept of an event target, the framework allows facile propagation of property change events to a single listening point without losing the identification of the original object the property of which was changed. A developer can experiment with the examples presented here using the free development version of the Guise™ framework library, available at http://www.guiseframework.com/.
发表评论
-
protocols
2011-04-03 19:22 924<!-- The protocols capabilit ... -
dfcap
2011-04-03 19:15 876<!-- The df capability has a ... -
booktrading /seller
2011-03-29 23:19 928<html><head><tit ... -
booktrading / manager
2011-03-29 23:18 1089<html><head><tit ... -
booktrading / common
2011-03-29 23:17 986<html><head><tit ... -
booktrading / buyer
2011-03-29 23:13 846<!-- <H3>The buyer age ... -
tomcat的context说明书
2011-03-20 17:39 803http://tomcat.apache.org/tomcat ... -
msyql的select语法
2010-09-13 22:52 107613.2.7. SELECT语法 13.2.7.1. ... -
zotero与word集成
2010-09-11 08:50 1767Manually Installing the Zotero ... -
university 2/n
2010-08-24 07:54 898Chapter 1.Introduction of regis ... -
university 1/n
2010-08-24 07:53 940chapter? Introduction ?.?The st ... -
Sun Java Bugs that affect lucene
2010-08-23 08:59 735Sometimes Lucene runs amok of b ... -
Snowball分词
2010-08-22 13:07 1226using System; using Lucene.Net. ... -
penn tree bank 6/6
2010-08-20 07:09 91811 This use of 12 Contact the - ... -
penn tree bank 5/n
2010-08-19 07:40 923always errs on the side of caut ... -
penn tree bank 4/n
2010-08-19 07:39 8184. Bracketing 4.1 Basic Methodo ... -
penn tree bank 3/n
2010-08-15 23:31 8192.3.1 Automated Stage. During t ... -
penn tree bank 2/n
2010-08-15 23:30 1505Mitchell P Marcus et al. Buildi ... -
capabilities 3/3
2010-08-11 22:58 77801<capability xmlns="ht ... -
capabilities 2/3
2010-08-11 22:57 740Fig.3.Element creation cases:a) ...
相关推荐
【毕业设计】基于yolov9实现目标追踪和计数源码.zip
MATLAB程序:多个无人船 协同围捕控制算法 3船围捕控制,围捕运动船只 可以仿真多个船之间的距离以及距离目标船的距离,特别适合学习、参考
基于线性模型预测控制(LMPC)的四旋翼飞行器(UAV)控制
资源说明: 1:csdn平台资源详情页的文档预览若发现'异常',属平台多文档混合解析和叠加展示风格,请放心使用。 2:32页图文详解文档(从零开始项目全套环境工具安装搭建调试运行部署,保姆级图文详解)。 3:34页范例参考毕业论文,万字长文,word文档,支持二次编辑。 4:27页范例参考答辩ppt,pptx格式,支持二次编辑。 5:工具环境、ppt参考模板、相关教程资源分享。 6:资源项目源码均已通过严格测试验证,保证能够正常运行,本项目仅用作交流学习参考,请切勿用于商业用途。 7:项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通。 内容概要: 本系统基于 B/S 网络结构,在IDEA中开发。服务端用 Java 并借 ssm 框架(Spring+SpringMVC+MyBatis)搭建后台。前台采用支持 HTML5 的 VUE 框架。用 MySQL 存储数据,可靠性强。 能学到什么: 学会用ssm搭建后台,提升效率、专注业务。学习 VUE 框架构建交互界面、前后端数据交互、MySQL管理数据、从零开始环境搭建、调试、运行、打包、部署流程。
内容概要:本文详细介绍了一种基于 Python 实现无人机自动拍摄的方法,具体涵盖无人机飞行控制系统与摄像头控制的交互流程。主要内容包括:环境搭建、所需第三方库安装、无人机初始化与控制逻辑解析、到达指定地理位置后的摄影动作实现及任务结束后的安全返程指令集发送机制。 适用人群:具有一定编程能力和硬件基础知识的技术爱好者、从事航空影像获取相关领域的工作人员以及自动化设备的研发者。 使用场景及目标:通过本指南可以帮助用户掌握如何构建基本但完整的无人机自动拍摄系统,从而适用于新闻报道、地质勘探、环境监测等多个应用场景中的快速响应数据采集任务。 其他说明:代码实例采用开源软件(如dronekit、opencv等),便于后续开发优化,同时强调了飞行安全性与法律法规遵从的重要意义,鼓励开发者先期模拟测试再逐步应用于实际项目中。
李团结业务招待费申报表20250104.pdf
含前后端源码,非线传,修复最新登录接口 梦想贩卖机升级版,变现宝吸取了资源变现类产品的很多优点,摒弃了那些无关紧要的东西,使本产品在运营和变现能力上,实现了质的超越。多领域素材资源知识变现营销裂变独立版。 实现流量互导,多渠道变现。独立部署,可绑自有独立域名不限制域名。
这是一个基于 Unofficial Airplay 协议规范的 C# 与 Apple TV 连接
【Golang设计模式】使用Golang泛型实现的设计模式(大话设计模式)
【C语言】2019年南航计算机学院操作系统课程的实验代码-实验心得-上机考试练习-笔试复习笔记_pgj
二十.核心动画 - 新年烟花:资源及源码
【毕业设计】Python 图形化麻将游戏 带蒙特卡洛AI源码.zip
离散数学是计算机科学中的一个基础且至关重要的领域,它主要研究不连续的、个体的、离散的数据结构和逻辑关系。02324离散数学自学考试试题集为考研复习提供了宝贵的参考资料,尤其对那些准备复试的学生来说,价值巨大。通过这些试题,考生可以系统地理解和掌握离散数学的基本概念、理论和方法。 离散数学的核心内容包括以下几个方面: 1. **集合论**:集合是最基本的数学概念,离散数学首先会介绍集合的定义、元素关系、集合的运算(如并集、交集、差集、笛卡尔积等)以及集合的性质。此外,还有幂集和良序原理等相关知识。 2. **逻辑与证明**:这包括命题逻辑和一阶逻辑,学习如何使用逻辑符号表达命题,并进行逻辑推理。证明技巧如归纳法、反证法和构造性证明也是重点。 3. **图论**:图是描述对象之间关系的重要工具,学习图的定义、度、路径、环、树等基本概念,以及欧拉图、哈密顿图、最短路径算法等问题。 4. **组合数学**:计数问题是离散数学中的一个重要部分,包括排列、组合、二项式定理、鸽巢原理、容斥原理等。它们。内容来源于网络分享,如有侵权请联系我删除。另外如果没有积分的同学需要下载,请私信我。
【Golang设计模式】使用Golang泛型实现的设计模式(大话设计模式)_pgj
资源说明: 1:csdn平台资源详情页的文档预览若发现'异常',属平台多文档混合解析和叠加展示风格,请放心使用。 2:32页图文详解文档(从零开始项目全套环境工具安装搭建调试运行部署,保姆级图文详解)。 3:34页范例参考毕业论文,万字长文,word文档,支持二次编辑。 4:27页范例参考答辩ppt,pptx格式,支持二次编辑。 5:工具环境、ppt参考模板、相关教程资源分享。 6:资源项目源码均已通过严格测试验证,保证能够正常运行,本项目仅用作交流学习参考,请切勿用于商业用途。 7:项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通。 内容概要: 本系统基于 B/S 网络结构,在 IDEA 中开发。服务端用 Java 并借 ssm 框架(Spring+SpringMVC+MyBatis)搭建后台。用 MySQL 存储数据,可靠性强。 能学到什么: 学会用ssm搭建后台,提升效率、专注业务。学习使用jsp、html构建交互界面、前后端数据交互、MySQL管理数据、从零开始环境搭建、调试、运行、打包、部署流程。
全方位讲解三菱Q系列QD173H、QD170运动控制器, 是事频,共25个小时的事频讲解,非常详细。 需要特殊播放器播放,一机一码,必须电脑本地播放,看清楚再拿哦 Q系列运动控制器是比较高级的内容,专门用于运动控制,比如圆弧插补、电子凸轮、同步运动等。 每结课的源程序和QD713H QD170都有,已经配置好了。 如果需要用,根据自己的实际应用稍作修改,灌入PLC就可以。 内容有:QD170 QD713的参数设置、模块介绍和NN通信、指令讲解与JOG编程、opr定位程序初写、QD170M的同步控制、双凸轮控制、凸轮进给、凸轮往复、实模式、虚模式。 可以说这两个模块的所有功能都有讲有。 包括有: 1、PLC源程序 2、QD170 QD713的配置文件 3、事频讲解,专门讲的QD713 这个是Q系列中比较高级的内容,需要比较好的基础 搞定这个15K不是很大的问题,需要好的基础。
资源说明: 1:csdn平台资源详情页的文档预览若发现'异常',属平台多文档混合解析和叠加展示风格,请放心使用。 2:32页图文详解文档(从零开始项目全套环境工具安装搭建调试运行部署,保姆级图文详解)。 3:34页范例参考毕业论文,万字长文,word文档,支持二次编辑。 4:27页范例参考答辩ppt,pptx格式,支持二次编辑。 5:工具环境、ppt参考模板、相关教程资源分享。 6:资源项目源码均已通过严格测试验证,保证能够正常运行,本项目仅用作交流学习参考,请切勿用于商业用途。 7:项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通。 内容概要: 本系统基于 B/S 网络结构,在IDEA中开发。服务端用 Java 并借 ssm 框架(Spring+SpringMVC+MyBatis)搭建后台。前台采用支持 HTML5 的 VUE 框架。用 MySQL 存储数据,可靠性强。 能学到什么: 学会用ssm搭建后台,提升效率、专注业务。学习 VUE 框架构建交互界面、前后端数据交互、MySQL管理数据、从零开始环境搭建、调试、运行、打包、部署流程。
感应电机 异步电机模型预测电流控制MPCC 感应电机MPCC系统将逆变器电压矢量遍历代入到定子磁链、定子电流预测模型,可得到下一时刻的定子电流,将预测得到的定子电流代入到表征系统控制性能的成本函数,并将令成本函数最小的电压矢量作为输出。 提供对应的参考文献;
资源说明: 1:csdn平台资源详情页的文档预览若发现'异常',属平台多文档混合解析和叠加展示风格,请放心使用。 2:32页图文详解文档(从零开始项目全套环境工具安装搭建调试运行部署,保姆级图文详解)。 3:34页范例参考毕业论文,万字长文,word文档,支持二次编辑。 4:27页范例参考答辩ppt,pptx格式,支持二次编辑。 5:工具环境、ppt参考模板、相关教程资源分享。 6:资源项目源码均已通过严格测试验证,保证能够正常运行,本项目仅用作交流学习参考,请切勿用于商业用途。 7:项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通。 内容概要: 本系统基于 B/S 网络结构,在 IDEA 中开发。服务端用 Java 并借 ssm 框架(Spring+SpringMVC+MyBatis)搭建后台。用 MySQL 存储数据,可靠性强。 能学到什么: 学会用ssm搭建后台,提升效率、专注业务。学习使用jsp、html构建交互界面、前后端数据交互、MySQL管理数据、从零开始环境搭建、调试、运行、打包、部署流程。
建筑暖通空调与微电网智能控制协同设计(代码)