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