/** * GRANITE DATA SERVICES * Copyright (C) 2006-2013 GRANITE DATA SERVICES S.A.S. * * This file is part of the Granite Data Services Platform. * * Granite Data Services is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * Granite Data Services is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser * General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, * USA, or see <http://www.gnu.org/licenses/>. */ package org.granite.builder; import java.io.File; import java.io.FileFilter; import java.text.DateFormat; import java.util.ArrayList; import java.util.Date; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.regex.Pattern; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IProject; import org.eclipse.core.resources.IResource; import org.eclipse.core.resources.IResourceDelta; import org.eclipse.core.resources.IResourceDeltaVisitor; import org.eclipse.core.resources.IResourceVisitor; import org.eclipse.core.resources.IncrementalProjectBuilder; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IPath; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.NullProgressMonitor; import org.granite.builder.properties.Gas3Source; import org.granite.builder.properties.Gas3Transformer; import org.granite.builder.properties.GranitePropertiesLoader; import org.granite.builder.ui.AddNatureWizard; import org.granite.builder.util.BuilderUtil; import org.granite.builder.util.FileUtil; import org.granite.builder.util.FlexConfigGenerator; import org.granite.builder.util.JavaClassInfo; import org.granite.builder.util.ProjectUtil; import org.granite.generator.Generator; import org.granite.generator.Listener; import org.granite.generator.Output; import org.granite.generator.Transformer; import org.granite.generator.as3.JavaAs3Input; import org.granite.generator.as3.JavaAs3Output; import org.granite.generator.as3.PackageTranslator; /** * @author Franck WOLFF */ public class GraniteBuilder extends IncrementalProjectBuilder { public static final String JAVA_BUILDER_ID = "org.eclipse.jdt.core.javabuilder"; public static final String FLEX_BUILDER_ID = "com.adobe.flexbuilder.project.flexbuilder"; public static final String GRANITE_BUILDER_ID = "org.granite.builder.granitebuilder"; private static final int PROGRESS_TOTAL = 100; private final Generator generator; private final BuilderListener listener; private BuilderConfiguration config; /////////////////////////////////////////////////////////////////////////// // Constructor. public GraniteBuilder() { super(); this.generator = new Generator(); this.listener = new BuilderListener(); } /////////////////////////////////////////////////////////////////////////// // Build. @SuppressWarnings("rawtypes") @Override protected IProject[] build(int kind, Map args, IProgressMonitor monitor) throws CoreException { listener.title("Building project \"" + getProject().getName() + "\" (" + DateFormat.getInstance().format(new Date()) + ")..."); long t0 = System.currentTimeMillis(); GenerationResult result = null; try { if (!GranitePropertiesLoader.exists(getProject())) { BuilderConsole.activate(); AddNatureWizard.run(getProject()); config = null; generator.clear(); } else if (args.containsKey(GraniteRebuildJob.RESET_KEY) || (config != null && config.isOutdated())) { config = null; generator.clear(); } config = getConfig(); config.resetClassLoader(); config.getGroovyTemplateFactory().cleanOutdated(); generator.setConfig(config); BuilderConsole.setDebugEnabled(config.getProperties().getGas3().isDebugEnabled()); if (generator.isEmpty()) { Gas3Transformer gas3Transformer = config.getProperties().getGas3().getTransformer(); if (gas3Transformer != null) { try { Transformer<?,?,?> transformer = BuilderUtil.newInstance(Transformer.class, gas3Transformer.getType(), config.getClassLoader()); transformer.setListener(listener); generator.add(transformer); } catch (Exception e) { listener.error("Could not load transformer: " + gas3Transformer.getType(), e); if (e instanceof CoreException) throw (CoreException)e; if (e.getCause() instanceof CoreException) throw (CoreException)e.getCause(); throw new CoreException(ProjectUtil.createErrorStatus( "Could not load transformer: " + gas3Transformer.getType(), null )); } } } if (monitor == null) monitor = new NullProgressMonitor(); try { if (kind == FULL_BUILD) result = fullBuild(monitor); else { IResourceDelta delta = getDelta(getProject()); if (delta == null) result = fullBuild(monitor); else result = incrementalBuild(delta, monitor); } } catch (CoreException e) { throw e; } catch (Exception e) { throw new CoreException(ProjectUtil.createErrorStatus("Granite Build Failed", e)); } boolean refreshFlexConfig = false; try { if (result.generateFlexConfig && config.getProperties().getGas3().isFlexConfig()) refreshFlexConfig = FlexConfigGenerator.generateFlexConfig(config, listener, getProject()); } catch (Exception e) { listener.warn("Could not generate Flex Builder configuration", e); } File projectDir = ProjectUtil.getProjectFile(getProject()); for (File dir : result.dirsToRefresh) { StringBuilder relativePath = new StringBuilder(); while (dir != null && !dir.equals(projectDir)) { relativePath.insert(0, '/').insert(1, dir.getName()); dir = dir.getParentFile(); } getProject().getFolder(relativePath.toString()).refreshLocal(IResource.DEPTH_INFINITE, monitor); } if (refreshFlexConfig) getProject().getFile(FlexConfigGenerator.FILE_NAME).refreshLocal(IResource.DEPTH_ZERO, monitor); } finally { long t1 = System.currentTimeMillis(); if (result != null) { listener.title( "Done (" + (result.affectedFiles > 0 ? result.affectedFiles + " affected files" : "nothing to do") + " - " + (t1 - t0) + "ms)." ); } else listener.title("Done (error) - " + (t1 - t0) + "ms)."); listener.title(""); } return null; } class GenerationResult { public int affectedFiles = 0; public Set<File> dirsToRefresh = new HashSet<File>(); public boolean generateFlexConfig = false; } /////////////////////////////////////////////////////////////////////////// // Full Build. private GenerationResult fullBuild(final IProgressMonitor monitor) throws CoreException { monitor.beginTask("Granite Full Build", PROGRESS_TOTAL); FullBuildVisitor visitor = new FullBuildVisitor(monitor); try { getProject().accept(visitor); } finally { monitor.done(); } return visitor.getResult(); } class FullBuildVisitor implements IResourceVisitor { private IProgressMonitor monitor; private GenerationResult result = new GenerationResult(); public FullBuildVisitor(IProgressMonitor monitor) { this.monitor = monitor; } @Override public boolean visit(IResource resource) throws CoreException { if (!resource.isAccessible() || resource.isPhantom()) return false; Output<?>[] outputs = generate(resource, monitor); if (outputs != null) { for (Output<?> output : outputs) { if (output.isOutdated()) { result.affectedFiles++; result.dirsToRefresh.add(((JavaAs3Output)output).getDir()); } } } result.generateFlexConfig = true; return true; } public GenerationResult getResult() { return result; } } /////////////////////////////////////////////////////////////////////////// // Incremental Build. /* * TODO: this part should be refactored, it assumes several things about * generated (.as extension, Base suffix, etc.) which should be deferred * to transformers... */ private GenerationResult incrementalBuild(IResourceDelta delta, IProgressMonitor monitor) throws CoreException { monitor.beginTask("Granite Incremental Build", PROGRESS_TOTAL); IncrementalBuildVisitor visitor = new IncrementalBuildVisitor(monitor); try { delta.accept(visitor); } finally { monitor.done(); } return visitor.getResult(); } class IncrementalBuildVisitor implements IResourceDeltaVisitor { private IProgressMonitor monitor; private GenerationResult result = new GenerationResult(); public IncrementalBuildVisitor(IProgressMonitor monitor) { this.monitor = monitor; } @Override public boolean visit(IResourceDelta delta) throws CoreException { IResource resource = delta.getResource(); String extension = resource.getFileExtension(); Output<?>[] outputs = null; switch (delta.getKind()) { case IResourceDelta.ADDED: if (!resource.isAccessible() || resource.isPhantom()) return false; outputs = generate(resource, monitor); if (!result.generateFlexConfig) result.generateFlexConfig = "as".equals(resource.getFileExtension()); break; case IResourceDelta.REMOVED: if (!result.generateFlexConfig) result.generateFlexConfig = "as".equals(extension); outputs = remove(resource, monitor); break; case IResourceDelta.CHANGED: if (!resource.isAccessible() || resource.isPhantom()) return false; outputs = generate(resource, monitor); break; } if (outputs != null) { for (Output<?> output : outputs) { if (output.isOutdated()) { result.affectedFiles++; result.dirsToRefresh.add(((JavaAs3Output)output).getDir()); result.generateFlexConfig = true; } } } return true; } public GenerationResult getResult() { return result; } } private BuilderConfiguration getConfig() { if (config == null || config.isOutdated()) config = new BuilderConfiguration(listener, getProject()); return config; } private Output<?>[] generate(IResource resource, IProgressMonitor monitor) { if (resource instanceof IFile && "class".equals(resource.getFileExtension())) { IFile file = (IFile)resource; try { JavaClassInfo info = ProjectUtil.getJavaClassInfo(config.getJavaProject(), (IFile)resource); if (info == null) { listener.warn("Could not get class informations for: " + resource.toString()); return null; } Gas3Source source = config.getProperties().getGas3().getMatchingSource( info.getSourceFolderPath(), info.getSourceFilePath() ); if (source != null) { monitor.subTask("Generating AS3 code for: " + file.getProjectRelativePath().toString()); try { Class<?> clazz = config.getClassLoader().loadClass(info.getClassName()); if (!clazz.isAnonymousClass() && config.isGenerated(clazz)) { Map<String, String> attributes = source.getAttributes(info.getSourceFolderPath(), info.getSourceFilePath()); JavaAs3Input input = new BuilderJavaClientInput(clazz, info.getClassFile(), source, attributes); return generator.generate(input); } } finally { monitor.worked(1); } } } catch (Throwable t) { listener.error("", t); } } return null; } private Output<?>[] remove(IResource resource, IProgressMonitor monitor) { if (resource instanceof IFile && "java".equals(resource.getFileExtension())) { try { IPath resourcePath = resource.getFullPath(); IPath resourceSourceFolder = null; List<IPath> sourceFolders = ProjectUtil.getSourceFolders(config.getJavaProject()); for (IPath sourceFolder : sourceFolders) { if (sourceFolder.isPrefixOf(resourcePath)) { resourceSourceFolder = sourceFolder; break; } } if (resourceSourceFolder == null) return null; String relativeJavaFile = FileUtil.makeRelativeTo(resourceSourceFolder, resourcePath).toPortableString(); Gas3Source source = config.getProperties().getGas3().getMatchingSource( FileUtil.makeRelativeTo(config.getJavaProject().getPath(), resourceSourceFolder).toPortableString(), relativeJavaFile ); if (source != null) { String packageName = ""; String className = relativeJavaFile.substring(0, relativeJavaFile.length() - 5); int lastSlash = className.lastIndexOf('/'); if (lastSlash != -1) { packageName = className.substring(0, lastSlash).replace('/', '.'); PackageTranslator translator = config.getPackageTranslator(packageName); if (translator != null) packageName = translator.translate(packageName); className = className.substring(lastSlash + 1); } monitor.subTask("Removing AS3 code for: " + resource.getProjectRelativePath().toString()); try { JavaAs3Input input = new BuilderJavaClientInput(null, null, source, null); File outputDir = config.getBaseOutputDir(input); String outputPrefix = outputDir.getName() + File.separator + packageName.replace('.', File.separatorChar) + File.separator + className; final Pattern pattern = Pattern.compile("^.*" + Pattern.quote(outputPrefix) + "(\\$.*)?(Base)?\\.as$"); List<File> matches = listFiles(outputDir, new FileFilter() { @Override public boolean accept(File file) { return pattern.matcher(file.getPath()).matches(); } }); if (!matches.isEmpty()) { Output<?>[] outputs = new Output<?>[matches.size()]; for (int i = 0; i < matches.size(); i++) { File file = matches.get(i); File renameToFile = new File(file.getParentFile(), file.getName() + "." + System.currentTimeMillis() + ".hid"); outputs[i] = new JavaAs3Output(null, null, renameToFile.getParentFile(), renameToFile, true, Listener.MSG_FILE_REMOVED); listener.removing(input, outputs[i]); file.renameTo(renameToFile); } return outputs; } } finally { monitor.worked(1); } } } catch (Throwable t) { listener.error("", t); } } return null; } private List<File> listFiles(File root, FileFilter filter) { final List<File> files = new ArrayList<File>(); listFiles(files, root, filter); return files; } private void listFiles(final List<File> files, final File parent, final FileFilter filter) { parent.listFiles(new FileFilter() { @Override public boolean accept(File file) { if (file.isDirectory()) listFiles(files, file, filter); if (filter.accept(file)) files.add(file); return false; } }); } }