001// Copyright 2010-2012 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.Asset;
018import org.apache.tapestry5.ComponentResources;
019import org.apache.tapestry5.annotations.Import;
020import org.apache.tapestry5.annotations.SetupRender;
021import org.apache.tapestry5.func.F;
022import org.apache.tapestry5.func.Mapper;
023import org.apache.tapestry5.func.Worker;
024import org.apache.tapestry5.ioc.services.SymbolSource;
025import org.apache.tapestry5.model.MutableComponentModel;
026import org.apache.tapestry5.plastic.*;
027import org.apache.tapestry5.services.AssetSource;
028import org.apache.tapestry5.services.TransformConstants;
029import org.apache.tapestry5.services.javascript.Initialization;
030import org.apache.tapestry5.services.javascript.JavaScriptSupport;
031import org.apache.tapestry5.services.transform.ComponentClassTransformWorker2;
032import org.apache.tapestry5.services.transform.TransformationSupport;
033
034import java.util.ArrayList;
035import java.util.List;
036
037/**
038 * Implements the {@link Import} annotation, both at the class and at the method level.
039 *
040 * @since 5.2.0
041 */
042public class ImportWorker implements ComponentClassTransformWorker2
043{
044    private final JavaScriptSupport javascriptSupport;
045
046    private final SymbolSource symbolSource;
047
048    private final AssetSource assetSource;
049
050    private final Worker<Asset> importLibrary = new Worker<Asset>()
051    {
052        public void work(Asset asset)
053        {
054            javascriptSupport.importJavaScriptLibrary(asset);
055        }
056    };
057
058    private final Worker<Asset> importStylesheet = new Worker<Asset>()
059    {
060        public void work(Asset asset)
061        {
062            javascriptSupport.importStylesheet(asset);
063        }
064    };
065
066    private final Mapper<String, String> expandSymbols = new Mapper<String, String>()
067    {
068        public String map(String element)
069        {
070            return symbolSource.expandSymbols(element);
071        }
072    };
073
074    public ImportWorker(JavaScriptSupport javascriptSupport, SymbolSource symbolSource, AssetSource assetSource)
075    {
076        this.javascriptSupport = javascriptSupport;
077        this.symbolSource = symbolSource;
078        this.assetSource = assetSource;
079    }
080
081    public void transform(PlasticClass componentClass, TransformationSupport support, MutableComponentModel model)
082    {
083        processClassAnnotationAtSetupRenderPhase(componentClass, model);
084
085        for (PlasticMethod m : componentClass.getMethodsWithAnnotation(Import.class))
086        {
087            decorateMethod(componentClass, model, m);
088        }
089    }
090
091    private void processClassAnnotationAtSetupRenderPhase(PlasticClass componentClass, MutableComponentModel model)
092    {
093        Import annotation = componentClass.getAnnotation(Import.class);
094
095        if (annotation != null)
096        {
097            PlasticMethod setupRender = componentClass.introduceMethod(TransformConstants.SETUP_RENDER_DESCRIPTION);
098
099            decorateMethod(componentClass, model, setupRender, annotation);
100
101            model.addRenderPhase(SetupRender.class);
102        }
103    }
104
105    private void decorateMethod(PlasticClass componentClass, MutableComponentModel model, PlasticMethod method)
106    {
107        Import annotation = method.getAnnotation(Import.class);
108
109        decorateMethod(componentClass, model, method, annotation);
110    }
111
112    private void decorateMethod(PlasticClass componentClass, MutableComponentModel model, PlasticMethod method,
113                                Import annotation)
114    {
115        importStacks(method, annotation.stack());
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);
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)
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);
249            }
250        });
251    }
252
253    private Asset[] convertPathsToAssetArray(final ComponentResources resources, String[] assetPaths)
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);
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                Asset[] assets = (Asset[]) access.get(invocation.getInstance());
272
273                F.flow(assets).each(operation);
274
275                invocation.proceed();
276            }
277        });
278    }
279}