blob: 707a96dba454d68829411be618a40fd737dcfbc5 [file] [log] [blame]
/*******************************************************************************
* 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.
*
* @deprecated This class is only used for backwards compatibility
*/
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 {
// this builder is unnecessary in WTP 1.5
return null;
// 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++) {
if (!classFolders[i].equals(outputFolder))
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()]);
}
}