001    /*
002      Copyright (C) 2002-2003 Renaud Pawlak <renaud@aopsys.com>, 
003                              Laurent Martelli <laurent@aopsys.com>
004      
005      This program is free software; you can redistribute it and/or modify
006      it under the terms of the GNU Lesser General Public License as
007      published by the Free Software Foundation; either version 2 of the
008      License, or (at your option) any later version.
009    
010      This program is distributed in the hope that it will be useful,
011      but WITHOUT ANY WARRANTY; without even the implied warranty of
012      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
013      GNU Lesser General Public License for more details.
014    
015      You should have received a copy of the GNU Lesser General Public License
016      along with this program; if not, write to the Free Software
017      Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */
018    
019    package org.objectweb.jac.aspects.gui;
020    
021    import java.util.Arrays;
022    import java.util.HashMap;
023    import java.util.HashSet;
024    import java.util.Hashtable;
025    import java.util.Iterator;
026    import java.util.List;
027    import java.util.Map;
028    import java.util.Set;
029    import java.util.WeakHashMap;
030    import org.aopalliance.intercept.ConstructorInvocation;
031    import org.aopalliance.intercept.MethodInvocation;
032    import org.apache.log4j.Logger;
033    import org.objectweb.jac.core.AspectComponent;
034    import org.objectweb.jac.core.Interaction;
035    import org.objectweb.jac.core.Wrappee;
036    import org.objectweb.jac.core.Wrapper;
037    import org.objectweb.jac.core.Wrapping;
038    import org.objectweb.jac.core.rtti.CollectionItem;
039    import org.objectweb.jac.core.rtti.FieldItem;
040    import org.objectweb.jac.core.rtti.MemberItem;
041    import org.objectweb.jac.core.rtti.MethodItem;
042    import org.objectweb.jac.util.Strings;
043    
044    /**
045     * This wrapper updates the views of a given object when its state
046     * changes, that is to say when a write method is called on the
047     * wrappee.<p>
048     *
049     * A view controller can control several views of the same wrappee at
050     * the same time.<p>
051     *
052     * This mecanism is similar to the famous MVC design pattern. The
053     * model is the wrappee and the controller is the wrapper.<p>
054     *
055     * @see View
056     * @see #registerObject(Wrappee,ObjectUpdate,Object)
057     * @see #registerField(Wrappee,FieldItem,FieldUpdate,Object)
058     * @see #registerCollection(Wrappee,CollectionItem,CollectionUpdate,Object)
059     * @see #unregisterObject(Wrappee,ObjectUpdate)
060     * @see #unregisterField(Wrappee,FieldItem,FieldUpdate)
061     * @see #unregisterCollection(Wrappee,CollectionItem,CollectionUpdate)
062     */
063    
064    public class ViewControlWrapper extends Wrapper {
065        static Logger logger = Logger.getLogger("gui.viewcontrol");
066        static Logger loggerReg = Logger.getLogger("gui.register");
067    
068            // FieldItem -> (FieldUpdate -> param)  (clients to notify for each field)
069            WeakHashMap fieldClients = new WeakHashMap();
070            // MethodItem -> (MethodUpdate -> param)  (clients to notify for each method)
071            WeakHashMap methodClients = new WeakHashMap();
072            // FieldItem -> (CollectionUpdate -> param)  (clients to notify for each collection)
073            WeakHashMap collectionClients = new WeakHashMap();
074            // ObjectUpdate -> param (clients to notify when the wrappee's state is modified)
075            WeakHashMap objectClients = new WeakHashMap();
076    
077            // FieldItem -> (FieldUpdate -> param)  (clients to notify for each field)
078            //Hashtable allFieldClients = new Hashtable();
079            // MethodItem -> (MethodUpdate -> param)  (clients to notify for each method)
080            static Hashtable allMethodClients = new Hashtable();
081            // FieldItem -> (CollectionUpdate -> param)  (clients to notify for each collection)
082            //Hashtable allCollectionClients = new Hashtable();
083            // ObjectUpdate -> param (clients to notify when the wrappee's state is modified)
084            //HashMap allObjectClients = new HashMap();
085    
086            /**
087             * Creates an empty  view control wrapper.<p>
088             */
089            public ViewControlWrapper(AspectComponent ac) {
090                    super(ac);
091            }
092    
093            /**
094             * Register for a fieldUpdated event.<p>
095             *
096             * @param wrappee
097             * @param field the field whose updates must be notified
098             * @param client the object to notify when the field is updated
099             * @param param
100             * @see #unregisterField(Wrappee,FieldItem,FieldUpdate) 
101             */
102            public void registerField(
103                    Wrappee wrappee,
104                    FieldItem field,
105                    FieldUpdate client,
106                    Object param) {
107                    Map clients = (Map) fieldClients.get(field.getName());
108                    if (clients == null) {
109                            clients = new HashMap();
110                            fieldClients.put(field.getName(), clients);
111                    }
112                    clients.put(client, param);
113                    loggerReg.debug(
114                "registerField(" + Strings.hex(client)
115                + ") on " + Strings.hex(wrappee) + "." + field.getName());
116            }
117    
118            /**
119             * Unregister from a fieldUpdated event.<p>
120             *
121             * @param field the field whose updates must not be notified anymore
122             * @param client the object not to notify anymore
123             * @see #registerField(Wrappee,FieldItem,FieldUpdate,Object) 
124             */
125            public void unregisterField(
126                    Wrappee wrappee,
127                    FieldItem field,
128                    FieldUpdate client) {
129                    Map clients = (Map) fieldClients.get(field.getName());
130                    if (clients != null) {
131                            loggerReg.debug("unregisterField("
132                                            + Strings.hex(client)
133                                            + ") on "
134                                            + Strings.hex(wrappee)
135                                            + "."
136                                            + field.getName());
137                            clients.remove(client);
138                    }
139            }
140    
141            /**
142             * Register for a collectionUpdated event.<p>
143             *
144             * @param collection the collection whose updates must be notified
145             * @param client the object to notify when the field is updated
146             * @see #unregisterCollection(Wrappee,CollectionItem,CollectionUpdate) 
147             */
148            public void registerCollection(
149                    Wrappee wrappee,
150                    CollectionItem collection,
151                    CollectionUpdate client,
152                    Object param) {
153                    Map clients = (Map) collectionClients.get(collection.getName());
154                    if (clients == null) {
155                            clients = new HashMap();
156                            collectionClients.put(collection.getName(), clients);
157                    }
158                    clients.put(client, param);
159                    loggerReg.debug("registerCollection("
160                                    + Strings.hex(client)
161                                    + ") on "
162                                    + Strings.hex(wrappee)
163                                    + "."
164                                    + collection.getName());
165            }
166    
167            /**
168             * Unregister from a collectionUpdated event.<p>
169             *
170             * @param collection the collection whose updates must not be notified anymore
171             * @param client the object not to notify anymore
172             * @see #registerCollection(Wrappee,CollectionItem,CollectionUpdate,Object) 
173             */
174    
175            public void unregisterCollection(
176                    Wrappee wrappee,
177                    CollectionItem collection,
178                    CollectionUpdate client) {
179                    Map clients = (Map) collectionClients.get(collection.getName());
180                    if (clients != null) {
181                            loggerReg.debug("unregisterCollection("
182                                            + Strings.hex(client)
183                                            + ") on "
184                                            + Strings.hex(wrappee)
185                                            + "."
186                                            + collection.getName());
187                            clients.remove(client);
188                    }
189            }
190    
191            /**
192             * Register for a fieldUpdated event.<p>
193             *
194             * @param wrappee
195             * @param method the method whose updates must be notified
196             * @param client the object to notify when the field is updated
197             * @param param
198             * @see #unregisterField(Wrappee,FieldItem,FieldUpdate) 
199             */
200            public void registerMethod(
201                    Wrappee wrappee,
202                    MethodItem method,
203                    MethodUpdate client,
204                    Object param) {
205                    String key = method.getFullName();
206                    Map clients = (Map) methodClients.get(key);
207                    if (clients == null) {
208                            clients = new HashMap();
209                            methodClients.put(key, clients);
210                    }
211                    clients.put(client, param);
212    
213                    clients = (Map) allMethodClients.get(key);
214                    if (clients == null) {
215                            clients = new HashMap();
216                            allMethodClients.put(key, clients);
217                    }
218                    clients.put(client, param);
219    
220                    loggerReg.debug("registerMethod("
221                                    + Strings.hex(client)
222                                    + ") on "
223                                    + Strings.hex(wrappee)
224                                    + "."
225                                    + key);
226            }
227    
228            public void unregisterMethod(
229                    Wrappee wrappee,
230                    MethodItem method,
231                    MethodUpdate client) {
232                    String key = method.getFullName();
233                    Map clients = (Map) methodClients.get(key);
234                    if (clients != null) {
235                            loggerReg.debug("unregisterMethod("
236                                            + Strings.hex(client)
237                                            + ") on "
238                                            + Strings.hex(wrappee)
239                                            + "."
240                                            + method.getName());
241                            clients.remove(client);
242                    }
243                    clients = (Map) allMethodClients.get(key);
244                    if (clients != null) {
245                            clients.remove(client);
246                    }
247            }
248    
249            /**
250             * Register for an objectUpdated event. 
251             *
252             * @param client whom to notify when the wrappee is updated
253             * @param param an object that will be passed back to client on
254             * each notification event.
255             */
256            public void registerObject(
257                    Wrappee wrappee,
258                    ObjectUpdate client,
259                    Object param) {
260                    loggerReg.debug(
261                "registerObject " + Strings.hex(client) +
262                " on " + Strings.hex(wrappee));
263                    objectClients.put(client, param);
264            }
265    
266            /**
267             * Unregister from an objectUpdated event. 
268             *
269             * @param client whom not to notify anymore
270             */
271            public void unregisterObject(Wrappee wrappee, ObjectUpdate client) {
272                    loggerReg.debug(
273                "unregisterObject(" + Strings.hex(client)
274                + ") on " + Strings.hex(wrappee));
275                    objectClients.remove(client);
276            }
277    
278            /**
279             * Unregister a client from all update events
280             * @param wrappee the object to unregister from
281             * @param client the client to unregister
282             */
283            public void unregister(Wrappee wrappee, Object client) {
284                    loggerReg.debug("unregister(" + Strings.hex(client)
285                            + ") on " + Strings.hex(wrappee));
286                    objectClients.remove(client);
287                    Iterator i;
288                    i = fieldClients.values().iterator();
289                    while (i.hasNext()) {
290                            Map clients = (Map) i.next();
291                            clients.remove(client);
292                    }
293    
294                    i = collectionClients.values().iterator();
295                    while (i.hasNext()) {
296                            Map clients = (Map) i.next();
297                            clients.remove(client);
298                    }
299            }
300    
301            /**
302             * Get the views controlled by this wrapper.<p>
303             *
304             * @return the set of controlled views
305             */
306    
307            /*
308            public Vector getViews() {
309               return controlledViews;
310            }
311            */
312    
313            /**
314             * A wrapping method that updates the views of the objects.<p>
315             *
316             * It uses the RTTI aspect to know the fields and the collections
317             * that are written, added, or removed by the currently wrapped
318             * method. Then it upcall the <code>refreshStateComponent</code> of
319             * all the controlled views to refresh the implied GUI
320             * components.<p>
321             *
322             * @see org.objectweb.jac.core.rtti
323             * @see ObjectUpdate
324             * @see FieldUpdate
325             * @see CollectionUpdate
326             */
327    
328            public Object updateView(Interaction interaction) {
329    
330                    Object ret = proceed(interaction);
331    
332                    logger.debug(this
333                                    + " checking view updating for method "
334                                    + Strings.hex(interaction.wrappee)
335                                    + "."
336                                    + interaction.method.getName());
337    
338                    MethodItem method = (MethodItem) interaction.method;
339    
340                    /*
341                    JTextArea console = (JTextArea) method.getAttribute("Gui.loggingMethod"); 
342                    if( console != null ) {
343                       console.append( (String)arg(0) );
344                       console.setCaretPosition( console.getText().length() );
345                    }
346                    */
347    
348                    // notify registered clients for fieldUpdated
349                    FieldItem[] writtenFields = method.getWrittenFields();
350                    if (writtenFields != null) {
351                            Class cl = interaction.getActualClass();
352                            for (int i = 0; i < writtenFields.length; i++) {
353                                    if (writtenFields[i].getGetter() == interaction.method) {
354                                            logger.warn(
355                                                    "Skipping "     + interaction.method
356                                                            + " since it's the getter of " + writtenFields[i]);
357                                            continue;
358                                    }
359                                    logger.debug(method.getClassItem() + "."        + method.getFullName()
360                                                    + " writes " + writtenFields[i].getLongName());
361                    onFieldWrite(interaction.wrappee,cl,writtenFields[i]);
362    
363                            }
364                    }
365    
366                    // notify registered clients for collectionUpdated
367                    CollectionItem[] addedCollections = method.getAddedCollections();
368                    CollectionItem[] removedCollections = method.getRemovedCollections();
369                    CollectionItem[] modifiedCollections = method.getModifiedCollections();
370                    HashSet clientCollections = new HashSet();
371                    if (addedCollections != null) {
372                            clientCollections.addAll(Arrays.asList(addedCollections));
373                    }
374                    if (removedCollections != null) {
375                            clientCollections.addAll(Arrays.asList(removedCollections));
376                    }
377                    if (modifiedCollections != null) {
378                            clientCollections.addAll(Arrays.asList(modifiedCollections));
379                    }
380                    Iterator i = clientCollections.iterator();
381                    while (i.hasNext()) {
382                            CollectionItem collection = (CollectionItem) i.next();
383                            HashMap clients =
384                                    (HashMap) collectionClients.get(collection.getName());
385                            if (clients != null) {
386                                    Iterator it = ((Map) clients.clone()).keySet().iterator();
387                                    Object value =
388                                            collection.getThroughAccessor(interaction.wrappee);
389                                    while (it.hasNext()) {
390                                            CollectionUpdate client = (CollectionUpdate) it.next();
391                                            logger.debug("collectionUpdated("
392                                                            + collection.getLongName()
393                                                            + ") on "
394                                                            + Strings.hex(client));
395                                            try {
396                                                    if (method.isAdder())
397                                                            client.onAdd(
398                                                                    interaction.wrappee,
399                                                                    collection,
400                                                                    value,
401                                                                    interaction.args[0],
402                                                                    clients.get(client));
403                                                    else if (method.isRemover())
404                                                            client.onRemove(
405                                                                    interaction.wrappee,
406                                                                    collection,
407                                                                    value,
408                                                                    interaction.args[0],
409                                                                    clients.get(client));
410                                                    else
411                                                            client.onChange(
412                                                                    interaction.wrappee,
413                                                                    collection,
414                                                                    value,
415                                                                    clients.get(client));
416                                            } catch (Exception e) {
417                                                    logger.error(
418                                                            "Caught exception in collectionUpdated("
419                                                                    + collection.getLongName()
420                                                                    + ") on "
421                                                                    + Strings.hex(client), 
422                                e);
423                                            }
424                                    }
425                            }
426                            notifyDependentCollections(
427                                    collection,
428                                    interaction.wrappee,
429                                    method,
430                                    interaction.args);
431                    }
432    
433                    // notify registered clients for objectUpdated
434                    if (method.isModifier()) {
435                            logger.debug(method + " is a modifier");
436                onObjectModified(interaction.wrappee);
437                    }
438    
439                    return ret;
440            }
441    
442        public void onObjectModified(Object substance) {
443            Iterator it = (new HashMap(objectClients)).keySet().iterator();
444            while (it.hasNext()) {
445                ObjectUpdate client = (ObjectUpdate) it.next();
446                logger.debug("objectUpdated("
447                    + Strings.hex(substance)
448                    + ") on "
449                    + Strings.hex(client));
450                try {
451                    client.objectUpdated(
452                        substance,
453                        objectClients.get(client));
454                } catch (Exception e) {
455                    logger.error(
456                        "Caught exception in objectUpdated("
457                        + Strings.hex(substance)
458                        + ") on "
459                        + Strings.hex(client), 
460                        e);
461                }
462            }
463        }
464    
465        /**
466         * @param substance the object whose field is written
467         * @param cl the class of substance
468         * @param writtenField the field which is written
469         */
470        public void onFieldWrite(Object substance, Class cl, FieldItem writtenField) {
471            logger.debug("onFieldWrite "+substance+"."+writtenField);
472    
473            HashMap clients =
474                (HashMap) fieldClients.get(writtenField.getName());
475            if (clients != null) {
476                Iterator it = ((Map) clients.clone()).entrySet().iterator();
477                Object value =
478                    writtenField.getThroughAccessor(substance);
479                while (it.hasNext()) {
480                    Map.Entry entry = (Map.Entry) it.next();
481                    FieldUpdate client = (FieldUpdate) entry.getKey();
482                    logger.debug("  fieldUpdated("
483                        + writtenField.getLongName()
484                        + ") on "
485                        + Strings.hex(client));
486                    try {
487                        client.fieldUpdated(
488                            substance,
489                            writtenField,
490                            value,
491                            entry.getValue());
492                    } catch (Exception e) {
493                        logger.error(
494                            "Caught exception in fieldUpdated("
495                            + writtenField.getLongName()
496                            + ") on "
497                            + Strings.hex(client), e);
498                    }
499                }
500            }
501    
502            // Notify dependent fields
503            FieldItem[] dependentFields =
504                writtenField.getDependentFields();
505            for (int j = 0; j < dependentFields.length; j++) {
506                if (!dependentFields[j]
507                    .getClassItem()
508                    .getActualClass()
509                    .isAssignableFrom(cl)) {
510                    logger.debug("  Skipping dependentField "
511                        + dependentFields[j]
512                        + " (class=" + cl.getName() + ")");
513                    continue;
514                }
515    
516                logger.debug("  dependent field " + dependentFields[j].getLongName());
517                List depSubstances = (List)dependentFields[j].getSubstances(substance);
518                Iterator sit = depSubstances.iterator();
519                while(sit.hasNext()) {
520                    Wrappee depSubstance = (Wrappee)sit.next();
521                    logger.debug("  iterating on " + depSubstance);
522                    if (depSubstance!=null && depSubstance!=substance) {
523                        Wrapping.invokeRoleMethod(
524                            depSubstance,
525                            ViewControlWrapper.class,
526                            "onFieldWrite",
527                            new Object[] {depSubstance,depSubstance.getClass(),dependentFields[j].getField()}
528                        );
529                        Wrapping.invokeRoleMethod(
530                            depSubstance,
531                            ViewControlWrapper.class,
532                            "onObjectModified",
533                            new Object[] {depSubstance}
534                        );
535                    }
536                }
537                clients =
538                    (HashMap) fieldClients.get(
539                        dependentFields[j].getName());
540                if (clients != null) {
541                    Iterator it =
542                        ((Map) clients.clone()).entrySet().iterator();
543                    Object value =
544                        dependentFields[j].getThroughAccessor(
545                            substance);
546                    while (it.hasNext()) {
547                        Map.Entry entry = (Map.Entry) it.next();
548                        FieldUpdate client = (FieldUpdate) entry.getKey();
549                        logger.debug("  fieldUpdated("
550                            + dependentFields[j].getLongName()
551                            + ") on " + Strings.hex(client));
552                        try {
553                            client.fieldUpdated(
554                                substance,
555                                dependentFields[j],
556                                value,
557                                entry.getValue());
558                        } catch (Exception e) {
559                            logger.error(
560                                "Caught exception in fieldUpdated("
561                                + dependentFields[j].getLongName()
562                                + ") on " + Strings.hex(client),
563                                e);
564                        }
565                    }
566                }
567            }
568    
569            notifyDependentMethods(
570                methodClients,
571                writtenField,
572                substance,
573                cl,
574                new HashSet());
575    
576        }
577    
578    
579            /**
580             * @param methodClients the clients to notify
581             * @param member member item which caused the notification
582             * @param wrappee object which caused the notification
583             * @param cl class of wrappee or null
584             * @param alreadyNotified already notified clients, so that we can
585             * avoid infinite loops and notifying a client several times
586             */
587            void notifyDependentMethods(
588                    Map methodClients,
589                    MemberItem member,
590                    Object wrappee,
591                    Class cl,
592                    Set alreadyNotified) {
593                    logger.debug("notifyDependentMethods for " + member + " / " + wrappee);
594                    // Notify dependent methods
595                    MethodItem[] dependentMethods = member.getDependentMethods();
596                    for (int i = 0; i < dependentMethods.length; i++) {
597                            // the class of the dependent method
598                            Class depClass =
599                                    dependentMethods[i].getClassItem().getActualClass();
600                            if ((cl != null && !depClass.isAssignableFrom(cl))
601                                    || alreadyNotified.contains(dependentMethods[i])) {
602                                    logger.debug("    skipping " + dependentMethods[i].getLongName());
603                                    continue;
604                            }
605                            logger.debug("dependent method "
606                                            + dependentMethods[i].getClassItem()
607                                            + "."
608                                            + dependentMethods[i].getFullName());
609                            alreadyNotified.add(dependentMethods[i]);
610                            HashMap clients =
611                                    (HashMap) methodClients.get(dependentMethods[i].getFullName());
612                            if (clients != null) {
613                                    Iterator it = ((Map) clients.clone()).entrySet().iterator();
614                                    while (it.hasNext()) {
615                                            Map.Entry entry = (Map.Entry) it.next();
616                                            MethodUpdate client = (MethodUpdate) entry.getKey();
617                                            logger.debug("MethodUpdated("
618                                                            + dependentMethods[i].getLongName()
619                                                            + ") on "
620                                                            + Strings.hex(client));
621                                            try {
622                                                    client.methodUpdated(
623                                                            wrappee,
624                                                            dependentMethods[i],
625                                                            entry.getValue());
626                                            } catch (Exception e) {
627                                                    logger.error(
628                                                            "Caught exception in methodUpdated("
629                                                                    + dependentMethods[i].getLongName()
630                                                                    + ") on " + Strings.hex(client),
631                                e);
632                                            }
633                                    }
634                            }
635                            if (member != dependentMethods[i])
636                                    notifyDependentMethods(
637                                            allMethodClients,
638                                            dependentMethods[i],
639                                            wrappee,
640                                            null,
641                                            alreadyNotified);
642                    }
643            }
644    
645            void notifyDependentCollections(
646                    CollectionItem collection,
647                    Wrappee wrappee,
648                    MethodItem method,
649                    Object[] args) {
650                    // Notify dependent fields
651                    FieldItem[] dependentFields = collection.getDependentFields();
652                    for (int j = 0; j < dependentFields.length; j++) {
653                            logger.debug("dependent field "
654                                            + dependentFields[j].getName()
655                                            + " for "
656                                            + collection.getName());
657                            HashMap clients =
658                                    (HashMap) collectionClients.get(dependentFields[j].getName());
659                            if (clients != null) {
660                                    Iterator it2 = ((Map) clients.clone()).entrySet().iterator();
661                                    Object value = dependentFields[j].getThroughAccessor(wrappee);
662                                    while (it2.hasNext()) {
663                                            Map.Entry entry = (Map.Entry) it2.next();
664                                            CollectionUpdate client = (CollectionUpdate) entry.getKey();
665                                            logger.debug(
666                            "collectionUpdated(" + dependentFields[j].getLongName()
667                            + ") on " + Strings.hex(client));
668                                            try {
669                                                    client.onChange(
670                                                            wrappee,
671                                                            (CollectionItem) dependentFields[j],
672                                                            value,
673                                                            entry.getValue());
674                                            } catch (Exception e) {
675                                                    logger.error(
676                                                            "Caught exception in collectionUpdated("
677                                                                    + dependentFields[j].getLongName()
678                                                                    + ") on " + Strings.hex(client),
679                                e);
680                                            }
681                                    }
682                            }
683                    }
684            }
685    
686            public Object invoke(MethodInvocation invocation) throws Throwable {
687                    return updateView((Interaction) invocation);
688            }
689    
690            public Object construct(ConstructorInvocation invocation)
691                    throws Throwable {
692                    throw new Exception("This wrapper does not support constructor wrapping");
693            }
694    
695    }