PROPOSAL: Lightweight Properties
Neal Gafter
neal at gafter.com
Tue Mar 3 12:17:30 PST 2009
Joe Darcy sort of ruled out adding property support in project coin in
http://blogs.sun.com/darcy/entry/guidance_measure_language_change_size
Regards,
Neal
On Tue, Mar 3, 2009 at 12:05 PM, Reinier Zwitserloot
<reinier at zwitserloot.com> wrote:
> You call that lightweight?
>
> How about following the beans spec more to the letter and just
> generate the addPropertyChangeListener, removePropertyChangeListener,
> setX(), and get/isX() method in response to seeing a keyword or
> annotation on a given field. You'll have to work out the details, but
> that sounds far, far simpler to understand.
>
> You'll need to flesh this out, but it would look something like:
>
> public class Foo {
> private property int x;
> }
>
> Which would generate the addPropertyChangeListener,
> removePropertyChangeListener, setX, getX methods, all public, along
> with the required infrastructure to make it tick. If you don't like
> the generation, for example because you want the setter to be package
> private, you just add the setter in the source file; the keyword will
> only generate the missing stuff. It doesn't cover every use case, but
> there's always the alternative of doing whatever people do now with
> beans. Something you didn't mention in your proposal, by the way.
>
> I think there's also a fully fleshed out property proposal (including
> a 'property' keyword) out there somewhere.
>
> Possibly make a way to opt out of generating the property change
> listener support, and just the getters/setters.
> --Reinier Zwitserloot
>
>
>
> On Mar 3, 2009, at 15:59, David Goodenough wrote:
>
>> Below is my proposal for Lightweight Properties. I know that the
>> syntax
>> change is an abbomination to some people, but I have tried to reduce
>> this to its absolute minimum, while still getting a significant
>> benefit.
>>
>> PROJECT COIN SMALL LANGUAGE CHANGE PROPOSAL FORM v1.0
>>
>> AUTHOR(S):
>>
>> David Goodenough, long time Java user. I can be reached at
>> david.goodenough at linkchoose.co.uk.
>>
>> OVERVIEW
>>
>> FEATURE SUMMARY:
>>
>> Lightweight Property support
>>
>> MAJOR ADVANTAGE:
>>
>> Both BeansBinding (whether JSR-295 or others such an JFace or the
>> JGoodies
>> binding) and the JPA Criteria API currently require field names (as
>> Strings)
>> as arguments, which an IDE/compiler can not check. With this
>> proposal the
>> strings would be abandoned, and the IDE/compiler will be able to
>> check the
>> correctness of the code.
>>
>> MAJOR BENEFIT:
>>
>> Manual checking no longer required. This proposal introduces a
>> simple well
>> defined IDE/compiler checkable solution.
>>
>> MAJOR DISADVANTAGE:
>>
>> It is a language change, and this seems to upset some people.
>>
>> ALTERNATIVES:
>>
>> None really, apart from using another language or continuing to use
>> String
>> names. The existing solutions all require String names which are
>> uncheckable.
>>
>> EXAMPLES
>>
>> Lets assume we have a POJO called foo, of type Foo with a field bar
>> of type
>> Bar, which itself has a field of type Jim called jim.
>>
>> There are two forms of lightweight properties:-
>>
>> 1) foo#bar would be translated by the compiler into:-
>>
>> new Property<Foo,Bar>(foo,"bar");
>>
>> while foo#bar#jim would be translated into:-
>>
>> new Property<Foo,Jim>(foo,"bar","jim");
>>
>> 2) Foo#bar would be translated into:-
>>
>> new Property<Foo,Bar>(Foo.class,"bar");
>>
>> while Foo#bar#jim would be translated into:-
>>
>> new Property<Foo,Jim>(Foo.class,"bar","jim");
>>
>> These two forms create (1) a bound Property, or (2) an unbound one.
>> Bound
>> Properties are explicitly bound to a particular instance of a class
>> (in this
>> case foo), while unbound Properties are templates which can be
>> applied to any
>> instance of class Foo. Actually bound properties can also be used as
>> unbound
>> properties, but that is a harmless and useful side effect not a
>> primary
>> intent.
>>
>> The Property class would need to be added (it is appended below),
>> and should
>> be added either to the java.beans package or to the
>> java.lang.reflect package
>> (with which is probably has more in common).
>>
>> Syntactically a "#" can be placed wherever a "." can be placed
>> (except inside
>> a number), and the same checks need to be made (that each field to
>> the right
>> of a # is a field in the left hand side) as would be made for a ".".
>> The only
>> difference is in field visibility - For the "#" any field is
>> visible, which
>> follows the model currently available in the Field class with
>> getDeclaredFields(). It also follows the model that while a field
>> might be
>> private and therefore not directly accessible from outside, getters
>> and
>> setters can provide access.
>>
>> The Property object provides type safe access to the field in the
>> form of
>> getters and setters. These come in pairs, one for bound and the
>> other for
>> unbound access. So for bound access no object is required to fetch
>> the value,
>> for an unbound object the parent object needs to be specified. So if
>> we
>> have:-
>>
>> Property<Foo,Bar>prop = foo#bar;
>>
>> we can later say:-
>>
>> Bar b = prop.get();
>>
>> or for an unbound one from a second Foo object foo2:-
>>
>> Bar b = prop.get(foo2);
>>
>> The getters and setters in the Property object will defer to
>> explicitly coded
>> getters and setters if present, otherwise they will use the Field
>> getter and
>> setter.
>>
>> If a setter is not explicitly coded, the implicit setter will look
>> for a
>> PropertyChangeSupport object in the parent object of the rightmost
>> field and
>> fire a PropertyChangeEvent to that object.
>>
>> There are also two Annotations provided by the Property class,
>> ReadOnly and
>> WriteOnly. These stop implicit getters and setters from trying to
>> read/write
>> the property.
>>
>> Talking of Annotations, this notation can also be used to get at the
>> Annotations for a field. So to test for the presence of an
>> Annotation Ann on
>> Foo.bar we would use:-
>>
>> if(Foo#bar.getFields()[0].isAnnotationPresent(Ann.class)) ...
>>
>> SIMPLE EXAMPLE:
>>
>> To take an example from BeansBinding (taken from Shannon Hickey's
>> blog):-
>>
>> // create a BeanProperty representing a bean's firstName
>> Property firstP = BeanProperty.create("firstName");
>> // Bind Duke's first name to the text property of a Swing JTextField
>> BeanProperty textP = BeanProperty.create("text");
>> Binding binding = Bindings.createAutoBinding(READ_WRITE, duke,
>> firstP, textfield, textP);
>> binding.bind();
>>
>>
>> would instead be written:-
>>
>> Binding binding = Bindings.createAutoBinding(READ_WRITE,
>> duke#firstName, textfield#text);
>> binding.bind();
>>
>> which of course can be checked by the IDE/compiler, and will not
>> wait until
>> run time (not even instantiation time) to show up the error.
>>
>> ADVANCED EXAMPLE:
>>
>> For a JComboBox (or JList or JTable or JTree) there is a need to map
>> a list of
>> objects to the value strings (or column contents). For this we need
>> to have
>> an unbound Property which can be applied to each element of the list.
>>
>> Duke duke;
>> List<Duke>dukes;
>> BoundComboBox combo = new
>> BoundComboBox(dukes,Duke#fullname,this#duke);
>>
>> and now the combo box will be populated from the list dukes, and the
>> display
>> values in the list will be taken from the fullname field of each Duke
>> element, and the initial value will be set from the local class
>> field duke
>> and any changes to the combo box selected element will be reflected
>> back to
>> the duke field.
>>
>> DETAILS
>>
>> SPECIFICATION:
>>
>> This proposal adds a new syntactic element, "#", which can be used
>> in the same
>> way that "." can be used to qualify fields within a Java object.
>>
>> COMPILATION:
>>
>> This proposal requires no change to the class files, and is
>> implemented by a
>> simple generation of the required instance using the relevant Property
>> constructor. Obviously the compiler would have to make sure that the
>> use that
>> the property object was being put to (in the examples above the left
>> hand
>> side of the assignment) had the correct Generic attributes.
>>
>> TESTING:
>>
>> How can the feature be tested?
>>
>> LIBRARY SUPPORT:
>>
>> The new Property class is required (see below).
>>
>> REFLECTIVE APIS:
>>
>> No changes are required to the reflective APIs although it makes
>> extensive use
>> of those APIs.
>>
>> OTHER CHANGES:
>>
>> No other changes are requires.
>>
>> MIGRATION:
>>
>> Fortunately there is no code that is formally part of J2SE 6 which
>> uses such
>> Properties. There are however two proposals which will need it
>> (BeansBinding
>> and JPA Criteria API), but neither of these seem to be destined to
>> be part of
>> J2SE 7 (BeansBinding seems to have died the death and the Criteria
>> API would
>> be part of the next J2EE which will follow J2SE 7), so this will
>> provide a
>> base for them to use and no existing code need to be updated.
>>
>> There are other extant Beans-Binding libraries, which could be
>> modified to use
>> this proposal, but as none of the existing features have been
>> changed there
>> is no need to change them (other than for type safety and compiler/IDE
>> checkability).
>>
>> COMPATIBILITY
>>
>> BREAKING CHANGES:
>>
>> None. This change should not make any existing correct code fail to
>> compile
>> or run or change the way in which it compiles/runs.
>>
>> EXISTING PROGRAMS:
>>
>> No change required to any existing programs
>>
>> REFERENCES
>>
>> EXISTING BUGS:
>>
>> None
>>
>> URL FOR PROTOTYPE (optional):
>>
>> I do not have the knowledge to make changes to the compiler, and the
>> only
>> documentation making such changes concentrated on adding operators not
>> changes at this level. So there is no prototype of the compiler
>> part, but the
>> Property class follows:-
>>
>> package java.lang.reflect;
>>
>> import java.beans.BeanInfo;
>> import java.beans.Introspector;
>> import java.beans.PropertyChangeSupport;
>> import java.beans.PropertyDescriptor;
>> import java.lang.reflect.Field;
>> import java.lang.reflect.Method;
>>
>> /**
>> * Property class
>> * This is the support class for use with the # notation to provide
>> lightweight
>> * Property support for Java.
>> *
>> * @copyright Copyright(C) 2009 David Goodenough Linkchoose Ltd
>> * @licence LPGL V2 : details of which can be found at http://fsf.org.
>> * @author david.goodenough at linkchoose.co.uk
>> *
>> * @param <C> The Parent class for this field
>> * @param <F> The Type of this field
>> */
>> public class Property<C,F> {
>> private C parent;
>> private Class<?> parentClass;
>> private Field[] fields;
>> private PropertyDescriptor[] pd = null;
>> /**
>> * Constructor used to create Property objects. The Parent object
>> may be
>> * null, but should normally be specified as it can be overridden
>> anyway.
>> * @param parent C object that contains the field
>> * @param field Field describing this field
>> */
>> public Property(C parent, String ... fieldNames) {
>> this.parent = parent;
>> this(parent.getClass(), fieldNames);
>> }
>> /**
>> * Constructor for unbound Properties, but also used internally
>> after
>> setting
>> * the parent object by the bound Property objects.
>> * @param parentClass Class of the parent object
>> * @param fieldNames String[] of field names
>> */
>> public Property(Class<?>parentClass, String .. fieldNames) {
>> this.parentClass = parentClass;
>> fields = new Field[fieldNames.length];
>> pd = new PropertyDescriptor[fieldNames.length];
>> outer: for(int index = 0; index < fields.length; index++) {
>> Field[]dclFields = parentClass.getDeclaredFields();
>> for(Field field:dclFields) {
>> if(field.getName().equals(fieldNames[index])) {
>> fields[index] = field;
>> field.setAccessible(true);
>> try {
>> BeanInfo beanInfo =
>> Introspector.getBeanInfo(parent.getClass());
>> PropertyDescriptor[]props =
>> beanInfo.getPropertyDescriptors();
>> for(PropertyDescriptor prop : props) {
>> if(prop.getName().equals(field.getName())) {
>> pd[index] = prop;
>> break;
>> }
>> }
>> } catch(Exception e) { /* assume can not find getter/
>> setter
>> */ }
>> parentClass = field.getType();
>> continue outer;
>> }
>> }
>> throw new IllegalArgumentException("Field " + fieldNames[index] +
>> " not found in class " +
>> parentClass.getCanonicalName());
>> }
>> }
>> /**
>> * Getter from the field in the parent specified when this
>> Property was
>> created.
>> * @see Property.get(C otherParent)
>> * @return F the value of this field
>> */
>> public F get() {
>> return get(parent);
>> }
>> /**
>> * Getter with explicit parent.
>> * This code will check see if this field is WriteOnly, and
>> complain if it
>> is.
>> * It will then see if the use has provided am explicit getter,
>> and call
>> that
>> * if present, otherwise it will just fetch the value through the
>> Field
>> provided
>> * method.
>> * @param otherParent C parent object
>> * @return F value of the field
>> */
>> @SuppressWarnings("unchecked") // This should actually not be
>> needed,
>> // but the Field.get method is not
>> typed
>> public F get(C otherParent) {
>> Object result = otherParent;
>> try {
>> for(int index = 0; index < fields.length; index++) {
>>
>> if(fields[index].getType().isAnnotationPresent(WriteOnly.class))
>> throw new IllegalAccessException(
>> "Can not get from a WriteOnly field - " +
>> fields[index].getName());
>> Method getter = pd[index] == null ? null :
>> pd[index].getReadMethod();
>> if(getter == null) result = fields[index].get(result);
>> else result = getter.invoke(result);
>> }
>> } catch(Exception e) {
>> throw new RuntimeException("Should not occur exception", e);
>> }
>> return (F)result;
>> }
>> /**
>> * Setter to set the value of the field in the parent object
>> declared with
>> the
>> * Property object
>> * @param newValue F new value of this field
>> */
>> public void set(F newValue) {
>> set(parent,newValue);
>> }
>> /**
>> * Setter to set the value of the field to an explicit parent
>> object.
>> * If there is a ReadOnly annotation, then we object. If there is
>> an
>> explicit
>> * setter then we use that, otherwise we set the field using the
>> Field
>> provided
>> * set method and if there is a PropertyChangeSupport field, fire a
>> property
>> * change event to it.
>> * We walk our way down the field chain, until we have the last
>> object and
>> its
>> * field, and then we do the set.
>> * @param parent C explicit parent object
>> * @param newValue F new value for field in parent
>> */
>> public void set(C parent,F newValue) {
>> try {
>> Object last = parent;
>> int index;
>> for(index = 0; index < fields.length - 1; index++) {
>>
>> if(fields[index].getType().isAnnotationPresent(WriteOnly.class))
>> throw new IllegalAccessException(
>> "Can not get from a WriteOnly field - " +
>> fields[index].getName());
>> Method getter = pd[index] == null ? null :
>> pd[index].getReadMethod();
>> if(getter == null) last = fields[index].get(last);
>> else last = getter.invoke(last);
>> }
>>
>> if(fields[index].getType().isAnnotationPresent(ReadOnly.class))
>> throw new IllegalAccessException(
>> "Can not get from a WriteOnly field - " +
>> fields[index].getName());
>> Method setter = pd[index] == null ? null :
>> pd[index].getWriteMethod();
>> if(setter == null) {
>> PropertyChangeSupport pcs = findPcs(last.getClass());
>> fields[index].set(last,newValue);
>> if(pcs != null)
>> pcs.firePropertyChange(fields[index].getName(),
>> newValue,
>>
>> fields[index].get(last));
>> } else setter.invoke(last,newValue);
>> } catch(Exception e) {
>> throw new RuntimeException("Should not occur
>> exception", e);
>> }
>> }
>> /**
>> * This is used so that the caller can view the Field name
>> * @return String field name
>> */
>> public String[] getFieldName() {
>> String[]names = new String[fields.length];
>> for(int index = 0; index < fields.length; index++) {
>> names[index] = fields[index].getName();
>> }
>> return names;
>> }
>> /**
>> * This method is used to fetch the Field array, which is useful
>> if you
>> need to
>> * access the Annotations of a field.
>> * @return Field[] the array of Fields describing this Property.
>> */
>> public Field[] getFields() {
>> return fields;
>> }
>> /**
>> * This private method looks for a PropertyChangeSupport object in
>> the
>> class and
>> * if one is found it will return it. It looks right the way up
>> the class
>> tree
>> * by recurring up the superClasses.
>> * @param parent Class to check for PropertyChangeSupport fields
>> * @return PropertyChangeSupport first found object, or null if
>> not found
>> */
>> private PropertyChangeSupport findPcs(Class<?> parent) {
>> Field fields[] = parent.getDeclaredFields();
>> for(Field field:fields) {
>> field.setAccessible(true);
>> try {
>> if(field.getType() == PropertyChangeSupport.class)
>> return (PropertyChangeSupport)field.get(parent);
>> } catch(Exception e) { }
>> }
>> // If we did not find it then try the superclass
>> Class<?>superClass = parent.getSuperclass();
>> if(superClass == null) return null;
>> return findPcs(parent.getClass().getSuperclass());
>> }
>> /**
>> * This annotation is used to mark a field as WriteOnly, i.e. it
>> can not
>> be read.
>> * This stops the automatic getter operation.
>> */
>> public @interface WriteOnly {
>> }
>> /**
>> * This annotation is used to mark a field as ReadOnly, i.e. it
>> can not be
>> written.
>> * This stops the automatic setter operation.
>> */
>> public @interface ReadOnly {
>> }
>> }
>>
>
>
>
More information about the coin-dev
mailing list