PROPOSAL: Lightweight Properties

Reinier Zwitserloot reinier at zwitserloot.com
Tue Mar 3 13:15:48 PST 2009


The language change required for your proposal is indeed lighter, but  
to understand it, it is seems a lot more complicated.

Also, it would be infeasible to introduce a 'lightweight' (according  
to anyone's definition) proposal now that is lacking in certain  
aspects, and then fix it later, unless that fix is syntactically very  
similar and backwards- and migration compatible. That's the major beef  
I have with this proposal: It effectively shuts the door on any other  
property proposal. In my view, a proposal solves the problem properly,  
or shouldn't be added at all; no quick hacky fixes that aren't part of  
a planned evolution path to a complete solution.

If you can highlight how a complete properties proposal will  
seamlessly work with your syntax, I'm more inclined to like it.

  --Reinier Zwitserloot



On Mar 3, 2009, at 22:04, David Goodenough wrote:

> Yes I do.  What you propose is much more invasive.  Look at it
> again and maybe you will see the Light(ness).
>
> David
>
> On Tuesday 03 March 2009, Reinier Zwitserloot 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