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