001    /*
002      Copyright (C) 2001-2004 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, but
011      WITHOUT ANY WARRANTY; without even the implied warranty of
012      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
013      Lesser General Public License for more details.
014    
015      You should have received a copy of the GNU Lesser General Public
016      License along with this program; if not, write to the Free Software
017      Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
018      USA */
019    
020    package org.objectweb.jac.aspects.gui;
021    
022    import java.lang.reflect.Modifier;
023    import java.util.Arrays;
024    import java.util.Collection;
025    import java.util.Iterator;
026    import org.aopalliance.intercept.ConstructorInvocation;
027    import org.aopalliance.intercept.MethodInvocation;
028    import org.apache.log4j.Logger;
029    import org.objectweb.jac.core.ACManager;
030    import org.objectweb.jac.core.AspectComponent;
031    import org.objectweb.jac.core.Collaboration;
032    import org.objectweb.jac.core.Display;
033    import org.objectweb.jac.core.Interaction;
034    import org.objectweb.jac.core.Wrappee;
035    import org.objectweb.jac.core.Wrapper;
036    import org.objectweb.jac.core.rtti.AbstractMethodItem;
037    import org.objectweb.jac.core.rtti.ClassItem;
038    import org.objectweb.jac.core.rtti.ClassRepository;
039    import org.objectweb.jac.core.rtti.ConstructorItem;
040    import org.objectweb.jac.core.rtti.FieldItem;
041    import org.objectweb.jac.core.rtti.MethodItem;
042    import org.objectweb.jac.core.rtti.NamingConventions;
043    import org.objectweb.jac.util.VoidException;
044    
045    /**
046     * This wrapper asks the user the parameters of the invoked method if
047     * the attribute <code>Gui.askForParameters</code> is defined in the
048     * current collaboration.
049     *
050     * @see org.objectweb.jac.core.Display#showInput(Object,AbstractMethodItem,Object[])
051     * @see InputSequence
052     */
053    
054    public class InputWrapper extends Wrapper {
055        static Logger logger = Logger.getLogger("gui.input");
056    
057        public InputWrapper(AspectComponent ac) {
058            super(ac);
059        }
060    
061        /**
062         * Returns true if all classe of an array are Wrappee classes.
063         */
064        boolean areAllWrappees(Class[] types) {
065            for (int i=0; i<types.length; i++) {
066                if (!Wrappee.class.isAssignableFrom(types[i])) {
067                    return false;
068                }
069            }
070            return true;
071        }
072    
073        /**
074         * Calls the <code>Display.showInput</code> method on the current
075         * display.
076         *
077         * @return the value returned by the wrapped method
078         * @see org.objectweb.jac.core.Display#showInput(Object,AbstractMethodItem,Object[]) 
079         */
080        public Object askForParameters(Interaction interaction)
081            throws InputFailedException, InputCanceledException 
082        {
083            boolean proceed = true;
084    
085            AbstractMethodItem m =
086                (AbstractMethodItem) attr(GuiAC.ASK_FOR_PARAMETERS);
087            AbstractMethodItem method = (AbstractMethodItem) interaction.method;
088    
089            logger.debug("Gui.askForParameters=" + (m!=null ? m.getLongName() : "null"));
090            logger.debug("current method=" + (method!=null ? method.getLongName(): "null"));
091    
092            if (m != null && method != null) {
093                logger.debug("actual classes: "
094                        + ((ClassItem) m.getParent()).getActualClass() + ","
095                        + ((ClassItem) method.getParent()).getActualClass());
096            }
097            Display display = null;
098            // DIRTY HACK
099            if (m != null && m.getLongName().equals(method.getLongName())) {
100                logger.debug("Entering parameters asking for "
101                             + interaction.wrappee + " : " + method.getLongName()
102                             + " askForParameters = " + m.getLongName()
103                             + " autoCreate=" + attr(GuiAC.AUTO_CREATE)
104                             + " view=" + attr(GuiAC.VIEW));
105    
106                AbstractMethodItem invoked = (AbstractMethodItem)attr(GuiAC.INVOKED_METHOD);
107                if (invoked!=null)
108                    method = invoked;
109                DisplayContext context =
110                    (DisplayContext) attr(GuiAC.DISPLAY_CONTEXT);
111                display = context.getDisplay();
112    
113                attrdef(GuiAC.ASK_FOR_PARAMETERS, null);
114    
115                if (display != null) {
116                    MethodItem interactionHandler =
117                        GuiAC.getInteractionHandler(method);
118                    if (interactionHandler != null) {
119                        logger.debug("invoking interactionHandler " + interactionHandler);
120                        proceed =
121                            ((Boolean) interactionHandler
122                                .invokeStatic(
123                                    new Object[] { interaction, context }))
124                                .booleanValue();
125                    } else {
126    
127                        Class[] paramTypes = method.getParameterTypes();
128                        // automatic created parameter handling
129                        if (attr(GuiAC.NO_AUTO_CREATE) == null
130                            && (GuiAC.isAutoCreateParameters(method)
131                                || attr(GuiAC.AUTO_CREATE) != null
132                                || (method.isAdder()
133                                    && GuiAC.isAutoCreate(
134                                        method.getAddedCollection()))
135                                || (method.isSetter() 
136                                    && GuiAC.isAutoCreate(
137                                        method.getSetField())))
138                            && areAllWrappees(paramTypes)) {
139                            proceed =
140                                autoCreate(
141                                    display,
142                                    context,
143                                    method,
144                                    interaction,
145                                    paramTypes);
146                        } else {
147                            proceed =
148                                askingSequence(
149                                    display,
150                                    context,
151                                    method,
152                                    interaction,
153                                    paramTypes);
154                        }
155                    }
156                } else {
157                    logger.debug("No display available");
158                    throw new InputFailedException("No display available");
159                }
160                logger.debug("proceed = " + proceed);
161                logger.debug("args=" + Arrays.asList(interaction.args));
162            }
163    
164            if (proceed) {
165                Object result = proceed(interaction);
166                return result;
167            } else {
168                throw new InputCanceledException(method);
169            }
170    
171        }
172    
173        protected boolean autoCreate(
174            Display display,
175            DisplayContext context,
176            AbstractMethodItem method,
177            Interaction interaction,
178            Class[] paramTypes) 
179        {
180            logger.debug("in parameter autoCreation");
181            if (method.isAdder()
182                 && GuiAC.isAutoCreate(
183                     method.getAddedCollection())) {
184                EventHandler.setOppositeRole(method.getAddedCollection());
185            } else if (method.isSetter() 
186                       && GuiAC.isAutoCreate(
187                           method.getSetField())) {
188                EventHandler.setOppositeRole(method.getSetField());
189            }
190            attrdef(GuiAC.AUTO_CREATION, Boolean.TRUE);
191            //attrdef(GuiAC.EMBEDDED_EDITORS, Boolean.TRUE);
192            ACManager.getACM().beforeWrappeeInit(null);
193            boolean proceed = true;
194            try {
195                boolean ok = true;
196                for (int i=0; i<paramTypes.length; i++) {
197    
198                    // do not create if already filled in!
199                    Object arg = method.getParameter(interaction.args,i);
200                    if (arg != null)
201                        continue;
202    
203                    //if( paramTypes[i] instanceof Wrappee ) {
204                    if (!Modifier.isPublic(paramTypes[i].getModifiers())) {
205                        logger.error(
206                            "cannot instantiate non public class "
207                                + paramTypes[i].getName());
208                    }
209                    Collaboration collab = Collaboration.get();
210                    collab.addAttribute(GuiAC.AUTOCREATE_REASON, interaction);
211                    try {
212                        method.setParameter(interaction.args,i,newInstance(paramTypes[i], display));
213                        arg = method.getParameter(interaction.args,i);
214                    } catch (Exception e) {
215                        e.printStackTrace();
216                    } finally {
217                        collab.removeAttribute(GuiAC.AUTOCREATE_REASON);
218                    }
219    
220                    if (arg != null) {
221                        // Use initializer if any
222                        MethodItem init = null;
223                        if (method.isAdder()) {
224                            init =
225                                GuiAC.getInitiliazer(
226                                    method.getAddedCollections()[0]);
227                        } else if (method.isSetter()) {
228                            init = GuiAC.getInitiliazer(method.getSetField());
229                        }
230                        if (init != null) {
231                            logger.debug("Initializing new instance of "
232                                    + paramTypes[i].getName()
233                                    + " with "
234                                    + init.getFullName());
235                            init.invoke(
236                                interaction.wrappee,
237                                new Object[] { arg });
238                        }
239    
240                        ok =
241                            display.showModal(
242                                arg,
243                                "Object",
244                                new String[] { GuiAC.AUTOCREATE_VIEW },
245                                interaction.args.length > 1
246                                    ? GuiAC.getLabel(method)
247                                        + " ("
248                                        + NamingConventions.getShortClassName(
249                                            arg.getClass())
250                                        + ")"
251                                    : GuiAC.getLabel(method),
252                                "Fill the needed information and validate.",
253                                context.getWindow(),
254                                true,
255                                true,
256                                false);
257                    } else {
258                        ok = false;
259                    }
260                    if (!ok) {
261                        logger.debug("Input cancelled for " + interaction.method);
262                        proceed = false;
263                        break;
264                    } else {
265                        if (attr(GuiAC.VIEW) != null) {
266                            /* What was this supposed to do ???
267                               Wrapping.invokeRoleMethod(
268                               ((Wrappee)interaction.args[i]),"addView",
269                               new Object[] {attr(GuiAC.VIEW)});
270                            */
271                        }
272                        // if some field must be autocreated, we ask the user
273                        // to input them (except if already filled)
274                        // this should be reccursive but it is not implemented
275                        // for the moment
276                        ClassItem parameterClass =
277                            ClassRepository.get().getClass(paramTypes[i]);
278                        String[] autoCreatedFields =
279                            (String[]) parameterClass.getAttribute(
280                                GuiAC.AUTO_CREATED_STATE);
281                        if (autoCreatedFields != null) {
282                            for (int j=0; j<autoCreatedFields.length; j++) {
283                                FieldItem f =
284                                    parameterClass.getField(autoCreatedFields[j]);
285                                if (f != null
286                                    && f.getThroughAccessor(arg) == null) {
287                                    Object newInstance = null;
288                                    try {
289                                        newInstance = f.getType().newInstance();
290                                        ok =
291                                            display.showModal(
292                                                newInstance,
293                                                "Object",
294                                                new String[] {
295                                                     GuiAC.AUTOCREATE_VIEW },
296                                                "Creation of a new "
297                                                    + NamingConventions
298                                                        .getShortClassName(
299                                                        newInstance.getClass()),
300                                                "You did not fill any "
301                                                    + f.getName()
302                                                    + ". "
303                                                    + "Fill the needed informations and validate.",
304                                                context.getWindow(),
305                                                true,
306                                                true,
307                                                false);
308                                        if (ok) {
309                                            f.setThroughWriter(
310                                                arg,
311                                                newInstance);
312                                        }
313                                    } catch (Exception e) {
314                                        e.printStackTrace();
315                                    }
316                                }
317                            }
318                        }
319                    }
320                }
321            } finally {
322                attrdef(GuiAC.AUTO_CREATION, null);
323                //attrdef(GuiAC.EMBEDDED_EDITORS, null);
324                ACManager.getACM().afterWrappeeInit(null);
325            }
326            return proceed;
327        }
328    
329        protected boolean askingSequence(
330            Display display,
331            DisplayContext context,
332            AbstractMethodItem method,
333            Interaction interaction,
334            Class[] paramTypes)
335            throws InputFailedException, InputCanceledException 
336        {
337            boolean proceed = true;
338            Object[] askingSequence =
339                (Object[]) method.getAttribute(GuiAC.ASKING_SEQUENCE);
340            boolean allCreated = false;
341            if (paramTypes.length > 0) {
342                if (askingSequence != null) {
343                    logger.debug("input sequence found");
344                    allCreated = true;
345                    for (int i=0; i<paramTypes.length; i++) {
346                        if (askingSequence[i].equals("autoCreate")) {
347                            method.setParameter(
348                                interaction.args,
349                                i,
350                                create(
351                                    ClassRepository.get().getClass(paramTypes[i]),
352                                    display));
353                        } else {
354                            allCreated = false;
355                        }
356                    }
357                }
358                if (!allCreated) {
359                    if (!display.fillParameters(method, interaction.args)) {
360                        logger.debug("asking for parameters");
361                        GuiAC.pushGraphicContext(method);
362                        try {
363                            proceed =
364                                display.showInput(
365                                    interaction.wrappee,
366                                    method,
367                                    interaction.args);
368                        } finally {
369                            GuiAC.popGraphicContext();
370                        }
371                        logger.debug("args=" + Arrays.asList(interaction.args));
372                    }
373                } else {
374                    logger.debug("skipping asking for parameters");
375                    proceed = true;
376                }
377            }
378            return proceed;
379        }
380    
381        /**
382         * This method performs all the inputs operations for an instance creation.
383         *
384         * @param classItem the class to instantiate
385         * @param display the display to use 
386         */
387        public Object create(ClassItem classItem, Display display)
388            throws InputFailedException, InputCanceledException 
389        {
390            Object newInstance = null;
391            Collection constructors = classItem.getConstructors();
392            Iterator it = constructors.iterator();
393            ConstructorItem c;
394            c = (ConstructorItem) it.next();
395            if (it.hasNext()) {
396                c = (ConstructorItem) it.next();
397            }
398            if (c.getParameterTypes().length > 0) {
399                Object[] parameters = new Object[c.getParameterTypes().length];
400    
401                String inputSequenceName =
402                    (String) c.getAttribute("Gui.inputSequence");
403                logger.debug("input sequence name = " + inputSequenceName);
404                if (inputSequenceName == null) {
405                    display.showInput(null, c, parameters);
406                } else {
407                    org.objectweb.jac.aspects.gui.InputSequence inputSeq = null;
408                    try {
409                        inputSeq =
410                            (org.objectweb.jac.aspects.gui.InputSequence) Class
411                                .forName(inputSequenceName)
412                                .getConstructor(
413                                    new Class[] {
414                                        Display.class,
415                                        AbstractMethodItem.class,
416                                        Object[].class })
417                                .newInstance(
418                                    new Object[] { display, c, parameters });
419                    } catch (Exception e) {
420                        throw new InputFailedException("unabled to create input sequence");
421                    }
422                    if (!inputSeq.proceedInputs()) {
423                        throw new InputCanceledException(c);
424                    }
425                }
426    
427                try {
428                    newInstance = c.newInstance(parameters);
429                } catch (Exception e) {
430                    e.printStackTrace();
431                }
432    
433            } else {
434                try {
435                    newInstance = c.newInstance();
436                } catch (Exception e) {
437                    e.printStackTrace();
438                }
439            }
440            return newInstance;
441        }
442    
443        /**
444         * Create a new instance of a class. If the class has known
445         * subclasses, the user will be given the choice of the actual
446         * class to instantiate.
447         * @param cl the class to instantiate
448         * @param display display to use for user interaction
449         * @return a new instance of the class
450         */
451        public static Object newInstance(Class cl, Display display)
452            throws InstantiationException, IllegalAccessException 
453        {
454            ClassItem cli = ClassRepository.get().getClass(cl);
455            Collection subClasses = cli.getChildren();
456            if (!subClasses.isEmpty() && Modifier.isAbstract(cl.getModifiers())) {
457                ClassChooser chooser = new ClassChooser(cli);
458                ClassItem classChooserClass =
459                    ClassRepository.get().getClass(ClassChooser.class);
460                ClassItem[] choice = new ClassItem[1];
461                boolean ok =
462                    display.showInput(
463                        chooser,
464                        classChooserClass.getMethod("setChoice"),
465                        choice);
466                if (!ok) {
467                    return null;
468                } else {
469                    return choice[0].newInstance();
470                }
471            } else {
472                return cl.newInstance();
473            }
474        }
475    
476        public void catchInputCanceled(InputCanceledException e) {
477            logger.debug("catching exception: " + e.toString());
478            attrdef(GuiAC.AUTO_CREATE, null);
479            throw new VoidException();
480        }
481    
482        public Object invoke(MethodInvocation invocation) throws Throwable {
483            return askForParameters((Interaction) invocation);
484        }
485    
486        public Object construct(ConstructorInvocation invocation)
487            throws Throwable {
488            return askForParameters((Interaction) invocation);
489        }
490    }
491    
492    class InputFailedException extends Exception {
493        public InputFailedException() {
494        }
495        public InputFailedException(String msg) {
496            super(msg);
497        }
498    }