wiki:PRO_LIB_CORE_TUTORIAL_NEW

Version 14 (modified by gogov, 16 years ago) (diff)

--

Error: Macro BackLinksMenu(None) failed
compressed data is corrupt

Properties Library Tutorial

The ProLib is a library written in Java which is intended to serve as somewhat an intermediate layer between client Sophie2 code and Java code. It has concepts and functionality which help solve a list of problems in an elegant (and hopefully efficient) manner. Its purpose is to ease the developer, not burden him, and in many cases leads to improved readability, more compact code which is also less error-prone if used correctly. Also, the problems which the ProLib solves are done (mostly) invisible to the developer so in the general case he shouldn't worry about how the ProLib works internally but just follow a set of rules and conventions for proper usage.

The ProLib is now in a relatively stable state, though there are still new ideas to implement and integrate inside it as well as some performance optimizations.

Basically, the structure looks like this:

source:branches/private/gogov/PRO_LIB_CORE_TUTORIAL_R1/sophie2-platform/modules/org.sophie2.core/src/test/resources/Layers.png

So, most Sophie2 client code is intended to use the ProLib in the cases when the ProLib solves problems and is helpful and use normal Java code in other cases.

ProLib basics

Here we'll provide a simple comparison between ProLib code and normal Java code as well as an example.

ProObject & Properties vs Java Object & Java fields

In Java, there are Objects and each Object has methods and fields.

In the ProLib, there are ProObjects and each ProObject has normal Java methods and Properties instead of fields. The different concept here are that Properties are something like smart Java fields.

  • You can create a ProObject by implementing the ProObject interface or by using the default implementation BaseProObject
  • You can declare Properties inside the ProObject with Java methods which return a Prop<T> (later you'll get what is this), where T is a concrete class like Integer. The method executes some logic and returns the actual Property.
    • There are different kind of Properties, so inside these methods you can provide logic which returns the desired kind of Property.

Example

Let's look at the following example:

// Basic rectangle class which is ProObject.
class Rectangle extends BaseProObject {
        // Integer property for the width of the rectangle. Analogical to an Integer Java field.
        RwProp<Integer> width() {
                return getBean().makeValueProp("width", Integer.class);
        }

        // Integer property for the height of the rectangle. Analogical to an Integer Java field.
        RwProp<Integer> height() {
                return getBean().makeValueProp("height", Integer.class);
        }

        // Smart property which returns the area of the rectangle.
        Prop<Integer> area() {
                class area extends AutoProperty<Integer> {
                        @Override
                        protected Integer compute() {
                                Integer res = width().get() * height().get();
                                return res;
                        }
                }

                return getBean().makeProp(area.class);
        }
}

// this is something like pseudo ProLib code which demonstrates basic ProLib usage
public void demoCode() {
    // create a new Rectangle
    Rectangle rectangle = new Rectangle();		

    // set the width to 6
    rectangle.width().set(6);

    // set the height to 8
    rectangle.height().set(8);

    // it is true then that the area is equal to 6*8 = 48
    assertEquals(48, (int)rectangle.area().get());
}

So, you can see the analogy with Java code, though the difference is with the area() Property. This is an AutoProperty: a property which is automatically computed. In the compute() method you specify the logic of the computation, and then this Property gets automatically computed. Even more, when the width and height change, this Property immediately gets recomputed and stay up-to-date which is very useful.

Return statements might seem a bit awkward, though later in the tutorial you'll learn how to use and understand them.

Problems Solved

So, ProLib is intended to be used as somewhat intermediate layer between client code and normal Java code, thus some concepts are implemented in the ProLib and thus all client code which uses ProLib can benefit from these features.

The ProLib gives solution to the following problems:

  • Dependencies, initialization order and updating problems:
    Let's say we've got lots of object which we're using in our application. These object depends on each other in a sense that:
    • object A holds a reference to object B;
    • object C must be initialized before object D because D needs data from object C to initialize itself;
    • object E changes and object F depends on E, so F needs to get informed when E changes, in order to update itself.

The ProLib helps solve this problem by introducing somewhat a declarative way to state such dependencies and then handles initialization order and update notifications in an invisible and convenient manner for the client code developer.

  • Undo/Redo:
    Serving as an intermediate layer helps the ProLib to keep histories of changes which occur to the ProObjects and properties involved in our application and thus the ProLib provides (at least soon will provide) a mechanism to automatically undo and redo these changes.
  • Save/Load:
    The ProLib has the means for automatic persistence of objects which use it, as well as custom persistence options which let the developer choose how he want his object to be persisted which helps implementing saving and loading as well as transmission over a network by serializing, for instance, much simpler for the developer.
  • Optimization:
    Being an intermediate layer allows the ProLib to serve as a central point for performance optimizations of the application and profiling and searching for bottlenecks.
  • Safety:
    The ProLib helps (in the future in will help even more) to detect errors in the client code such as cyclic dependencies between objects (in the cases when such dependencies are unacceptable), missing logic, etc.

It's fundamental to know that there are not many defensive mechanisms implemented in the ProLib against improper usage yet, so the developer can easily write stupid and not working ProLib code, though if he does it in the way ProLib is intended to be used, it will save him lots of time and headache. So, at least for now, a list of rules and recommendations on proper ProLib usage should be followed, which will be described later.

ProObjects and Properties

Now, we'll describe the ProLib fundamental classes and interfaces, what is their purpose and how are they intended to be used.

ProObjects

source:branches/private/gogov/PRO_LIB_CORE_TUTORIAL_R1/sophie2-platform/modules/org.sophie2.core/src/test/resources/ProObjects.png

  • ProObject is the base interface for all objects which want to use Properties.
  • BaseProObject is the default implementation of ProObject. You should use it when possible to ease your life.
  • Each ProObject is associated one-to-one with its own ProBean. A ProBean is basically the manager of the ProObject - it takes care for creating and remembering the Properties of the ProObject, initalizing them and other administrative work.
  • Each ProObject can declare one or many Properties, like each Java object can declare one or many fields and methods.

Properties

source:branches/private/gogov/PRO_LIB_CORE_TUTORIAL_R1/sophie2-platform/modules/org.sophie2.core/src/test/resources/Property Hierarchy.png

  • Interfaces and classes:
    Above the gray line are basic interfaces which Properties implement. Below the gray line are classes which implement these interfaces.
  • Fundamental (GREEN):
    These are the fundamental interfaces and classes for all Properties.
    • Prop<T>:
      This is the most basic interface which all Properties implement.
      • Each Property holds an object, and with this interface you can use get() to get this object (which is actually the value of the Property).
      • You can also get the owner of the Property (which is a ProObject) by using the owner() method.
    • RwProp<T>:
      Extends the Prop<T> interface.
      • The only thing this interface adds is the ability to set the value of the Properrty with the set() method. Thus Prop<T> provides read-only access and RwProp<T> provides read-write access to the value of the Property.
    • ListProp<T>:
      While Prop<T>s hold reference to a single value, ListProp<T>s holds a reference to a ProList<T> which is the base interface for lists in the ProLib.
      • You get the number of values this Property holds by the size() method;
      • You can also get the value at a valid position in the ProList<T> by the get(int index) method.
    • RwListProp<T>:
      This interface extends ListProp<T> and in analogy with RwProp<T>, provides read-write access to the ProList<T> which is the RwListProp<T> holds.
      • You can add elements at the end of the underlying ProList<T>, remove a given element from it, or change a given element at a valid position by using the add(T element), set(int index, T element) and remove(int index) methods respectively.
    • Property<T>:
      This is the base abstract implementation of the Prop<T> interface.
      • It allows you to set an initial value to the Property by the init() method.
        You should extend this class if you want to provide new Property kinds in the ProLib, like MapProperty for instance (good luck and God bless you if you do it (: ).
    • ObjectProperty<T>:
      This class extends Property<T> and provides the base usable implementation of Prop<T> used for dealing with single object values.
      • It implements the get() logic. You should use this class when you need single Properties which you only need to initialize and get.
    • ListProperty<T>:
      This is the base abstract implementation of the ListProp<T> interface.
      • It provides basic get(), get(int index) and size() implementation. You should extend this class if you want to provide new ListProperty kinds in the ProLib, like SkipListProperty (which internally uses skiplists) for instance (again, good luck and God bless (: ).

  • Single Properties (BLUE): These are classes which extend ObjectProperty<T> and thus inherit its capabilities, though they implement specific behavior for specific purposes.
    • ValueProperty<T>: This Property is the equvalent to a Java field of type T which allows getting and setting a value. The different thing is that any Property A which depends on a ValueProperty B gets notified when B changes and thus A can update accordingly. Sample usage was already shown in the rectangle example above:
      ...
          RwProp<Integer> width() {
              return getBean().makeValueProp("width", Integer.class);
          }
      ...
      
      So, this is actually a Java method which returns a RwProp of Integer. The previously awkward return statement is now more clear: it instructs the ProBean of the ProObject to create a new ValueProperty with the name width which has a value of type Integer. Other Properties are created in a similar fashion as is described later.
      Thus, when you want to set or get the value of this Property you just use the set() and get() methods like in the demoCode() method above.
    • FinalProperty<T>: This Property is somewhat analogical to the final fields in Java. You can set the value of a FinalProperty only once, though not it's not mandatory to do it in the constructor of the ProObject.
      Such Properties are intended to be used in cases where some user action leads to set the value of a given Property exactly once. This is different than const AutoProperties as described below.
    • AutoProperty<T>: This Property is one of the most commonly used and convenient Properties the ProLib offers.

Basically this is a Property which has a value which is computed directly from the values of some other Properties. In a way, the developer sets the logic of the computation of the AutoProperty's value in a declarative manner and the actual computation of the value is done automatically. Even more, the when some of the Properties on which the AutoProperty depends change their value, the AutoProperty automatically recomputes its value which is very convenient. This saves the developer the hassle of manually implementing some Observer like functionality in all objects a certain Java field X would logically depend and then manually notifying X when they've changed so X can then be manually forced to recompute.

So, like inside the Rectangle example, the area() AutoProperty is implemented like this:

    Prop<Integer> area() {
        class area extends AutoProperty<Integer> {
            @Override
            protected Integer compute() {
                Integer res = width().get() * height().get();
                    return res;
                }
            }
        }

        return getBean().makeProp(area.class);
    }

In order to create an AutoProperty, by convention you declare a method structure like the structure of the area() method which declares a local class which extends the AutoProperty class. The type argument of the local class should be identical to the type argument of the Prop which the area() method returns - after all an AutoProperty is a Property which computes a given value following some logic and that value has a type which in our case is Integer.
So you override the compute() method to provide the AutoProperty's value computation logic inside. You can provide an arbitrary complex logic inside. In this example whenever the width() and height() Properties change their value, the AutoProperty is smart enough to call the compute() method again and update its value.
Something very important: compute() will be recomputed only in the cases when some of the Properties used inside it change their value!! If compute() depends on some normal Java field, there's no way for the ProLib to know when this field has changed its value and then notify the AutoProperty to change its value!! There's no magic here!''' Expecting the ProLib to detect that means hooking to JVM's system events and maybe bytecode instrumentation which obviously we don't want to use (:

  • Actually, AutoProperties have a doSet() method, which can be overriden to provide setting manually a value of a given AutoProperty. This can be used in very rare cases when you have an AutoProperty X which depends on other Properties but also there's an AutoProperty Y which depends on X. So let's say that we need Y to have a valid value but at this stage of execution of our program we don't have all the Properties which X depends on initialized. That's when we can manually set the value of X, so Y gets property computed, and later, when all the Properties X depends on initialize, then X gets recomputed and Y respectively.
    This should be used very rarely, so if you think you need this, consult first with someone else (:
  • There's a special case of AutoProperties which are const AutoProperties. Such AutoProperties depend on some other Properties, but their value should be computed only once. For instance you can have this case:
        public Prop<Integer> X() { ... }
        public Prop<Char> Y() { ... }
        public Prop<String> Z() { ... }
    
        @Const
        @Override
        public Prop<JLabel> swingComponent() {
    	class swingComponent extends AutoProperty<JLabel> {
    	    @Override
    	    protected JLabel compute() {
    		assert getLastValue() == null;
    
    		JLabel res = new JLabel();
    		res.setBackground(Color.green);
                    res.setText("text is " + X().get() + ", " + Y().get() + ", " + Z().get());
    	
       	        return res;
    	    }
    	}
    
        return getBean().makeProp(swingComponent.class);
    }
    
    In this case the AutoProperty swingComponent() depends on X(), Y(), Z(), provide some relatively complex initializing logic in the compute() method and you want to make sure this component is created only once. Thus:
    • You'll be happy if you don't want to worry about when X(), Y() and Z() get initialized and this way the ProLib can do it for you,
    • You can capsulate the initialization logic in the compute() method so your ProObject constructor doesn't get bloated with it instead,
    • By convention you insert assert getLastValue() == null; assertion which makes sure that the Property is still not initialized by the time the compute() method is called (which means that it's value is still null).
    • Also by convention, you annotate such AutoProperties with @Const.
  • Multiple Properties (RED):

A Little Bit Of Internals

  • ProObject - Object with properties. All objects needing property support should implement this interface.
  • ProBean - Each bean is associated one to one with a ProObject.
  • Prop<T> - Basic interface to access all properties. While the Property class is the base class for the implementation, once implemented, the clients interested in the value of the properties should use this interface to access it.
    • Property - Basic class for all properties.
  • BeanData - BeanData holds the information (properties, etc of a bean, and also a behavior for the make methods).
  • PropMetaInfo - Meta information for a property. Immutable.
  • PropretyStatus - A state of a trackable object.
    • CREATED - initial state, you can only add/remove listeners
    • META_INITIALIZED - get will throw NotInitialized exception, other methods will work
    • READY - get will return value
    • DESTROYED - again get will throw..
  • Utils
    • ReflectionUtil - Misc helper static methods.
    • SwingUtil - Class which wraps a (legacy) JavaBean for use in ProBeans.

Properties Kinds

  • Property
    • ObjectProperty - A property which holds a reference to a single object.
      • FinalProperty - One time write property. Once set, its value never changes.
      • AutoProperty - Smart property. A property which automatically monitors the information it depends on. In order to work, it should rely only on other properties and immutable values.
      • ResourceProperty - A property which holds a mutable resource. Useful for mutable non pro things like swing gui elements.
      • ValueProperty - A property that has a reference to an object and allows getting and setting it.
        • ParentProperty - Property which will hold a link to parent for parent-child relations.
        • UndoProperty - A property which holds the undo manager for given bean. If the bean does not have such property, the properties try to locate UndoManager up (to parent).
    • ListProperty - Basic list property. Holds references to many objects.
      • ChildrenProperty - Property which manages list of children for a child/parent relation.

ToDo

  • const autoproperties
  • @Own & parent properties
  • @Shared
  • ProObject cloning
  • Immutables;
  • only Immutables and ProObjects

Rules

  • use properties, not fields; fields can be used, though in specific purposes if you know what you're doing.

BadCode

  • init final property with constant which isn;t an argument of the constructor
  • finalproperty.init(new stuff())

Code Examples with different Properties Kinds

How to make

  • FinalProperty
            public Prop<FrameView> frameView() {
    		return getBean().makeFinalProp("frameView", FrameView.class);
    	}
    
  • AutoProperty
    	public Prop<JScrollPane> scrollPane() {
    		class scrollPane extends AutoProperty<JScrollPane> {
    			@Override
    			protected JScrollPane compute() {
    				JScrollPane res = new JScrollPane(list().get());
    
    				res.setVerticalScrollBarPolicy(
    						ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS);
    
    				return res;
    			}
    		}
    		return getBean().makeReadProp(scrollPane.class);
    	}
    
  • ResourceProperty
    	public Prop<JMenu> swingMenu() {
    		class swingMenu extends ResourceProperty<JMenu> {
    
    			@Override
    			protected JMenu create() {
    				return new JMenu();
    			}
    
    			@Override
    			protected void destroy(JMenu resource) {
    				// TODO Auto-generated method stub
    				
    			}
    
    			@Override
    			protected void setup(JMenu res) {
    				List<MenuMember> data = items().get();
    				assert data.size() == 0 || Collections.frequency(data, data.get(0)) == 1 : data;
    				
    				res.removeAll();
    				for(MenuMember m : data) {
    					res.add(m.swingMenu().get());
    				}
    			}
    		}
    		return getBean().makeReadProp(swingMenu.class);
    	}
    
  • ParentProperty
            public RwProp<Page> parent() {
    		return getBean().makeParentProp(Page.class, "frames");
    	}
    
  • UndoProperty
            public UndoProperty undoManager() {
    		return getBean().makeUndoProperty();
    	}
    

Methods To Use

These are methods of the ProBean class.

  • Creates a read-write value prop by given property class.
            public <T> RwProp<T> makeRwProp(Class<? extends ObjectProperty<T>> cl) {
    		return makeProp(cl);
    	}
    
  • Creates a read-only prop by given property class.
    	public <T> Prop<T> makeReadProp(Class<? extends Property<T>> cl) {
    		// XXX access exposure
    		return makeProp(cl);
    	}
    
  • Makes (or returns if already made) a simple value property by the given information.
            public <T> RwProp<T> makeValueProp(String id, Class<T> valueClass) {
    		return getData().makeValueProp(id, valueClass);
    	}
    

or

	public <T> RwProp<T> makeValueProp(String id, Class<T> valueClass,
			T initValue) {
		return getData().makeValueProp(id, valueClass, initValue);
	}
  • Creates a children property.
            public <T extends ProObject> ChildrenProperty<T> makeChildProp(String id,
    			Class<T> childClass) {
    
    		return getData().makeChildProp(id, childClass);
    	}
    
  • Creates a parent property.
    	public <T extends ProObject> ParentProperty<T> makeParentProp(
    			Class<T> parentClass, String childPropId) {
    		return getData().makeParentProp(parentClass, childPropId);
    	}
    
  • Creates a list property.
    	public <T> RwListProp<T> makeListProp(String id, Class<T> elementClass) {
    		return getData().makeListProp(id, elementClass);
    	}
    

or

	public <T> RwListProp<T> makeListProp(
			Class<? extends ListProperty<T>> propClass) {
		return makeProp(propClass);
	}
  • Makes (or returns if already created) a final property (whose value may be written only once).
    	public <T> Prop<T> makeFinalProp(String id, Class<T> valueClass) {
    		return getData().makeFinalProp(id, valueClass);
    	}
    
  • Creates (or returns if already have) an undo manager of this bean.
    	public UndoProperty makeUndoProperty() {
    		return getData().makeUndoProperty();
    	}
    

Bad Code Examples

  • Resource Property is a better choice.
        public Prop<JComponent> swingComponent() {
		class swingComponent extends AutoProperty<JComponent> {
			@Override
			protected JComponent compute() {
				JComponent res = new JPanel();
				res.setLayout(new BoxLayout(res, BoxLayout.Y_AXIS));
				res.setPreferredSize(new Dimension(100, 100));
				res.add(headComponent().get());
				res.add(scrollPane().get());
				res.setVisible(true);
				res.validate();
        
				return res;
			}
		}
		return getBean().makeReadProp(swingComponent.class);
	}

Debugging ProLib

  • Inspector - A GUI tool that allows browsing the application state while it still works.

Demo

  • Tutorial unit test demonstrating the usage of AutoProperty

How to apply properties to Sophie 2.0

  • To get rid of the fields, except for immutable classes, and public static final things.
    • Note that final instance fields are not so safe, because they are initialized after the super constructors.
  • To convert mutable classes to ProObjects
  • To gather more requirements to the properties library and apply them
  • To keep the properties library with high quality, because everything else will depend on it.
  • To be careful of cyclic dependencies (like a = b + 1, b = a + 1). No library can solve them.

Comments

  • The tutorial should be updated with the new things about properties - @Own annotation, deprecation of ChildrenProperty, etc. --boyan@2009-01-12