001// Copyright 2011 The Apache Software Foundation
002//
003// Licensed under the Apache License, Version 2.0 (the "License");
004// you may not use this file except in compliance with the License.
005// You may obtain a copy of the License at
006//
007// http://www.apache.org/licenses/LICENSE-2.0
008//
009// Unless required by applicable law or agreed to in writing, software
010// distributed under the License is distributed on an "AS IS" BASIS,
011// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
012// See the License for the specific language governing permissions and
013// limitations under the License.
014
015package org.apache.tapestry5.internal.transform;
016
017import org.apache.tapestry5.ComponentResources;
018import org.apache.tapestry5.func.F;
019import org.apache.tapestry5.func.Mapper;
020import org.apache.tapestry5.func.Predicate;
021import org.apache.tapestry5.internal.plastic.PlasticInternalUtils;
022import org.apache.tapestry5.ioc.services.FieldValueConduit;
023import org.apache.tapestry5.model.MutableComponentModel;
024import org.apache.tapestry5.plastic.*;
025import org.apache.tapestry5.runtime.Component;
026import org.apache.tapestry5.services.*;
027import org.apache.tapestry5.services.MethodInvocationResult;
028import org.apache.tapestry5.services.transform.TransformationSupport;
029import org.slf4j.Logger;
030
031import java.lang.annotation.Annotation;
032import java.lang.reflect.Method;
033import java.util.List;
034
035/**
036 * A re-implementation of {@link ClassTransformation} around an instance of {@link PlasticClass}, acting as a bridge
037 * for code written against the 5.2 and earlier APIs to work with the 5.3 API.
038 *
039 * @since 5.3
040 */
041@SuppressWarnings("deprecation")
042public class BridgeClassTransformation implements ClassTransformation
043{
044    private final PlasticClass plasticClass;
045
046    private final TransformationSupport support;
047
048    private final MutableComponentModel model;
049
050    private static final class WrapMethodHandleAsMethodAccess implements MethodAccess
051    {
052        private final MethodHandle handle;
053
054        private WrapMethodHandleAsMethodAccess(MethodHandle handle)
055        {
056            this.handle = handle;
057        }
058
059        public MethodInvocationResult invoke(Object target, Object... arguments)
060        {
061            final org.apache.tapestry5.plastic.MethodInvocationResult plasticResult = handle.invoke(target, arguments);
062
063            return new MethodInvocationResult()
064            {
065                public void rethrow()
066                {
067                    plasticResult.rethrow();
068                }
069
070                public boolean isFail()
071                {
072                    return plasticResult.didThrowCheckedException();
073                }
074
075                public <T extends Throwable> T getThrown(Class<T> throwableClass)
076                {
077                    return plasticResult.getCheckedException(throwableClass);
078                }
079
080                public Object getReturnValue()
081                {
082                    return plasticResult.getReturnValue();
083                }
084            };
085        }
086    }
087
088    private static <T> ComputedValue<T> toComputedValue(final ComponentValueProvider<T> provider)
089    {
090        return new ComputedValue<T>()
091        {
092            public T get(InstanceContext context)
093            {
094                ComponentResources resources = context.get(ComponentResources.class);
095
096                return provider.get(resources);
097            }
098        };
099    }
100
101    private static FieldConduit<Object> toFieldConduit(final FieldValueConduit fieldValueConduit)
102    {
103        return new FieldConduit<Object>()
104        {
105            public Object get(Object instance, InstanceContext context)
106            {
107                return fieldValueConduit.get();
108            }
109
110            public void set(Object instance, InstanceContext context, Object newValue)
111            {
112                fieldValueConduit.set(newValue);
113            }
114        };
115    }
116
117    private static TransformMethodSignature toMethodSignature(MethodDescription description)
118    {
119        return new TransformMethodSignature(description.modifiers, description.returnType, description.methodName,
120                description.argumentTypes, description.checkedExceptionTypes);
121    }
122
123    private static MethodDescription toMethodDescription(TransformMethodSignature signature)
124    {
125        return new MethodDescription(signature.getModifiers(), signature.getReturnType(), signature.getMethodName(),
126                signature.getParameterTypes(), signature.getSignature(), signature.getExceptionTypes());
127    }
128
129    private static class BridgeTransformField implements TransformField
130    {
131        private static final class WrapFieldHandleAsFieldAccess implements FieldAccess
132        {
133            private final FieldHandle handle;
134
135            private WrapFieldHandleAsFieldAccess(FieldHandle handle)
136            {
137                this.handle = handle;
138            }
139
140            public void write(Object instance, Object value)
141            {
142                handle.set(instance, value);
143            }
144
145            public Object read(Object instance)
146            {
147                return handle.get(instance);
148            }
149        }
150
151        private static final class WrapFieldValueConduitAsFieldConduit implements FieldConduit
152        {
153            private final FieldValueConduit conduit;
154
155            private WrapFieldValueConduitAsFieldConduit(FieldValueConduit conduit)
156            {
157                this.conduit = conduit;
158            }
159
160            public Object get(Object instance, InstanceContext context)
161            {
162                return conduit.get();
163            }
164
165            public void set(Object instance, InstanceContext context, Object newValue)
166            {
167                conduit.set(newValue);
168            }
169        }
170
171        private static final class WrapFieldHandleForFieldValueConduitAsFieldConduit implements FieldConduit<Object>
172        {
173            private final FieldHandle conduitHandle;
174
175            private WrapFieldHandleForFieldValueConduitAsFieldConduit(FieldHandle conduitHandle)
176            {
177                this.conduitHandle = conduitHandle;
178            }
179
180            private FieldValueConduit conduit(Object instance)
181            {
182                return (FieldValueConduit) conduitHandle.get(instance);
183            }
184
185            public Object get(Object instance, InstanceContext context)
186            {
187                return conduit(instance).get();
188            }
189
190            public void set(Object instance, InstanceContext context, Object newValue)
191            {
192                conduit(instance).set(newValue);
193            }
194        }
195
196        private static final class WrapCVP_FieldValueConduit_as_CV_FieldConduit implements
197                ComputedValue<FieldConduit<Object>>
198        {
199            private final ComponentValueProvider<FieldValueConduit> conduitProvider;
200
201            private WrapCVP_FieldValueConduit_as_CV_FieldConduit(
202                    ComponentValueProvider<FieldValueConduit> conduitProvider)
203            {
204                this.conduitProvider = conduitProvider;
205            }
206
207            public FieldConduit<Object> get(InstanceContext context)
208            {
209                ComponentResources resources = context.get(ComponentResources.class);
210
211                FieldValueConduit fieldValueConduit = conduitProvider.get(resources);
212
213                return toFieldConduit(fieldValueConduit);
214            }
215        }
216
217        private final PlasticField plasticField;
218
219        public BridgeTransformField(PlasticField plasticField)
220        {
221            this.plasticField = plasticField;
222        }
223
224        public int compareTo(TransformField o)
225        {
226            throw new IllegalStateException("compareTo() not yet implemented.");
227        }
228
229        public <T extends Annotation> T getAnnotation(Class<T> annotationClass)
230        {
231            return plasticField.getAnnotation(annotationClass);
232        }
233
234        public String getName()
235        {
236            return plasticField.getName();
237        }
238
239        public String getType()
240        {
241            return plasticField.getTypeName();
242        }
243
244        public String getSignature()
245        {
246            return plasticField.getGenericSignature();
247        }
248
249        public void claim(Object tag)
250        {
251            plasticField.claim(tag);
252        }
253
254        public void replaceAccess(final ComponentValueProvider<FieldValueConduit> conduitProvider)
255        {
256            plasticField.setComputedConduit(new WrapCVP_FieldValueConduit_as_CV_FieldConduit(conduitProvider));
257        }
258
259        /**
260         * We assume that the conduit field contains a {@link FieldValueConduit}, and that the field
261         * was introduced through this instance of BridgeClassTransformation.
262         */
263        public void replaceAccess(TransformField conduitField)
264        {
265            // Ugly:
266            PlasticField conduitFieldPlastic = ((BridgeTransformField) conduitField).plasticField;
267
268            final FieldHandle conduitHandle = conduitFieldPlastic.getHandle();
269
270            plasticField.setConduit(new WrapFieldHandleForFieldValueConduitAsFieldConduit(conduitHandle));
271        }
272
273        public void replaceAccess(final FieldValueConduit conduit)
274        {
275            plasticField.setConduit(new WrapFieldValueConduitAsFieldConduit(conduit));
276        }
277
278        public int getModifiers()
279        {
280            return plasticField.getModifiers();
281        }
282
283        public void inject(Object value)
284        {
285            plasticField.inject(value);
286        }
287
288        public <T> void injectIndirect(ComponentValueProvider<T> provider)
289        {
290            plasticField.injectComputed(toComputedValue(provider));
291        }
292
293        public FieldAccess getAccess()
294        {
295            final FieldHandle handle = plasticField.getHandle();
296
297            return new WrapFieldHandleAsFieldAccess(handle);
298        }
299    }
300
301    private static BridgeTransformField toTransformField(PlasticField plasticField)
302    {
303        return new BridgeTransformField(plasticField);
304    }
305
306    private static Mapper<PlasticField, TransformField> TO_TRANSFORM_FIELD = new Mapper<PlasticField, TransformField>()
307    {
308        public TransformField map(PlasticField element)
309        {
310            return toTransformField(element);
311        }
312    };
313
314    private static final class WrapMethodAdviceAsComponentMethodAdvice implements MethodAdvice
315    {
316        private final ComponentMethodAdvice advice;
317
318        private WrapMethodAdviceAsComponentMethodAdvice(ComponentMethodAdvice advice)
319        {
320            this.advice = advice;
321        }
322
323        public void advise(final MethodInvocation invocation)
324        {
325            advice.advise(new ComponentMethodInvocation()
326            {
327                public ComponentResources getComponentResources()
328                {
329                    return invocation.getInstanceContext().get(ComponentResources.class);
330                }
331
332                public void rethrow()
333                {
334                    invocation.rethrow();
335                }
336
337                public void proceed()
338                {
339                    invocation.proceed();
340                }
341
342                public void overrideThrown(Exception thrown)
343                {
344                    invocation.setCheckedException(thrown);
345                }
346
347                public void overrideResult(Object newResult)
348                {
349                    invocation.setReturnValue(newResult);
350                }
351
352                public void override(int index, Object newParameter)
353                {
354                    invocation.setParameter(index, newParameter);
355                }
356
357                public boolean isFail()
358                {
359                    return invocation.didThrowCheckedException();
360                }
361
362                public <T extends Throwable> T getThrown(Class<T> throwableClass)
363                {
364                    return invocation.getCheckedException(throwableClass);
365                }
366
367                public Class getResultType()
368                {
369                    return method().getReturnType();
370                }
371
372                public Object getResult()
373                {
374                    return invocation.getReturnValue();
375                }
376
377                public Class getParameterType(int index)
378                {
379                    return method().getParameterTypes()[index];
380                }
381
382                public int getParameterCount()
383                {
384                    return method().getParameterTypes().length;
385                }
386
387                public Object getParameter(int index)
388                {
389                    return invocation.getParameter(index);
390                }
391
392                public String getMethodName()
393                {
394                    return method().getName();
395                }
396
397                private Method method()
398                {
399                    return invocation.getMethod();
400                }
401
402                public <T extends Annotation> T getMethodAnnotation(Class<T> annotationClass)
403                {
404                    return invocation.getAnnotation(annotationClass);
405                }
406
407                public Component getInstance()
408                {
409                    return (Component) invocation.getInstance();
410                }
411            });
412        }
413    }
414
415    private static final class WrapAfterComponentInstanceOperationAsMethodAdvice implements MethodAdvice
416    {
417        private final ComponentInstanceOperation operation;
418
419        private WrapAfterComponentInstanceOperationAsMethodAdvice(ComponentInstanceOperation operation)
420        {
421            this.operation = operation;
422        }
423
424        public void advise(MethodInvocation invocation)
425        {
426            invocation.proceed();
427
428            operation.invoke((Component) invocation.getInstance());
429        }
430    }
431
432    private static final class WrapBeforeComponentInstanceOperationAsMethodAdvice implements MethodAdvice
433    {
434        private final ComponentInstanceOperation operation;
435
436        private WrapBeforeComponentInstanceOperationAsMethodAdvice(ComponentInstanceOperation operation)
437        {
438            this.operation = operation;
439        }
440
441        public void advise(MethodInvocation invocation)
442        {
443            operation.invoke((Component) invocation.getInstance());
444
445            invocation.proceed();
446        }
447    }
448
449    private class BridgeTransformMethod implements TransformMethod
450    {
451        private final PlasticMethod plasticMethod;
452
453        private TransformMethodSignature signature;
454
455        public BridgeTransformMethod(PlasticMethod plasticMethod)
456        {
457            this.plasticMethod = plasticMethod;
458        }
459
460        public int compareTo(TransformMethod o)
461        {
462            throw new IllegalStateException("compareTo() not yet implemented.");
463        }
464
465        public <T extends Annotation> T getAnnotation(Class<T> annotationClass)
466        {
467            return plasticMethod.getAnnotation(annotationClass);
468        }
469
470        public TransformMethodSignature getSignature()
471        {
472            if (signature == null)
473            {
474                signature = toMethodSignature(plasticMethod.getDescription());
475            }
476
477            return signature;
478        }
479
480        public String getName()
481        {
482            return plasticMethod.getDescription().methodName;
483        }
484
485        public MethodAccess getAccess()
486        {
487            final MethodHandle handle = plasticMethod.getHandle();
488
489            return new WrapMethodHandleAsMethodAccess(handle);
490        }
491
492        public void addAdvice(final ComponentMethodAdvice advice)
493        {
494            MethodAdvice plasticAdvice = new WrapMethodAdviceAsComponentMethodAdvice(advice);
495
496            plasticMethod.addAdvice(plasticAdvice);
497        }
498
499        public void addOperationBefore(final ComponentInstanceOperation operation)
500        {
501            plasticMethod.addAdvice(new WrapBeforeComponentInstanceOperationAsMethodAdvice(operation));
502        }
503
504        public void addOperationAfter(final ComponentInstanceOperation operation)
505        {
506            plasticMethod.addAdvice(new WrapAfterComponentInstanceOperationAsMethodAdvice(operation));
507        }
508
509        public String getMethodIdentifier()
510        {
511            return String.format("%s.%s", plasticClass.getClassName(), getSignature().getMediumDescription());
512        }
513
514        public boolean isOverride()
515        {
516            return plasticMethod.isOverride();
517        }
518
519        public <A extends Annotation> A getParameterAnnotation(int index, Class<A> annotationType)
520        {
521            return plasticMethod.getParameters().get(index).getAnnotation(annotationType);
522        }
523    }
524
525    private final Mapper<PlasticMethod, TransformMethod> toTransformMethod = new Mapper<PlasticMethod, TransformMethod>()
526    {
527        public TransformMethod map(PlasticMethod element)
528        {
529            return new BridgeTransformMethod(element);
530        }
531    };
532
533    public BridgeClassTransformation(PlasticClass plasticClass, TransformationSupport support,
534                                     MutableComponentModel model)
535    {
536        this.plasticClass = plasticClass;
537        this.support = support;
538        this.model = model;
539    }
540
541    public <T extends Annotation> T getAnnotation(Class<T> annotationClass)
542    {
543        return plasticClass.getAnnotation(annotationClass);
544    }
545
546    public String getClassName()
547    {
548        return plasticClass.getClassName();
549    }
550
551    public String newMemberName(String suggested)
552    {
553        return newMemberName("_", PlasticInternalUtils.toPropertyName(suggested));
554    }
555
556    public String newMemberName(String prefix, String baseName)
557    {
558        return new StringBuilder(prefix).append(PlasticUtils.nextUID()).append(baseName).toString();
559    }
560
561    public List<TransformField> matchFieldsWithAnnotation(Class<? extends Annotation> annotationClass)
562    {
563        return F.flow(plasticClass.getFieldsWithAnnotation(annotationClass)).map(TO_TRANSFORM_FIELD).toList();
564    }
565
566    public List<TransformMethod> matchMethods(Predicate<TransformMethod> predicate)
567    {
568        return F.flow(plasticClass.getMethods()).map(toTransformMethod).filter(predicate).toList();
569    }
570
571    public List<TransformMethod> matchMethodsWithAnnotation(Class<? extends Annotation> annotationType)
572    {
573        return F.flow(plasticClass.getMethodsWithAnnotation(annotationType)).map(toTransformMethod).toList();
574    }
575
576    public List<TransformField> matchFields(Predicate<TransformField> predicate)
577    {
578        return F.flow(plasticClass.getAllFields()).map(TO_TRANSFORM_FIELD).filter(predicate).toList();
579    }
580
581    public TransformField getField(String fieldName)
582    {
583        for (PlasticField f : plasticClass.getAllFields())
584        {
585            if (f.getName().equals(fieldName))
586            {
587                return toTransformField(f);
588            }
589        }
590
591        throw new IllegalArgumentException(String.format("Class %s does not contain a field named '%s'.",
592                plasticClass.getClassName(), fieldName));
593    }
594
595    public List<TransformField> matchUnclaimedFields()
596    {
597        return F.flow(plasticClass.getUnclaimedFields()).map(TO_TRANSFORM_FIELD).toList();
598    }
599
600    public boolean isField(String fieldName)
601    {
602        throw new IllegalArgumentException("isField() not yet implemented.");
603    }
604
605    public TransformField createField(int modifiers, String type, String suggestedName)
606    {
607        // TODO: modifiers are ignored
608
609        PlasticField newField = plasticClass.introduceField(type, suggestedName);
610
611        return toTransformField(newField);
612    }
613
614    public String addInjectedField(Class type, String suggestedName, Object value)
615    {
616        // TODO: The injected field is not actually protected or shared
617
618        PlasticField field = plasticClass.introduceField(type, suggestedName).inject(value);
619
620        return field.getName();
621    }
622
623    public <T> TransformField addIndirectInjectedField(Class<T> type, String suggestedName,
624                                                       ComponentValueProvider<T> provider)
625    {
626
627        PlasticField field = plasticClass.introduceField(type, suggestedName).injectComputed(toComputedValue(provider));
628
629        return toTransformField(field);
630    }
631
632    public void addImplementedInterface(Class interfaceClass)
633    {
634        plasticClass.introduceInterface(interfaceClass);
635    }
636
637    public Class toClass(String type)
638    {
639        return support.toClass(type);
640    }
641
642    public Logger getLogger()
643    {
644        return model.getLogger();
645    }
646
647    public boolean isRootTransformation()
648    {
649        return support.isRootTransformation();
650    }
651
652    public TransformMethod getOrCreateMethod(TransformMethodSignature signature)
653    {
654        MethodDescription md = toMethodDescription(signature);
655
656        PlasticMethod plasticMethod = plasticClass.introduceMethod(md);
657
658        return new BridgeTransformMethod(plasticMethod);
659    }
660
661    public boolean isDeclaredMethod(TransformMethodSignature signature)
662    {
663        final MethodDescription md = toMethodDescription(signature);
664
665        return !F.flow(plasticClass.getMethods()).filter(new Predicate<PlasticMethod>()
666        {
667            public boolean accept(PlasticMethod element)
668            {
669                return element.getDescription().equals(md);
670            }
671        }).isEmpty();
672    }
673
674    public void addComponentEventHandler(String eventType, int minContextValues, String methodDescription,
675                                         ComponentEventHandler handler)
676    {
677        support.addEventHandler(eventType, minContextValues, methodDescription, handler);
678    }
679}