Table of Contents
- ProLib basics
- ProObject & Properties vs Java Object & Java fields
- Problems Solved
- ProObjects and Properties
- Pros and ProLists
- @Own & ParentProperties
- @Shared & ProUtil.clone()
- @Immutable
- ProObjects, @Immutables and junk
- ProLists, findOne(), findAll(), ProUtil.getKey() and ListEntry
- Debugging ProLib
- Good Practices
- TODO
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:
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 kinds 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
- 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
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 (: ).
- It allows you to set an initial value to the Property by the init() method.
- 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.
- ResourceProperty<T>:
Important note: ResourceProperties just share partially their name with Resources. They are totally different concepts so don't mix them. This Property is something like advanced AutoProperty. It wraps some nonProLib entity like a Swing Component, for instance, and manages creation, destruction and updating of this entity, so it's something like an AutoProperty-like bridge between nonProLib entities and the ProLib and makes life easier. You create a ResourceProperty<T> in a similar fashion to creating AutoProperties<T> though there are more methods to override:- There's a create() method which is responsible for the initial creation of the managed entity. You should provide custom (maybe quite complex) creation logic here.
- There's a destroy() method which is responsible for the destruction of the managed entity. Currently it doesn't work very well, though it is intended to provide destruction logic like closing streams, freeing memory etc.
- There is a setup() method which is very similar to the compute() method of AutoProperties - each time something the ResourceProperty X depends on changes, X's setup() method gets invoked so X can update accordingly.
- Here's an example:
... @Override public Prop<JButton> swingComponent() { class swingComponent extends ResourceProperty<JButton> { @Override protected JButton create() { JButton res = new JButton(); res.addActionListener(new ActionListener() { @Override public void actionPerformed(final ActionEvent e) { userClick(); } }); return res; } @Override protected void setup(JButton res) { Icon icon = icon().get().toIcon(); res.setIcon(icon); Dimension sz = size().get().toDimension(); res.setPreferredSize(sz); res.setSize(sz); res.setToolTipText(toolTip().get()); } @Override protected void destroy(JButton res) { // nothing } } return getBean().makeProp(swingComponent.class); } ...
- Further more, the setup() method can be "split" in pieces by the @Setup annotation. In the ResourceProperty descendant class you provide, swingComponent in our case, you can provide a multitude of methods which are annotated with @Setup and together they form the logic for updating the ResourceProperty. This is useful form performance reasons, because let's say you've got a very heavy setup() method which consists of logically independent parts P1 and P2, and P1 depends on some Property X while P2 doesn't depend on X so when X changes, P2 doesn't need to be executed again. Thus, providing two new setup methods which correspond to P1 and P2 leads to performance optimization (and capsulation of independent code in different methods) because the ProLib is smart enough to invoke only the needed setup methods.
For example the above setup() method can be split like this:
... @Setup protected void setupIcon(JButton res) { Icon icon = icon().get().toIcon(); res.setIcon(icon); } @Setup protected void setupSize(JButton res) { Dimension sz = size().get().toDimension(); res.setPreferredSize(sz); res.setSize(sz); } @Setup protected void setupToolTipText(JButton res) { res.setToolTipText(toolTip().get()); } ...
- Further more, the setup() method can be "split" in pieces by the @Setup annotation. In the ResourceProperty descendant class you provide, swingComponent in our case, you can provide a multitude of methods which are annotated with @Setup and together they form the logic for updating the ResourceProperty. This is useful form performance reasons, because let's say you've got a very heavy setup() method which consists of logically independent parts P1 and P2, and P1 depends on some Property X while P2 doesn't depend on X so when X changes, P2 doesn't need to be executed again. Thus, providing two new setup methods which correspond to P1 and P2 leads to performance optimization (and capsulation of independent code in different methods) because the ProLib is smart enough to invoke only the needed setup methods.
For example the above setup() method can be split like this:
Multi Properties (RED)
The following Properties are all the current multi Properties, so they both implement RwProp<T> and extend ListProperty<T>. Important remark: Actually ListProperty<T> implements RwProp<ProList<T>>. ProList<T> is the base List interface intended to be used in the ProLib so what this means is that ListProperties actually hold one single value which is a List, and not multiple values. This List is actually a ProList and as the ListProperty is initialized a ProList instance is created and it doesn't change over time compared to the value instance of normal Properties which can change over time. From a client point of view you don't need to know internal specifics of Pros and ProLists, though if you want to use ListProperties effectively, you should read the Pros and ProLists section below.
- ValueListProperty<T>
This is the most basic ListProperty. It's analogical to ValueProperty in the sense you can set and get the values.
- This Property implements add, remove and set behavior by using the add(T element), remove(int index) and set(int index, T value) methods.
- It also has a getInitContents() method which you can override to specify a List<T> of initial content for the ListProperty when it's initialized.
Here's an example:
... public RwListProp<String> items() { class Items extends ValueListProperty<String> { @Override protected List<String> getInitContents() { return Arrays.asList("dog", "cat", null, "parrot"); } } return getBean().makeListProp(Items.class); } ...
- AutoListProperty<T>
This Property is analogical to AutoProperty, though it works with an underlying list of values. Basically, in a similar fashion to AutoProperty, you override one method where you specify the logic of computing the List of values and that List gets updated when any of the Properties it depends on change.
- You should override the constructList() method to return a ProList<T> and inside you specify the computing logic. Actually, there are a multitude of ProList<T> implementations which you can use and one of them is the ComposingProList<T> which is used in the example below. In the Pros and ProLists section below, you'll get the details. In the example below there is an AutoListProperty names() which depends on the Properties janitor(), president() and alcoholic() and basically returns a List of all these Properties' values. Whenever any of these changes, names() gets recomputed.
Here's the example:
... RwProp<String> janitor() { ... } RwProp<String> president() { ... } RwProp<String> alcoholic() { ... } public ListProp<String> names() { class names extends AutoListProperty<String> { @Override protected ProList<String> constructList() { return new ComposingProList<String>() { @Override protected List<String> computeData() { return Arrays.asList(janitor().get(), president().get(), alcoholic().get()); } }; } } return getBean().makeProp(names.class); } ...
- You should override the constructList() method to return a ProList<T> and inside you specify the computing logic. Actually, there are a multitude of ProList<T> implementations which you can use and one of them is the ComposingProList<T> which is used in the example below. In the Pros and ProLists section below, you'll get the details. In the example below there is an AutoListProperty names() which depends on the Properties janitor(), president() and alcoholic() and basically returns a List of all these Properties' values. Whenever any of these changes, names() gets recomputed.
Here's the example:
Pros and ProLists
Note: For a more basic usage of the ProLib, you don't need to read this. If you do want to understand and use ListProperties effectively, then read (:
Interfaces(PURPLE)
So, these are the most fundamental interfaces in the ProLib.
- Pro:
This is THE most fundamental interface in the ProLib though if you don't refactor the ProLib, you'll most probably never need to know it exists.
Basically a Pro is something which implements an Observer-like behavior so ProListeners can be attached and detached to/from it. So again, there's no magic in the ProLib, just some smart concepts, and actually detecting dependencies, automatically solving initialization order problems and getting automatic updates is implemented with the help of this Observer-like behavior laid down at the very foundations of the ProLib (:
- Prop<T>:
Prop<T> was already mentioned, though here we'll add that Prop<T> actually extends Pro so it's also trackable for changes which means that all Properties can be tracked when they and respectively their value changes so that's how the magic happens.
- List<T>:
This is simply the standard Java interface List<T> (:
- ProList<T>:
ProList<T> extends Pro and List<T> so it's basically a List which can be tracked for changes. Now you should understand why ListProp<T> extends Prop<ProList<T>> (:
Implementations(YELLOW)
- BaseProList<T>:
This is the abstract base implementation of the ProList<T> interface. From a client point of view you use it as a normal Java List<T>, though internally notifications for changes fly to interested parties and the ProLib is able to execute correctly.
All of the following extend BaseProList<T> to provide specific behaviour.
- EmptyProList<T>:
This is just an empty ProList<T> and you cannot modify it in any way. Trying to do so will result in an UnsupprotedOperation Exception. This class is a useful shorthand for cases when you need and empty ProList, analogical to Collections.emptyList(), for instance.
- ValueProList<T>:
This is the simplest modifiable ProList<T>. The major difference with BaseProList<T> is that ValueProList<T> allows read-write access to its values.
- ComposingProList<T>:
This is the most powerful, yet one of the simpler ProList<T>. It is abstract, so you should provide your own descendant class and override a method if you want to use it.- It has a computeData method, which similarly like the compute() method of the AutoProperty, defines the creation logic for its content. Now we can actually say the truth that AutoProperties, ComposingProList<T> and others actually monitor which Pros they depend on and update when any of them changes and notifies its listeners that it's changed.
- So if you take a look at the AutoListProperty example, you'll see that in the constructList() method of names() actually a new ComposingProList<T> is created and it's computeData() method is overridden to provide the custom creation logic. Very convenient. Important remark: When a Pro that a given ComposingProList<T> X depends on changes, the whole list gets constructed from scratch which is very inefficient, so this ProList should be used with caution, otherwise performance can be easily degraded. In lots of other cases another ProList<T> implementation should be used to improve performance.
- TrackingProList<T>:
This is a ProList<T> which is something like an alias for another ProList<T>.- It's got a computeSource() method which you should override when extending TrackingProList<T>. Inside this method you can specify a custom logic what ProList you should return. Analogical to the compute() method of AutoProperty, when the ProList X this TrackingProList Y depends on changes, then Y gets updated.
- One application of this is that you can simply return another ProList<T> inside this method which kindof makes the TrackingProList<T> an alias of the returned ProList<T>.
Here's an example from the FakeModuleRegistry:
public ListProp<SophieModule> activeModules() { class activeModules extends AutoListProperty<SophieModule> { @Override protected ProList<SophieModule> constructList() { return new TrackingProList<SophieModule>() { @Override protected ProList<SophieModule> computeSource() { return registeredModules().get(); } }; } } return getBean().makeProp(activeModules.class); }
- Another application is to provide more custom logic inside the computeSource() method, like this:
new TrackingProList<String>() { @Override protected ProList<String> computeSource() { try { ... some logic return a ProList; } catch (Exception e) { return EmptyProList.get(String.class); } } }
Here, if the retrieval logic succeeds, some ProList is returned, though if it fails, an EmptyProList is returned which can be useful in certain cases.
- One application of this is that you can simply return another ProList<T> inside this method which kindof makes the TrackingProList<T> an alias of the returned ProList<T>.
Here's an example from the FakeModuleRegistry:
- It's got a computeSource() method which you should override when extending TrackingProList<T>. Inside this method you can specify a custom logic what ProList you should return. Analogical to the compute() method of AutoProperty, when the ProList X this TrackingProList Y depends on changes, then Y gets updated.
Filtering and transformations
Actually, the ProLib borrows two declarative concepts from functional programming, namely map and filter.
- FilteringProList<T>:
So I lied to you earlier - the ProList<T> interface has some more useful methods. One of them is ProList<T> filter(ProListFilter<T> filter).
When this method is applied to any BaseProList<T>, it creates a FilteringProList<T>. This ProList<T> is basically a filtered view of another ProList<T>. The ProListFilter<T> is a visitor object which, given a T element, decides whether it should be filtered or not. You can create such filters by extending ProListFilter<T> and overriding its accepts() method to provide your custom logic.
- TransformingProList<S, T>: (S for source, T for target)
Another useful method of the ProList<T> interface is <T> ProList<T> transform(ProListTransformer<S, T> transformer) (it's analogical to map in functional languages).
When this method is applied to any BaseProList<T>, it creates a TransformingProList<T>. This ProList<T> is basically a transformed view of another ProList<S>. The ProListTransformer<S, T> is a visitor object which, given a S element, decides how to transform it to a T object. You can create such transformers by extending ProListTransformer<S, T> and overriding its translate() method to provide your custom logic.
Now, here's an example which uses these ProLists as well as other of the ProLib stuff. It's from the EmbeddedBooksPalette. If you understand it, then you're cool (:
@Override public ListProp<BookItem> items() { class items extends AutoListProperty<BookItem> { @Override protected ProList<BookItem> constructList() { return new TrackingProList<ResourceRef>() { @SuppressWarnings("synthetic-access") @Override protected ProList<ResourceRef> computeSource() { try { Book parentBook = ((OpenBooksPalette.BookItem) openBooksPalette().get().firstSelectedItem() .get()).book().get(); return parentBook.children().get(); } catch (NullPointerException e) { return EmptyProList.get(ResourceRef.class); } } }.filter(new ProListFilter<ResourceRef>() { @Override public boolean accepts(ResourceRef item) { return item.get(Resource.class) instanceof Book; } }).transform(new ProListTransformer<ResourceRef, BookItem>() { @Override public BookItem translate(ResourceRef source) { BookItem res = new BookItem(); res.book().set(source.get(Book.class)); return res; } }); } } return getBean().makeProp(items.class); }
@Own & ParentProperties
So, you've noticed I missed describing the pink ParentProperty in the Properties section (: Good for you, it shows that you read carefully.
- ParentProperty:
In many cases you've got an object which you want to have another object as a parent. So there are ProObjects and ParentProperty<T extends ProObject>. Note that T extends ProObject so each parent of a ProObject must be a ProObject.- With the ProLib, parents are set automatically via the @Own annotation.
class Person implements ProObject { ... @Override public Prop<Meddle> parent() { return getBean().makeParentProp(Meddle.class); } ... } class Meddle implements ProObject { ... @Own Prop<Person> jesus() { ... } ... }
So jesus() is a Property which returns a Person object. But let's say that we want to "bind" this jesus Property only to one Meddle, so other Meddles couldn't get the same Person object, which jesus() holds, to be "bound" to them. So, each Person much declare a ParentProperty<Meddle> like in the example. In the Meddle object, the jesus() property is annotated with @Own. So the ProLib takes care for two things:- Sets the parent() Property of the Person hold by the jesus() Property, to the concrete Meddle instance.
- And also makes sure that other Meddles cannot "own" the same jesus().
- With the ProLib, parents are set automatically via the @Own annotation.
@Shared & ProUtil.clone()
There's a useful util class called ProUtil. It contains various methods which ease ProLib usage.
- The ProUtil.clone() method is responsible for cloning a ProObject into a new ProObject which is needed in certain cases. So, standard questions arise like whether to do deep copying or not. For instance:
class Sheep implements ProObject { ... @Shared Prop<BankAccount> account() { ... } ... }
When you clone this Sheep, the question what to do with all the Sheep's Properties arise. In the case of @Shared, when the Sheep dolly1 is cloned, the new Sheep dolly2's account() will hold the same BankAccount which dolly1 has.
In this context we should mention that the ProLib also makes sure that if account was @Own instead of @Shared, then dolly2 won't have the same BankAccount (:
@Immutable
This is an annotation which is used to mark a given class as immutable - once it's created, it doesn't change.
In the next section you'll see why this is needed by the ProLib.
Note: The ProLib doesn't ensure that object annotated as @Immutable change because this is equal (again) to doing bytecode instrumentation. It is just a convention which has to be followed.
ProObjects, @Immutables and junk
In OOP there are two kinds of objects - mutable and immutable. In the ProLib mutable objects are intended to be only ProObjects and immutable objects should either be annotated with @Immutable or expand the list of known immutables in the ProLib which currently is limited to standard JDK immutables like Integer, Double etc. Why is this important:
This is an example how the dependency graph between ProObjects should look.
- It should contain cycles, otherwise we can never initialize this graph.
- An Immutable shouldn't depend on anything.
- ProObjects could depend on other ProObjects and Immutables.
- The leafs of this acyclic graph should be Immutables.
If a ProObject A depends by a Prop<X> P on some object X which doesn't depend on anything, then there are two cases:
- X is immutable. Then when someone changes P, it would be through P's interface so the ProLib can detect this change and A can get notified that X has changed.
- X is mutable and ProObject. X can change in two ways:
- A Property Q of X has changed. Then P would detect when X's Q changes and A would get notified about it.
- The same as the above but Q is a normal Java field. Well, nothing conventional can be done to detect when Q changes, so we're screwed, so don't use normal Java fields in ProObjects, unless you're sure what you're doing.
- X is mutable but is not a ProObject. In this case obviously nothing can be automatically done by the ProLib to detect when X changes.
This means that you should follow the rules above in order to produce non-flawed ProLib code. In general you can use ProObject, Immutables and the rest is junk (:
ProLists, findOne(), findAll(), ProUtil.getKey() and ListEntry
Let's have this case:
... class Party implements ProObject { public ListProp<Person> people() { ... } public ListProp<Person> invitedPeople() { class invitedPeople extends AutoListProperty<Person> { @Override protected ProList<Person> constructList() { return new ComposingProList<Person>() { @Override protected ProList<Person> computeData() { ArrayList<Person> res = new ArrayList<Person>(); for (Person person : people().get()) { if (person.getName() == "Meddle") { res.add(person); } }; } } } } return getBean().makeProp(invitedPeople.class); } ...
So, in this example you want to get the List of all invited people to your party, invite only people whose name is "Meddle" and update this list when a potential candidate to come to the party is added. This is kinda slow though, because each time a new Person is added to People, I don't want to reconstruct the whole invitedPeople list.
ProLists are smart though so they act not only as lists but as maps. In the ProLib there is the concept of keys when using ProList, depending on what kind of items are inside:
- Immutables -- the key of an Immutable is the Immutable itself,
- ListEntries -- there is a ListEntry class which is basically a pair of a key and a value. The key of a ListEntry is its key (:
- others -- key is always null.
You can get the key of a given object by using the ProUitl.getKey() method.
ProLists check if they're working with ListEntry descendants, so you can subclass ListEntry to improve the above example like this:
... class Party implements ProObject { static class PersonEntry extends ListEntry { PersonEntry(String name, ... ) { ... } Object getKey() { return name; } Object getValue() { ... } } public ListProp<PersonEntry> people() { ... } public ListProp<PersonEntry> invitedPeople() { class invitedPeople extends AutoListProperty<PersonEntry> { @Override protected ProList<PersonEntry> constructList() { return new ComposingProList<PersonEntry>() { @Override protected ProList<PersonEntry> computeData() { return people().findAll("Meddle"); } } } } return getBean().makeProp(invitedPeople.class); } ...
Here the E findAll(Object key) method of ProList<E> is used which basically returns all elements in the list which have the specified key.
The difference here is that ProLib is smart enough to recompute invitedPeople() only when another PersonEntry with key "Meddle" is entered, such is removed or modified. Otherwise it doesn't make sense to recompute because the list hasn't changed if for instance you've added a PersonEntry with key "Christ".
E findOne(Object key) is the same but it expects to find at most one element with the specified key and returns it. If there are more than one, an Exception is thrown.
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
Some advice:
- Print. Debugging with a debugger is uglier that usual.
- Run the tests before commit. Don't break tests.
- Use the Inspector - this is a GUI tool that allows browsing the Properties in the application while it's running.
Good Practices
- Use ProObject and Immutables when using the ProLib. The rest is junk. Although if you know what you're doing, you can find useful stuff in the junk (:
- Convert mutable classes to ProObjects.
- Keep the ProLib and with high quality, because everything else will depend on it.
- Be careful not to create cyclic dependencies (like a = b + 1, b = a + 1). No library can solve them.
TODO
- init final property with constant which isn't an argument of the constructor
- finalproperty.init(new stuff())