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                Asset[] assets = (Asset[]) access.get(invocation.getInstance());
272
273                F.flow(assets).each(operation);
274
275                invocation.proceed();
276            }
277        });
278    }
279}