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 }