/* * Copyright 2009-2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.codehaus.jdt.groovy.internal.compiler; import java.util.Comparator; import java.util.Map; import java.util.Set; import java.util.TreeMap; import org.codehaus.jdt.groovy.integration.LanguageSupportFactory; import org.codehaus.jdt.groovy.model.GroovyNature; import org.eclipse.core.resources.ICommand; import org.eclipse.core.resources.IContainer; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IFolder; import org.eclipse.core.resources.IProject; import org.eclipse.core.resources.IResource; import org.eclipse.core.resources.IWorkspaceRoot; import org.eclipse.core.resources.ResourceAttributes; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IPath; import org.eclipse.core.runtime.Path; import org.eclipse.jdt.core.IClasspathEntry; import org.eclipse.jdt.core.IJavaProject; import org.eclipse.jdt.core.JavaCore; import org.eclipse.jdt.core.JavaModelException; import org.eclipse.jdt.core.compiler.BuildContext; import org.eclipse.jdt.core.compiler.CategorizedProblem; import org.eclipse.jdt.core.compiler.CompilationParticipant; import org.eclipse.jdt.groovy.core.util.ScriptFolderSelector; import org.eclipse.jdt.groovy.core.util.ScriptFolderSelector.FileKind; import org.eclipse.jdt.internal.compiler.problem.DefaultProblem; import org.eclipse.jdt.internal.compiler.problem.ProblemSeverities; import org.eclipse.jdt.internal.core.util.Util; /** * Compilation participant for notification when a compile completes. Copies over specified script files into the output directory * * @author Andrew Eisenberg * @created Oct 5, 2010 */ public class ScriptFolderCompilationParticipant extends CompilationParticipant { /** * use this to ensure that the longest paths are looked at first * ensures that nested source folders will be appropriately found */ static class PathLengthComparator implements Comparator<IContainer> { public int compare(IContainer c1, IContainer c2) { if (c1 == null) { if (c2 == null) { return 0; } return -1; } if (c2 == null) { return 1; } int len1 = c1.getFullPath().segmentCount(); int len2 = c2.getFullPath().segmentCount(); if (len1 > len2) { // a larger path should come first return -1; } else if (len1 == len2) { // then compare by text: return c1.toString().compareTo(c2.toString()); } else { return 1; } } } private static final PathLengthComparator comparator = new PathLengthComparator(); private IJavaProject project; /** * We care only about Groovy projects */ @Override public boolean isActive(IJavaProject project) { boolean hasGroovyNature = GroovyNature.hasGroovyNature(project.getProject()); if (!hasGroovyNature) { return false; } this.project = project; return true; } @Override public void buildStarting(BuildContext[] compiledFiles, boolean isBatch) { if (!sanityCheckBuilder(compiledFiles)) { // problem happened, do not copy return; } try { IProject iproject = project.getProject(); if (compiledFiles == null || !ScriptFolderSelector.isEnabled(iproject)) { return; } ScriptFolderSelector selector = new ScriptFolderSelector(iproject); Map<IContainer, IContainer> sourceToOut = generateSourceToOut(project); for (BuildContext compiledFile : compiledFiles) { IFile file = compiledFile.getFile(); if (selector.getFileKind(file) == FileKind.SCRIPT) { IPath filePath = file.getFullPath(); IContainer containingSourceFolder = findContainingSourceFolder(sourceToOut, filePath); // if null, that means the out folder is the same as the source folder if (containingSourceFolder != null) { IPath packagePath = findPackagePath(filePath, containingSourceFolder); IContainer out = sourceToOut.get(containingSourceFolder); copyFile(file, packagePath, out); } } } } catch (CoreException e) { Util.log(e, "Error when copying scripts to output folder"); } } /** * Some simple checks that we can do to ensure that the builder is set up properly * * @param files */ private boolean sanityCheckBuilder(BuildContext[] files) { // GRECLIPSE-1230 also do a check to ensure the proper compiler is being used if (!LanguageSupportFactory.isGroovyLanguageSupportInstalled()) { for (BuildContext buildContext : files) { buildContext.recordNewProblems(createProblem(buildContext)); } } // also check if this project has the JavaBuilder. // note that other builders (like the ajbuilder) may implement the CompilationParticipant API try { ICommand[] buildSpec = project.getProject().getDescription().getBuildSpec(); boolean found = false; for (ICommand command : buildSpec) { if (command.getBuilderName().equals(JavaCore.BUILDER_ID)) { found = true; break; } } if (!found) { for (BuildContext buildContext : files) { buildContext.recordNewProblems(createProblem(buildContext)); } } return found; } catch (CoreException e) { Util.log(e); return false; } } /** * @param buildContext * @return */ private CategorizedProblem[] createProblem(BuildContext buildContext) { DefaultProblem problem = new DefaultProblem(buildContext.getFile().getFullPath().toOSString().toCharArray(), "Error compiling Groovy project. Either the Groovy-JDT patch is not installed or JavaBuilder is not being used.", 0, new String[0], ProblemSeverities.Error, 0, 0, 1, 0); return new CategorizedProblem[] { problem }; } @Override public void buildFinished(IJavaProject project) { // try { // IProject iproject = project.getProject(); // if (compiledFiles == null || !ScriptFolderSelector.isEnabled(iproject)) { // return; // } // // ScriptFolderSelector selector = new ScriptFolderSelector(iproject); // Map<IContainer, IContainer> sourceToOut = generateSourceToOut(project); // for (BuildContext compiledFile : compiledFiles) { // IFile file = compiledFile.getFile(); // if (selector.getFileKind(file) == FileKind.SCRIPT) { // IPath filePath = file.getFullPath(); // IContainer containingSourceFolder = findContainingSourceFolder(sourceToOut, filePath); // // // if null, that means the out folder is the same as the source folder // if (containingSourceFolder != null) { // IPath packagePath = findPackagePath(filePath, containingSourceFolder); // IContainer out = sourceToOut.get(containingSourceFolder); // copyFile(file, packagePath, out); // } // } // } // } catch (CoreException e) { // Util.log(e, "Error in Script folder compilation participant"); // } finally { // compiledFiles = null; // } } /** * @param file * @param containingSourceFolder * @return */ private IPath findPackagePath(IPath filePath, IContainer containingSourceFolder) { IPath containerPath = containingSourceFolder.getFullPath(); filePath = filePath.removeFirstSegments(containerPath.segmentCount()); filePath = filePath.removeLastSegments(1); return filePath; } private IContainer findContainingSourceFolder(Map<IContainer, IContainer> sourceToOut, IPath filePath) { Set<IContainer> sourceFolders = sourceToOut.keySet(); for (IContainer container : sourceFolders) { if (container.getFullPath().isPrefixOf(filePath)) { return container; } } return null; } private void copyFile(IFile file, IPath packagePath, IContainer outputFolder) throws CoreException { IContainer createdFolder = createFolder(packagePath, outputFolder, true); IFile toFile = createdFolder.getFile(new Path(file.getName())); if (toFile.exists()) { toFile.delete(true, null); } file.copy(toFile.getFullPath(), true, null); ResourceAttributes newAttrs = new ResourceAttributes(); newAttrs.setReadOnly(false); newAttrs.setHidden(false); toFile.setResourceAttributes(newAttrs); toFile.setDerived(true, null); toFile.refreshLocal(IResource.DEPTH_ZERO, null); } /** * Creates folder with the given path in the given output folder. This method is taken from * org.eclipse.jdt.internal.core.builder.AbstractImageBuilder.createFolder(..) */ private IContainer createFolder(IPath packagePath, IContainer outputFolder, boolean derived) throws CoreException { if (!outputFolder.exists() && outputFolder instanceof IFolder) { ((IFolder) outputFolder).create(true, true, null); } if (packagePath.isEmpty()) return outputFolder; IFolder folder = outputFolder.getFolder(packagePath); folder.refreshLocal(IResource.DEPTH_ZERO, null); if (!folder.exists()) { createFolder(packagePath.removeLastSegments(1), outputFolder, derived); folder.create(true, true, null); folder.setDerived(derived, null); folder.refreshLocal(IResource.DEPTH_ZERO, null); } return folder; } private Map<IContainer, IContainer> generateSourceToOut(IJavaProject project) throws JavaModelException { IProject p = project.getProject(); IWorkspaceRoot root = (IWorkspaceRoot) p.getParent(); IClasspathEntry[] cp = project.getRawClasspath(); // determine default out folder IPath defaultOutPath = project.getOutputLocation(); IContainer defaultOutContainer; if (defaultOutPath.segmentCount() > 1) { defaultOutContainer = root.getFolder(defaultOutPath); } else { defaultOutContainer = p; } Map<IContainer, IContainer> sourceToOut = new TreeMap<IContainer, IContainer>(comparator); for (IClasspathEntry cpe : cp) { if (cpe.getEntryKind() == IClasspathEntry.CPE_SOURCE) { // determine source folder IContainer sourceContainer; IPath sourcePath = cpe.getPath(); if (sourcePath.segmentCount() > 1) { sourceContainer = root.getFolder(sourcePath); } else { sourceContainer = p; } // determine out folder IPath outPath = cpe.getOutputLocation(); IContainer outContainer; if (outPath == null) { outContainer = defaultOutContainer; } else if (outPath.segmentCount() > 1) { outContainer = root.getFolder(outPath); } else { outContainer = p; } // if the two containers are equal, that means no copying should be done // do not add to map if (!sourceContainer.equals(outContainer)) { sourceToOut.put(sourceContainer, outContainer); } } } return sourceToOut; } }