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 021package org.granite.builder; 022 023import java.io.File; 024import java.io.FileFilter; 025import java.text.DateFormat; 026import java.util.ArrayList; 027import java.util.Date; 028import java.util.HashSet; 029import java.util.List; 030import java.util.Map; 031import java.util.Set; 032import java.util.regex.Pattern; 033 034import org.eclipse.core.resources.IFile; 035import org.eclipse.core.resources.IProject; 036import org.eclipse.core.resources.IResource; 037import org.eclipse.core.resources.IResourceDelta; 038import org.eclipse.core.resources.IResourceDeltaVisitor; 039import org.eclipse.core.resources.IResourceVisitor; 040import org.eclipse.core.resources.IncrementalProjectBuilder; 041import org.eclipse.core.runtime.CoreException; 042import org.eclipse.core.runtime.IPath; 043import org.eclipse.core.runtime.IProgressMonitor; 044import org.eclipse.core.runtime.NullProgressMonitor; 045import org.granite.builder.properties.Gas3Source; 046import org.granite.builder.properties.Gas3Transformer; 047import org.granite.builder.properties.GranitePropertiesLoader; 048import org.granite.builder.ui.AddNatureWizard; 049import org.granite.builder.util.BuilderUtil; 050import org.granite.builder.util.FileUtil; 051import org.granite.builder.util.FlexConfigGenerator; 052import org.granite.builder.util.JavaClassInfo; 053import org.granite.builder.util.ProjectUtil; 054import org.granite.generator.Generator; 055import org.granite.generator.Listener; 056import org.granite.generator.Output; 057import org.granite.generator.Transformer; 058import org.granite.generator.as3.JavaAs3Input; 059import org.granite.generator.as3.JavaAs3Output; 060import org.granite.generator.as3.PackageTranslator; 061 062/** 063 * @author Franck WOLFF 064 */ 065public 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}