PROPOSAL: Lightweight Properties

Joseph D. Darcy Joe.Darcy at Sun.COM
Tue Mar 3 17:29:28 PST 2009


Neal Gafter wrote:
> Joe Darcy sort of ruled out adding property support in project coin in
> http://blogs.sun.com/darcy/entry/guidance_measure_language_change_size
>   

Correct; properties (and closures and reified generics) are examples of 
changes out of scope for Project Coin.

-Joe

> 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