X-Git-Url: http://git.phpeclipse.com diff --git a/net.sourceforge.phpeclipse/src/net/sourceforge/phpdt/internal/core/builder/PHPBuilder.java b/net.sourceforge.phpeclipse/src/net/sourceforge/phpdt/internal/core/builder/PHPBuilder.java new file mode 100644 index 0000000..ee59f34 --- /dev/null +++ b/net.sourceforge.phpeclipse/src/net/sourceforge/phpdt/internal/core/builder/PHPBuilder.java @@ -0,0 +1,598 @@ +/******************************************************************************* + * Copyright (c) 2000, 2003 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Common Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/cpl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package net.sourceforge.phpdt.internal.core.builder; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Date; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Map; + +import net.sourceforge.phpdt.core.IClasspathEntry; +import net.sourceforge.phpdt.core.IJavaModelMarker; +import net.sourceforge.phpdt.core.JavaModelException; +import net.sourceforge.phpdt.core.compiler.CharOperation; +import net.sourceforge.phpdt.internal.core.JavaModel; +import net.sourceforge.phpdt.internal.core.JavaModelManager; +import net.sourceforge.phpdt.internal.core.JavaProject; +import net.sourceforge.phpdt.internal.core.Util; +import net.sourceforge.phpdt.internal.core.util.SimpleLookupTable; +import net.sourceforge.phpeclipse.PHPCore; + +import org.eclipse.core.resources.IMarker; +import org.eclipse.core.resources.IProject; +import org.eclipse.core.resources.IResource; +import org.eclipse.core.resources.IResourceChangeEvent; +import org.eclipse.core.resources.IResourceDelta; +import org.eclipse.core.resources.IWorkspaceRoot; +import org.eclipse.core.resources.IncrementalProjectBuilder; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IPath; +import org.eclipse.core.runtime.IProgressMonitor; + +public class PHPBuilder extends IncrementalProjectBuilder { + + IProject currentProject; + JavaProject javaProject; + IWorkspaceRoot workspaceRoot; + NameEnvironment nameEnvironment; + SimpleLookupTable binaryLocationsPerProject; // maps a project to its binary resources (output folders, class folders, zip/jar files) + State lastState; + BuildNotifier notifier; + char[][] extraResourceFileFilters; + String[] extraResourceFolderFilters; + + public static final String CLASS_EXTENSION = "class"; //$NON-NLS-1$ + + public static boolean DEBUG = true; + + /** + * A list of project names that have been built. + * This list is used to reset the JavaModel.existingExternalFiles cache when a build cycle begins + * so that deleted external jars are discovered. + */ + static ArrayList builtProjects = null; + + public static IMarker[] getProblemsFor(IResource resource) { + try { + if (resource != null && resource.exists()) + return resource.findMarkers(IJavaModelMarker.JAVA_MODEL_PROBLEM_MARKER, false, IResource.DEPTH_INFINITE); + } catch (CoreException e) { + } // assume there are no problems + return new IMarker[0]; + } + + public static IMarker[] getTasksFor(IResource resource) { + try { + if (resource != null && resource.exists()) + return resource.findMarkers(IJavaModelMarker.TASK_MARKER, false, IResource.DEPTH_INFINITE); + } catch (CoreException e) { + } // assume there are no tasks + return new IMarker[0]; + } + + public static void finishedBuilding(IResourceChangeEvent event) { + BuildNotifier.resetProblemCounters(); + } + + public static void removeProblemsFor(IResource resource) { + try { + if (resource != null && resource.exists()) + resource.deleteMarkers(IJavaModelMarker.JAVA_MODEL_PROBLEM_MARKER, false, IResource.DEPTH_INFINITE); + } catch (CoreException e) { + } // assume there were no problems + } + + public static void removeTasksFor(IResource resource) { + try { + if (resource != null && resource.exists()) + resource.deleteMarkers(IJavaModelMarker.TASK_MARKER, false, IResource.DEPTH_INFINITE); + } catch (CoreException e) { + } // assume there were no problems + } + + public static void removeProblemsAndTasksFor(IResource resource) { + try { + if (resource != null && resource.exists()) { + resource.deleteMarkers(IJavaModelMarker.JAVA_MODEL_PROBLEM_MARKER, false, IResource.DEPTH_INFINITE); + resource.deleteMarkers(IJavaModelMarker.TASK_MARKER, false, IResource.DEPTH_INFINITE); + } + } catch (CoreException e) { + } // assume there were no problems + } + + public static State readState(IProject project, DataInputStream in) throws IOException { + return State.read(project, in); + } + + public static void writeState(Object state, DataOutputStream out) throws IOException { + ((State) state).write(out); + } + + public PHPBuilder() { + } + + protected IProject[] build(int kind, Map ignored, IProgressMonitor monitor) throws CoreException { + this.currentProject = getProject(); + if (currentProject == null || !currentProject.isAccessible()) + return new IProject[0]; + + if (DEBUG) + System.out.println("\nStarting build of " + currentProject.getName() //$NON-NLS-1$ + +" @ " + new Date(System.currentTimeMillis())); //$NON-NLS-1$ + this.notifier = new BuildNotifier(monitor, currentProject); + notifier.begin(); + boolean ok = false; + try { + notifier.checkCancel(); + initializeBuilder(); + + if (isWorthBuilding()) { + if (kind == FULL_BUILD) { + buildAll(); + } else { + if ((this.lastState = getLastState(currentProject)) == null) { + if (DEBUG) + System.out.println("Performing full build since last saved state was not found"); //$NON-NLS-1$ + buildAll(); + } else if (hasClasspathChanged()) { + // if the output location changes, do not delete the binary files from old location + // the user may be trying something + buildAll(); + } else if (nameEnvironment.sourceLocations.length > 0) { + // if there is no source to compile & no classpath changes then we are done + SimpleLookupTable deltas = findDeltas(); + if (deltas == null) + buildAll(); + else if (deltas.elementSize > 0) + buildDeltas(deltas); + else if (DEBUG) + System.out.println("Nothing to build since deltas were empty"); //$NON-NLS-1$ + } else { + if (hasStructuralDelta()) { // double check that a jar file didn't get replaced in a binary project + buildAll(); + } else { + if (DEBUG) + System.out.println("Nothing to build since there are no source folders and no deltas"); //$NON-NLS-1$ + lastState.tagAsNoopBuild(); + } + } + } + ok = true; + } + } catch (CoreException e) { + Util.log(e, "JavaBuilder handling CoreException"); //$NON-NLS-1$ + IMarker marker = currentProject.createMarker(IJavaModelMarker.JAVA_MODEL_PROBLEM_MARKER); + marker.setAttribute(IMarker.MESSAGE, Util.bind("build.inconsistentProject", e.getLocalizedMessage())); //$NON-NLS-1$ + marker.setAttribute(IMarker.SEVERITY, IMarker.SEVERITY_ERROR); + } catch (ImageBuilderInternalException e) { + Util.log(e.getThrowable(), "JavaBuilder handling ImageBuilderInternalException"); //$NON-NLS-1$ + IMarker marker = currentProject.createMarker(IJavaModelMarker.JAVA_MODEL_PROBLEM_MARKER); + marker.setAttribute(IMarker.MESSAGE, Util.bind("build.inconsistentProject", e.coreException.getLocalizedMessage())); //$NON-NLS-1$ + marker.setAttribute(IMarker.SEVERITY, IMarker.SEVERITY_ERROR); + } catch (MissingClassFileException e) { + // do not log this exception since its thrown to handle aborted compiles because of missing class files + if (DEBUG) + System.out.println(Util.bind("build.incompleteClassPath", e.missingClassFile)); //$NON-NLS-1$ + IMarker marker = currentProject.createMarker(IJavaModelMarker.JAVA_MODEL_PROBLEM_MARKER); + marker.setAttribute(IMarker.MESSAGE, Util.bind("build.incompleteClassPath", e.missingClassFile)); //$NON-NLS-1$ + marker.setAttribute(IMarker.SEVERITY, IMarker.SEVERITY_ERROR); + } catch (MissingSourceFileException e) { + // do not log this exception since its thrown to handle aborted compiles because of missing source files + if (DEBUG) + System.out.println(Util.bind("build.missingSourceFile", e.missingSourceFile)); //$NON-NLS-1$ + removeProblemsAndTasksFor(currentProject); // make this the only problem for this project + IMarker marker = currentProject.createMarker(IJavaModelMarker.JAVA_MODEL_PROBLEM_MARKER); + marker.setAttribute(IMarker.MESSAGE, Util.bind("build.missingSourceFile", e.missingSourceFile)); //$NON-NLS-1$ + marker.setAttribute(IMarker.SEVERITY, IMarker.SEVERITY_ERROR); + } finally { + if (!ok) + // If the build failed, clear the previously built state, forcing a full build next time. + clearLastState(); + notifier.done(); + cleanup(); + } + IProject[] requiredProjects = getRequiredProjects(true); + if (DEBUG) + System.out.println("Finished build of " + currentProject.getName() //$NON-NLS-1$ + +" @ " + new Date(System.currentTimeMillis())); //$NON-NLS-1$ + return requiredProjects; + } + + private void buildAll() { + notifier.checkCancel(); + notifier.subTask(Util.bind("build.preparingBuild")); //$NON-NLS-1$ + if (DEBUG && lastState != null) + System.out.println("Clearing last state : " + lastState); //$NON-NLS-1$ + clearLastState(); + BatchImageBuilder imageBuilder = new BatchImageBuilder(this); + imageBuilder.build(); + recordNewState(imageBuilder.newState); + } + + private void buildDeltas(SimpleLookupTable deltas) { + notifier.checkCancel(); + notifier.subTask(Util.bind("build.preparingBuild")); //$NON-NLS-1$ + if (DEBUG && lastState != null) + System.out.println("Clearing last state : " + lastState); //$NON-NLS-1$ + clearLastState(); // clear the previously built state so if the build fails, a full build will occur next time + IncrementalImageBuilder imageBuilder = new IncrementalImageBuilder(this); + if (imageBuilder.build(deltas)) + recordNewState(imageBuilder.newState); + else + buildAll(); + } + + private void cleanup() { + this.nameEnvironment = null; + this.binaryLocationsPerProject = null; + this.lastState = null; + this.notifier = null; + this.extraResourceFileFilters = null; + this.extraResourceFolderFilters = null; + } + + private void clearLastState() { + JavaModelManager.getJavaModelManager().setLastBuiltState(currentProject, null); + } + + boolean filterExtraResource(IResource resource) { + if (extraResourceFileFilters != null) { + char[] name = resource.getName().toCharArray(); + for (int i = 0, l = extraResourceFileFilters.length; i < l; i++) + if (CharOperation.match(extraResourceFileFilters[i], name, true)) + return true; + } + if (extraResourceFolderFilters != null) { + IPath path = resource.getProjectRelativePath(); + String pathName = path.toString(); + int count = path.segmentCount(); + if (resource.getType() == IResource.FILE) + count--; + for (int i = 0, l = extraResourceFolderFilters.length; i < l; i++) + if (pathName.indexOf(extraResourceFolderFilters[i]) != -1) + for (int j = 0; j < count; j++) + if (extraResourceFolderFilters[i].equals(path.segment(j))) + return true; + } + return false; + } + + private SimpleLookupTable findDeltas() { + notifier.subTask(Util.bind("build.readingDelta", currentProject.getName())); //$NON-NLS-1$ + IResourceDelta delta = getDelta(currentProject); + SimpleLookupTable deltas = new SimpleLookupTable(3); + if (delta != null) { + if (delta.getKind() != IResourceDelta.NO_CHANGE) { + if (DEBUG) + System.out.println("Found source delta for: " + currentProject.getName()); //$NON-NLS-1$ + deltas.put(currentProject, delta); + } + } else { + if (DEBUG) + System.out.println("Missing delta for: " + currentProject.getName()); //$NON-NLS-1$ + notifier.subTask(""); //$NON-NLS-1$ + return null; + } + + Object[] keyTable = binaryLocationsPerProject.keyTable; + Object[] valueTable = binaryLocationsPerProject.valueTable; + nextProject : for (int i = 0, l = keyTable.length; i < l; i++) { + IProject p = (IProject) keyTable[i]; + if (p != null && p != currentProject) { + State s = getLastState(p); + if (!lastState.wasStructurallyChanged(p, s)) { // see if we can skip its delta + if (s.wasNoopBuild()) + continue nextProject; // project has no source folders and can be skipped + // ClasspathLocation[] classFoldersAndJars = (ClasspathLocation[]) valueTable[i]; + boolean canSkip = true; + // for (int j = 0, m = classFoldersAndJars.length; j < m; j++) { + // if (classFoldersAndJars[j].isOutputFolder()) + // classFoldersAndJars[j] = null; // can ignore output folder since project was not structurally changed + // else + // canSkip = false; + // } + if (canSkip) + continue nextProject; // project has no structural changes in its output folders + } + + notifier.subTask(Util.bind("build.readingDelta", p.getName())); //$NON-NLS-1$ + delta = getDelta(p); + if (delta != null) { + if (delta.getKind() != IResourceDelta.NO_CHANGE) { + if (DEBUG) + System.out.println("Found binary delta for: " + p.getName()); //$NON-NLS-1$ + deltas.put(p, delta); + } + } else { + if (DEBUG) + System.out.println("Missing delta for: " + p.getName()); //$NON-NLS-1$ + notifier.subTask(""); //$NON-NLS-1$ + return null; + } + } + } + notifier.subTask(""); //$NON-NLS-1$ + return deltas; + } + + private State getLastState(IProject project) { + return (State) JavaModelManager.getJavaModelManager().getLastBuiltState(project, notifier.monitor); + } + + /* Return the list of projects for which it requires a resource delta. This builder's project + * is implicitly included and need not be specified. Builders must re-specify the list + * of interesting projects every time they are run as this is not carried forward + * beyond the next build. Missing projects should be specified but will be ignored until + * they are added to the workspace. + */ + private IProject[] getRequiredProjects(boolean includeBinaryPrerequisites) { + if (javaProject == null || workspaceRoot == null) + return new IProject[0]; + + ArrayList projects = new ArrayList(); + try { + IClasspathEntry[] entries = javaProject.getExpandedClasspath(true); + for (int i = 0, l = entries.length; i < l; i++) { + IClasspathEntry entry = entries[i]; + IPath path = entry.getPath(); + IProject p = null; + switch (entry.getEntryKind()) { + case IClasspathEntry.CPE_PROJECT : + p = workspaceRoot.getProject(path.lastSegment()); // missing projects are considered too + break; + case IClasspathEntry.CPE_LIBRARY : + if (includeBinaryPrerequisites && path.segmentCount() > 1) { + // some binary resources on the class path can come from projects that are not included in the project references + IResource resource = workspaceRoot.findMember(path.segment(0)); + if (resource instanceof IProject) + p = (IProject) resource; + } + } + if (p != null && !projects.contains(p)) + projects.add(p); + } + } catch (JavaModelException e) { + return new IProject[0]; + } + IProject[] result = new IProject[projects.size()]; + projects.toArray(result); + return result; + } + + private boolean hasClasspathChanged() { + ClasspathMultiDirectory[] newSourceLocations = nameEnvironment.sourceLocations; + ClasspathMultiDirectory[] oldSourceLocations = lastState.sourceLocations; + int newLength = newSourceLocations.length; + int oldLength = oldSourceLocations.length; + int n, o; + for (n = o = 0; n < newLength && o < oldLength; n++, o++) { + if (newSourceLocations[n].equals(oldSourceLocations[o])) + continue; // checks source & output folders + try { + if (newSourceLocations[n].sourceFolder.members().length == 0) { // added new empty source folder + o--; + continue; + } + } catch (CoreException ignore) { + } + if (DEBUG) + System.out.println(newSourceLocations[n] + " != " + oldSourceLocations[o]); //$NON-NLS-1$ + return true; + } + while (n < newLength) { + try { + if (newSourceLocations[n].sourceFolder.members().length == 0) { // added new empty source folder + n++; + continue; + } + } catch (CoreException ignore) { + } + if (DEBUG) + System.out.println("Added non-empty source folder"); //$NON-NLS-1$ + return true; + } + if (o < oldLength) { + if (DEBUG) + System.out.println("Removed source folder"); //$NON-NLS-1$ + return true; + } + + // ClasspathLocation[] newBinaryLocations = nameEnvironment.binaryLocations; + // ClasspathLocation[] oldBinaryLocations = lastState.binaryLocations; + // newLength = newBinaryLocations.length; + // oldLength = oldBinaryLocations.length; + // for (n = o = 0; n < newLength && o < oldLength; n++, o++) { + // if (newBinaryLocations[n].equals(oldBinaryLocations[o])) continue; + // if (DEBUG) + // System.out.println(newBinaryLocations[n] + " != " + oldBinaryLocations[o]); //$NON-NLS-1$ + // return true; + // } + // if (n < newLength || o < oldLength) { + // if (DEBUG) + // System.out.println("Number of binary folders/jar files has changed"); //$NON-NLS-1$ + // return true; + // } + return false; + } + + private boolean hasStructuralDelta() { + // handle case when currentProject has only .class file folders and/or jar files... no source/output folders + IResourceDelta delta = getDelta(currentProject); + if (delta != null && delta.getKind() != IResourceDelta.NO_CHANGE) { + // ClasspathLocation[] classFoldersAndJars = (ClasspathLocation[]) binaryLocationsPerProject.get(currentProject); + // if (classFoldersAndJars != null) { + // for (int i = 0, l = classFoldersAndJars.length; i < l; i++) { + // ClasspathLocation classFolderOrJar = classFoldersAndJars[i]; // either a .class file folder or a zip/jar file + // if (classFolderOrJar != null) { + // IPath p = classFolderOrJar.getProjectRelativePath(); + // if (p != null) { + // IResourceDelta binaryDelta = delta.findMember(p); + // if (binaryDelta != null && binaryDelta.getKind() != IResourceDelta.NO_CHANGE) + // return true; + // } + // } + // } + // } + } + return false; + } + + private void initializeBuilder() throws CoreException { + this.javaProject = (JavaProject) PHPCore.create(currentProject); + this.workspaceRoot = currentProject.getWorkspace().getRoot(); + + // Flush the existing external files cache if this is the beginning of a build cycle + String projectName = currentProject.getName(); + if (builtProjects == null || builtProjects.contains(projectName)) { + JavaModel.flushExternalFileCache(); + builtProjects = new ArrayList(); + } + builtProjects.add(projectName); + + this.binaryLocationsPerProject = new SimpleLookupTable(3); + this.nameEnvironment = new NameEnvironment(workspaceRoot, javaProject, binaryLocationsPerProject); + + String filterSequence = javaProject.getOption(PHPCore.CORE_JAVA_BUILD_RESOURCE_COPY_FILTER, true); + char[][] filters = filterSequence != null && filterSequence.length() > 0 ? CharOperation.splitAndTrimOn(',', filterSequence.toCharArray()) : null; + if (filters == null) { + this.extraResourceFileFilters = null; + this.extraResourceFolderFilters = null; + } else { + int fileCount = 0, folderCount = 0; + for (int i = 0, l = filters.length; i < l; i++) { + char[] f = filters[i]; + if (f.length == 0) + continue; + if (f[f.length - 1] == '/') + folderCount++; + else + fileCount++; + } + this.extraResourceFileFilters = new char[fileCount][]; + this.extraResourceFolderFilters = new String[folderCount]; + for (int i = 0, l = filters.length; i < l; i++) { + char[] f = filters[i]; + if (f.length == 0) + continue; + if (f[f.length - 1] == '/') + extraResourceFolderFilters[--folderCount] = new String(CharOperation.subarray(f, 0, f.length - 1)); + else + extraResourceFileFilters[--fileCount] = f; + } + } + } + + private boolean isClasspathBroken(IClasspathEntry[] classpath, IProject p) throws CoreException { + if (classpath == JavaProject.INVALID_CLASSPATH) // the .classpath file could not be read + return true; + + IMarker[] markers = p.findMarkers(IJavaModelMarker.BUILDPATH_PROBLEM_MARKER, false, IResource.DEPTH_ZERO); + for (int i = 0, l = markers.length; i < l; i++) + if (((Integer) markers[i].getAttribute(IMarker.SEVERITY)).intValue() == IMarker.SEVERITY_ERROR) + return true; + return false; + } + + private boolean isWorthBuilding() throws CoreException { + boolean abortBuilds = PHPCore.ABORT.equals(javaProject.getOption(PHPCore.CORE_JAVA_BUILD_INVALID_CLASSPATH, true)); + if (!abortBuilds) + return true; + + // Abort build only if there are classpath errors +// if (isClasspathBroken(javaProject.getRawClasspath(), currentProject)) { +// if (DEBUG) +// System.out.println("Aborted build because project has classpath errors (incomplete or involved in cycle)"); //$NON-NLS-1$ +// +// JavaModelManager.getJavaModelManager().deltaProcessor.addForRefresh(javaProject); +// +// removeProblemsAndTasksFor(currentProject); // remove all compilation problems +// +// IMarker marker = currentProject.createMarker(IJavaModelMarker.JAVA_MODEL_PROBLEM_MARKER); +// marker.setAttribute(IMarker.MESSAGE, Util.bind("build.abortDueToClasspathProblems")); //$NON-NLS-1$ +// marker.setAttribute(IMarker.SEVERITY, IMarker.SEVERITY_ERROR); +// return false; +// } + + // make sure all prereq projects have valid build states... only when aborting builds since projects in cycles do not have build states + // except for projects involved in a 'warning' cycle (see below) + IProject[] requiredProjects = getRequiredProjects(false); + next : for (int i = 0, l = requiredProjects.length; i < l; i++) { + IProject p = requiredProjects[i]; + if (getLastState(p) == null) { + // The prereq project has no build state: if this prereq project has a 'warning' cycle marker then allow build (see bug id 23357) + JavaProject prereq = (JavaProject) PHPCore.create(p); + if (prereq.hasCycleMarker() && PHPCore.WARNING.equals(javaProject.getOption(PHPCore.CORE_CIRCULAR_CLASSPATH, true))) + continue; + if (DEBUG) + System.out.println("Aborted build because prereq project " + p.getName() //$NON-NLS-1$ + +" was not built"); //$NON-NLS-1$ + + removeProblemsAndTasksFor(currentProject); // make this the only problem for this project + IMarker marker = currentProject.createMarker(IJavaModelMarker.JAVA_MODEL_PROBLEM_MARKER); + marker.setAttribute(IMarker.MESSAGE, isClasspathBroken(prereq.getRawClasspath(), p) ? Util.bind("build.prereqProjectHasClasspathProblems", p.getName()) //$NON-NLS-1$ + : Util.bind("build.prereqProjectMustBeRebuilt", p.getName())); //$NON-NLS-1$ + marker.setAttribute(IMarker.SEVERITY, IMarker.SEVERITY_ERROR); + return false; + } + } + return true; + } + + /* + * Instruct the build manager that this project is involved in a cycle and + * needs to propagate structural changes to the other projects in the cycle. + */ + void mustPropagateStructuralChanges() { + HashSet cycleParticipants = new HashSet(3); + javaProject.updateCycleParticipants(null, new ArrayList(), cycleParticipants, workspaceRoot, new HashSet(3)); + IPath currentPath = javaProject.getPath(); + Iterator i = cycleParticipants.iterator(); + while (i.hasNext()) { + IPath participantPath = (IPath) i.next(); + if (participantPath != currentPath) { + IProject project = this.workspaceRoot.getProject(participantPath.segment(0)); + if (hasBeenBuilt(project)) { + if (DEBUG) + System.out.println("Requesting another build iteration since cycle participant " + project.getName() //$NON-NLS-1$ + +" has not yet seen some structural changes"); //$NON-NLS-1$ + needRebuild(); + return; + } + } + } + } + + private void recordNewState(State state) { + Object[] keyTable = binaryLocationsPerProject.keyTable; + for (int i = 0, l = keyTable.length; i < l; i++) { + IProject prereqProject = (IProject) keyTable[i]; + if (prereqProject != null && prereqProject != currentProject) + state.recordStructuralDependency(prereqProject, getLastState(prereqProject)); + } + + if (DEBUG) + System.out.println("Recording new state : " + state); //$NON-NLS-1$ + // state.dump(); + JavaModelManager.getJavaModelManager().setLastBuiltState(currentProject, state); + } + + /** + * String representation for debugging purposes + */ + public String toString() { + return currentProject == null ? "JavaBuilder for unknown project" //$NON-NLS-1$ + : "JavaBuilder for " + currentProject.getName(); //$NON-NLS-1$ + } +}