GROUP_PRO_LIB_PERFORMANCE_R0: 2009-05-08-21-22 patch

File 2009-05-08-21-22 patch, 45.3 KB (added by gogov, 16 years ago)

old patch

Line 
1### Eclipse Workspace Patch 1.0
2#P sophie2-platform
3Index: modules/org.sophie2.core/src/test/java/org/sophie2/core/prolib/list/ValueProListTest.java
4===================================================================
5--- modules/org.sophie2.core/src/test/java/org/sophie2/core/prolib/list/ValueProListTest.java   (revision 2425)
6+++ modules/org.sophie2.core/src/test/java/org/sophie2/core/prolib/list/ValueProListTest.java   (working copy)
7@@ -167,9 +167,9 @@
8                assertEquals(0, this.l1.size());
9                List<Pro> reads = Registry.get().endReadTrack(this);
10                assertEquals(1, reads.size());
11-               assertSame(this.l1, reads.get(0));
12+               assertSame(this.l1.getRwCache().getSizeAspect(), reads.get(0));
13               
14-               //no changes
15+               // no changes
16                assertEquals(Arrays.asList(), this.l1c);
17                assertEquals(Arrays.asList(), this.dpc);
18               
19@@ -218,7 +218,7 @@
20                assertEquals(""+reads, 1, reads.size()); //it actually reads, add depends on whats inside...
21               
22                assertEquals(1, reads.size());
23-               assertSame(this.l1, reads.get(0));
24+               assertSame(this.l1.getRwCache().getSizeAspect(), reads.get(0));
25                assertEquals(1, this.l1c.size());
26                assertEquals(0, this.dpc.size());
27                BaseProList<String>.BaseProListChange c0 = autoCast(this.l1c.get(0));
28@@ -230,7 +230,7 @@
29 
30                assertTrue(this.l1.add("b1"));
31               
32-               assertSame(this.l1, reads.get(0));
33+               assertSame(this.l1.getRwCache().getSizeAspect(), reads.get(0));
34                assertEquals(2, this.l1c.size());
35                assertEquals(0, this.dpc.size());
36                BaseProList<String>.BaseProListChange c1 = autoCast(this.l1c.get(1));
37Index: modules/org.sophie2.core/src/test/java/org/sophie2/core/prolib/list/BaseProListAspectsTest.java
38===================================================================
39--- modules/org.sophie2.core/src/test/java/org/sophie2/core/prolib/list/BaseProListAspectsTest.java     (revision 0)
40+++ modules/org.sophie2.core/src/test/java/org/sophie2/core/prolib/list/BaseProListAspectsTest.java     (revision 0)
41@@ -0,0 +1,381 @@
42+package org.sophie2.core.prolib.list;
43+
44+import java.awt.Color;
45+import java.util.ArrayList;
46+import java.util.Arrays;
47+import java.util.Collection;
48+import java.util.List;
49+import java.util.Map;
50+
51+import org.junit.Test;
52+import org.sophie2.core.prolib.Aspect;
53+import org.sophie2.core.prolib.ProStatus;
54+import org.sophie2.core.prolib.impl.AutoProperty;
55+import org.sophie2.core.prolib.impl.BaseProObject;
56+import org.sophie2.core.prolib.interfaces.Pro;
57+import org.sophie2.core.prolib.interfaces.Prop;
58+import org.sophie2.core.prolib.interfaces.RwListProp;
59+import org.sophie2.core.prolib.util.ChangeManager;
60+import org.sophie2.core.prolib.util.DefaultChangeManager;
61+import org.sophie2.core.prolib.util.Registry;
62+import org.sophie2.core.testing.UnitTestBase;
63+
64+/**
65+ * El mucho grande test for {@link Aspect}s usage with {@link BaseProList}s.
66+ * Test is meant to work with the size {@link Aspect} enabled and it does!
67+ * Currently size {@link Aspect} is disabled so this test fails.
68+ * Will fix when size {@link Aspect} is fixed. --gogov 2009-05-08 8:56 PM
69+ *
70+ * @author gogov
71+ */
72+@SuppressWarnings("all")
73+public class BaseProListAspectsTest extends UnitTestBase {
74+       class TestProList<T> extends BaseProList<T> {
75+               public TestProList() {
76+                       this.new StatusChange(ProStatus.READY).register();
77+               }
78+               
79+               public Aspect getSizeAspect() {
80+                       return getRwCache().getSizeAspect();
81+               }
82+               
83+               public Map<Object, Aspect> getKeyAspectsMap() {
84+                       return getMultimap().getAspectsMap();
85+               }
86+               
87+               public List<Aspect> getKeyAspects() {
88+                       return new ArrayList<Aspect>(getKeyAspectsMap().values());
89+               }
90+               
91+               public List<Aspect> getAllAspects() {
92+                       List<Aspect> res = getKeyAspects();
93+                       res.add(0, getSizeAspect());
94+                       
95+                       return res;
96+               }
97+               
98+               @Override
99+               protected List<T> getPublicCache() {
100+                       return getRwCache();
101+               }
102+               
103+               @Override
104+               public String fullId() {
105+                       return "TestProList";
106+               }
107+
108+               @Override
109+               public ChangeManager locateChangeManager() {
110+                       return DefaultChangeManager.get();
111+               }
112+               
113+       }
114+
115+       abstract class ReadsHelper {
116+               public List<Pro> run() {
117+                       try  {
118+                               Registry.get().beginReadTrack(this);
119+                               method();
120+                       }
121+                       finally  {
122+                               return Registry.get().endReadTrack(this);
123+                       }
124+               }
125+
126+               public List<Pro> run(int expectedReadsCount) {
127+                       try  {
128+                               Registry.get().beginReadTrack(this);
129+                               method();
130+                       }
131+                       finally  {
132+                               List<Pro> reads = Registry.get().endReadTrack(this);
133+                               assertEquals(expectedReadsCount, reads.size());
134+                               
135+                               return reads;
136+                       }
137+               }               
138+               
139+               public List<Pro> run(List<Pro> expectedReadPros) {
140+                       try  {
141+                               Registry.get().beginReadTrack(this);
142+                               method();
143+                       }
144+                       finally  {
145+                               List<Pro> reads = Registry.get().endReadTrack(this);
146+                               assertSameContent(expectedReadPros, reads);
147+                               
148+                               return reads;
149+                       }
150+               }               
151+
152+               public List<Pro> run(Pro ... expectedReadsPro) {
153+                       return run(Arrays.asList(expectedReadsPro));
154+               }
155+
156+               
157+               abstract protected void method();
158+       }
159+
160+       private TestProList beers = new TestProList<String>();
161+       
162+       @Test
163+       public void testCacheListAspectsReads() {
164+               new ReadsHelper() {
165+                       @Override
166+                       protected void method() {
167+                               beers.add("zagorka"); // read of size aspect
168+                               beers.add("kamenitza"); // read of size aspect
169+                               beers.add("pirinsko"); // read of size aspect
170+                               beers.add("shumensko"); // read of size aspect
171+                       }
172+               }.run(beers.getSizeAspect());
173+
174+               new ReadsHelper() {
175+                       @Override
176+                       protected void method() {
177+                               beers.set(0, "stolichno"); // read of root aspect                       
178+                       }
179+               }.run((Pro)beers);
180+               
181+               new ReadsHelper() {
182+                       @Override
183+                       protected void method() {
184+                               beers.remove(0); // read of root aspect                 
185+                       }
186+               }.run((Pro)beers);
187+
188+               new ReadsHelper() {
189+                       @Override
190+                       protected void method() {
191+                               beers.size(); // read of size aspect                   
192+                       }
193+               }.run(beers.getSizeAspect());
194+               
195+               new ReadsHelper() {
196+                       @Override
197+                       protected void method() {
198+                               beers.get(0); // read of root aspect
199+                       }
200+               }.run((Pro)beers);
201+       }
202+
203+       @Test
204+       public void testKeyAspectsReads() {
205+               beers.add("zagorka");
206+               beers.add("zagorka");
207+               beers.add("kamenitza");
208+               beers.add("pirinsko");
209+               beers.add("shumensko");
210+               beers.set(0, "stolichno");
211+               beers.remove(0);
212+               beers.size();
213+               
214+               List<Pro> reads =
215+               new ReadsHelper() {
216+                       @Override
217+                       protected void method() {
218+                               beers.findAll("");
219+                               beers.findAll("zagorka");
220+                               beers.findAll("meddle");
221+                               beers.findOne("meddle");
222+                               beers.findAll("meddle");                       
223+                               beers.findOne("non-existing-key");
224+                               beers.findOne("non-existing-key");
225+                               beers.findOne("non-existing-key");
226+                       }
227+               }.run();
228+
229+               assertSameContent(
230+                               new ArrayList(values(beers.getKeyAspectsMap(),
231+                                               "", "zagorka", "meddle", "non-existing-key")),
232+                               reads);
233+       }
234+
235+       class JunkProObject extends BaseProObject {
236+               public RwListProp<Object> records() {
237+                       return getBean().makeListProp("records", Object.class);
238+               }
239+       }
240+
241+       final JunkProObject junkie = new JunkProObject();
242+       
243+
244+       abstract class AutoHelper extends BaseProObject {
245+               private int calls;
246+               
247+               public AutoHelper() {
248+                       this.calls = 0;
249+               }
250+               
251+               public int getCalls() {
252+                       return this.calls;
253+               }
254+               
255+               public Prop<Integer> prop() {
256+                       class prop extends AutoProperty<Integer> {
257+                               protected Integer compute() {
258+                                       ++AutoHelper.this.calls;
259+                                       method();
260+                                       return AutoHelper.this.calls;
261+                               }
262+                       }
263+                       return getBean().makeProp(prop.class);
264+               }
265+               
266+               abstract protected void method();
267+       }
268+       
269+       @Test
270+       public void testAutoPropertyCalls() {
271+               AutoHelper h0 = new AutoHelper() {
272+                       @Override
273+                       protected void method() {
274+                               // nothing
275+                       }
276+               };
277+               
278+               AutoHelper h1 = new AutoHelper() {
279+                       @Override
280+                       protected void method() {
281+                               junkie.records().get().size();
282+                       }
283+               };
284+               
285+               AutoHelper h2 = new AutoHelper() {
286+                       @Override
287+                       protected void method() {
288+                               junkie.records().get().findAll("person");
289+                       }
290+               };
291+
292+               AutoHelper h3 = new AutoHelper() {
293+                       @Override
294+                       protected void method() {
295+                               junkie.records().get().findAll("beer");
296+                       }
297+               };
298+
299+               AutoHelper h4 = new AutoHelper() {
300+                       @Override
301+                       protected void method() {
302+                               junkie.records().get().findAll(null);
303+                       }
304+               };
305+
306+               AutoHelper h5 = new AutoHelper() {
307+                       @Override
308+                       protected void method() {
309+                               junkie.records().get().findAll(new Integer(6));
310+                       }
311+               };
312+
313+               AutoHelper h6 = new AutoHelper() {
314+                       @Override
315+                       protected void method() {
316+                               for(Object obj : junkie.records().get()) {
317+                                       // nothing
318+                               }
319+                       }
320+               };
321+
322+               AutoHelper h7 = new AutoHelper() {
323+                       @Override
324+                       protected void method() {
325+                               junkie.records().get().size();
326+                               junkie.records().get().findAll("beer");
327+                       }
328+               };
329+
330+               AutoHelper h8 = new AutoHelper() {
331+                       @Override
332+                       protected void method() {
333+                               junkie.records().get().findAll("junk-key1");
334+                               junkie.records().get().findAll("junk-key2");
335+                               junkie.records().get().findAll("junk-key3");
336+                               junkie.records().get().findAll("junk-key4");
337+                               junkie.records().get().findAll("junk-key5");
338+                               junkie.records().get().findAll("junk-key6");                           
339+                       }
340+               };             
341+               
342+               AutoHelper h9 = new AutoHelper() {
343+                       @Override
344+                       protected void method() {
345+                               junkie.records().get().size();
346+                               junkie.records().get().findAll("beer");
347+                               junkie.records().get().findOne(new Integer(6));
348+                               junkie.records().get().findAll("never-added");
349+                               junkie.records().get().findAll("meddle");
350+                       }
351+               };
352+               
353+               h0.prop().get();
354+               h1.prop().get();
355+               h2.prop().get();
356+               h3.prop().get();
357+               h4.prop().get();
358+               h5.prop().get();
359+               h6.prop().get();
360+               h7.prop().get();
361+               h8.prop().get();
362+               h9.prop().get();
363+               
364+               junkie.records().add(new ListEntry("person", "meddle"));
365+               junkie.records().add(new ListEntry("person", "pique"));
366+               junkie.records().add(new ListEntry("person", "drogba"));
367+               junkie.records().add(new ListEntry("person", "gay-referee"));
368+               junkie.records().add(new ListEntry("beer", "zagorka"));
369+               junkie.records().add(Integer.valueOf(6));
370+               junkie.records().add("meddle");
371+               junkie.records().add(null);             
372+               junkie.records().add(new ListEntry("junk-key1", 5));
373+               junkie.records().add(new ListEntry("junk-key2", new Object()));
374+               junkie.records().add(new ListEntry("junk-key3", "jeha"));
375+               junkie.records().add(new ListEntry("junk-key4", Color.black));
376+               junkie.records().add(new ListEntry("junk-key5", 'a'));         
377+               junkie.records().add(new ListEntry("junk-key6", 123.543));
378+               junkie.records().remove(3);             
379+               junkie.records().remove(5);             
380+               junkie.records().remove(1);
381+               junkie.records().remove(0);
382+               junkie.records().remove(0);             
383+               junkie.records().set(0, "meddle");
384+               junkie.records().set(2, new ListEntry("person", "o'sullivan"));         
385+               junkie.records().set(3, "meddle");             
386+               junkie.records().set(2, new ListEntry("beer", "stolichno"));           
387+               junkie.records().set(5, "meddle");             
388+                               
389+               h0.prop().get();
390+               h1.prop().get();
391+               h2.prop().get();
392+               h3.prop().get();
393+               h4.prop().get();
394+               h5.prop().get();
395+               h6.prop().get();
396+               h7.prop().get();
397+               h8.prop().get();
398+               h9.prop().get();       
399+
400+               assertEquals(1, h0.getCalls());
401+               assertEquals(20, h1.getCalls());               
402+               assertEquals(11, h2.getCalls());
403+               assertEquals(4, h3.getCalls());         
404+               assertEquals(3, h4.getCalls());
405+               assertEquals(2, h5.getCalls());         
406+               assertEquals(25, h6.getCalls());
407+
408+               assertEquals(23, h7.getCalls());               
409+               assertEquals(9, h8.getCalls());
410+               assertEquals(29, h9.getCalls());               
411+       }
412+       
413+       public static <K, V> Collection<V> values(Map<K, V> map, K ... keys) {
414+               Collection<V> c = new ArrayList<V>();
415+
416+               for(K key : keys) {
417+                       c.add(map.get(key));
418+               }
419+
420+               return c;
421+       }
422+}
423Index: modules/org.sophie2.core/src/main/java/org/sophie2/core/prolib/BasePro.java
424===================================================================
425--- modules/org.sophie2.core/src/main/java/org/sophie2/core/prolib/BasePro.java (revision 2364)
426+++ modules/org.sophie2.core/src/main/java/org/sophie2/core/prolib/BasePro.java (working copy)
427@@ -49,7 +49,6 @@
428         *
429         */
430        protected void registerRead() {
431-               
432                Registry.get().registerRead(this);
433               
434                if(getLastState() == ProStatus.CREATED) {
435@@ -119,11 +118,11 @@
436         * @param change
437         *            The change to be sent.
438         */
439-       private void fireChanged(ProChange change) {
440+       protected void fireChanged(ProChange change) {
441                assert change.getSource() == this;
442                Object key = new Object();
443                try {
444-                       // ignore all reades caused by changes
445+                       // ignore all reads caused by changes
446                        Registry.get().beginReadTrack(key);
447                        changed(change);
448                        List<ProListener> list = new ArrayList<ProListener>(
449@@ -166,7 +165,7 @@
450                public BaseProChange(BasePro source) {
451                        super(source);
452 
453-                       // and check whether hasEffect is overriden in the bottom level
454+                       // and check whether hasEffect is overridden in the bottom level
455                        // class
456                        try {
457                                getClass().getDeclaredMethod("hasEffect");
458@@ -183,11 +182,9 @@
459                        super(BasePro.this);
460                }
461 
462-               @SuppressWarnings("synthetic-access")
463                @Override
464                protected void fire() {
465                        getSource().fireChanged(this);
466-
467                }
468 
469                @Override
470@@ -220,7 +217,7 @@
471        }
472 
473        /**
474-        * A change of the state of a BasePro. Implementors, dont forget ot override
475+        * A change of the state of a BasePro. Implementors, don't forget to override
476         * {@link #hasEffect()}!
477         *
478         * @author milo
479@@ -244,15 +241,15 @@
480                public StatusChange(BasePro source, ProStatus oldStatus,
481                                ProStatus newStatus) {
482                        super(source);
483-                       assert newStatus.isMetaAvailable(); //can not revert
484-                       //to crreated
485+                       assert newStatus.isMetaAvailable(); // can not revert
486+                       // to created
487                        this.oldStatus = oldStatus;
488                        this.newStatus = newStatus;
489                       
490                }
491 
492                /**
493-                * Simpler constructor. Uses the state of the eclosing class for
494+                * Simpler constructor. Uses the state of the enclosing class for
495                 * oldStatus.
496                 *
497                 * @param newStatus
498Index: modules/org.sophie2.core/src/main/java/org/sophie2/core/prolib/impl/ObjectProperty.java
499===================================================================
500--- modules/org.sophie2.core/src/main/java/org/sophie2/core/prolib/impl/ObjectProperty.java     (revision 2425)
501+++ modules/org.sophie2.core/src/main/java/org/sophie2/core/prolib/impl/ObjectProperty.java     (working copy)
502@@ -292,7 +292,7 @@
503         * Gets the last (on last update) value or null if never computed. If the
504         * property is in {@link ProStatus#READY} state it will do the same as
505         * {@link #get()} However, this method does not cause a read to be
506-        * registered, so it is usefull for assertions.
507+        * registered, so it is useful for assertions.
508         *
509         * @return The last computed value.
510         */
511Index: modules/org.sophie2.core/src/main/java/org/sophie2/core/prolib/util/ReflectionUtil.java
512===================================================================
513--- modules/org.sophie2.core/src/main/java/org/sophie2/core/prolib/util/ReflectionUtil.java     (revision 2425)
514+++ modules/org.sophie2.core/src/main/java/org/sophie2/core/prolib/util/ReflectionUtil.java     (working copy)
515@@ -8,7 +8,8 @@
516 import java.lang.reflect.ParameterizedType;
517 import java.lang.reflect.Type;
518 import java.lang.reflect.TypeVariable;
519-
520+import java.util.HashMap;
521+import java.util.Map;
522 
523 import org.apache.log4j.Logger;
524 import org.sophie2.core.prolib.annot.NoProp;
525@@ -74,6 +75,56 @@
526                }
527                return findMethodAnnotation(m, c.getSuperclass(), annotationClass);
528        }
529+       
530+       
531+       private static class AnnoQuery {
532+               private final Method method;
533+               private final Class<?> annoClass;
534+               public AnnoQuery(Method method, Class<?> annoClass) {
535+                       super();
536+                       this.method = method;
537+                       this.annoClass = annoClass;
538+               }
539+               public Method getMethod() {
540+                       return this.method;
541+               }
542+               public Class<?> getAnnoClass() {
543+                       return this.annoClass;
544+               }
545+               @Override
546+               public int hashCode() {
547+                       final int prime = 31;
548+                       int result = 1;
549+                       result = prime * result
550+                                       + ((this.annoClass == null) ? 0 : this.annoClass.hashCode());
551+                       result = prime * result
552+                                       + ((this.method == null) ? 0 : this.method.hashCode());
553+                       return result;
554+               }
555+               @Override
556+               public boolean equals(Object obj) {
557+                       if (this == obj)
558+                               return true;
559+                       if (obj == null)
560+                               return false;
561+                       if (getClass() != obj.getClass())
562+                               return false;
563+                       AnnoQuery other = (AnnoQuery) obj;
564+                       if (this.annoClass == null) {
565+                               if (other.annoClass != null)
566+                                       return false;
567+                       } else if (!this.annoClass.equals(other.annoClass))
568+                               return false;
569+                       if (this.method == null) {
570+                               if (other.method != null)
571+                                       return false;
572+                       } else if (!this.method.equals(other.method))
573+                               return false;
574+                       return true;
575+               }
576+               
577+       }
578+       private static Map<AnnoQuery, Object> annoCache = new HashMap<AnnoQuery, Object>();
579 
580        /**
581         * Finds annotation for a method traversing its hierarchy up. If the annotation
582@@ -84,9 +135,15 @@
583         * @param annotationClass the annotation class.
584         * @return the found annotation, or null.
585         */
586+       @SuppressWarnings("unchecked")
587        public static <T extends Annotation> T findMethodAnnotation(Method m,
588                        Class<T> annotationClass) {
589-               return findMethodAnnotation(m, m.getDeclaringClass(), annotationClass);
590+               AnnoQuery q = new AnnoQuery(m, annotationClass);
591+               if(!annoCache.containsKey(q)) {
592+                       T res = findMethodAnnotation(m, m.getDeclaringClass(), annotationClass);
593+                       annoCache.put(q, res);
594+               }
595+               return (T)annoCache.get(q);
596        }
597 
598        /**
599Index: modules/org.sophie2.core/src/main/java/org/sophie2/core/prolib/list/ProList.java
600===================================================================
601--- modules/org.sophie2.core/src/main/java/org/sophie2/core/prolib/list/ProList.java    (revision 2425)
602+++ modules/org.sophie2.core/src/main/java/org/sophie2/core/prolib/list/ProList.java    (working copy)
603@@ -99,27 +99,26 @@
604         * The key is:
605         *              * the key of the {@link ListEntry} if {@link ListEntry}s were previously added to the list.
606         *              * an {@link Immutable} object if immutables were previously added.
607-        *              * null in all other cases. Note that if nulls, simple Objects or ProObject are added, this method will
608+        *              * null in all other cases. Note that if nulls, simple Objects or ProObject are added, and this method will
609         * return a {@link List} of all these. That is why it is not recommended to use this method if the list contains
610         * nulls, Objects or ProObjects.
611         *              Although the the method is supposed to behave properly if the list contains different value types,
612         * one is not encouraged to use the method in such cases since it may lead to misunderstandings.
613         *
614-        *
615         * @param key
616         *              The key to search for.
617         * @return
618         *              {@link List} of elements whose key is the specified key. The {@link List} will be empty if no such
619         * key is previously added.
620         */
621-       public List<ListEntry> findAll (Object key);
622+       public List<E> findAll (Object key);
623       
624        /**
625         *              Finds all elements that have the provided key.
626-        * The key is:
627+        * The key, by convention, is:
628         *              * the key of the {@link ListEntry} if {@link ListEntry}s were previously added to the list.
629         *              * an {@link Immutable} object if immutables were previously added.
630-        *              * null in all other cases. Note that if nulls, simple Objects or ProObject are added, this method will
631+        *              * null in all other cases. Note that if nulls, simple Objects or ProObject are added, and this method will
632         * return a {@link List} of all these. That is why it is not recommended to use this method if the list contains
633         * nulls, Objects or ProObjects.
634         *      Throws {@link RuntimeException} if more that one elements are contained with
635@@ -128,9 +127,9 @@
636         * one is not encouraged to use the method in such cases since it may lead to misunderstandings.
637         *
638         * @param key
639-        *              The key to find {@link ListEntry} for.
640+        *              The key to search for.
641         * @return
642-        *              The {@link ListEntry} found or null if it does not exist.
643+        *              The element found or null if it does not exist.
644         */
645-       public ListEntry findOne (Object key);
646+       public E findOne (Object key);
647 }
648\ No newline at end of file
649Index: modules/org.sophie2.core/src/main/java/org/sophie2/core/prolib/Aspect.java
650===================================================================
651--- modules/org.sophie2.core/src/main/java/org/sophie2/core/prolib/Aspect.java  (revision 0)
652+++ modules/org.sophie2.core/src/main/java/org/sophie2/core/prolib/Aspect.java  (revision 0)
653@@ -0,0 +1,93 @@
654+package org.sophie2.core.prolib;
655+
656+import java.util.ArrayList;
657+import java.util.List;
658+
659+import org.sophie2.core.prolib.errors.NotAvailableException;
660+import org.sophie2.core.prolib.events.ProChange;
661+import org.sophie2.core.prolib.events.ProListener;
662+import org.sophie2.core.prolib.list.BaseProList;
663+import org.sophie2.core.prolib.util.ChangeManager;
664+import org.sophie2.core.prolib.util.DefaultChangeManager;
665+import org.sophie2.core.prolib.util.Registry;
666+
667+/**
668+ * A class representing a given aspect of a given {@link BasePro}.
669+ * Currently used only with {@link BaseProList}s.
670+ *
671+ * {@link Aspect}s can be used by {@link BasePro}s internally to reduce the attached listeners
672+ * and the registered reads.
673+ *
674+ * This way:
675+ * 1) Some reads could be registered not with the {@link BasePro} itself but with an {@link Aspect}
676+ * of the {@link BasePro}, and so potentially lots of listeners will be attached to the
677+ * {@link Aspect}s, instead of the {@link BasePro}.
678+ * 2) ProChanges will be fired from the {@link Aspect}s of the {@link BasePro} to registered
679+ * listeners, instead of always firing from the {@link BasePro} itself.
680+ *
681+ * @author gogov
682+ */
683+public class Aspect extends BasePro {
684+       private final BasePro owner;
685+       
686+       /**
687+        * Creates an {@link Aspect} with a given owner.
688+        * @param owner
689+        *                      The owner this {@link Aspect} belongs to.
690+        */
691+       public Aspect(BasePro owner)  {
692+               this.owner = owner;
693+       }
694+       
695+       @Override
696+       public String fullId() {
697+               return "Aspect";
698+       }
699+
700+       @Override
701+       public ChangeManager locateChangeManager() {
702+               return DefaultChangeManager.get();
703+       }
704+       
705+       /**
706+        * Returns the owner of this {@link Aspect}.
707+        * @return
708+        *                      The owner of this {@link Aspect}.
709+        */
710+       public BasePro getOwner() {
711+               return this.owner;
712+       }
713+       
714+       @Override
715+       public void registerRead() {
716+               Registry.get().registerRead(this);
717+               
718+               if(getOwner().getLastState() == ProStatus.CREATED) {
719+                       throw new NotAvailableException(fullId() + " attributes not initialized");
720+               }
721+               else if(getOwner().getLastState() == ProStatus.DESTROYED) {
722+                       throw new NotAvailableException(fullId() + " already destroyed!");
723+               }
724+               else if(getOwner().getLastState() == ProStatus.META_INITIALIZED) {
725+                       throw new NotAvailableException(fullId() + " value not initialized!", lastProblem());
726+               }               
727+       }
728+       
729+       @Override
730+       public void fireChanged(ProChange change) {
731+               assert change.getSource() == getOwner();
732+               Object key = new Object();
733+               try {
734+                       // ignore all reads caused by changes
735+                       Registry.get().beginReadTrack(key);
736+                       changed(change);
737+                       List<ProListener> list = new ArrayList<ProListener>(
738+                                       getListeners());
739+                       for (ProListener l : list) {
740+                               l.changed(change);
741+                       }
742+               } finally {
743+                       Registry.get().endReadTrack(key);
744+               }
745+       }
746+}
747Index: modules/org.sophie2.core/src/main/java/org/sophie2/core/prolib/impl/AutoTracker.java
748===================================================================
749--- modules/org.sophie2.core/src/main/java/org/sophie2/core/prolib/impl/AutoTracker.java        (revision 2425)
750+++ modules/org.sophie2.core/src/main/java/org/sophie2/core/prolib/impl/AutoTracker.java        (working copy)
751@@ -135,7 +135,7 @@
752        }
753 
754        /**
755-        * Override this method to provide a name that can distinguis this tracked.
756+        * Override this method to provide a name that can distinguish this tracked.
757         * It helps debugging.
758         * @return a string identifier.
759         */
760Index: modules/org.sophie2.core/src/test/java/org/sophie2/core/prolib/list/BaseProListIndexingTest.java
761===================================================================
762--- modules/org.sophie2.core/src/test/java/org/sophie2/core/prolib/list/BaseProListIndexingTest.java    (revision 2425)
763+++ modules/org.sophie2.core/src/test/java/org/sophie2/core/prolib/list/BaseProListIndexingTest.java    (working copy)
764@@ -263,9 +263,9 @@
765                }
766               
767                this.immDummyList.remove(new ImmDummy(10));
768-               ListEntry immDummyEntry = this.immDummyList.findOne(new ImmDummy(10));
769-               assertEquals(immDummy, immDummyEntry.getValue());
770-               assertSame(immDummy, immDummyEntry.getValue());
771+               ImmDummy immDummyFound = this.immDummyList.findOne(new ImmDummy(10));
772+               assertEquals(immDummy, immDummyFound);
773+               assertSame(immDummy, immDummyFound);
774               
775                Dummy dummy1 = new Dummy(3);
776                Dummy dummy2 = new Dummy(4);
777@@ -282,9 +282,9 @@
778               
779                this.dummyList.remove(dummy2);
780               
781-               ListEntry dummyEntry = this.dummyList.findOne(null);
782-               assertSame(dummy1, dummyEntry.getValue());
783-               assertEquals(dummy1, dummyEntry.getValue());
784+               Dummy dummyFound = this.dummyList.findOne(null);
785+               assertSame(dummy1, dummyFound);
786+               assertEquals(dummy1, dummyFound);
787               
788                this.entryList.add(new ListEntry("sophie2", 123));
789               
790@@ -315,11 +315,11 @@
791               
792                this.objectList.remove(1);
793               
794-               assertEquals(o1, this.objectList.findOne(null).getValue());
795+               assertEquals(o1, this.objectList.findOne(null));
796               
797                this.nullList.add(null);
798               
799-               assertNull(this.nullList.findOne(null).getValue());
800+               assertNull(this.nullList.findOne(null));
801               
802                this.nullList.add(null);
803               
804@@ -344,7 +344,7 @@
805               
806                this.mixedList.add(new ListEntry("Sophie", "Ala Bala"));
807               
808-               assertSame(new ListEntry("Sophie", "Ala Bala").getValue(), this.mixedList.findOne("Sophie").getValue());
809+               assertEquals("Ala Bala", ((ListEntry)this.mixedList.findOne("Sophie")).getValue());
810               
811                this.mixedList.add(new ListEntry("Sophie", "Blaaaa"));
812               
813@@ -354,8 +354,6 @@
814                } catch (RuntimeException e) {
815                        // OK.
816                }
817-               
818-               
819        }
820 
821        //removes all elements from all lists.
822@@ -368,4 +366,5 @@
823                this.objectList.clear();
824        }
825       
826+       // TODO add AutoProperty tests to test tracking
827 }
828Index: modules/org.sophie2.core/src/main/java/org/sophie2/core/prolib/list/ListEntry.java
829===================================================================
830--- modules/org.sophie2.core/src/main/java/org/sophie2/core/prolib/list/ListEntry.java  (revision 2425)
831+++ modules/org.sophie2.core/src/main/java/org/sophie2/core/prolib/list/ListEntry.java  (working copy)
832@@ -1,19 +1,16 @@
833 package org.sophie2.core.prolib.list;
834 
835+import org.sophie2.core.prolib.annot.Immutable;
836+
837 /**
838- * A class representing an Entry for lists.
839+ * An Immutable class representing an Entry for lists.
840  *
841- * @author peko, nenko
842- *
843+ * @author peko, nenko, gogov
844  */
845-/**
846- * @author peko
847- *
848- */
849+@Immutable
850 public class ListEntry {
851-
852        private final Object key;
853-       private Object value;
854+       private final Object value;
855       
856        /**
857         * Constructor.
858@@ -35,13 +32,6 @@
859        }
860 
861        /**
862-        * @param value the new value for the entry.
863-        */
864-       public void setValue(Object value) {
865-               this.value = value;
866-       }
867-
868-       /**
869         * @return the key for this entry.
870         */
871        public Object getKey() {
872@@ -50,7 +40,6 @@
873       
874        @Override
875        public String toString() {
876-               return "Key: "+getKey()+"       Value: "+getValue();
877+               return "ListEntry: Key=" + getKey() + ", Value=" + getValue();
878        }
879-       
880 }
881Index: modules/org.sophie2.core/src/main/java/org/sophie2/core/prolib/list/BaseProList.java
882===================================================================
883--- modules/org.sophie2.core/src/main/java/org/sophie2/core/prolib/list/BaseProList.java        (revision 2425)
884+++ modules/org.sophie2.core/src/main/java/org/sophie2/core/prolib/list/BaseProList.java        (working copy)
885@@ -10,6 +10,7 @@
886 import java.util.ListIterator;
887 import java.util.Map;
888 
889+import org.sophie2.core.prolib.Aspect;
890 import org.sophie2.core.prolib.BasePro;
891 import org.sophie2.core.prolib.ProStatus;
892 import org.sophie2.core.prolib.interfaces.Pro;
893@@ -31,13 +32,15 @@
894  *
895  * @param <E>
896  */
897+
898 public abstract class BaseProList<E> extends BasePro implements ProList<E> {
899 
900        private final Class<E> elementClass;
901        private final CacheList cache;
902        private final List<E> roCache; // a read only cache, for public access
903        private Pro owner = null;
904-       private Map<Object, List<ListEntry>> keyIndex = null;
905+
906+       private final ElementsMultimap multimap;
907       
908        /**
909         * Constructor. Tries to auto-detect the element class.
910@@ -93,12 +96,13 @@
911 
912                this.cache = new CacheList(mode);
913                this.roCache = Collections.unmodifiableList(this.cache);
914-               this.keyIndex = new HashMap<Object, List<ListEntry>>();
915+               this.multimap = new ElementsMultimap();
916+               
917                resetData(initData);
918        }
919 
920        /**
921-        * Immutable mode for a pro list behaviour.
922+        * Immutable mode for a ProList behavior.
923         *
924         * NOTE: this currently supports only "unique" mode, which is basically a
925         * hack.
926@@ -107,16 +111,16 @@
927         *
928         */
929        public static class ProListMode {
930-               private final boolean uniquie;
931+               private final boolean unique;
932 
933                /**
934                 * Constructor
935                 *
936                 * @param unique
937-                *            whether the elements hould be unique.
938+                *            whether the elements should be unique.
939                 */
940                protected ProListMode(boolean unique) {
941-                       this.uniquie = unique;
942+                       this.unique = unique;
943                }
944 
945                /**
946@@ -125,7 +129,7 @@
947                 * @return the unique mode
948                 */
949                public boolean isUnique() {
950-                       return this.uniquie;
951+                       return this.unique;
952                }
953 
954                /**
955@@ -150,7 +154,8 @@
956 
957                private final List<E> inner;
958                private final ProListMode mode;
959-
960+               private final Aspect sizeAspect;
961+               
962                /**
963                 * The internal cache list (with RW access).
964                 *
965@@ -161,6 +166,7 @@
966                        // we can not used checked array, because it does not allow nulls :/
967                        this.inner = new ArrayList<E>();
968                        this.mode = mode;
969+                       this.sizeAspect = new Aspect(BaseProList.this);
970                }
971 
972                /**
973@@ -192,6 +198,8 @@
974                @Override
975                public int size() {
976                        registerRead();
977+//                     FIXME: size aspect should work
978+//                     getSizeAspect().registerRead();
979                        return getInner().size();
980                }
981 
982@@ -208,8 +216,11 @@
983                        }
984 
985                        E oldValue = getInner().get(index);
986+
987+                       // register change to the list                 
988                        BaseProListChange change = createSet(index, oldValue, element);
989                        change.register();
990+
991                        registerRead();
992                        return oldValue;
993                }
994@@ -220,6 +231,7 @@
995                        if (getMode().isUnique() && getInner().contains(element)) {
996                                return;
997                        }
998+                       // register change to the list
999                        BaseProListChange change = createAdd(index, element);
1000                        change.register();
1001                }
1002@@ -228,8 +240,11 @@
1003                @Override
1004                public E remove(int index) {
1005                        E oldValue = getInner().get(index);
1006+
1007+                       // register change to the list
1008                        BaseProListChange change = createRemove(index, oldValue);
1009                        change.register();
1010+
1011                        registerRead();
1012                        return oldValue;
1013                }
1014@@ -238,11 +253,120 @@
1015                public String toString() {
1016                        // guess... this does not cause reads :)
1017                        // why? because every this and that calls it
1018+                       // (:
1019                        return this.inner.toString();
1020                }
1021+               
1022+               /**
1023+                * Gets the size {@link Aspect} responsible for this BaseProList.
1024+                * @return The size {@link Aspect} responsible for this BaseProList.
1025+                */
1026+               public Aspect getSizeAspect() {
1027+                       return this.sizeAspect;
1028+               }
1029        }
1030 
1031        /**
1032+        * Helper class for implementing the inner elements multimap of {@link BaseProList}.
1033+        *
1034+        *  @author gogov
1035+        */
1036+       protected class ElementsMultimap {
1037+               private final Map<Object, List<E>> innerMap;
1038+               private final Map<Object, Aspect> aspectsMap;
1039+               
1040+               /**
1041+                * Constructor.
1042+                */
1043+               public ElementsMultimap() {
1044+                       this.innerMap = new HashMap<Object, List<E>>();
1045+                       this.aspectsMap = new HashMap<Object, Aspect>();
1046+               }
1047+
1048+               /**
1049+                * Add an element to the multimap.
1050+                * @param element
1051+                *                      The element to be added.
1052+                */
1053+               public void add(E element) {
1054+                       Object key = ProUtil.getKey(element);
1055+                       if(!this.innerMap.containsKey(key)) {
1056+                               this.innerMap.put(key, new ArrayList<E>());
1057+                               this.aspectsMap.put(key, new Aspect(BaseProList.this));
1058+                       }
1059+                       this.innerMap.get(key).add(element);
1060+               }
1061+               
1062+               /**
1063+                * Remove an element from the multimap.
1064+                * @param element
1065+                *                      The element to be removed.
1066+                */
1067+               public void remove(E element) {
1068+                       Object key = ProUtil.getKey(element);
1069+                       if(this.innerMap.containsKey(key)) {
1070+                               this.innerMap.get(key).remove(element);
1071+                       }
1072+               }
1073+               
1074+               /**
1075+                * Updates a value in the multimap.
1076+                * @param oldElement
1077+                *                      The old value which is to be erased.
1078+                * @param newElement
1079+                *                      The new value which is to be assigned.
1080+                */
1081+               public void set(E oldElement, E newElement) {
1082+                       remove(oldElement);
1083+                       add(newElement);
1084+               }
1085+               
1086+               /**
1087+                * Returns an unmodifiable {@link List} of all the values
1088+                * mapped to a given key.
1089+                * @param key
1090+                *                      The key to search values for.
1091+                * @return
1092+                *                      An unmodifiable {@link List} of all the values
1093+                *                      mapped to a given key.
1094+                */
1095+               public List<E> get(Object key) {
1096+                       // this is needed
1097+                       if(!this.innerMap.containsKey(key)) {
1098+                               this.innerMap.put(key, new ArrayList<E>());
1099+                               this.aspectsMap.put(key, new Aspect(BaseProList.this));
1100+                       }
1101+                       
1102+                       List<E> list = this.innerMap.get(key);
1103+                       this.aspectsMap.get(key).registerRead();
1104+                       
1105+                       return Collections.unmodifiableList(list);
1106+               }
1107+               
1108+               /**
1109+                * Returns the {@link Aspect} responsible for a given key.
1110+                * @param key
1111+                *                      The key to find an {@link Aspect} for.
1112+                * @return
1113+                *                      The {@link Aspect} responsible for a given key.
1114+                */
1115+               public Aspect getAspect(Object key) {
1116+                       return this.aspectsMap.get(key);
1117+               }
1118+               
1119+               /**
1120+                * Gets an unmodifiable view of the inner aspects {@link Map}
1121+                * of the {@link BaseProList};
1122+                * @return
1123+                *                      An unmodifiable view of the inner aspects {@link Map}
1124+                *                      of the {@link BaseProList};
1125+                */
1126+               public Map<Object, Aspect> getAspectsMap() {
1127+                       return Collections.unmodifiableMap(this.aspectsMap);
1128+               }
1129+       }
1130+       
1131+       /**
1132         * A change on the undеrlying list...
1133         *
1134         * @author milo
1135@@ -259,12 +383,11 @@
1136                 * @param index
1137                 *            the index where it happened
1138                 * @param oldValue
1139-                *            the old value (or null if not aplicable)
1140+                *            the old value (or null if not applicable)
1141                 * @param newValue
1142-                *            the new value (or null if not aplicable)
1143+                *            the new value (or null if not applicable)
1144                 */
1145                public BaseProListChange(Kind kind, int index, E oldValue, E newValue) {
1146-
1147                        this.kind = kind;
1148                        this.index = index;
1149                        this.oldValue = oldValue;
1150@@ -287,18 +410,19 @@
1151                        this.index = index;
1152                        this.newValue = newValue;
1153                        switch (kind) {
1154-                       case ADD:
1155-                               this.oldValue = null;
1156-                               break;
1157-                       case REMOVE:
1158-                               assert this.newValue == null;
1159-                               this.oldValue = getSource().cache.inner.get(this.index);
1160-                               break;
1161-                       case SET:
1162-                               this.oldValue = getSource().cache.inner.get(this.index);
1163-                               break;
1164-                       default:
1165-                               assert false;
1166+                               case ADD:
1167+                                       this.oldValue = null;
1168+                                       break;
1169+                               case REMOVE:
1170+                                       assert this.newValue == null;
1171+                                       this.oldValue = getSource().getRwCache().inner.get(this.index);
1172+                                       break;
1173+                               case SET:
1174+                                       this.oldValue = getSource().getRwCache().inner.get(this.index);
1175+                                       break;
1176+                               default:
1177+                                       assert false;
1178+                                       break;
1179                        }
1180                }
1181 
1182@@ -341,7 +465,6 @@
1183                public BaseProList<E> getSource() {
1184                        return (BaseProList<E>) super.getSource();
1185                }
1186-
1187                /**
1188                 * Modifies a list property to reflect the change. Useful for
1189                 * synchronizations.
1190@@ -354,7 +477,6 @@
1191                        case ADD:
1192                                target.add(getIndex(), getNewValue());
1193                                break;
1194-
1195                        case REMOVE:
1196                                target.remove(getIndex());
1197                                break;
1198@@ -365,7 +487,6 @@
1199                                assert false;
1200                                break;
1201                        }
1202-
1203                }
1204 
1205                @Override
1206@@ -397,19 +518,24 @@
1207                public void perform() {
1208                        logger().trace("performing change: " + this + " on " + fullId());
1209                        checkElement(this.newValue);
1210+                       
1211                        switch (getKind()) {
1212                        case ADD:
1213-                               getSource().cache.inner.add(getIndex(), getNewValue());
1214-                               addToKeyIndex(getNewValue());
1215+                               getSource().getRwCache().inner.add(getIndex(), getNewValue());
1216+                               getSource().getMultimap().add(getNewValue());
1217                                break;
1218                        case REMOVE:
1219-                               E element = getSource().cache.inner.remove(getIndex());
1220-                               removeFromKeyIndex(element);
1221+                               E element = getSource().getRwCache().inner.remove(getIndex());
1222+                               getSource().getMultimap().remove(element);
1223                                break;
1224                        case SET:
1225-                               getSource().cache.inner.set(getIndex(), getNewValue());
1226-                               setForKeyInKeyIndex(getSource().cache.inner.get(getIndex()), getNewValue());
1227+                               getSource().getRwCache().inner.set(getIndex(), getNewValue());
1228+//                             getSource().getMultimap().set(getSource().getRwCache().inner.get(getIndex()), getNewValue());
1229+                               getSource().getMultimap().set(getOldValue(), getNewValue());
1230                                break;
1231+                       default:
1232+                               assert false;
1233+                               break;                 
1234                        }
1235                }
1236 
1237@@ -424,104 +550,37 @@
1238                        return this.reverse;
1239                }
1240               
1241-               @SuppressWarnings("synthetic-access")
1242-               private void addToKeyIndex(E element){
1243-                       
1244-                       ListEntry newEntry = null;
1245-                       if(element == null){
1246-                               newEntry = new ListEntry(null, null);
1247-                       }else if(ListEntry.class.isInstance(element)){
1248-                               newEntry = (ListEntry)element;
1249-                       }
1250-                       else if(ProUtil.isImmutable(element.getClass())){
1251-                               newEntry = new ListEntry(element, element);
1252-                       }
1253-                       else{
1254-                               newEntry = new ListEntry(null, element);
1255-                       }
1256-                       
1257-                       List<ListEntry> entries = getEntries(newEntry.getKey());
1258-                       
1259-                       entries.add(newEntry);
1260-                       
1261-               }
1262-               
1263-               @SuppressWarnings("synthetic-access")
1264-               private void removeFromKeyIndex(E element){
1265-                       
1266-                       Object keyToRemove = null;
1267-                       Object valueToRemove = null;
1268-                       
1269-                       if(element==null){
1270-                               //nothing. both ketToRemove and valueToRemove are null.
1271-                       }else if(ListEntry.class.isInstance(element)){
1272-                               ListEntry entry = (ListEntry)element;
1273-                               valueToRemove = entry.getValue();
1274-                               keyToRemove = entry.getKey();
1275-                               
1276-                       }
1277-                       else if(ProUtil.isImmutable(element.getClass())){
1278-                               keyToRemove = element;
1279-                               valueToRemove = element;
1280-                               
1281-                       }
1282-                       else{
1283-                               valueToRemove = element;
1284-                       }
1285-                       
1286-                       List<ListEntry> entries = getEntries(keyToRemove);
1287-               
1288-                       
1289-                       ListEntry toRemove = null;
1290-                       for (ListEntry e : entries) {
1291-                               if (e.getValue() == valueToRemove
1292-                                               || (e.getValue() != null && e.getValue().equals(
1293-                                                               valueToRemove))) {
1294-                                       toRemove = e;
1295-                                       break;
1296-                               }
1297-                       }
1298-                       entries.remove(toRemove);
1299-                       
1300-                       
1301-               }
1302-               
1303-               @SuppressWarnings("synthetic-access")
1304-               private void setForKeyInKeyIndex(E oldElement, E newElement){
1305+               @Override
1306+               protected void fire() {
1307+                       // fire from the BaseProList
1308+                       super.fire();
1309                       
1310-                       Object keyToSet = null;
1311-                       Object oldV = null;
1312-                       Object newV = null;                     
1313+                       // fire from affected Aspects
1314+                       switch (getKind()) {
1315+                       case ADD:
1316+                               getMultimap().getAspect(ProUtil.getKey(getNewValue())).fireChanged(this);
1317+//                             FIXME: size aspect should work
1318+//                             getRwCache().getSizeAspect().fireChanged(this);
1319+                               break;
1320+                       case REMOVE:
1321+                               getMultimap().getAspect(ProUtil.getKey(getOldValue())).fireChanged(this);                               
1322+//                             FIXME: size aspect should work
1323+//                             getRwCache().getSizeAspect().fireChanged(this);
1324+                               break;
1325+                       case SET:
1326+                               getMultimap().getAspect(ProUtil.getKey(getNewValue())).fireChanged(this);
1327 
1328-                       if(oldElement==null){
1329-                               //nothing. both ketToRemove and valueToRemove are null.
1330-                       }else if(ListEntry.class.isInstance(oldElement)){
1331-                               ListEntry entry = (ListEntry)oldElement;
1332-                               keyToSet = entry.getKey();
1333-                               oldV = entry.getValue();
1334-                               newV = (newElement == null ? null : ((ListEntry)newElement).getValue());
1335-                       }
1336-                       else if(ProUtil.isImmutable(oldElement.getClass())){
1337-                               keyToSet = oldElement;
1338-                               oldV = oldElement;
1339-                               newV = (newElement == null ? null : newElement);
1340-                       }
1341-                       else{
1342-                               oldV = oldElement;
1343-                               newV = (newElement == null ? null : newElement);
1344-                       }
1345-                       
1346-                       List<ListEntry> entries = getEntries(keyToSet);
1347-                       
1348-                       for(ListEntry e : entries){
1349-                               if(e.getValue()==oldV || e.getValue().equals(oldV)){
1350-                                       e.setValue(newV);
1351-                                       break;
1352+                               if(!ProUtil.equal(getNewValue(), getOldValue())) {
1353+                                       getMultimap().getAspect(ProUtil.getKey(getOldValue())).fireChanged(this);
1354                                }
1355+                               break;
1356+                       default:
1357+                               assert false;
1358+                               break;
1359                        }
1360                }
1361        }
1362-
1363+       
1364        /**
1365         * Creates an add change.
1366         *
1367@@ -567,7 +626,7 @@
1368 
1369        /**
1370         * Gets the read-write cache. It is intended to be used by implementors
1371-        * only. Changes to it fire apropriate events.
1372+        * only. Changes to it fire appropriate events.
1373         *
1374         * @return the read-write cache.
1375         */
1376@@ -587,6 +646,15 @@
1377        }
1378 
1379        /**
1380+        * Returns the inner {@link ElementsMultimap} for this {@link BaseProList}.
1381+        * @return
1382+        *                      The inner {@link ElementsMultimap} for this {@link BaseProList}.
1383+        */
1384+       protected ElementsMultimap getMultimap() {
1385+               return this.multimap;
1386+       }
1387+       
1388+       /**
1389         * Resets the whole data to a new one. Fires appropriate changes.
1390         *
1391         * @param data
1392@@ -594,15 +662,15 @@
1393         */
1394        @SuppressWarnings("synthetic-access")
1395        protected void resetData(List<? extends E> data) {
1396-// This is the old version. Use in case of bugs in the optimized version.
1397+//// This is the old version. Use in case of bugs in the optimized version.
1398 //             // clear ... fire lots of remove changes
1399 //             // use internal thing, not to cause self reads...
1400 //             for (int i = getRwCache().inner.size() - 1; i >= 0; --i) {
1401-//                     createRemove(i, getRwCache().inner.get(i)).redo();
1402+//                     createRemove(i, getRwCache().inner.get(i)).register();
1403 //             }
1404 //             // add new ...
1405 //             for (int i = 0; i < data.size(); ++i) {
1406-//                     createAdd(i, data.get(i)).redo();
1407+//                     createAdd(i, data.get(i)).register();
1408 //             }
1409               
1410               
1411@@ -792,7 +860,7 @@
1412                return getPublicCache().equals(obj);
1413        }
1414 
1415-       // end of List delagating methods
1416+       // end of List delegating methods
1417 
1418        @SuppressWarnings("synthetic-access")
1419        @Override
1420@@ -802,7 +870,7 @@
1421                // There were nasty bugs because of this!
1422                try {
1423                        Registry.get().beginReadTrack(this);
1424-                       return fullId() + this.cache.inner.toString();
1425+                       return fullId() + this.getRwCache().inner.toString();
1426                } finally {
1427                        Registry.get().endReadTrack(this);
1428 
1429@@ -827,7 +895,7 @@
1430                this.owner = owner;
1431 
1432        }
1433-
1434+       
1435        /**
1436         * Gets the owner's full id (or null if no owner).
1437         *
1438@@ -838,31 +906,20 @@
1439        }
1440 
1441        @Override
1442-       public List<ListEntry> findAll(Object key) {
1443-               registerRead();
1444-               List<ListEntry> entries = getEntries(key);
1445-               return Collections.unmodifiableList(entries);
1446+       public List<E> findAll(Object key) {
1447+               return getMultimap().get(key);
1448        }
1449 
1450        @Override
1451-       public ListEntry findOne(Object key) {
1452-               List<ListEntry> entries = findAll(key);
1453-               if(entries.size()>1){
1454-                       throw new RuntimeException("There are more than one entries with the specified key: "+key+" !!!");
1455-               }else if(entries.size()==0){
1456-                       return null;
1457+       public E findOne(Object key) {
1458+               // XXX
1459+               List<E> elements = findAll(key);
1460+               if(elements.size() > 1) {
1461+                       throw new RuntimeException("There are more than one entries with the specified key: " + key + " !!!");
1462                }
1463-               return entries.get(0);
1464-       }
1465-       
1466-       private List<ListEntry> getEntries(Object key){
1467-               List<ListEntry> result = this.keyIndex.get(key);
1468-               if(result == null){
1469-                       result = new ArrayList<ListEntry>();
1470-                       this.keyIndex.put(key, result);
1471+               else if(elements.size() == 0) {
1472+                       return null;
1473                }
1474-               
1475-               return result;
1476+               return elements.get(0);
1477        }
1478-
1479 }
1480Index: modules/org.sophie2.core/src/main/java/org/sophie2/core/prolib/util/ProUtil.java
1481===================================================================
1482--- modules/org.sophie2.core/src/main/java/org/sophie2/core/prolib/util/ProUtil.java    (revision 2425)
1483+++ modules/org.sophie2.core/src/main/java/org/sophie2/core/prolib/util/ProUtil.java    (working copy)
1484@@ -24,6 +24,7 @@
1485 import org.sophie2.core.prolib.impl.ValueProperty;
1486 import org.sophie2.core.prolib.interfaces.ListProp;
1487 import org.sophie2.core.prolib.interfaces.ProObject;
1488+import org.sophie2.core.prolib.list.ListEntry;
1489 
1490 /**
1491  * Utils related to pro-objects and properties.
1492@@ -300,4 +301,25 @@
1493                }
1494                return a == b;
1495        }
1496+       
1497+       /**
1498+        * Helper method for guessing the key of a given ProList item.
1499+        * @param listItem
1500+        *                       The given ProList item.
1501+        * @return the key of the item.
1502+        */
1503+       public static Object getKey(Object listItem)  {
1504+               if(listItem == null) {
1505+                       return null;
1506+               }
1507+               else if(listItem instanceof ListEntry) {
1508+                       return ((ListEntry)listItem).getKey();
1509+               }
1510+               else if(isImmutable(listItem.getClass())) {
1511+                       return listItem;
1512+               }
1513+               else {
1514+                       return null;
1515+               }
1516+       }
1517 }
1518Index: modules/org.sophie2.core/src/main/java/org/sophie2/core/prolib/util/Registry.java
1519===================================================================
1520--- modules/org.sophie2.core/src/main/java/org/sophie2/core/prolib/util/Registry.java   (revision 2425)
1521+++ modules/org.sophie2.core/src/main/java/org/sophie2/core/prolib/util/Registry.java   (working copy)
1522@@ -6,6 +6,8 @@
1523 import java.util.IdentityHashMap;
1524 import java.util.List;
1525 
1526+import org.sophie2.core.prolib.Aspect;
1527+import org.sophie2.core.prolib.impl.AutoTracker;
1528 import org.sophie2.core.prolib.interfaces.Pro;
1529 
1530 
1531@@ -88,9 +90,13 @@
1532        }
1533       
1534        /**
1535-        * Ends a read tracking.
1536+        * Ends a read tracking.
1537         * @param key The key of this tracking.
1538-        * @return All properties which was read.
1539+        * @return The list of {@link Pro}s which were read.
1540+        *              In case both reads of an {@link Aspect}s and its owner are registered,
1541+        *              the {@link Aspect} is filtered and only the owner is left.
1542+        *              Thus any listening entities (like {@link AutoTracker}) would get
1543+        *              updated only once.
1544         */
1545        public List<Pro> endReadTrack(Object key) {
1546                assert getReadMap().containsKey(key);
1547@@ -99,16 +105,26 @@
1548                List<Pro> track = getReadMap().remove(key).getReads();
1549                getKeyStack().removeLast();
1550               
1551-               
1552+               // filter out duplicates
1553                IdentityHashMap<Pro, Void> unique = new IdentityHashMap<Pro, Void>();
1554-               
1555-               
1556+
1557                for(Pro p : track){
1558                        if(!unique.containsKey(p)) {
1559                                unique.put(p, null);
1560                        }
1561                }
1562-               return new ArrayList<Pro>(unique.keySet());
1563+               
1564+               ArrayList<Pro> res = new ArrayList<Pro>();
1565+               
1566+               // filter out {@link Aspect}s whose owner was also read
1567+               for (Pro pro : unique.keySet()) {
1568+                       if(!(pro instanceof Aspect &&
1569+                                       unique.containsKey(((Aspect)pro).getOwner()))) {
1570+                               res.add(pro);
1571+                       }
1572+               }
1573+               
1574+               return res;
1575        }
1576       
1577        /**
1578@@ -131,7 +147,7 @@
1579        };
1580       
1581        /**
1582-        * Gets the only (currently) instrance of the property registry.
1583+        * Gets the only (currently) instance of the property registry.
1584         * @return the only (currently) instance of the property registry.
1585         */
1586        public static Registry get() {