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    
021    package org.granite.generator.ant;
022    
023    import java.io.File;
024    import java.io.FileNotFoundException;
025    import java.io.PrintWriter;
026    import java.io.StringWriter;
027    import java.util.ArrayList;
028    import java.util.HashMap;
029    import java.util.List;
030    import java.util.Map;
031    
032    import org.apache.tools.ant.AntClassLoader;
033    import org.apache.tools.ant.BuildException;
034    import org.apache.tools.ant.DirectoryScanner;
035    import org.apache.tools.ant.Project;
036    import org.apache.tools.ant.Task;
037    import org.apache.tools.ant.types.FileSet;
038    import org.apache.tools.ant.types.Path;
039    import org.apache.tools.ant.types.Reference;
040    import org.apache.tools.ant.types.ZipFileSet;
041    import org.granite.generator.Generator;
042    import org.granite.generator.Output;
043    import org.granite.generator.TemplateUri;
044    import org.granite.generator.Transformer;
045    import org.granite.generator.as3.As3TypeFactory;
046    import org.granite.generator.as3.DefaultEntityFactory;
047    import org.granite.generator.as3.DefaultRemoteDestinationFactory;
048    import org.granite.generator.as3.EntityFactory;
049    import org.granite.generator.as3.JavaAs3GroovyConfiguration;
050    import org.granite.generator.as3.JavaAs3Input;
051    import org.granite.generator.as3.PackageTranslator;
052    import org.granite.generator.as3.RemoteDestinationFactory;
053    import org.granite.generator.as3.reflect.JavaType.Kind;
054    import org.granite.generator.gsp.GroovyTemplateFactory;
055    
056    /**
057     * @author Franck WOLFF
058     */
059    public 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 = name.substring(0, name.length() - 6).replace(File.separatorChar, '.');
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 = name.substring(0, name.length() - 6).replace(File.separatorChar, '.');
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        ///////////////////////////////////////////////////////////////////////////
459        // Configuration implementation methods.
460        
461        @Override
462            public As3TypeFactory getAs3TypeFactory() {
463                    return clientTypeFactoryImpl;
464            }
465        
466        public As3TypeFactory getClientTypeFactory() {
467                    return clientTypeFactoryImpl;
468            }
469        
470        @Override
471            public EntityFactory getEntityFactory() {
472            return entityFactoryImpl;
473        }
474        
475        @Override
476            public RemoteDestinationFactory getRemoteDestinationFactory() {
477            return remoteDestinationFactoryImpl;
478        }
479    
480            @Override
481            public File getBaseOutputDir(JavaAs3Input input) {
482                    if (baseOutputDirFile == null)
483                            baseOutputDirFile = new File(baseoutputdir != null ? baseoutputdir : outputdir);
484                    return baseOutputDirFile;
485            }
486    
487            @Override
488            public File getOutputDir(JavaAs3Input input) {
489                    if (outputDirFile == null)
490                            outputDirFile = new File(outputdir);
491                    return outputDirFile;
492            }
493    
494            @Override
495            public TemplateUri[] getTemplateUris(Kind kind, Class<?> clazz) {
496                    switch (kind) {
497                    case ENTITY:
498                    return entityTemplateUris;
499                    case INTERFACE:
500                            return interfaceTemplateUris;
501                    case ENUM:
502                            return enumTemplateUris;
503                    case BEAN:
504                            return beanTemplateUris;
505                    case REMOTE_DESTINATION:
506                            return remoteTemplateUris;
507                    default:
508                            throw new IllegalArgumentException("Unknown template kind: " + kind + " / " + clazz);
509                    }
510            }
511            
512            protected abstract String defaultTemplateUri(String type);
513    
514            @Override
515            public List<PackageTranslator> getTranslators() {
516                    return translators;
517            }
518            
519            @Override
520            public String getUid() {
521                    return uid;
522            }
523    
524            @Override
525            public boolean isGenerated(Class<?> clazz) {
526                    return filesetClasses.containsKey(clazz);
527            }
528    
529            @Override
530            public ClassLoader getClassLoader() {
531                    return Thread.currentThread().getContextClassLoader();
532            }
533    
534            @Override
535            public GroovyTemplateFactory getGroovyTemplateFactory() {
536                    if (groovyTemplateFactory == null)
537                            groovyTemplateFactory = new GroovyTemplateFactory();
538                    return groovyTemplateFactory;
539            }
540    
541            @Override
542            public File getWorkingDirectory() {
543                    return getProject().getBaseDir();
544            }
545    
546        ///////////////////////////////////////////////////////////////////////////
547        // Utilities.
548    
549            @SuppressWarnings("unchecked")
550            private <T> T newInstance(ClassLoader loader, String className) {
551                    try {
552                return (T)loader.loadClass(className).newInstance();
553            } catch (Exception e) {
554                log(getStackTrace(e));
555                throw new BuildException("Could not instantiate custom class: " + className, e);
556            }
557        }
558    
559        private static String getStackTrace(Exception e) {
560            StringWriter sw = new StringWriter();
561            PrintWriter pw = new PrintWriter(sw);
562            e.printStackTrace(pw);
563            return sw.toString();
564        }
565            
566            private TemplateUri[] createTemplateUris(String baseUri, String uri) {
567                    TemplateUri[] templateUris = new TemplateUri[baseUri == null ? 1 : 2];
568                    int i = 0;
569                    if (baseUri != null)
570                            templateUris[i++] = new TemplateUri(baseUri, true);
571                    templateUris[i] = new TemplateUri(uri, false);
572                    return templateUris;
573            }
574    }