| /******************************************************************************* |
| * Copyright (c) 2003, 2005 IBM Corporation and others. |
| * All rights reserved. This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License v1.0 |
| * which accompanies this distribution, and is available at |
| * http://www.eclipse.org/legal/epl-v10.html |
| * |
| * Contributors: |
| * IBM Corporation - initial API and implementation |
| *******************************************************************************/ |
| package org.eclipse.jst.j2ee.internal.plugin; |
| |
| |
| import java.io.File; |
| import java.util.ArrayList; |
| import java.util.List; |
| import java.util.Map; |
| |
| 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.IResourceDelta; |
| import org.eclipse.core.resources.IResourceDeltaVisitor; |
| import org.eclipse.core.resources.IResourceVisitor; |
| import org.eclipse.core.resources.IncrementalProjectBuilder; |
| import org.eclipse.core.resources.ResourcesPlugin; |
| import org.eclipse.core.runtime.CoreException; |
| import org.eclipse.core.runtime.IPath; |
| import org.eclipse.core.runtime.IProgressMonitor; |
| 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.jem.workbench.utility.JemProjectUtilities; |
| |
| /** |
| * An example incremental project builder that copies additional class files from a library package |
| * fragment root folder into a Java project's output directory. |
| * |
| * General parameters: |
| * <ul> |
| * <li>The project should be a Java project.</li> |
| * <li>The class files are in the "imported_classes" folder of the project.</li> |
| * <li>This builder should run <b>after </b> the Java builder.</li> |
| * <li>Full build should copy class files from a secondary library folder into the output folder |
| * maintaining package hierarchy; existing class files must never be overwritten.</li> |
| * <li>Only *.class files should be copied (not other resource files).</li> |
| * <li>Incremental build and auto-build should will perform the copy when there is an add/change in |
| * the "imported_classes" folder.</li> |
| * <li>Changing the project's output folder should be handled.</li> |
| * </ul> |
| * Note: the builder is not currently invoking the Minimize helper, it is relying on the copy to not |
| * replace existing class files, and the build path order to ensure that compiled classes override |
| * imported ones. |
| */ |
| public class LibCopyBuilder extends IncrementalProjectBuilder { |
| /** |
| * Internal debug tracing. |
| */ |
| static boolean DEBUG = false; |
| |
| /** |
| * Builder id of this incremental project builder. |
| */ |
| public static final String BUILDER_ID = J2EEPlugin.LIBCOPY_BUILDER_ID; |
| |
| /** |
| * The path where we expect to find the .class files to be copied. |
| */ |
| public static final String IMPORTED_CLASSES_PATH = "imported_classes"; //$NON-NLS-1$ |
| |
| /** |
| * The path of the output folder that we last copied class files into, or <code>null</code> if |
| * this builder has not built this project before. |
| */ |
| private IPath lastOutputPath = null; |
| |
| private List sourceContainers; |
| |
| private boolean needOutputRefresh; |
| |
| /** |
| * Creates a new instance of the library copying builder. |
| * <p> |
| * All incremental project builders are required to have a public 0-argument constructor. |
| * </p> |
| */ |
| public LibCopyBuilder() { |
| super(); |
| } |
| |
| |
| /** |
| * |
| * The <code>LibCopyBuilder</code> implementation of this |
| * <code>IncrementalProjectBuilder</code> method copies additional class files into the output |
| * folder. |
| * <p> |
| * [Issue: the implementation should report progress.] |
| * </p> |
| * <p> |
| * [Issue: the implementation should probably use a workspace runnable.] |
| * </p> |
| */ |
| protected IProject[] build(int kind, Map args, IProgressMonitor monitor) throws CoreException { |
| sourceContainers = null; |
| needOutputRefresh = false; |
| if (DEBUG) { |
| System.out.println(BUILDER_ID + J2EEPluginResourceHandler.__Start_build_project_INFO_ + getProject().getName()); |
| } |
| |
| boolean builderOrderOK = checkBuilderOrdering(); |
| |
| if (DEBUG && !builderOrderOK) { |
| System.out.println(BUILDER_ID + J2EEPluginResourceHandler.__Bad_builder_order_for_project_INFO_ + getProject().getName()); |
| } |
| |
| IFolder[] classFolders = getClassesFolders(); |
| if (classFolders.length == 0) { |
| // no files to copy |
| if (DEBUG) |
| System.out.println(BUILDER_ID + J2EEPluginResourceHandler.__No_imported_classes_folder__quitting_INFO_); |
| return null; |
| } |
| |
| IJavaProject jproject = JavaCore.create(getProject()); |
| if (jproject == null) { |
| // not a java project (anymore?) |
| return null; |
| } |
| |
| IPath outputPath = jproject.getOutputLocation(); |
| IFolder outputFolder = getProject().getParent().getFolder(outputPath); |
| if (outputPath.equals(lastOutputPath)) { |
| if (kind == INCREMENTAL_BUILD || kind == AUTO_BUILD) { |
| processDelta(getDelta(getProject()), outputFolder, monitor, classFolders); |
| refreshOutputIfNecessary(outputFolder); |
| return null; |
| } |
| } |
| |
| if (DEBUG) { |
| System.out.println(BUILDER_ID + J2EEPluginResourceHandler.__Full_first_build_INFO_); |
| } |
| copyAllClassFolders(monitor, classFolders, outputFolder); |
| lastOutputPath = outputPath; |
| refreshOutputIfNecessary(outputFolder); |
| return null; |
| } |
| |
| /** |
| * |
| */ |
| private void refreshOutputIfNecessary(IFolder outputFolder) throws CoreException { |
| if (needOutputRefresh && outputFolder != null && outputFolder.exists()) |
| outputFolder.refreshLocal(IResource.DEPTH_INFINITE, null); |
| } |
| |
| private void copyAllClassFolders(IProgressMonitor monitor, IFolder[] classFolders, IFolder outputFolder) throws CoreException { |
| for (int i = 0; i < classFolders.length; i++) { |
| copyClassFiles(classFolders[i], outputFolder, monitor); |
| } |
| |
| } |
| |
| |
| /** |
| * Process an incremental build delta. |
| * |
| * @return <code>true</code> if the delta requires a copy |
| * @param dest |
| * the destination folder; may or may not exist |
| * @param monitor |
| * the progress monitor, or <code>null</code> if none |
| * @exception CoreException |
| * if something goes wrong |
| */ |
| protected void processDelta(IResourceDelta delta, final IFolder outputFolder, final IProgressMonitor monitor, final IFolder[] classesFolders) { |
| if (DEBUG) { |
| System.out.println(BUILDER_ID + J2EEPluginResourceHandler.__Considering_delta_INFO_ + delta); |
| } |
| IResourceDeltaVisitor visitor = new IResourceDeltaVisitor() { |
| private List copiedClassFolders = new ArrayList(classesFolders.length); |
| |
| public boolean visit(IResourceDelta subdelta) throws CoreException { |
| IResource resource = subdelta.getResource(); |
| if (resource.getType() == IResource.FILE) { |
| IFolder classesFolder = retrieveClassesFolder(resource, classesFolders); |
| if (classesFolder != null && !copiedClassFolders.contains(classesFolder)) { |
| int kind = subdelta.getKind(); |
| switch (kind) { |
| case IResourceDelta.ADDED : |
| case IResourceDelta.CHANGED : |
| if (DEBUG) { |
| System.out.println(BUILDER_ID + J2EEPluginResourceHandler.__Delta_build_INFO_ + subdelta); |
| } |
| copyClassFiles(classesFolder, outputFolder, monitor); |
| break; |
| case IResourceDelta.REMOVED : |
| deleteCorrespondingFile((IFile) resource, classesFolder, outputFolder, monitor); |
| break; |
| case IResourceDelta.ADDED_PHANTOM : |
| break; |
| case IResourceDelta.REMOVED_PHANTOM : |
| break; |
| } |
| |
| } |
| } else if (resource.getType() == IResource.FOLDER && resource.equals(outputFolder)) { |
| copyAllClassFolders(null, classesFolders, outputFolder); |
| return false; |
| } |
| return true; |
| } |
| }; |
| if (delta != null) { |
| try { |
| delta.accept(visitor); |
| } catch (CoreException e) { |
| // should not happen |
| } |
| } |
| } |
| |
| /** |
| * @param file |
| * @param classesFolder |
| * @param outputFolder |
| * @param monitor |
| */ |
| protected void deleteCorrespondingFile(IFile file, IFolder classesFolder, IFolder outputFolder, IProgressMonitor monitor) throws CoreException { |
| IPath path = file.getFullPath(); |
| int segCount = classesFolder.getFullPath().segmentCount(); |
| path = path.removeFirstSegments(segCount); |
| IFile javaFile = findCorrespondingJavaFile(path); |
| if (javaFile != null && javaFile.exists()) |
| return; //There is nothing to do because the file in the output location is from the |
| // java compilation not the copy. |
| IFile outFile = outputFolder.getFile(path); |
| if (outFile.exists()) |
| outFile.delete(true, false, monitor); |
| } |
| |
| |
| /** |
| * Method retrieveClassesFolder. |
| * |
| * @param resource |
| * @return IFolder |
| */ |
| protected IFolder retrieveClassesFolder(IResource resource, IFolder[] classesFolders) { |
| for (int i = 0; i < classesFolders.length; i++) { |
| if (classesFolders[i].getName().equals(resource.getProjectRelativePath().segment(0))) |
| return classesFolders[i]; |
| } |
| return null; |
| } |
| |
| |
| /** |
| * Checks whether this builder is configured to run <b>after </b> the Java builder. |
| * |
| * @return <code>true</code> if the builder order is correct, and <code>false</code> |
| * otherwise |
| * @exception CoreException |
| * if something goes wrong |
| */ |
| private boolean checkBuilderOrdering() throws CoreException { |
| // determine relative builder position from project's buildspec |
| ICommand[] cs = getProject().getDescription().getBuildSpec(); |
| int myIndex = -1; |
| int javaBuilderIndex = -1; |
| for (int i = 0; i < cs.length; i++) { |
| if (cs[i].getBuilderName().equals(JavaCore.BUILDER_ID)) { |
| javaBuilderIndex = i; |
| } else if (cs[i].getBuilderName().equals(BUILDER_ID)) { |
| myIndex = i; |
| } |
| } |
| return myIndex > javaBuilderIndex; |
| } |
| |
| /** |
| * Copies class files from the given source folder to the given destination folder. The |
| * destination folder will be created if required, but only if at least one class file is |
| * copied. |
| * |
| * @param source |
| * the source folder; must exist |
| * @param dest |
| * the destination folder; may or may not exist |
| * @param monitor |
| * the progress monitor, or <code>null</code> if none |
| * @exception CoreException |
| * if something goes wrong |
| */ |
| private void copyClassFiles(IFolder source, final IFolder dest, final IProgressMonitor monitor) throws CoreException { |
| if (DEBUG) { |
| System.out.println(BUILDER_ID + ": Begin copying class files from " + source.getFullPath() + " to " + dest.getFullPath()); //$NON-NLS-1$ //$NON-NLS-2$ |
| } |
| |
| final int sourcePathLength = source.getFullPath().segmentCount(); |
| |
| class Visitor implements IResourceVisitor { |
| public boolean visit(IResource res) throws CoreException { |
| if (res.getType() == IResource.FILE) { |
| IFile file = (IFile) res; |
| |
| // compute relative path from source folder to this file |
| IPath filePath = file.getFullPath(); |
| IPath dpath = filePath.removeFirstSegments(sourcePathLength); |
| IFile targetFile = dest.getFile(dpath); |
| copyFile(file, targetFile, dpath, monitor); |
| } |
| return true; |
| } |
| } |
| |
| try { |
| source.accept(new Visitor()); |
| } catch (CoreException e) { |
| // should not happen |
| } |
| |
| } |
| |
| /** |
| * Copies the given file to the given destination file. Does nothing if the destination file |
| * already exists. |
| * |
| * @param source |
| * the source file; must exist |
| * @param dest |
| * the destination file; may or may not exist; never overwritten |
| * @param monitor |
| * the progress monitor, or <code>null</code> if none |
| * @exception CoreException |
| * if something goes wrong |
| */ |
| private void copyFile(IFile source, IFile dest, IPath fileRelativePath, IProgressMonitor monitor) throws CoreException { |
| if (pruneForJavaSource(source, fileRelativePath, monitor)) |
| return; //no copy necessary. |
| File sourceFile = null, destFile = null; |
| if (source.exists()) |
| sourceFile = source.getLocation().toFile(); |
| if (dest.exists()) |
| destFile = dest.getLocation().toFile(); |
| |
| if (destFile != null && sourceFile != null) { |
| if (DEBUG) |
| System.out.println(BUILDER_ID + ": " + dest.getFullPath() + " already exists."); //$NON-NLS-1$ //$NON-NLS-2$ |
| if (destFile.lastModified() == sourceFile.lastModified()) |
| return; |
| dest.setContents(source.getContents(false), true, false, monitor); //we have to force |
| // b/c set the mod |
| // stamp makes it |
| // think it is out of |
| // synch. |
| synchronizeModificationStamps(sourceFile, destFile); |
| return; |
| } |
| if (DEBUG) { |
| System.out.println(BUILDER_ID + ": Creating " + dest.getFullPath()); //$NON-NLS-1$ |
| } |
| |
| IContainer parent = dest.getParent(); |
| if (parent.getType() == IResource.FOLDER) { |
| mkdirs((IFolder) parent, monitor); |
| } |
| dest.create(source.getContents(false), false, monitor); |
| destFile = dest.getLocation().toFile(); |
| synchronizeModificationStamps(sourceFile, destFile); |
| dest.setDerived(true); |
| } |
| |
| /** |
| * Return true if a corresponding .java file is found. Remove the .class file from the |
| * imported_classes folder (i.e., delete the source file). |
| * |
| * @param source |
| * @param monitor |
| * @return |
| */ |
| private boolean pruneForJavaSource(IFile classFile, IPath fileRelativePath, IProgressMonitor monitor) throws CoreException { |
| if (classFile.exists()) { |
| IFile javaFile = findCorrespondingJavaFile(fileRelativePath); |
| if (javaFile != null && javaFile.exists()) { |
| ResourcesPlugin.getWorkspace().validateEdit(new IFile[]{javaFile}, null); |
| classFile.delete(true, false, monitor); |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| |
| /** |
| * @param classFilePath |
| * @return |
| */ |
| private IFile findCorrespondingJavaFile(IPath classFilePath) { |
| IPath javaPath = convertToJavaPath(classFilePath); |
| List sourceFolders = getSourceContainers(); |
| IContainer cont; |
| IFile javaFile; |
| for (int i = 0; i < sourceFolders.size(); i++) { |
| cont = (IContainer) sourceFolders.get(i); |
| javaFile = cont.getFile(javaPath); |
| if (javaFile.exists()) |
| return javaFile; |
| } |
| return null; |
| } |
| |
| |
| private List getSourceContainers() { |
| if (sourceContainers == null) |
| sourceContainers = JemProjectUtilities.getSourceContainers(getProject()); |
| return sourceContainers; |
| } |
| |
| |
| /** |
| * @param classFile |
| * @return |
| */ |
| private IPath convertToJavaPath(IPath classFilePath) { |
| IPath javaPath = classFilePath.removeFileExtension(); |
| //handle inner classes...look for outermost java file |
| String fileName = classFilePath.lastSegment(); |
| int innerIndex = fileName.indexOf('$'); |
| if (innerIndex > -1) { |
| javaPath = javaPath.removeLastSegments(1); |
| javaPath = javaPath.append(fileName.substring(0, innerIndex)); |
| } |
| javaPath = javaPath.addFileExtension("java"); //$NON-NLS-1$ |
| return javaPath; |
| } |
| |
| |
| /** |
| * @param source |
| * @param dest |
| */ |
| private void synchronizeModificationStamps(File sourceFile, File destFile) { |
| if (destFile != null && sourceFile != null) { |
| destFile.setLastModified(sourceFile.lastModified()); |
| needOutputRefresh = true; |
| } |
| } |
| |
| |
| /** |
| * Creates the given folder, and its containing folders, if required. Does nothing if the given |
| * folder already exists. |
| * |
| * @param folder |
| * the folder to create |
| * @param monitor |
| * the progress monitor, or <code>null</code> if none |
| * @exception CoreException |
| * if something goes wrong |
| */ |
| private void mkdirs(IFolder folder, IProgressMonitor monitor) throws CoreException { |
| if (folder.exists()) { |
| return; |
| } |
| IContainer parent = folder.getParent(); |
| if (!parent.exists() && parent.getType() == IResource.FOLDER) { |
| mkdirs((IFolder) parent, monitor); |
| } |
| folder.create(false, true, monitor); |
| } |
| |
| private IFolder[] getClassesFolders() { |
| IProject project = getProject(); |
| IJavaProject javaProj = JemProjectUtilities.getJavaProject(project); |
| if (javaProj == null) |
| return new IFolder[0]; |
| List result = null; |
| IClasspathEntry[] entries; |
| try { |
| entries = javaProj.getResolvedClasspath(true); |
| } catch (JavaModelException e) { |
| return new IFolder[0]; |
| } |
| for (int i = 0; i < entries.length; i++) { |
| IClasspathEntry entry = entries[i]; |
| if (entry.getEntryKind() == IClasspathEntry.CPE_LIBRARY) { |
| IPath path = entry.getPath(); |
| IResource res = project.getWorkspace().getRoot().findMember(path); |
| if (res != null && res.isAccessible() && res.getType() == IResource.FOLDER && res.getProject().equals(project)) { |
| if (result == null) |
| result = new ArrayList(1); |
| result.add(res); |
| } |
| } |
| } |
| if (result == null) |
| return new IFolder[0]; |
| return (IFolder[]) result.toArray(new IFolder[result.size()]); |
| } |
| } |