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