001/**
002 *   GRANITE DATA SERVICES
003 *   Copyright (C) 2006-2014 GRANITE DATA SERVICES S.A.S.
004 *
005 *   This file is part of the Granite Data Services Platform.
006 *
007 *   Granite Data Services is free software; you can redistribute it and/or
008 *   modify it under the terms of the GNU Lesser General Public
009 *   License as published by the Free Software Foundation; either
010 *   version 2.1 of the License, or (at your option) any later version.
011 *
012 *   Granite Data Services is distributed in the hope that it will be useful,
013 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
014 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
015 *   General Public License for more details.
016 *
017 *   You should have received a copy of the GNU Lesser General Public
018 *   License along with this library; if not, write to the Free Software
019 *   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
020 *   USA, or see <http://www.gnu.org/licenses/>.
021 */
022package org.granite.generator.ant;
023
024import java.io.File;
025import java.io.FileNotFoundException;
026import java.io.PrintWriter;
027import java.io.StringWriter;
028import java.util.ArrayList;
029import java.util.HashMap;
030import java.util.List;
031import java.util.Map;
032
033import org.apache.tools.ant.AntClassLoader;
034import org.apache.tools.ant.BuildException;
035import org.apache.tools.ant.DirectoryScanner;
036import org.apache.tools.ant.Project;
037import org.apache.tools.ant.Task;
038import org.apache.tools.ant.types.FileSet;
039import org.apache.tools.ant.types.Path;
040import org.apache.tools.ant.types.Reference;
041import org.apache.tools.ant.types.ZipFileSet;
042import org.granite.generator.Generator;
043import org.granite.generator.Output;
044import org.granite.generator.TemplateUri;
045import org.granite.generator.Transformer;
046import org.granite.generator.as3.As3TypeFactory;
047import org.granite.generator.as3.DefaultEntityFactory;
048import org.granite.generator.as3.DefaultRemoteDestinationFactory;
049import org.granite.generator.as3.EntityFactory;
050import org.granite.generator.as3.JavaAs3GroovyConfiguration;
051import org.granite.generator.as3.JavaAs3Input;
052import org.granite.generator.as3.PackageTranslator;
053import org.granite.generator.as3.RemoteDestinationFactory;
054import org.granite.generator.as3.reflect.JavaType.Kind;
055import org.granite.generator.gsp.GroovyTemplateFactory;
056
057/**
058 * @author Franck WOLFF
059 */
060public abstract class AbstractAntJavaGenTask extends Task implements JavaAs3GroovyConfiguration {
061
062    ///////////////////////////////////////////////////////////////////////////
063    // Configurable fields (xml attributes).
064
065    private String outputdir = ".";
066    private String baseoutputdir = null;
067
068    private String uid = "uid";
069
070    private String entitytemplate = null;
071    private String entitybasetemplate = null;
072
073    private String beantemplate = null;
074    private String beanbasetemplate = null;
075
076    private String interfacetemplate = null;
077
078    private String enumtemplate = null;
079
080    private String remotetemplate = null;
081    private String remotebasetemplate = null;
082
083    private boolean tide = false;
084
085    private String clienttypefactory = null;
086    private String entityfactory = null;
087    private String remotedestinationfactory = null;
088    private String transformer = null;
089
090    private Path classpath = null;
091    private List<FileSet> fileSets = new ArrayList<FileSet>();
092    private List<ZipFileSet> zipFileSets = new ArrayList<ZipFileSet>();
093
094    private List<PackageTranslator> translators = new ArrayList<PackageTranslator>();
095
096    ///////////////////////////////////////////////////////////////////////////
097    // Configuration implementation fields.
098
099    private File outputDirFile = null;
100    private File baseOutputDirFile = null;
101    
102    private As3TypeFactory clientTypeFactoryImpl = null;
103    private Transformer<?, ?, ?> transformerImpl = null;
104    private EntityFactory entityFactoryImpl = null;
105    private RemoteDestinationFactory remoteDestinationFactoryImpl = null;
106    
107    private GroovyTemplateFactory groovyTemplateFactory = null;
108    
109    private TemplateUri[] entityTemplateUris = null;
110    private TemplateUri[] interfaceTemplateUris = null;
111    private TemplateUri[] beanTemplateUris = null;
112    private TemplateUri[] enumTemplateUris = null;
113    private TemplateUri[] remoteTemplateUris = null;
114    
115    private Map<Class<?>, File> filesetClasses = null;
116    
117    ///////////////////////////////////////////////////////////////////////////
118    // Task attributes.
119
120    public void setOutputdir(String outputdir) {
121        this.outputdir = outputdir;
122    }
123
124    public void setBaseoutputdir(String baseoutputdir) {
125                this.baseoutputdir = baseoutputdir;
126        }
127
128        public void setAs3typefactory(String as3typefactory) {
129        this.clienttypefactory = as3typefactory;
130    }
131
132        public void setClienttypefactory(String clienttypefactory) {
133        this.clienttypefactory = clienttypefactory;
134    }
135
136        public void setEntityfactory(String entityfactory) {
137        this.entityfactory = entityfactory;
138    }
139
140        public void setRemotedestinationfactory(String remotedestinationfactory) {
141        this.remotedestinationfactory = remotedestinationfactory;
142    }
143
144    public void setUid(String uid) {
145        this.uid = uid;
146    }
147
148    public void setEntitytemplate(String entitytemplate) {
149    
150        this.entitytemplate = entitytemplate;
151    }
152    public void setEntitybasetemplate(String entitybasetemplate) {
153        this.entitybasetemplate = entitybasetemplate;
154    }
155
156    public void setBeantemplate(String beantemplate) {
157        this.beantemplate = beantemplate;
158    }
159    public void setBeanbasetemplate(String beanbasetemplate) {
160        this.beanbasetemplate = beanbasetemplate;
161    }
162
163    public void setInterfacetemplate(String interfacetemplate) {
164        this.interfacetemplate = interfacetemplate;
165    }
166
167    public void setEnumtemplate(String enumtemplate) {
168        this.enumtemplate = enumtemplate;
169    }
170
171    public void setRemotetemplate(String remotetemplate) {
172        this.remotetemplate = remotetemplate;
173    }
174    public void setRemotebasetemplate(String remotebasetemplate) {
175        this.remotebasetemplate = remotebasetemplate;
176    }
177
178    public void setTide(boolean tide) {
179        this.tide = tide;
180    }
181
182        public void setTransformer(String transformer) {
183                this.transformer = transformer;
184        }
185        
186        protected abstract As3TypeFactory initDefaultClientTypeFactory();
187        
188        protected abstract Transformer<?, ?, ?> initDefaultTransformer();
189
190    ///////////////////////////////////////////////////////////////////////////
191    // Task inner elements.
192
193        public void addFileset(FileSet fileSet) {
194        fileSets.add(fileSet);
195    }
196
197        public void addZipfileset(ZipFileSet zipFileSet) {
198                zipFileSets.add(zipFileSet);
199    }
200
201    public void setClasspath(Path path) {
202        if (classpath == null)
203            classpath = path;
204        else
205            classpath.append(path);
206    }
207
208    public Path createClasspath() {
209        if (classpath == null)
210            classpath = new Path(getProject());
211        return classpath.createPath();
212    }
213
214    public void setClasspathRef(Reference r) {
215        createClasspath().setRefid(r);
216    }
217
218    public void addTranslator(PackageTranslator translator) {
219        translators.add(translator);
220    }
221
222    ///////////////////////////////////////////////////////////////////////////
223    // Task execution.
224
225    @Override
226    public void execute() throws BuildException {
227
228        log("Using output dir: " + outputdir, Project.MSG_INFO);
229        log("Using classpath: " + classpath, Project.MSG_INFO);
230
231        AntClassLoader loader = new AntClassLoader(AntJavaAs3Task.class.getClassLoader(), getProject(), classpath, true);
232        try {
233            loader.setThreadContextLoader();
234
235            filesetClasses = new HashMap<Class<?>, File>();
236
237            // Build a Set of all ".class" files in filesets (ignoring nested classes).
238            log("Loading all Java classes referenced by inner fileset(s) {", Project.MSG_INFO);
239            for (FileSet fileSet : fileSets) {
240                DirectoryScanner scanner = fileSet.getDirectoryScanner(getProject());
241                scanner.setCaseSensitive(true);
242                scanner.scan();
243
244                StringBuilder sb = new StringBuilder("    ");
245                String[] names = scanner.getIncludedFiles();
246                for (String name : names) {
247                    if (name.endsWith(".class")) {
248                        log(name, Project.MSG_VERBOSE);
249                        try {
250                            File jFile = new File(scanner.getBasedir(), name);
251                            if (!jFile.exists())
252                                throw new FileNotFoundException(jFile.toString());
253
254                            String jClassName = normalizeClassName(name.substring(0, name.length() - 6));
255                            Class<?> jClass = loader.loadClass(jClassName);
256                            
257                            if (!jClass.isMemberClass() || jClass.isEnum()) {
258                                    sb.setLength(4);
259                                    sb.append(jClass.toString());
260                                    log(sb.toString(), Project.MSG_INFO);
261        
262                                    filesetClasses.put(jClass, jFile);
263                            }
264                        } catch (Exception e) {
265                            log(getStackTrace(e));
266                            throw new BuildException("Could not load Java class file: " + name, e);
267                        }
268                    }
269                    else
270                        log("Skipping non class file: " + name, Project.MSG_WARN);
271                }
272            }
273            log("}", Project.MSG_INFO);
274                        
275            // Add all ".class" files from zipfilesets (ignoring nested classes).
276            log("Loading all Java classes referenced by inner zipfileset(s) {", Project.MSG_INFO);
277            
278            for (ZipFileSet zipFileSet : zipFileSets) {
279                File zip = zipFileSet.getSrc(getProject());
280                if (zip == null)
281                        throw new BuildException("zipfileset must use the src attribute");
282                if (!zip.getName().endsWith(".jar"))
283                        throw new BuildException("Zipfileset src isn't a '.jar' file: " + zip);
284                
285                log("  -- scanning: " + zip + " --", Project.MSG_INFO);
286
287                DirectoryScanner scanner = zipFileSet.getDirectoryScanner(getProject());
288                scanner.setCaseSensitive(true);
289                scanner.scan();
290                
291                StringBuilder sb = new StringBuilder("    ");
292                String[] names = scanner.getIncludedFiles();
293                for (String name : names) {
294                    if (name.endsWith(".class")) {
295                        log(name, Project.MSG_VERBOSE);
296                        try {
297                                String jClassName = normalizeClassName(name.substring(0, name.length() - 6));
298                            Class<?> jClass = loader.loadClass(jClassName);
299                            
300                            if (!jClass.isMemberClass() || jClass.isEnum()) {
301                                    sb.setLength(4);
302                                    sb.append(jClass.toString());
303                                    log(sb.toString(), Project.MSG_INFO);
304        
305                                    filesetClasses.put(jClass, zip);
306                            }
307                            } catch (Exception e) {
308                                log(getStackTrace(e));
309                                throw new BuildException("Could not load Java class file: " + name, e);
310                            }
311                    }
312                    else
313                        log("Skipping non class file: " + name, Project.MSG_WARN);
314                }
315            }
316            
317            log("}", Project.MSG_INFO);
318
319            log("Setting up the generator...", Project.MSG_INFO);
320
321            // ClientTypeFactory.
322            if (clienttypefactory == null) {
323                clientTypeFactoryImpl = initDefaultClientTypeFactory();
324            }
325            else {
326                log("Instantiating custom ClientTypeFactory class: " + clienttypefactory, Project.MSG_INFO);
327                clientTypeFactoryImpl = newInstance(loader, clienttypefactory);
328            }
329            
330            // EntityFactory
331            if (entityfactory == null)
332                entityFactoryImpl = new DefaultEntityFactory();
333            else {
334                log("Instantiating custom EntityFactory class: " + entityfactory, Project.MSG_INFO);
335                entityFactoryImpl = newInstance(loader, entityfactory);
336            }
337            
338            // RemoteDestinationFactory
339            if (remotedestinationfactory == null)
340                remoteDestinationFactoryImpl = new DefaultRemoteDestinationFactory();
341            else {
342                log("Instantiating custom RemoteDestinationFactory class: " + remotedestinationfactory, Project.MSG_INFO);
343                remoteDestinationFactoryImpl = newInstance(loader, remotedestinationfactory);
344            }
345
346            // Listener.
347            AntListener listener = new AntListener(this);
348
349            // Transformer.
350            if (transformer == null)
351                transformerImpl = initDefaultTransformer();
352            else {
353                log("Instantiating custom Transformer class: " + transformer, Project.MSG_INFO);
354                transformerImpl = newInstance(loader, transformer);
355            }
356            transformerImpl.setListener(listener);
357            
358            // Enum templates.
359            String baseTemplateUri = null;
360            String templateUri = defaultTemplateUri("ENUM");
361            if (enumtemplate != null) {
362                log("Using custom enum template: " + enumtemplate, Project.MSG_INFO);
363                templateUri = enumtemplate;
364            }
365            enumTemplateUris = createTemplateUris(baseTemplateUri, templateUri);
366            
367            // Interface templates.
368            templateUri = defaultTemplateUri("INTERFACE");
369            if (interfacetemplate != null) {
370                log("Using custom interface template: " + interfacetemplate, Project.MSG_INFO);
371                templateUri = interfacetemplate;
372            }
373            interfaceTemplateUris = createTemplateUris(baseTemplateUri, templateUri);
374            
375            // Entity templates.
376            baseTemplateUri = defaultTemplateUri("ENTITY_BASE");
377            templateUri = defaultTemplateUri("ENTITY");
378            if (entitytemplate != null) {
379                log("Using custom entity template: " + entitytemplate, Project.MSG_INFO);
380                templateUri = entitytemplate;
381            }
382            if (entitybasetemplate != null) {
383                log("Using custom entity base template: " + entitybasetemplate, Project.MSG_INFO);
384                baseTemplateUri = entitybasetemplate;
385            }
386            else if (tide) {
387                log("Using tide entity base template.", Project.MSG_INFO);
388                baseTemplateUri = defaultTemplateUri("TIDE_ENTITY_BASE");
389            }
390            entityTemplateUris = createTemplateUris(baseTemplateUri, templateUri);
391            
392            // Other bean templates.
393            baseTemplateUri = defaultTemplateUri("BEAN_BASE");
394            templateUri = defaultTemplateUri("BEAN");
395            if (beantemplate != null) {
396                log("Using custom bean template: " + beantemplate, Project.MSG_INFO);
397                templateUri = beantemplate;
398            }
399            if (beanbasetemplate != null) {
400                log("Using custom bean base template: " + beanbasetemplate, Project.MSG_INFO);
401                baseTemplateUri = beanbasetemplate;
402            }
403            else if (tide) {
404                log("Using tide bean base template.", Project.MSG_INFO);
405                baseTemplateUri = defaultTemplateUri("TIDE_BEAN_BASE");
406            }
407                beanTemplateUris = createTemplateUris(baseTemplateUri, templateUri);
408            
409                // Remote service templates.
410            baseTemplateUri = defaultTemplateUri("REMOTE_BASE");
411            templateUri = defaultTemplateUri("REMOTE");
412            if (remotetemplate != null) {
413                log("Using custom remote template: " + remotetemplate, Project.MSG_INFO);
414                templateUri = remotetemplate;
415            }
416            else if (tide) {
417                log("Using Tide remote destination template.", Project.MSG_INFO);
418                templateUri = defaultTemplateUri("TIDE_REMOTE");
419            }
420            if (remotebasetemplate != null) {
421                log("Using custom remote base template: " + remotebasetemplate, Project.MSG_INFO);
422                baseTemplateUri = remotebasetemplate;
423            }
424            else if (tide) {
425                log("Using Tide remote destination base template.", Project.MSG_INFO);
426                baseTemplateUri = defaultTemplateUri("TIDE_REMOTE_BASE");
427            }
428                remoteTemplateUris = createTemplateUris(baseTemplateUri, templateUri);
429                
430                // Create the generator.
431            Generator generator = new Generator(this);
432            generator.add(transformerImpl);
433            
434            // Call the generator for each ".class".
435            log("Calling the generator for each Java class {", Project.MSG_INFO);
436            int count = 0;
437            for (Map.Entry<Class<?>, File> classFile : filesetClasses.entrySet()) {
438                if (classFile.getKey().isAnonymousClass())
439                        continue;
440                try {
441                        JavaAs3Input input = new JavaAs3Input(classFile.getKey(), classFile.getValue());
442                    for (Output<?> output : generator.generate(input)) {
443                        if (output.isOutdated())
444                                count++;
445                    }
446                } catch (Exception e) {
447                    log(getStackTrace(e));
448                    throw new BuildException("Could not generate AS3 beans for: " + classFile.getKey(), e);
449                }
450            }
451
452            log("}", Project.MSG_INFO);
453            log("Files affected: " + count +  (count == 0 ? " (nothing to do)." : "."));
454        } finally {
455                loader.resetThreadContextLoader();
456        }
457    }
458    
459    private String normalizeClassName(String name) {
460        return name.replace('/', '.').replace('\\', '.');
461    }
462
463    ///////////////////////////////////////////////////////////////////////////
464    // Configuration implementation methods.
465    
466    @Override
467        public As3TypeFactory getAs3TypeFactory() {
468                return clientTypeFactoryImpl;
469        }
470    
471    public As3TypeFactory getClientTypeFactory() {
472                return clientTypeFactoryImpl;
473        }
474    
475    @Override
476        public EntityFactory getEntityFactory() {
477        return entityFactoryImpl;
478    }
479    
480    @Override
481        public RemoteDestinationFactory getRemoteDestinationFactory() {
482        return remoteDestinationFactoryImpl;
483    }
484
485        @Override
486        public File getBaseOutputDir(JavaAs3Input input) {
487                if (baseOutputDirFile == null)
488                        baseOutputDirFile = new File(baseoutputdir != null ? baseoutputdir : outputdir);
489                return baseOutputDirFile;
490        }
491
492        @Override
493        public File getOutputDir(JavaAs3Input input) {
494                if (outputDirFile == null)
495                        outputDirFile = new File(outputdir);
496                return outputDirFile;
497        }
498
499        @Override
500        public TemplateUri[] getTemplateUris(Kind kind, Class<?> clazz) {
501                switch (kind) {
502                case ENTITY:
503                return entityTemplateUris;
504                case INTERFACE:
505                        return interfaceTemplateUris;
506                case ENUM:
507                        return enumTemplateUris;
508                case BEAN:
509                        return beanTemplateUris;
510                case REMOTE_DESTINATION:
511                        return remoteTemplateUris;
512                default:
513                        throw new IllegalArgumentException("Unknown template kind: " + kind + " / " + clazz);
514                }
515        }
516        
517        protected abstract String defaultTemplateUri(String type);
518
519        @Override
520        public List<PackageTranslator> getTranslators() {
521                return translators;
522        }
523        
524        @Override
525        public String getUid() {
526                return uid;
527        }
528
529        @Override
530        public boolean isGenerated(Class<?> clazz) {
531                return filesetClasses.containsKey(clazz);
532        }
533
534        @Override
535        public ClassLoader getClassLoader() {
536                return Thread.currentThread().getContextClassLoader();
537        }
538
539        @Override
540        public GroovyTemplateFactory getGroovyTemplateFactory() {
541                if (groovyTemplateFactory == null)
542                        groovyTemplateFactory = new GroovyTemplateFactory();
543                return groovyTemplateFactory;
544        }
545
546        @Override
547        public File getWorkingDirectory() {
548                return getProject().getBaseDir();
549        }
550
551    ///////////////////////////////////////////////////////////////////////////
552    // Utilities.
553
554        @SuppressWarnings("unchecked")
555        private <T> T newInstance(ClassLoader loader, String className) {
556                try {
557            return (T)loader.loadClass(className).newInstance();
558        } catch (Exception e) {
559            log(getStackTrace(e));
560            throw new BuildException("Could not instantiate custom class: " + className, e);
561        }
562    }
563
564    private static String getStackTrace(Exception e) {
565        StringWriter sw = new StringWriter();
566        PrintWriter pw = new PrintWriter(sw);
567        e.printStackTrace(pw);
568        return sw.toString();
569    }
570        
571        private TemplateUri[] createTemplateUris(String baseUri, String uri) {
572                TemplateUri[] templateUris = new TemplateUri[baseUri == null ? 1 : 2];
573                int i = 0;
574                if (baseUri != null)
575                        templateUris[i++] = new TemplateUri(baseUri, true);
576                templateUris[i] = new TemplateUri(uri, false);
577                return templateUris;
578        }
579}