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.builder;
022    
023    import java.io.File;
024    import java.io.FileFilter;
025    import java.text.DateFormat;
026    import java.util.ArrayList;
027    import java.util.Date;
028    import java.util.HashSet;
029    import java.util.List;
030    import java.util.Map;
031    import java.util.Set;
032    import java.util.regex.Pattern;
033    
034    import org.eclipse.core.resources.IFile;
035    import org.eclipse.core.resources.IProject;
036    import org.eclipse.core.resources.IResource;
037    import org.eclipse.core.resources.IResourceDelta;
038    import org.eclipse.core.resources.IResourceDeltaVisitor;
039    import org.eclipse.core.resources.IResourceVisitor;
040    import org.eclipse.core.resources.IncrementalProjectBuilder;
041    import org.eclipse.core.runtime.CoreException;
042    import org.eclipse.core.runtime.IPath;
043    import org.eclipse.core.runtime.IProgressMonitor;
044    import org.eclipse.core.runtime.NullProgressMonitor;
045    import org.granite.builder.properties.Gas3Source;
046    import org.granite.builder.properties.Gas3Transformer;
047    import org.granite.builder.properties.GranitePropertiesLoader;
048    import org.granite.builder.ui.AddNatureWizard;
049    import org.granite.builder.util.BuilderUtil;
050    import org.granite.builder.util.FileUtil;
051    import org.granite.builder.util.FlexConfigGenerator;
052    import org.granite.builder.util.JavaClassInfo;
053    import org.granite.builder.util.ProjectUtil;
054    import org.granite.generator.Generator;
055    import org.granite.generator.Listener;
056    import org.granite.generator.Output;
057    import org.granite.generator.Transformer;
058    import org.granite.generator.as3.JavaAs3Input;
059    import org.granite.generator.as3.JavaAs3Output;
060    import org.granite.generator.as3.PackageTranslator;
061    
062    /**
063     * @author Franck WOLFF
064     */
065    public class GraniteBuilder extends IncrementalProjectBuilder {
066    
067        
068        public static final String JAVA_BUILDER_ID = "org.eclipse.jdt.core.javabuilder";
069        public static final String FLEX_BUILDER_ID = "com.adobe.flexbuilder.project.flexbuilder";
070        public static final String GRANITE_BUILDER_ID = "org.granite.builder.granitebuilder";
071    
072        private static final int PROGRESS_TOTAL = 100;
073    
074        private final Generator generator;
075        private final BuilderListener listener;
076        private BuilderConfiguration config;
077    
078        ///////////////////////////////////////////////////////////////////////////
079        // Constructor.
080    
081        public GraniteBuilder() {
082                    super();
083                    this.generator = new Generator();
084                    this.listener = new BuilderListener();
085            }
086    
087        ///////////////////////////////////////////////////////////////////////////
088        // Build.
089    
090            @SuppressWarnings("rawtypes")
091        @Override
092        protected IProject[] build(int kind, Map args, IProgressMonitor monitor) throws CoreException {
093            listener.title("Building project \"" + getProject().getName() + "\" (" + DateFormat.getInstance().format(new Date()) + ")...");
094            long t0 = System.currentTimeMillis();
095            
096            
097            GenerationResult result = null;
098            try {
099                    if (!GranitePropertiesLoader.exists(getProject())) {
100                            BuilderConsole.activate();
101                    AddNatureWizard.run(getProject());
102                            config = null;
103                            generator.clear();
104                    } else if (args.containsKey(GraniteRebuildJob.RESET_KEY) || (config != null && config.isOutdated())) {
105                            config = null;
106                            generator.clear();
107                    }
108                    
109                    config = getConfig();
110                    config.resetClassLoader();
111                    config.getGroovyTemplateFactory().cleanOutdated();
112                    
113                    generator.setConfig(config);
114                    
115                    BuilderConsole.setDebugEnabled(config.getProperties().getGas3().isDebugEnabled());
116                            
117                    if (generator.isEmpty()) {
118                            for (Gas3Transformer gas3Transformer : config.getProperties().getGas3().getTransformers()) {
119                            try {
120                                    Transformer<?,?,?> transformer = BuilderUtil.newInstance(Transformer.class, gas3Transformer.getType(), config.getClassLoader());
121                                    transformer.setListener(listener);
122                                    generator.add(transformer);
123                            } catch (Exception e) {
124                                    listener.error("Could not load transformer: " + gas3Transformer.getType(), e);
125                                    
126                                    if (e instanceof CoreException)
127                                            throw (CoreException)e;
128                                    if (e.getCause() instanceof CoreException)
129                                            throw (CoreException)e.getCause();
130                                    throw new CoreException(ProjectUtil.createErrorStatus(
131                                            "Could not load transformer: " + gas3Transformer.getType(), null
132                                    ));
133                            }
134                            }
135                    }
136            
137                    
138                    if (monitor == null)
139                        monitor = new NullProgressMonitor();
140            
141                    try {
142                        if (kind == FULL_BUILD)
143                            result = fullBuild(monitor);
144                        else {
145                            IResourceDelta delta = getDelta(getProject());
146                            if (delta == null)
147                                    result = fullBuild(monitor);
148                            else
149                                    result = incrementalBuild(delta, monitor);
150                        }
151                    } catch (CoreException e) {
152                        throw e;
153                    } catch (Exception e) {
154                        throw new CoreException(ProjectUtil.createErrorStatus("Granite Build Failed", e));
155                    }
156                    
157                    boolean refreshFlexConfig = false;
158                    try {
159                            if (result.generateFlexConfig && config.getProperties().getGas3().isFlexConfig())
160                                    refreshFlexConfig = FlexConfigGenerator.generateFlexConfig(config, listener, getProject());
161                    }
162                    catch (Exception e) {
163                            listener.warn("Could not generate Flex Builder configuration", e);
164                    }
165            
166                    File projectDir = ProjectUtil.getProjectFile(getProject());
167                    for (File dir : result.dirsToRefresh) {
168                            StringBuilder relativePath = new StringBuilder();
169                            while (dir != null && !dir.equals(projectDir)) {
170                                    relativePath.insert(0, '/').insert(1, dir.getName());
171                                    dir = dir.getParentFile();
172                            }
173                            getProject().getFolder(relativePath.toString()).refreshLocal(IResource.DEPTH_INFINITE, monitor);
174                    }
175                    if (refreshFlexConfig)
176                            getProject().getFile(FlexConfigGenerator.FILE_NAME).refreshLocal(IResource.DEPTH_ZERO, monitor);
177            }
178            finally {
179                    long t1 = System.currentTimeMillis();
180                    
181                    if (result != null) {
182                            listener.title(
183                                    "Done (" + (result.affectedFiles > 0 ? result.affectedFiles + " affected files" : "nothing to do") +
184                                    " - " + (t1 - t0) + "ms)."
185                            );
186                    } else
187                            listener.title("Done (error) - " + (t1 - t0) + "ms).");
188                    
189                    listener.title("");
190            }
191            
192            return null;
193        }
194            
195            class GenerationResult {
196                    public int affectedFiles = 0;
197                    public Set<File> dirsToRefresh = new HashSet<File>();
198                    public boolean generateFlexConfig = false;
199            }
200    
201        ///////////////////////////////////////////////////////////////////////////
202        // Full Build.
203    
204        private GenerationResult fullBuild(final IProgressMonitor monitor) throws CoreException {
205            monitor.beginTask("Granite Full Build", PROGRESS_TOTAL);
206            FullBuildVisitor visitor = new FullBuildVisitor(monitor);
207            try {
208                getProject().accept(visitor);
209            } finally {
210                monitor.done();
211            }
212            return visitor.getResult();
213        }
214    
215        class FullBuildVisitor implements IResourceVisitor {
216    
217            private IProgressMonitor monitor;
218            private GenerationResult result = new GenerationResult();
219    
220            public FullBuildVisitor(IProgressMonitor monitor) {
221                this.monitor = monitor;
222            }
223    
224            @Override
225                    public boolean visit(IResource resource) throws CoreException {
226                if (!resource.isAccessible() || resource.isPhantom())
227                    return false;
228    
229                Output<?>[] outputs = generate(resource, monitor);
230                if (outputs != null) {
231                    for (Output<?> output : outputs) {
232                            if (output.isOutdated()) {
233                                    result.affectedFiles++;
234                                    result.dirsToRefresh.add(((JavaAs3Output)output).getDir());
235                            }
236                    }
237                }
238                            result.generateFlexConfig = true;
239    
240                return true;
241            }
242    
243                    public GenerationResult getResult() {
244                            return result;
245                    }
246        }
247    
248        ///////////////////////////////////////////////////////////////////////////
249        // Incremental Build.
250    
251        /*
252         * TODO: this part should be refactored, it assumes several things about
253         * generated (.as extension, Base suffix, etc.) which should be deferred
254         * to transformers...
255         */
256        
257        private GenerationResult incrementalBuild(IResourceDelta delta, IProgressMonitor monitor) throws CoreException {
258            monitor.beginTask("Granite Incremental Build", PROGRESS_TOTAL);
259            IncrementalBuildVisitor visitor = new IncrementalBuildVisitor(monitor);
260            try {
261                delta.accept(visitor);
262            } finally {
263                monitor.done();
264            }
265            return visitor.getResult();
266        }
267    
268        class IncrementalBuildVisitor implements IResourceDeltaVisitor {
269    
270            private IProgressMonitor monitor;
271            private GenerationResult result = new GenerationResult();
272    
273            public IncrementalBuildVisitor(IProgressMonitor monitor) {
274                this.monitor = monitor;
275            }
276    
277            @Override
278                    public boolean visit(IResourceDelta delta) throws CoreException {
279                IResource resource = delta.getResource();
280                String extension = resource.getFileExtension();
281    
282                Output<?>[] outputs = null;
283                
284                switch (delta.getKind()) {
285                case IResourceDelta.ADDED:
286                    if (!resource.isAccessible() || resource.isPhantom())
287                        return false;
288                    outputs = generate(resource, monitor);
289                    if (!result.generateFlexConfig)
290                            result.generateFlexConfig = "as".equals(resource.getFileExtension());
291                    break;
292                case IResourceDelta.REMOVED:
293                    if (!result.generateFlexConfig)
294                            result.generateFlexConfig = "as".equals(extension);
295                    outputs = remove(resource, monitor);
296                    break;
297                case IResourceDelta.CHANGED:
298                    if (!resource.isAccessible() || resource.isPhantom())
299                        return false;
300                    outputs = generate(resource, monitor);
301                    break;
302                }
303                
304                if (outputs != null) {
305                    for (Output<?> output : outputs) {
306                            if (output.isOutdated()) {
307                                    result.affectedFiles++;
308                                    result.dirsToRefresh.add(((JavaAs3Output)output).getDir());
309                                    result.generateFlexConfig = true;
310                            }
311                    }
312                }
313    
314                return true;
315            }
316    
317                    public GenerationResult getResult() {
318                            return result;
319                    }
320        }
321        
322        private BuilderConfiguration getConfig() {
323            if (config == null || config.isOutdated())
324                    config = new BuilderConfiguration(listener, getProject());
325            return config;
326        }
327    
328        private Output<?>[] generate(IResource resource, IProgressMonitor monitor) {
329            if (resource instanceof IFile && "class".equals(resource.getFileExtension())) {
330                IFile file = (IFile)resource;
331                            
332                try {
333                        JavaClassInfo info = ProjectUtil.getJavaClassInfo(config.getJavaProject(), (IFile)resource);
334                        if (info == null) {
335                            listener.warn("Could not get class informations for: " + resource.toString());
336                            return null;
337                        }
338                        
339                        Gas3Source source = config.getProperties().getGas3().getMatchingSource(
340                            info.getSourceFolderPath(),
341                            info.getSourceFilePath()
342                        );
343                        
344                        if (source != null) {
345                                monitor.subTask("Generating AS3 code for: " + file.getProjectRelativePath().toString());
346                                try {
347                                    Class<?> clazz = config.getClassLoader().loadClass(info.getClassName());
348                                    if (!clazz.isAnonymousClass() && config.isGenerated(clazz)) {
349                                            Map<String, String> attributes = source.getAttributes(info.getSourceFolderPath(), info.getSourceFilePath());
350                                            JavaAs3Input input = new BuilderJavaClientInput(clazz, info.getClassFile(), source, attributes);
351                                            return generator.generate(input);
352                                    }
353                                } finally {
354                                    monitor.worked(1);
355                                }
356                        }
357                } catch (Throwable t) {
358                    listener.error("", t);
359                }
360            }
361            
362            return null;
363        }
364        
365        private Output<?>[] remove(IResource resource, IProgressMonitor monitor) {
366            if (resource instanceof IFile && "java".equals(resource.getFileExtension())) {
367                try {
368                    IPath resourcePath = resource.getFullPath();
369                    IPath resourceSourceFolder = null;
370                    
371                        List<IPath> sourceFolders = ProjectUtil.getSourceFolders(config.getJavaProject());
372                        for (IPath sourceFolder : sourceFolders) {
373                            if (sourceFolder.isPrefixOf(resourcePath)) {
374                                    resourceSourceFolder = sourceFolder;
375                                    break;
376                            }
377                        }
378                        
379                        if (resourceSourceFolder == null)
380                            return null;
381                
382                        String relativeJavaFile = FileUtil.makeRelativeTo(resourceSourceFolder, resourcePath).toPortableString();
383                        Gas3Source source = config.getProperties().getGas3().getMatchingSource(
384                            FileUtil.makeRelativeTo(config.getJavaProject().getPath(), resourceSourceFolder).toPortableString(),
385                            relativeJavaFile
386                        );
387                        
388                        if (source != null) {
389                            
390                            String packageName = "";
391                            String className = relativeJavaFile.substring(0, relativeJavaFile.length() - 5);
392                            
393                            int lastSlash = className.lastIndexOf('/');
394                            if (lastSlash != -1) {
395                                    packageName = className.substring(0, lastSlash).replace('/', '.');
396                                    PackageTranslator translator =  config.getPackageTranslator(packageName);
397                                    if (translator != null)
398                                            packageName = translator.translate(packageName);
399                                    className = className.substring(lastSlash + 1);
400                            }
401                            
402                            
403                                monitor.subTask("Removing AS3 code for: " + resource.getProjectRelativePath().toString());
404                                try {
405                                    JavaAs3Input input = new BuilderJavaClientInput(null, null, source, null);
406                                    File outputDir = config.getBaseOutputDir(input);
407                                    
408                                    String outputPrefix = outputDir.getName() + File.separator + packageName.replace('.', File.separatorChar) + File.separator + className;
409                                    final Pattern pattern = Pattern.compile("^.*" + Pattern.quote(outputPrefix) + "(\\$.*)?(Base)?\\.as$");
410                                    List<File> matches = listFiles(outputDir, new FileFilter() {
411                                                            @Override
412                                                            public boolean accept(File file) {
413                                                                    return pattern.matcher(file.getPath()).matches();
414                                                            }
415                                    });
416                                    
417                                    if (!matches.isEmpty()) {
418                                            Output<?>[] outputs = new Output<?>[matches.size()];
419                                            for (int i = 0; i < matches.size(); i++) {
420                                                    File file = matches.get(i);
421                                                    File renameToFile = new File(file.getParentFile(), file.getName() + "." + System.currentTimeMillis() + ".hid");
422                                                    outputs[i] = new JavaAs3Output(null, null, renameToFile.getParentFile(), renameToFile, true, Listener.MSG_FILE_REMOVED);
423                                                    listener.removing(input, outputs[i]);
424                                                    file.renameTo(renameToFile);
425                                            }
426                                            
427                                            return outputs;
428                                    }
429                                } finally {
430                                    monitor.worked(1);
431                                }
432                        }
433                    } catch (Throwable t) {
434                    listener.error("", t);
435                }
436            }
437            
438            return null;
439        }
440        
441        private List<File> listFiles(File root, FileFilter filter) {
442            final List<File> files = new ArrayList<File>();
443            listFiles(files, root, filter);
444            return files;
445        }
446    
447        private void listFiles(final List<File> files, final File parent, final FileFilter filter) {
448            parent.listFiles(new FileFilter() {
449                            @Override
450                            public boolean accept(File file) {
451                                    if (file.isDirectory())
452                                            listFiles(files, file, filter);
453                                    if (filter.accept(file))
454                                            files.add(file);
455                                    return false;
456                            }
457            });
458        }
459    }