    001// Licensed under the Apache License, Version 2.0 (the "License");
    002// you may not use this file except in compliance with the License.
    003// You may obtain a copy of the License at
    004//
    005// http://www.apache.org/licenses/LICENSE-2.0
    006//
    007// Unless required by applicable law or agreed to in writing, software
    008// distributed under the License is distributed on an "AS IS" BASIS,
    009// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    010// See the License for the specific language governing permissions and
    011// limitations under the License.
    012
    013package org.apache.tapestry5.internal.transform;
    014
    015import org.apache.tapestry5.Asset;
    016import org.apache.tapestry5.ComponentResources;
    017import org.apache.tapestry5.annotations.Import;
    018import org.apache.tapestry5.annotations.SetupRender;
    019import org.apache.tapestry5.func.F;
    020import org.apache.tapestry5.func.Mapper;
    021import org.apache.tapestry5.func.Worker;
    022import org.apache.tapestry5.ioc.services.SymbolSource;
    023import org.apache.tapestry5.model.MutableComponentModel;
    024import org.apache.tapestry5.plastic.*;
    025import org.apache.tapestry5.services.AssetSource;
    026import org.apache.tapestry5.services.TransformConstants;
    027import org.apache.tapestry5.services.javascript.Initialization;
    028import org.apache.tapestry5.services.javascript.JavaScriptSupport;
    029import org.apache.tapestry5.services.transform.ComponentClassTransformWorker2;
    030import org.apache.tapestry5.services.transform.TransformationSupport;
    031
    032import java.util.ArrayList;
    033import java.util.List;
    034
    035/**
    036 * Implements the {@link Import} annotation, both at the class and at the method level.
    037 *
    038 * @since 5.2.0
    039 */
    040public class ImportWorker implements ComponentClassTransformWorker2
    041{
    042    private final JavaScriptSupport javascriptSupport;
    043
    044    private final SymbolSource symbolSource;
    045
    046    private final AssetSource assetSource;
    047
    048    private final Worker<Asset> importLibrary = new Worker<Asset>()
    049    {
    050        public void work(Asset asset)
    051        {
    052            javascriptSupport.importJavaScriptLibrary(asset);
    053        }
    054    };
    055
    056    private final Worker<Asset> importStylesheet = new Worker<Asset>()
    057    {
    058        public void work(Asset asset)
    059        {
    060            javascriptSupport.importStylesheet(asset);
    061        }
    062    };
    063
    064    private final Mapper<String, String> expandSymbols = new Mapper<String, String>()
    065    {
    066        public String map(String element)
    067        {
    068            return symbolSource.expandSymbols(element);
    069        }
    070    };
    071
    072    public ImportWorker(JavaScriptSupport javascriptSupport, SymbolSource symbolSource, AssetSource assetSource)
    073    {
    074        this.javascriptSupport = javascriptSupport;
    075        this.symbolSource = symbolSource;
    076        this.assetSource = assetSource;
    077    }
    078
    079    public void transform(PlasticClass componentClass, TransformationSupport support, MutableComponentModel model)
    080    {
    081        processClassAnnotationAtSetupRenderPhase(componentClass, model);
    082
    083        for (PlasticMethod m : componentClass.getMethodsWithAnnotation(Import.class))
    084        {
    085            decorateMethod(componentClass, model, m);
    086        }
    087    }
    088
    089    private void processClassAnnotationAtSetupRenderPhase(PlasticClass componentClass, MutableComponentModel model)
    090    {
    091        Import annotation = componentClass.getAnnotation(Import.class);
    092
    093        if (annotation != null)
    094        {
    095            PlasticMethod setupRender = componentClass.introduceMethod(TransformConstants.SETUP_RENDER_DESCRIPTION);
    096
    097            decorateMethod(componentClass, model, setupRender, annotation);
    098
    099            model.addRenderPhase(SetupRender.class);
    100        }
    101    }
    102
    103    private void decorateMethod(PlasticClass componentClass, MutableComponentModel model, PlasticMethod method)
    104    {
    105        Import annotation = method.getAnnotation(Import.class);
    106
    107        decorateMethod(componentClass, model, method, annotation);
    108    }
    109
    110    private void decorateMethod(PlasticClass componentClass, MutableComponentModel model, PlasticMethod method,
    111                                Import annotation)
    112    {
    113        importStacks(method, annotation.stack());
    114
    115        String libraryName = model.getLibraryName();
    116
    117        importLibraries(componentClass, model, method, annotation.library());
    118
    119        importStylesheets(componentClass, model, method, annotation.stylesheet());
    120
    121        importModules(method, annotation.module());
    122    }
    123
    124    private void importStacks(PlasticMethod method, String[] stacks)
    125    {
    126        if (stacks.length != 0)
    127        {
    128            method.addAdvice(createImportStackAdvice(stacks));
    129        }
    130    }
    131
    132    private MethodAdvice createImportStackAdvice(final String[] stacks)
    133    {
    134        return new MethodAdvice()
    135        {
    136            public void advise(MethodInvocation invocation)
    137            {
    138                for (String stack : stacks)
    139                {
    140                    javascriptSupport.importStack(stack);
    141                }
    142
    143                invocation.proceed();
    144            }
    145        };
    146    }
    147
    148    private void importModules(PlasticMethod method, String[] moduleNames)
    149    {
    150        if (moduleNames.length != 0)
    151        {
    152            method.addAdvice(createImportModulesAdvice(moduleNames));
    153        }
    154    }
    155
    156    class ModuleImport
    157    {
    158        final String moduleName, functionName;
    159
    160        ModuleImport(String moduleName, String functionName)
    161        {
    162            this.moduleName = moduleName;
    163            this.functionName = functionName;
    164        }
    165
    166        void apply(JavaScriptSupport javaScriptSupport)
    167        {
    168            Initialization initialization = javaScriptSupport.require(moduleName);
    169
    170            if (functionName != null)
    171            {
    172                initialization.invoke(functionName);
    173            }
    174        }
    175    }
    176
    177    private MethodAdvice createImportModulesAdvice(final String[] moduleNames)
    178    {
    179        final List<ModuleImport> moduleImports = new ArrayList<ModuleImport>(moduleNames.length);
    180
    181        for (String name : moduleNames)
    182        {
    183            int colonx = name.indexOf(':');
    184
    185            String moduleName = colonx < 0 ? name : name.substring(0, colonx);
    186            String functionName = colonx < 0 ? null : name.substring(colonx + 1);
    187
    188            moduleImports.add(new ModuleImport(moduleName, functionName));
    189        }
    190
    191        return new MethodAdvice()
    192        {
    193            public void advise(MethodInvocation invocation)
    194            {
    195                for (ModuleImport moduleImport : moduleImports)
    196                {
    197                    moduleImport.apply(javascriptSupport);
    198                }
    199
    200                invocation.proceed();
    201            }
    202        };
    203    }
    204
    205    private void importLibraries(PlasticClass plasticClass, MutableComponentModel model, PlasticMethod method,
    206                                 String[] paths)
    207    {
    208        decorateMethodWithOperation(plasticClass, model, method, paths, importLibrary);
    209    }
    210
    211    private void importStylesheets(PlasticClass plasticClass, MutableComponentModel model, PlasticMethod method,
    212                                   String[] paths)
    213    {
    214        decorateMethodWithOperation(plasticClass, model, method, paths, importStylesheet);
    215    }
    216
    217    private void decorateMethodWithOperation(PlasticClass componentClass, MutableComponentModel model,
    218                                             PlasticMethod method, String[] paths, Worker<Asset> operation)
    219    {
    220        if (paths.length == 0)
    221        {
    222            return;
    223        }
    224
    225        String[] expandedPaths = expandPaths(paths);
    226
    227        PlasticField assetListField = componentClass.introduceField(Asset[].class,
    228                "importedAssets_" + method.getDescription().methodName);
    229
    230        initializeAssetsFromPaths(expandedPaths, assetListField, model.getLibraryName());
    231
    232        addMethodAssetOperationAdvice(method, assetListField.getHandle(), operation);
    233    }
    234
    235    private String[] expandPaths(String[] paths)
    236    {
    237        return F.flow(paths).map(expandSymbols).toArray(String.class);
    238    }
    239
    240    private void initializeAssetsFromPaths(final String[] expandedPaths, PlasticField assetsField, final String libraryName)
    241    {
    242        assetsField.injectComputed(new ComputedValue<Asset[]>()
    243        {
    244            public Asset[] get(InstanceContext context)
    245            {
    246                ComponentResources resources = context.get(ComponentResources.class);
    247
    248                return convertPathsToAssetArray(resources, expandedPaths, libraryName);
    249            }
    250        });
    251    }
    252
    253    private Asset[] convertPathsToAssetArray(final ComponentResources resources, String[] assetPaths, final String libraryName)
    254    {
    255        return F.flow(assetPaths).map(new Mapper<String, Asset>()
    256        {
    257            public Asset map(String assetPath)
    258            {
    259                return assetSource.getComponentAsset(resources, assetPath, libraryName);
    260            }
    261        }).toArray(Asset.class);
    262    }
    263
    264    private void addMethodAssetOperationAdvice(PlasticMethod method, final FieldHandle access,
    265                                               final Worker<Asset> operation)
    266    {
    267        method.addAdvice(new MethodAdvice()
    268        {
    269            public void advise(MethodInvocation invocation)
    270            {
    271                invocation.proceed();
    272
    273                Asset[] assets = (Asset[]) access.get(invocation.getInstance());
    274
    275                F.flow(assets).each(operation);
    276            }
    277        });
    278    }
    279}