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    
023    package org.granite.generator.ant;
024    
025    import java.io.File;
026    import java.io.FileNotFoundException;
027    import java.io.PrintWriter;
028    import java.io.StringWriter;
029    import java.util.ArrayList;
030    import java.util.HashMap;
031    import java.util.List;
032    import java.util.Map;
033    
034    import org.apache.tools.ant.AntClassLoader;
035    import org.apache.tools.ant.BuildException;
036    import org.apache.tools.ant.DirectoryScanner;
037    import org.apache.tools.ant.Project;
038    import org.apache.tools.ant.Task;
039    import org.apache.tools.ant.types.FileSet;
040    import org.apache.tools.ant.types.Path;
041    import org.apache.tools.ant.types.Reference;
042    import org.apache.tools.ant.types.ZipFileSet;
043    import org.granite.generator.Generator;
044    import org.granite.generator.Output;
045    import org.granite.generator.TemplateUri;
046    import org.granite.generator.Transformer;
047    import org.granite.generator.as3.As3TypeFactory;
048    import org.granite.generator.as3.DefaultEntityFactory;
049    import org.granite.generator.as3.DefaultRemoteDestinationFactory;
050    import org.granite.generator.as3.EntityFactory;
051    import org.granite.generator.as3.JavaAs3GroovyConfiguration;
052    import org.granite.generator.as3.JavaAs3Input;
053    import org.granite.generator.as3.PackageTranslator;
054    import org.granite.generator.as3.RemoteDestinationFactory;
055    import org.granite.generator.as3.reflect.JavaType.Kind;
056    import org.granite.generator.gsp.GroovyTemplateFactory;
057    
058    /**
059     * @author Franck WOLFF
060     */
061    public 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    }