/*******************************************************************************
* Copyright (c) 2005 Business Objects Software Limited and others.
* All rights reserved.
* This file is 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:
* Business Objects Software Limited - initial API and implementation based on Eclipse 3.1.2 code for
* /org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/builder/AbstractImageBuilder.java
* /org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/builder/BatchImageBuilder.java
* /org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/builder/IncrementalImageBuilder.java
* Eclipse source is available at: http://www.eclipse.org/downloads/
*******************************************************************************/
/*
* CALBuilder.java
* Creation date: Nov 2, 2005.
* By: Edward Lam
*/
package org.openquark.cal.eclipse.core.builder;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.eclipse.core.resources.IContainer;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IFolder;
import org.eclipse.core.resources.IMarker;
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.IStorage;
import org.eclipse.core.resources.IWorkspaceRunnable;
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.core.runtime.OperationCanceledException;
import org.eclipse.jdt.core.IPackageFragmentRoot;
import org.openquark.cal.compiler.CompilerMessage;
import org.openquark.cal.compiler.CompilerMessageLogger;
import org.openquark.cal.compiler.ForeignContextProvider;
import org.openquark.cal.compiler.MessageKind;
import org.openquark.cal.compiler.MessageLogger;
import org.openquark.cal.compiler.ModuleContainer;
import org.openquark.cal.compiler.ModuleName;
import org.openquark.cal.compiler.ModuleSourceDefinitionGroup;
import org.openquark.cal.compiler.ModuleTypeInfo;
import org.openquark.cal.compiler.QualifiedName;
import org.openquark.cal.compiler.Refactorer;
import org.openquark.cal.compiler.Scope;
import org.openquark.cal.compiler.ScopedEntity;
import org.openquark.cal.compiler.SourceIdentifier;
import org.openquark.cal.compiler.SourcePosition;
import org.openquark.cal.compiler.SourceRange;
import org.openquark.cal.compiler.ModuleContainer.ISourceManager;
import org.openquark.cal.compiler.ModuleContainer.ISourceManager2;
import org.openquark.cal.compiler.ScopedEntityNamingPolicy.UnqualifiedIfUsingOrSameModule;
import org.openquark.cal.compiler.SourceIdentifier.Category;
import org.openquark.cal.eclipse.core.CALEclipseCorePlugin;
import org.openquark.cal.eclipse.core.CALModelManager;
import org.openquark.cal.eclipse.core.CALModelMarker;
import org.openquark.cal.eclipse.core.CoreOptionIDs;
import org.openquark.cal.eclipse.core.EclipseModuleSourceDefinitionGroup;
import org.openquark.cal.eclipse.core.CALModelManager.SourceManagerFactory;
import org.openquark.cal.eclipse.core.util.Messages;
import org.openquark.cal.eclipse.core.util.Util;
import org.openquark.cal.machine.ProgramResourceLocator;
import org.openquark.cal.machine.StatusListener;
import org.openquark.cal.services.FileSystemResourceHelper;
import org.openquark.cal.services.ProgramModelManager;
import org.openquark.cal.services.ResourcePath;
import org.openquark.util.Pair;
import org.openquark.util.UnsafeCast;
/**
* The incremental project builder for CAL.
* It is important to note that the incremental project builder is responsible for building only a single project.
*
* Static members are used to track state across a complete build iteration, which may involve invocation of
* multiple incremental project builders.
*
* @author Edward Lam
*/
public class CALBuilder extends IncrementalProjectBuilder {
public static boolean DEBUG = false;
/** The number of ticks with which new monitors are initialized. */
private static final int TICKS_PER_MONITOR = 1000;
/** The set of projects for which cal modules have been loaded.
* TODOEL: (HACK?) It would be nicer to serialize this info, and find in per-project info. */
// not used!!
// private static final Set loadedProjects = new HashSet();
/**
* The shared foreign context provider for modules built by the cal builder.
*/
private static final ForeignContextProvider sharedForeignContextProvider = new CALBuilderForeignContextProvider();
/**
* The last built state of the current project.
* This is updated after a build for this builder's project has taken place.
* It is null if clean() is called, or if never built.
* */
private ProjectBuildState lastState;
/**
* List of listeners of modules that have been compiled.
*/
private static final List<BuildMessagesListener> listeners = new ArrayList<BuildMessagesListener>();
/**
* The foreign context provider for modules built by the cal builder.
* @author Edward Lam
*/
private static class CALBuilderForeignContextProvider implements ForeignContextProvider {
/**
* {@inheritDoc}
*/
public ClassLoader getClassLoader(ModuleName moduleName) {
// Get the source file for the module.
IStorage storage = CALModelManager.getCALModelManager().getInputSourceFile(moduleName);
if (storage == null) {
return null;
}
if (storage instanceof IFile) {
// Get the project which contains the source file, and get the corresponding java project.
IFile inputFile = (IFile)storage;
return CALModelManager.getCALModelManager().getClassLoader(inputFile.getProject());
} else {
// eg. a Jar Entry
ICALResourceContainer container = CALModelManager.getCALModelManager().getInputSourceFileContainer(moduleName);
if (container == null) {
return null;
}
IProject project = container.getPackageRoot().getJavaProject().getProject();
return CALModelManager.getCALModelManager().getClassLoader(project);
}
}
}
/**
* A helper class to find changed files in the CAL input folders for this project by visiting a resource delta.
* @author Edward Lam
*/
private static class ChangedCALFolderFileFinder implements IResourceDeltaVisitor {
/** Map<IResource, IFolder> the map of files to their input folders for files in the delta which were added. */
private final Map<IFile, IFolder> addedFiles = new HashMap<IFile, IFolder>();
/** Map<IResource, IFolder> the map of files to their input folders for files in the delta which were removed. */
private final Map<IFile, IFolder> removedFiles = new HashMap<IFile, IFolder>();
/** Map<IResource, IFolder> the map of files to their input folders for files in the delta which changed. */
private final Map<IFile, IFolder> changedFiles = new HashMap<IFile, IFolder>();
/** The input folders for this project. */
private final ICALResourceContainer[] resourceContainers;
ChangedCALFolderFileFinder(IProject project) {
ICALResourceContainer[] containers = CALModelManager.getCALModelManager().getInputFolders(Util.getCalProject(project));
List<ICALResourceContainer> inputFoldersList = new LinkedList<ICALResourceContainer>();
for (final ICALResourceContainer container : containers) {
// we only care about source (writable) packages, not binary
if (container.isWritable()) {
inputFoldersList.add(container);
}
}
this.resourceContainers = inputFoldersList.toArray(new ICALResourceContainer[inputFoldersList.size()]);
}
/**
* {@inheritDoc}
*/
public boolean visit(IResourceDelta delta) throws CoreException {
// The delta resource can be anything from the changed project to the changed .cal file.
IResource deltaResource = delta.getResource();
if (deltaResource.getType() == IResource.FILE) {
IFile deltaFile = (IFile)deltaResource;
IFolder deltaInputFolder = null;
for (final ICALResourceContainer resourceContainer : resourceContainers) {
IPath inputFolderFullPath = resourceContainer.getPath();
// this will only find writable resource containers (ie. not in a jar), but that is fine
if (inputFolderFullPath.matchingFirstSegments(deltaResource.getFullPath()) == inputFolderFullPath.segmentCount()) {
IFolder inputFolder = deltaResource.getProject().getFolder(inputFolderFullPath);
deltaInputFolder = inputFolder;
break;
}
}
if (deltaInputFolder != null) {
switch (delta.getKind()) {
case IResourceDelta.ADDED:
addedFiles.put(deltaFile, deltaInputFolder);
break;
case IResourceDelta.REMOVED:
removedFiles.put(deltaFile, deltaInputFolder);
break;
case IResourceDelta.CHANGED:
changedFiles.put(deltaFile, deltaInputFolder);
break;
default:
// must be a phantom?
break;
}
}
return false;
}
// Must be a container.
IPath deltaResourcePath = deltaResource.getFullPath(); // This can be anything from the changed project to the changed .cal file.
int nDeltaResourceSegments = deltaResourcePath.segmentCount();
for (final ICALResourceContainer container : resourceContainers) {
IPath resourceContainerPath = container.getPath();
if (deltaResourcePath.equals(resourceContainerPath)) {
return true;
} else {
int nInputFolderSegments = resourceContainerPath.segmentCount();
if (nDeltaResourceSegments < nInputFolderSegments) {
// Check if the delta is an ancestor of the input folder.
if (deltaResourcePath.matchingFirstSegments(resourceContainerPath) == nDeltaResourceSegments) {
return true;
}
} else if (nDeltaResourceSegments > nInputFolderSegments) {
// The delta is deeper than the input folder, check if the input folder is an acestor of the delta
if (resourceContainerPath.matchingFirstSegments(deltaResourcePath) == nInputFolderSegments) {
return true;
}
} else {
// (nDeltaResourceSegments == nInputFolderSegments) -- Same number of segments, but the segments don't match.
}
}
}
return false;
}
/**
* @return a copy of the added cal files from the delta.
*/
public Set<IFile> getAddedFiles() {
return Collections.unmodifiableSet(addedFiles.keySet());
}
/**
* @return a copy of the removed cal files from the delta.
*/
public Set<IFile> getRemovedFiles() {
return Collections.unmodifiableSet(removedFiles.keySet());
}
/**
* @return a copy of the changed cal files from the delta.
*/
public Set<IFile> getChangedFiles() {
return Collections.unmodifiableSet(changedFiles.keySet());
}
}
/**
* A status listener which provides the build notifier with updates.
* @author Edward Lam
*/
private static class BuildStatusListener implements StatusListener {
/** (Set of ModuleName) the names of modules for which SM_LOADED module status events have been received by this listener. */
private final Set/*ModuleName*/<ModuleName> loadedModuleNames = new HashSet<ModuleName>();
private final IProgressMonitor progressMonitor;
BuildStatusListener(IProgressMonitor progressMonitor) {
progressMonitor = Util.monitorFor(progressMonitor);
this.progressMonitor = progressMonitor;
progressMonitor.beginTask("", TICKS_PER_MONITOR);
}
/**
* {@inheritDoc}
*/
public void incrementCompleted(double d) {
checkAbort();
// Note that d is a percentage.
progressMonitor.worked((int)((d / 100.0) * TICKS_PER_MONITOR));
}
/**
* {@inheritDoc}
*/
public void setModuleStatus(StatusListener.Status.Module moduleStatus, ModuleName moduleName) {
checkAbort();
if (moduleStatus == StatusListener.SM_LOADED) {
progressMonitor.subTask("Loaded module " + moduleName);
loadedModuleNames.add(moduleName);
}
}
/**
* {@inheritDoc}
*/
public void setEntityStatus(StatusListener.Status.Entity entityStatus, String entityName) {
checkAbort();
if (entityStatus == StatusListener.SM_COMPILED) {
progressMonitor.subTask("Compiled: " + entityName);
} else if (entityStatus == StatusListener.SM_ENTITY_GENERATED) {
progressMonitor.subTask("Generated: " + entityName);
} else if (entityStatus == StatusListener.SM_ENTITY_GENERATED_FILE_WRITTEN) {
progressMonitor.subTask("Wrote: " + entityName);
}
}
/**
* @return (Set of ModuleName) the names of modules for which SM_LOADED module status events have been received by this listener.
*/
public Set/*ModuleName*/<ModuleName> getLoadedModuleNames() {
return loadedModuleNames;
}
/**
* Abort compilation if cancelation is requested.
*/
private void checkAbort() {
// TODOEL: This successfully aborts compilation, but currently is reported as an internal error, ie. "Please contact Business Objects."
// The JavaBuilder calls BuildNotifier.checkCancelWithinCompiler, which throws an AbortCompilation runtime exception.
//
// One possibility here is to log a fatal error to the compiler's message logger.
Util.checkCanceled(progressMonitor);
}
/**
* Signal to the status listener that compilation is done.
*/
public void done() {
progressMonitor.done();
}
}
/**
* Hook allowing to initialize some static state before a complete build iteration.
* This hook is invoked during PRE_AUTO_BUILD notification
*/
public static void buildStarting() {
// build is about to start
}
/**
* Hook allowing to reset some static state after a complete build iteration.
* This hook is invoked during POST_AUTO_BUILD notification
*/
public static void buildFinished() {
/*
* TODOEL: Not yet called.
*/
GlobalBuildState.resetProblemCounters();
}
/**
* Get a subprogress monitor of a given monitor.
* @param parentMonitor the monitor for which to get a subprogress monitor.
* @param fraction the fraction of the parent monitor's ticks to which to allocate to the child.
* @return a subprogress monitor with the given attributes.
*/
private static IProgressMonitor getSubMonitor(IProgressMonitor parentMonitor, double fraction) {
return Util.subMonitorFor(parentMonitor, (int)(TICKS_PER_MONITOR * fraction));
}
/**
* @return true if the CALBuilder is enabled.
*/
public static boolean isEnabled(){
return CoreOptionIDs.ENABLED.equals(CALEclipseCorePlugin.getOption(CoreOptionIDs.CORE_CAL_BUILD_ENABLE));
}
/**
* {@inheritDoc}
*/
@Override
protected IProject[] build(int kind, Map args, IProgressMonitor monitor) throws CoreException {
// Check if the current project is valid.
IProject currentProject = getProject();
if (currentProject == null || !currentProject.isAccessible()) {
return new IProject[0];
}
// Check if the builder is enabled.
if (!isEnabled()) {
// Remove CAL builder problem markers.
removeProblemsAndTasksFor(currentProject);
return new IProject[0];
}
if (DEBUG) {
Util.printlnWithDate("\nStarting build of " + currentProject.getName()); //$NON-NLS-1$
}
monitor = Util.monitorFor(monitor);
monitor.beginTask("", TICKS_PER_MONITOR);
boolean ok = false;
try {
Util.checkCanceled(monitor);
if (isWorthBuilding()) {
if (kind == FULL_BUILD) {
buildAll(getSubMonitor(monitor, 1.0));
} else {
Set<IResourceDelta> deltas = findDeltas(currentProject, getSubMonitor(monitor, 0.2));
if (deltas == null) {
buildAll(getSubMonitor(monitor, 0.8));
} else {
buildDeltas(deltas, getSubMonitor(monitor, 0.8));
}
}
ok = true;
}
} catch (CoreException e) {
Util.log(e, "CALBuilder handling CoreException while building: " + currentProject.getName()); //$NON-NLS-1$
IMarker marker = currentProject.createMarker(CALModelMarker.CAL_MODEL_PROBLEM_MARKER);
marker.setAttribute(IMarker.MESSAGE, Messages.bind(Messages.build_inconsistentProject, e.getLocalizedMessage()));
marker.setAttribute(IMarker.SEVERITY, IMarker.SEVERITY_ERROR);
} finally {
// If the build failed, clear the previously built state, forcing a full build next time.
if (!ok) {
// clearLastState();
}
monitor.done();
cleanup();
}
IProject[] requiredProjects = getRequiredProjects(currentProject, true);
if (DEBUG) {
Util.printlnWithDate("Finished build of " + currentProject.getName()); //$NON-NLS-1$
}
return requiredProjects;
}
/**
* Common pre-build set up.
* @param monitor the tracking monitor.
*/
private void preBuild(IProgressMonitor monitor) {
Util.checkCanceled(monitor);
monitor.subTask(Messages.build_preparingBuild);
// 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
}
/**
* Common post-build debrief.
* @param logger the logger from the build.
* @param updatedInputFiles (List of IResource) the files which were updated (loaded or otherwise changed) during this build.
* ie. the resource delta was IResourceDelta.CHANGED (not ADDED or REMOVED).
* If null, all resources in the project are taken as changed.
* @param monitor the tracking monitor.
*/
private void postBuild(CompilerMessageLogger logger, List<IFile> updatedInputFiles, IProgressMonitor monitor) {
monitor.beginTask("", TICKS_PER_MONITOR);
try {
// For now, dump all errors to the console.
for (final CompilerMessage message : logger.getCompilerMessages()) {
System.out.println(message);
}
// Update the problem markers from the logger.
updateProblemMarkers(updatedInputFiles, logger);
// Log internal errors from the logger;
logInternalErrors(logger);
// mark output folders as derived.
IFolder[] outputFolders = getOutputFolders();
for (final IFolder folder : outputFolders) {
try {
folder.setDerived(true);
} catch (CoreException e) {
// Resource doesn't exist.
// this could happen if the file was added to the project while the build was going on.
}
}
if (logger.getNErrors() > 0) {
// If the build failed, clear the previously built state, forcing a full build next time.
// clearLastState();
}
// Record the new build state.
this.lastState = new ProjectBuildState(this);
} finally {
monitor.done();
}
}
/**
* A message logger that will not keep messages that are already in the logger.
*
* @author Greg McClement
*/
private static class MessageLoggerWithNoDuplicates extends MessageLogger{
/**
* @param range1 first range to compare
* @param range2 second range to compare
* @return true if the given source range cover the same positions
*/
public boolean same(SourceRange range1, SourceRange range2){
if (SourcePosition.compareByPosition.compare(range1.getStartSourcePosition(), range2.getStartSourcePosition()) != 0){
return false;
}
if (SourcePosition.compareByPosition.compare(range1.getEndSourcePosition(), range2.getEndSourcePosition()) != 0){
return false;
}
return true;
}
private boolean same(CompilerMessage m1, CompilerMessage m2){
if (m1.getSourceRange() != null && m2.getSourceRange() != null){
if (!same(m1.getSourceRange(), m2.getSourceRange())){
return false;
}
}
else{
if (m1.getSourceRange() == null && m2.getSourceRange() == null){
}
else{
// on is null and the other isn't
return false;
}
}
if (!m1.toString().equals(m2.toString())){
return false;
}
return true;
}
private boolean alreadyLogged(CompilerMessage passedInMessage) {
for (final CompilerMessage message : getCompilerMessages()) {
if (same(passedInMessage, message)) {
return true;
}
}
return false;
}
@Override
public void logMessage(CompilerMessage compilerMessage) {
if (alreadyLogged(compilerMessage)){
return;
}
super.logMessage(compilerMessage);
}
@Override
public void logMessages(CompilerMessageLogger otherLogger) {
final List<CompilerMessage> messages = otherLogger.getCompilerMessages();
for (final CompilerMessage message : messages) {
logMessage(message);
}
}
}
/**
* @return a new message logger instance.
*/
private CompilerMessageLogger getNewMessageLogger() {
CompilerMessageLogger messageLogger = new MessageLoggerWithNoDuplicates();
String number = CALEclipseCorePlugin.getOption(CoreOptionIDs.COMPILER_PB_MAX_PER_UNIT);
try {
Integer.parseInt(number);
} catch (NumberFormatException e) {
// What to do??
}
return messageLogger;
}
/**
* Helper message to determine whether an "Unresolved external module import" message is one we can ignore.
* @param compilerMessage a compiler message encountered during compilation
* @return whether this message is an "Unresolved external module import" message which can be ignored
* because it results from a compilation error in the module being resolved.
*/
private boolean isIgnorableUnresolvedExternalModuleImportMessage(CompilerMessage compilerMessage) {
MessageKind messageKind = compilerMessage.getMessageKind();
String message = messageKind.getMessage();
// "Unresolved external module import Cal.Core.String."
String unresolvedExternalModuleImportPrefixString = "Unresolved external module import ";
if (!message.startsWith(unresolvedExternalModuleImportPrefixString)) {
return false;
}
// The error messages looks like
//
// Unresolved external module import MathZogu.
// Unresolved external module import Math. Did you mean Cal.Utilities.Math?
//
int indexOfEndOfFirstSentence = message.indexOf("Did you mean");
if (indexOfEndOfFirstSentence == -1) {
indexOfEndOfFirstSentence = message.length() - 1;
}
// Parse out the module name.
int endOfSentenceIndex = message.lastIndexOf('.', indexOfEndOfFirstSentence); // This is the period at the end of the sentence, guaranteed not a part of a hierarchical module name.
if (endOfSentenceIndex < 0) {
assert false;
return false;
}
ModuleName moduleName = ModuleName.maybeMake(message.substring(unresolvedExternalModuleImportPrefixString.length(), endOfSentenceIndex));
if (moduleName == null) {
assert false;
return false;
}
// Only ignorable if the input file exists in a project referenced by this one.
ICALResourceContainer container = CALModelManager.getCALModelManager().getInputSourceFileContainer(moduleName);
if (container == null) {
return false;
}
IProject inputSourceFileProject =
container.getPackageRoot().getJavaProject().getProject();
IProject[] referencedProjects = null;
try {
referencedProjects = getProject().getReferencedProjects();
} catch (CoreException e) {
// shouldn't happen
e.printStackTrace();
return false;
}
// We can ignore if it's indeed in a referenced project. ie. it just has a compile error.
return Arrays.asList(referencedProjects).contains(inputSourceFileProject);
}
/**
* Update the problem markers associated with the build of this project.
* @param updatedInputFiles (List of IResource) the files which were loaded or otherwise changed during this build.
* ie. the resource delta was IResourceDelta.CHANGED (not ADDED or REMOVED).
* If null, all resources in the project are taken as changed.
* @param messageLogger the logger from which to update warnings and errors.
*/
private void updateProblemMarkers(List<IFile> updatedInputFiles, CompilerMessageLogger messageLogger) {
/*
* TODOEL: Create a marker subtype for compilation problems.
* Just remove those problems in this method -- there may be other types of problems in this project.
*/
CALModelManager modelManager = CALModelManager.getCALModelManager();
/*
* Delete problem markers.
*/
final HashSet<ModuleName> wasCompiled = new HashSet<ModuleName>();
if (updatedInputFiles == null) {
removeProblemsFor(getProject());
} else {
for (final IFile resource : updatedInputFiles) {
removeProblemsFor(resource);
ModuleName moduleName = modelManager.getModuleName(resource);
if (moduleName != null) {
wasCompiled.add(moduleName);
}
}
}
/*
* Add new problem markers.
*/
for (final CompilerMessage cm : messageLogger.getCompilerMessages(CompilerMessage.Severity.WARNING)) {
final CompilerMessage compilerMessage = cm;
/*
* TODOEL: ignore fatal? (ie. unable to recover..)
*/
// Only consider things at least as bad as warnings..
if (compilerMessage.getSeverity().compareTo(CompilerMessage.Severity.WARNING) < 0) {
continue;
}
// Check for messages that we can ignore and for the UnsupportedClassVersionError messages.
final boolean isUnsupportedClassVersionError;
{
if (compilerMessage.getException() instanceof OperationCanceledException) {
// Compilation was aborted.
// continue;
// We may have to do this instead to get the builder to call us again on this project.
throw (RuntimeException)compilerMessage.getException();
}
/*
* **HACK** -- don't have access to the message kind class, since those are package private.
* Want a comparison such as:
* if (messageKind.getClass() == MessageKind.Error.UnresolvedExternalModuleImport.class) { ... }
* For now we parse the actual message string -- *yuck*.
*/
MessageKind messageKind = compilerMessage.getMessageKind();
String message = messageKind.getMessage();
if (message.indexOf("java.lang.UnsupportedClassVersionError") != -1){
isUnsupportedClassVersionError = true;
} else {
isUnsupportedClassVersionError = false;
if (isIgnorableUnresolvedExternalModuleImportMessage(compilerMessage)) {
continue;
}
}
}
// Get the resource for the marker.
IStorage markerResourceTemp = null;
final SourceRange sourceRange = compilerMessage.getSourceRange();
if (sourceRange != null) {
ModuleName sourceName = ModuleName.maybeMake(sourceRange.getSourceName());
if (sourceName != null) {
wasCompiled.add(sourceName);
markerResourceTemp = modelManager.getInputSourceFile(sourceName);
}
}
// Assign to final var so that it can be acccessed by the runnable.
// we only care about updating markers if this is a file, not a jar entry
if (! (markerResourceTemp instanceof IFile)) {
return;
}
final IFile markerResource = (IFile) markerResourceTemp;
// Construct the attribute map then create the marker with the attributes.
try {
// The attributes for the marker.
final Map<String, Object> attributeMap = new HashMap<String, Object>();
// attributeMap.put(IMarker.MESSAGE, Messages.bind(Messages.build_inconsistentProject, "foo"));
String markerMessage = compilerMessage.getMessage();
Exception compileException = compilerMessage.getException();
if (isUnsupportedClassVersionError){
markerMessage += " " + Messages.error_hint_UnsupportedClassVersionError;
}
if (compileException != null) {
markerMessage += " " + compileException.getLocalizedMessage();
}
attributeMap.put(IMarker.MESSAGE, markerMessage);
if (compilerMessage.getSeverity() == CompilerMessage.Severity.WARNING) {
attributeMap.put(IMarker.SEVERITY, Integer.valueOf(IMarker.SEVERITY_WARNING));
} else {
attributeMap.put(IMarker.SEVERITY, Integer.valueOf(IMarker.SEVERITY_ERROR));
}
// Add source position.
if (sourceRange != null) {
// Get the source text.
// Note: SLOW.
ByteArrayOutputStream baos = new ByteArrayOutputStream(4096);
InputStream contents = null;
try {
contents = (markerResource).getContents();
FileSystemResourceHelper.transferData(contents, baos);
} catch (CoreException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
baos.close();
} catch (IOException e) {
e.printStackTrace();
}
if (contents != null) {
try {
contents.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
String sourceText = new String(baos.toByteArray()); // TODOEL: converted using platform charset.
SourcePosition startSourcePosition = sourceRange.getStartSourcePosition();
if (startSourcePosition.getLine() != 0){
final int startPosition;
try {
startPosition = sourceRange.getStartSourcePosition().getPosition(sourceText);
} catch (IllegalArgumentException e){
// TEMP: shouldn't happen.
// dump some info so that source position problems can be debugged.
// A string with the compiler messages for this build.
StringBuilder messagesBuf = new StringBuilder();
for (final CompilerMessage message : messageLogger.getCompilerMessages(CompilerMessage.Severity.WARNING)) {
messagesBuf.append(message.toString() + '\n');
}
// the path to the marker with the problem.
String pathString = markerResource.getFullPath().toString();
// Log to the error log.
String errorMessage = "Exception getting position from resource " + pathString + ".\n" + messagesBuf.toString();
System.err.println(errorMessage);
Util.log(e, errorMessage);
// rethrow the IllegalArgumentException for now.
throw e;
}
attributeMap.put(IMarker.CHAR_START, Integer.valueOf(startPosition));
try {
final int endPosition = sourceRange.getEndSourcePosition().getPosition(sourceText);
attributeMap.put(IMarker.CHAR_END, Integer.valueOf(endPosition));
} catch (IllegalArgumentException e){
// If the error is at the end of file then the source range goes one past the
// end of the file so this exception will be thrown. In this case, we will just
// use the start position as the end position since that is the correct spot
// anyway.
attributeMap.put(IMarker.CHAR_END, Integer.valueOf(startPosition));
}
int lineNumber = sourceRange.getStartLine();
if (lineNumber > 0) {
attributeMap.put(IMarker.LINE_NUMBER, Integer.valueOf(sourceRange.getStartLine()));
attributeMap.put("startColumn", Integer.valueOf(sourceRange.getStartColumn()));
// Don't set the location attribute unless we don't have a line number.
// If this is unset, the line number attribute is used in the location column.
// attributeMap.put(IMarker.LOCATION, "#" + sourcePosition.getLine());
}
} else {
// an error that does not map to source
attributeMap.put(IMarker.CHAR_START, Integer.valueOf(0));
attributeMap.put(IMarker.CHAR_END, Integer.valueOf(0));
}
}
// Post the marker (unless it's a duplicate)
postMarkerIfNonDuplicate(markerResource, attributeMap);
} catch (CoreException e) {
// TODOEL Auto-generated catch block
// Project or marker doesn't exist.
Util.log(e, e.getMessage());
}
/*
* TODOEL: quick fix? :)
*/
}
// Add problems for duplicate resources
IStorage[] duplicateSourceFiles = modelManager.getDuplicateSourceFiles();
IProject project = getProject();
for (final IStorage duplicateIStorage : duplicateSourceFiles) {
if (duplicateIStorage instanceof IFile) {
final IFile duplicateSourceFile = (IFile)duplicateIStorage;
if (duplicateSourceFile.getProject() == project) {
// Batch marker creation with updates so that only 1 resource change is broadcast.
try {
ResourcesPlugin.getWorkspace().run(new IWorkspaceRunnable() {
public void run(IProgressMonitor monitor) throws CoreException {
IMarker marker = duplicateSourceFile.createMarker(CALModelMarker.CAL_MODEL_PROBLEM_MARKER);
marker.setAttribute(IMarker.MESSAGE, "Source file " + duplicateSourceFile.getName() + " already exists.");
marker.setAttribute(IMarker.SEVERITY, IMarker.SEVERITY_ERROR);
}
}, null);
} catch (CoreException e) {
// TODOEL Auto-generated catch block
// Project or marker doesn't exist.
e.printStackTrace();
}
}
}
}
// Add problems for invalid resource names.
{
Set<Pair<IStorage, IPackageFragmentRoot>> resourcesWithInvalidNames = modelManager.getResourcesWithInvalidNames(project);
if (resourcesWithInvalidNames != null) {
for (Pair<IStorage, IPackageFragmentRoot> invalidResourceInfo : resourcesWithInvalidNames) {
IStorage iStorage = invalidResourceInfo.fst();
String message = "Resource name does not correspond to a module name: " + iStorage.getFullPath().toString();
attachErrorMarkerToStorage(iStorage, invalidResourceInfo.snd(), message);
}
}
}
notify(wasCompiled);
}
/**
* Attach an error marker to the given IStorage
* @param iStorage the IStorage to which the marker should be attached.
* @param packageRoot the package fragment root associated with iStorage.
* @param message the message associated with the marker.
*/
private void attachErrorMarkerToStorage(IStorage iStorage, IPackageFragmentRoot packageRoot, String message) {
// Determine the associated resource.
IResource markerResource;
if (iStorage instanceof IResource) {
markerResource = (IResource)iStorage;
} else {
// The storage isn't a resource. The package root should correspond to a resource.
markerResource = (IResource)packageRoot.getAdapter(IResource.class);
if (markerResource == null) {
// Maybe the best we can do is to associate the marker with the project.
markerResource = getProject();
}
}
// Add an error marker
Map<String, Object> attributeMap = new HashMap<String, Object>();
attributeMap.put(IMarker.MESSAGE, message);
attributeMap.put(IMarker.SEVERITY, Integer.valueOf(IMarker.SEVERITY_ERROR));
try {
CALBuilder.postMarkerIfNonDuplicate(markerResource, attributeMap);
} catch (CoreException e) {
Util.log(e, "Exception posting marker to resource " + markerResource);
}
}
/**
* Post the given marker, unless there is already a marker which contains the attributes in the attribute map.
* The marker can have other attributes as well, for instance if the user has marked it as important -- these will be ignored.
*
* @param markerResource the resource on which the marker will be created.
* @param attributeMap the attribute names and values the marker will have.
* @throws CoreException if there is a problem creating the marker or accessing its attributes.
*/
private static void postMarkerIfNonDuplicate(final IResource markerResource, final Map<String, Object> attributeMap) throws CoreException {
IMarker[] existingMarkers = markerResource.findMarkers(CALModelMarker.CAL_MODEL_PROBLEM_MARKER, false, IResource.DEPTH_ZERO);
boolean markerExists = false;
for (final IMarker existingMarker : existingMarkers) {
// Check whether there is a marker with the same attributes.
// The marker can have other attributes as well, for instance if the user has marked it as important.
if (markerHasAttributes(existingMarker, attributeMap)) {
markerExists = true;
break;
}
}
// Post the marker.
if (!markerExists) {
// Batch marker creation with updates so that only 1 resource change is broadcast.
ResourcesPlugin.getWorkspace().run(new IWorkspaceRunnable() {
public void run(IProgressMonitor monitor) throws CoreException {
IMarker marker = markerResource.createMarker(CALModelMarker.CAL_MODEL_PROBLEM_MARKER);
marker.setAttributes(attributeMap);
}
}, null);
}
}
/**
* Strings (in resource bundle) which correspond to internal errors - case insensitive
* Used by logInternalErrors()
* HACK - we shouldn't be analyzing the message text.
*/
private static final String[] internalErrorStrings = {
"Please contact Business Objects.",
"internal coding error",
"Error while packaging module",
"Could not create a module", // CouldNotCreateModuleInPackage
"Tree parsing error"
};
/**
* Analyze the messageLogger from a recent compilation and log anything which corresponds to internal errors to the Eclipse error log.
* @param messageLogger the logger whose messages will be analyzed.
*/
private void logInternalErrors(CompilerMessageLogger messageLogger) {
for (final CompilerMessage cm : messageLogger.getCompilerMessages(CompilerMessage.Severity.WARNING)) {
final CompilerMessage compilerMessage = cm;
String upperCaseCompilerMessageText = compilerMessage.getMessage().toUpperCase();
/*
* Only consider fatals which are internal errors:
* InternalCodingError
* CodeGenerationAbortedDueToInternalCodingError
* CompilationAbortedDueToInternalModuleLoadingError
* ErrorWhilePackagingModule
* CouldNotCreateModuleInPackage
* TreeParsingError
* UnexpectedUnificationFailure
* MoreThanOneDefinitionInANonRecursiveLet
* UnliftedLambdaExpression
*/
boolean isInternalError = false;
for (final String internalErrorString : internalErrorStrings) {
String upperCaseInternalErrorString = internalErrorString.toUpperCase();
if (upperCaseCompilerMessageText.indexOf(upperCaseInternalErrorString) > -1) {
isInternalError = true;
break;
}
}
if (isInternalError) {
String messageToLog = "Internal compiler error.\nCompiler message: " + compilerMessage.getMessage();
Exception exceptionToLog = compilerMessage.getException(); // can be null
Util.log(exceptionToLog, messageToLog);
}
}
}
/**
* Return whether a marker has the attributes in a provided attribute map.
* @param marker the marker to consider
* @param attributeMap the attribute map to consider
* @return whether the marker has the attributes in the attribute map.
* False if any attribute values differ. Note that the marker is allowed to have more attributes than provided in the map.
* @throws CoreException if there was a problem getting the value of the attribute from either of the markers.
*/
private static boolean markerHasAttributes(IMarker marker, Map<String, Object> attributeMap) throws CoreException {
Map<String, Object> markerAttributeMap = UnsafeCast.unsafeCast(marker.getAttributes()); // attribute value - string, integer, boolean, or null
// Check for too many attributes.
if (markerAttributeMap.size() > attributeMap.size()) {
return false;
}
// Check for different attribute values.
for (final Map.Entry<String, Object> mapEntry : markerAttributeMap.entrySet()) {
Object value1 = mapEntry.getValue();
Object value2 = attributeMap.get(mapEntry.getKey());
if (value1 == null) {
if (value2 != null) {
return false;
} else {
// both null
}
} else {
if (value2 == null || !value1.equals(value2)) {
return false;
} else {
// same value
}
}
}
// They're the same.
return true;
}
/**
* @return CompilationOptions to use when compiling modules from this builder.
*/
private ProgramModelManager.CompilationOptions getCompilationOptions() {
// Add a compilation option for a custom foreign context provider.
ProgramModelManager.CompilationOptions compilationOptions = new ProgramModelManager.CompilationOptions();
compilationOptions.setCustomForeignContextProvider(sharedForeignContextProvider);
return compilationOptions;
}
/**
* Build everything for the current project.
*/
private void buildAll(IProgressMonitor monitor) {
monitor.beginTask("", TICKS_PER_MONITOR);
try {
preBuild(getSubMonitor(monitor, 0.1));
// BatchImageBuilder imageBuilder = new BatchImageBuilder(this);
// imageBuilder.build();
// recordNewState(imageBuilder.newState);
// analyse deltas:
// find source files in delta: 0.1
// find affected sources: 0.1
// add affected sources: 0.05
// compile loop: 0.4
// Should update status listener to notify when finished compiling a module.
CALModelManager modelManager = CALModelManager.getCALModelManager();
// Remove any and all modules referenced by the old build state.
if (lastState != null) {
ModuleSourceDefinitionGroup moduleSourceDefinitionGroup = lastState.getModuleSourceDefinitionGroup();
for (int i = 0; i < moduleSourceDefinitionGroup.getNModules(); i++) {
ModuleName moduleName = moduleSourceDefinitionGroup.getModuleSource(i).getModuleName();
modelManager.getProgramModelManager().removeModule(moduleName);
}
}
EclipseModuleSourceDefinitionGroup sourceDefinitionGroup = getModuleSourceDefinitionGroup();
CompilerMessageLogger logger = getNewMessageLogger();
if (sourceDefinitionGroup != null) {
aboutToCompile(getProject().getName(), monitor);
ProgramModelManager programModelManager = modelManager.getProgramModelManager();
BuildStatusListener buildStatusListener = new BuildStatusListener(getSubMonitor(monitor, 0.7));
programModelManager.addStatusListener(buildStatusListener);
try {
// Compile all modules in the program model manager.
programModelManager.compile(sourceDefinitionGroup, logger, false, null, getCompilationOptions());
} finally {
programModelManager.removeStatusListener(buildStatusListener);
}
buildStatusListener.done();
} else {
// No source definition group.
}
postBuild(logger, null, getSubMonitor(monitor, 0.2));
} finally {
monitor.done();
}
}
/**
* @return the ModuleSourceDefinitionGroup for the current project.
*/
EclipseModuleSourceDefinitionGroup getModuleSourceDefinitionGroup() {
IProject currentProject = getProject();
return CALModelManager.getCALModelManager().getModuleSourceDefinitionGroup(currentProject);
}
/**
* Announce that a unit is about to be compiled.
* @param unitName the name of the unit to compile.
* @param monitor the tracking monitor.
*/
public void aboutToCompile(String unitName, IProgressMonitor monitor) {
/*
* TODOEL:
* The Java compiler has a nice feature where, as problems are accumulated,
* it displays a running total of the number of problems which are new, and which are fixed.
*/
String message = Messages.bind(Messages.build_compiling, unitName);
monitor.subTask(message);
}
/**
* Build deltas.
* @param deltas (IResourceDelta) Set of resource deltas for that project.
*/
private void buildDeltas(Set<IResourceDelta> deltas, IProgressMonitor monitor) {
monitor.beginTask("", TICKS_PER_MONITOR);
try {
// JavaBuilder goes through the IncrementalImageBuilder.
preBuild(getSubMonitor(monitor, 0.1));
IProject project = getProject();
CALModelManager modelManager = CALModelManager.getCALModelManager();
ProgramModelManager programModelManager = modelManager.getProgramModelManager();
EclipseModuleSourceDefinitionGroup definitionGroup = getModuleSourceDefinitionGroup();
monitor.subTask(Messages.build_analyzingSources);
monitor.subTask(Messages.build_analyzingDeltas);
// Get the changed resources, convert this info into changed modules.
// Relevant resource: it's in an input folder, and it ends with ".cal"
Set<IFile> addedInputFiles = new HashSet<IFile>();
Set<IFile> removedInputFiles = new HashSet<IFile>();
Set<IFile> changedInputFiles = new HashSet<IFile>();
for (final IResourceDelta resourceDelta : deltas) {
ChangedCALFolderFileFinder finder = new ChangedCALFolderFileFinder(project);
try {
resourceDelta.accept(finder);
} catch (CoreException e) {
// open error dialog with syncExec, or dump to log.
}
addedInputFiles.addAll(finder.getAddedFiles());
removedInputFiles.addAll(finder.getRemovedFiles());
changedInputFiles.addAll(finder.getChangedFiles());
}
// Remove cal resources for removed cal files.
boolean badnessHasOccurred = false;
for (final IFile removedInputFile : removedInputFiles) {
ModuleName moduleName = Util.getModuleNameFromStorage(removedInputFile);
if (moduleName == null) {
// the storage name doesn't correspond to a module name
continue;
}
// figure out which resource folder must be deleted
// must do this now, while we can still locate the resource
ProgramResourceLocator.Folder moduleResourceLocator = new ProgramResourceLocator.Folder(moduleName, ResourcePath.EMPTY_PATH);
IFolder resourceFolder = getFolder(moduleResourceLocator, removedInputFile);
// Update program for removed modules and dependees.
// We only really need to do this for removed modules, to guard against stale ModuleTypeInfo in the ProgramModelManager.
if (lastState != null) {
ModuleSourceDefinitionGroup moduleSourceDefinitionGroup = lastState.getModuleSourceDefinitionGroup();
if (moduleSourceDefinitionGroup.getModuleSource(moduleName) != null) {
boolean moduleRemoved = modelManager.getProgramModelManager().removeModule(moduleName);
if (!moduleRemoved) {
System.err.println("Could not remove module: " + moduleName);
}
}
}
// OK, now we can delete the resources
try {
if (resourceFolder != null && resourceFolder.exists()) {
// force, progressMonitor
resourceFolder.delete(true, null);
}
} catch (CoreException e) {
Util.log(new IOException().initCause(e), "Couldn't delete " + resourceFolder);
badnessHasOccurred = true;
}
}
if (badnessHasOccurred) {
return;
}
if (definitionGroup == null) {
// This can happen if we removed the last cal file from a definition group, causing it to no longer be a group.
return;
}
ModuleSourceDefinitionGroup writableSubGroup = definitionGroup.getWritableSubGroup();
List<ModuleName> namesOfModulesToCompileList = new ArrayList<ModuleName>();
getAffectedModuleSourceFiles(removedInputFiles, writableSubGroup, namesOfModulesToCompileList);
getAffectedModuleSourceFiles(addedInputFiles, writableSubGroup, namesOfModulesToCompileList);
getAffectedModuleSourceFiles(changedInputFiles, writableSubGroup, namesOfModulesToCompileList);
aboutToCompile(getProject().getName(), monitor);
CompilerMessageLogger logger = getNewMessageLogger();
BuildStatusListener buildStatusListener = new BuildStatusListener(getSubMonitor(monitor, 0.7));
programModelManager.addStatusListener(buildStatusListener);
try {
ProgramModelManager.CompilationOptions compilationOptions = getCompilationOptions();
if (!namesOfModulesToCompileList.isEmpty()) {
/*
* TODOEL: Don't compile twice.
* We should be able to perform the dirty computation ourselves (ie. which modules depend on the modules with deltas).
*/
// Compile modules with deltas.
ModuleName[] namesOfModulesToCompile = namesOfModulesToCompileList.toArray(new ModuleName[namesOfModulesToCompileList.size()]);
programModelManager.makeModules(namesOfModulesToCompile, definitionGroup, logger, compilationOptions);
}
// Still need to recompile dirty, since dependee modules may have changed.
programModelManager.compile(definitionGroup, logger, true, buildStatusListener, compilationOptions);
} finally {
programModelManager.removeStatusListener(buildStatusListener);
}
// Work out which cal files which were compiled.
// Start out with changed files.
// Might not show up below if a file to compile has a compile error (and thus not loaded).
Set<IFile> updatedInputFiles = new HashSet<IFile>(changedInputFiles);
// Add all the files which were loaded (valid changes + compiled dependencies)
for (final ModuleName loadedModuleName : buildStatusListener.getLoadedModuleNames()) {
IStorage inputSourceFile = CALModelManager.getCALModelManager().getInputSourceFile(loadedModuleName);
if (inputSourceFile instanceof IFile) {
// only files have been compiled
updatedInputFiles.add((IFile)inputSourceFile);
}
}
List<IFile> updatedInputFilesList = new ArrayList<IFile>(updatedInputFiles);
postBuild(logger, updatedInputFilesList, getSubMonitor(monitor, 0.2));
} finally {
monitor.done();
}
}
/**
* Determines the resource folder corresponding to the calFile
* @param moduleResourceLocator the folder path of the resource to locate
* @param calFile the cal file that creates the folder to locate
* @return the folder, somewhere under the resource folder (lecc_runtime)
*/
private IFolder getFolder(ProgramResourceLocator.Folder moduleResourceLocator,
IFile calFile) {
ICALResourceContainer container =
CALModelManager.getCALModelManager().getInputFolder(calFile);
return (IFolder) container.getProgramFolder(moduleResourceLocator, false).getResource();
}
/**
* Helper method to build up the names of modules to compile.
* @param calFiles the set of cal files to analyze.
* @param definitionGroup the current module source definition group.
* @param namesOfModulesToCompileList the list of modules to populate as affected.
* Names of modules from the first argument will be added to this list if they are in the source definition group.
*/
private static void getAffectedModuleSourceFiles(Set<IFile> calFiles, ModuleSourceDefinitionGroup definitionGroup, List<ModuleName> namesOfModulesToCompileList) {
for (final IFile calFile : calFiles) {
ModuleName moduleName = Util.getModuleNameFromStorage(calFile);
if (moduleName != null) {
// Check that the module source is actually in the module source definition group.
// It may not be, if there is another module source with the same name in a dependee project.
if (definitionGroup.getModuleSource(moduleName) != null) {
namesOfModulesToCompileList.add(moduleName);
}
} else {
// the storage name doesn't correspond to a module name
}
}
}
/**
* {@inheritDoc}
*/
@Override
protected void clean(IProgressMonitor monitor) throws CoreException {
IProject currentProject = getProject();
if (currentProject == null || !currentProject.isAccessible()) {
return;
}
if (DEBUG) {
Util.printlnWithDate("\nCleaning " + currentProject.getName()); //$NON-NLS-1$
}
monitor.beginTask("", TICKS_PER_MONITOR);
try {
Util.checkCanceled(monitor);
if (DEBUG) {
System.out.println("Clearing last state as part of clean : " + lastState); //$NON-NLS-1$
}
if (lastState != null) {
ProgramModelManager programModelManager = CALModelManager.getCALModelManager().getProgramModelManager();
ModuleSourceDefinitionGroup moduleSourceDefinitionGroup = lastState.getModuleSourceDefinitionGroup();
for (int i = 0; i < moduleSourceDefinitionGroup.getNModules(); i++) {
ModuleName moduleName = moduleSourceDefinitionGroup.getModuleSource(i).getModuleName();
programModelManager.removeModule(moduleName);
}
}
clearLastState();
removeProblemsAndTasksFor(currentProject);
monitor.subTask(Messages.build_cleaningOutput);
cleanOutputFolders(getSubMonitor(monitor, 1.0));
CALModelManager.getCALModelManager().clearClassLoader(currentProject);
CALModelManager.getCALModelManager().clearCachedResourceContainers(currentProject);
} catch (CoreException e) {
Util.log(e, "CALBuilder handling CoreException while cleaning: " + currentProject.getName()); //$NON-NLS-1$
IMarker marker = currentProject.createMarker(CALModelMarker.CAL_MODEL_PROBLEM_MARKER);
marker.setAttribute(IMarker.MESSAGE, Messages.bind(Messages.build_inconsistentProject, e.getLocalizedMessage()));
marker.setAttribute(IMarker.SEVERITY, IMarker.SEVERITY_ERROR);
} finally {
monitor.done();
cleanup();
}
if (DEBUG) {
Util.printlnWithDate("Finished cleaning " + currentProject.getName()); //$NON-NLS-1$
}
}
/**
* @return the output folders for the builder's project.
*/
private IFolder[] getOutputFolders() {
return CALModelManager.getCALModelManager().getOutputFolders(Util.getCalProject(getProject()));
}
/**
* Clean up the project's output folder(s).
* @throws CoreException
*/
private void cleanOutputFolders(IProgressMonitor monitor) throws CoreException {
boolean cleanOutputFolders = CoreOptionIDs.CLEAN.equals(
CALEclipseCorePlugin.getOption(CoreOptionIDs.CORE_JAVA_BUILD_CLEAN_OUTPUT_FOLDER));
if (!cleanOutputFolders) {
return;
}
IContainer[] outputFolders = getOutputFolders();
int nOutputfolders = outputFolders.length;
monitor.beginTask("Cleaning output folders", nOutputfolders);
try {
for (int i = 0; i < nOutputfolders; i++) {
IContainer outputFolder = outputFolders[i];
// TODOEL: the output folder should already exist.
if (!outputFolder.exists()) {
continue;
}
IResource[] members = outputFolder.members();
for (final IResource member : members) {
if (!member.isDerived()) {
member.accept(new IResourceVisitor() {
public boolean visit(IResource resource) throws CoreException {
resource.setDerived(true);
return resource.getType() != IResource.FILE;
}
});
}
member.delete(IResource.FORCE, null);
}
monitor.worked(1);
Util.checkCanceled(monitor);
}
} finally {
monitor.done();
}
}
private void cleanup() {
// this.nameEnvironment = null;
// this.binaryLocationsPerProject = null;
// this.lastState = null;
// this.extraResourceFileFilters = null;
// this.extraResourceFolderFilters = null;
}
private void clearLastState() {
lastState = null;
// JavaModelManager.getJavaModelManager().setLastBuiltState(currentProject, null);
}
/**
* Find deltas in a project.
* @param currentProject the project for which to find the deltas.
* @return (Set of IResourceDelta) The set of deltas for the project.
* Null if the project wasn't associated with the builder.
* May also be empty.
*/
private Set<IResourceDelta> findDeltas(IProject currentProject, IProgressMonitor monitor) {
monitor.beginTask("", TICKS_PER_MONITOR);
try {
monitor.subTask(Messages.bind(Messages.build_readingDelta, currentProject.getName()));
IResourceDelta delta = getDelta(currentProject);
Set<IResourceDelta> deltas = new HashSet<IResourceDelta>(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.add(delta);
}
} else {
if (DEBUG) {
System.out.println("Missing delta for: " + currentProject.getName()); //$NON-NLS-1$
}
monitor.subTask(""); //$NON-NLS-1$
return null;
}
monitor.subTask(""); //$NON-NLS-1$
return deltas;
} finally {
monitor.done();
}
}
/**
* 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(IProject currentProject, boolean includeBinaryPrerequisites) {
// The Java builder has to consider binary resources on the class path from projects not included in project references.
// Also consider missing projects.
// For now, depends on all referenced projects.
// We should really limit to projects with a cal nature.
// If we try to institute something like classpaths, we can try to emulate behaviour of Java as above.
// Perhaps first, we can try to calculate what the project dependencies are using the model?
return CALModelManager.getDependeeProjects(currentProject);
}
/**
* Pre-build check for whether we should attempt to build the current project.
* @return whether a build should be attempted for the current project.
* @throws CoreException
*/
private boolean isWorthBuilding() throws CoreException {
// The Java builder aborts if there are classpath errors..
return true;
}
public static IMarker[] getProblemsFor(IResource resource) {
try {
if (resource != null && resource.exists()) {
return resource.findMarkers(CALModelMarker.CAL_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(CALModelMarker.TASK_MARKER, false, IResource.DEPTH_INFINITE);
}
} catch (CoreException e) {
// assume there are no tasks
}
return new IMarker[0];
}
public static void removeProblemsFor(IResource resource) {
try {
if (resource != null && resource.exists()) {
resource.deleteMarkers(CALModelMarker.CAL_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(CALModelMarker.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(CALModelMarker.CAL_MODEL_PROBLEM_MARKER, false, IResource.DEPTH_INFINITE);
resource.deleteMarkers(CALModelMarker.TASK_MARKER, false, IResource.DEPTH_INFINITE);
}
} catch (CoreException e) {
// assume there were no problems
}
}
/**
* String representation for debugging purposes
*/
@Override
public String toString() {
IProject currentProject = getProject();
return currentProject == null ?
"CALBuilder for unknown project" : //$NON-NLS-1$
"CALBuilder for " + currentProject.getName(); //$NON-NLS-1$
}
public static void addListener(BuildMessagesListener listener){
synchronized(listeners){
listeners.add(listener);
}
}
public static void removeListener(BuildMessagesListener listener){
synchronized(listeners){
listeners.remove(listener);
}
}
public static void notify(Collection<ModuleName> wasCompiled){
final List<BuildMessagesListener> listenersCopy;
synchronized(listeners){
listenersCopy = new ArrayList<BuildMessagesListener>(listeners);
}
final Collection<ModuleName> unmodifiableModulesWithErrors = Collections.unmodifiableCollection(wasCompiled);
for (final BuildMessagesListener listener : listenersCopy) {
listener.notify(unmodifiableModulesWithErrors);
}
}
/**
* Called with a list of modules that have been compiled by the builder.
*
* @author Greg McClement
*/
public interface BuildMessagesListener{
public void notify(Collection<ModuleName> modulesWithErrors);
}
/**
* A quick fix for a given compiler error.
*/
public interface IQuickFix{
public SourcePosition applyFix(final int startLine, final int startColumn, CompilerMessageLogger messageLogger);
public String getDescription();
}
/**
* Create a quick fix that will import the given symbol into the given module.
*/
private static IQuickFix createImportQuickFix(final QualifiedName importSymbol, final SourceIdentifier.Category category, final ModuleName moduleName, final SourceManagerFactory smf){
return new IQuickFix(){
public SourcePosition applyFix(final int startLine, final int startColumn, CompilerMessageLogger messageLogger){
Refactorer.InsertImport refactorer = new Refactorer.InsertImport_Symbol(
CALModelManager.getCALModelManager().getModuleContainer(smf),
moduleName,
importSymbol,
category,
startLine,
startColumn);
refactorer.calculateModifications(messageLogger);
if (messageLogger.getNErrors() == 0){
refactorer.apply(messageLogger);
}
return refactorer.getNewSourcePosition();
}
public String getDescription(){
return Messages.bind(Messages.quickFix_importThisModule, importSymbol);
}
};
}
/**
* Create a quick fix that will import one module into another module.
*/
private static IQuickFix createImportOnlyQuickFix(final ModuleName importModule, final ModuleName moduleName, final SourceManagerFactory smf){
return new IQuickFix(){
public SourcePosition applyFix(final int startLine, final int startColumn, CompilerMessageLogger messageLogger){
Refactorer.InsertImport refactorer = new Refactorer.InsertImport_Only(
CALModelManager.getCALModelManager().getModuleContainer(smf),
moduleName,
importModule,
startLine,
startColumn);
refactorer.calculateModifications(messageLogger);
if (messageLogger.getNErrors() == 0){
refactorer.apply(messageLogger);
}
return refactorer.getNewSourcePosition();
}
public String getDescription(){
return Messages.bind(Messages.quickFix_importThisModule, importModule);
}
};
}
/**
* Create a quick fix that will qualify the given name. This does not use the ractorer and instead
* directly modifies the source. This is better because the undo is able to function propertly when
* just the effected code is modified.
*/
private static IQuickFix createQualifyNameQuickFixDirect(final ModuleName moduleName, final String oldText, final String newText, final int startIndex, final int endIndex, final SourceManagerFactory smf){
final ModuleContainer moduleContainer = CALModelManager.getCALModelManager().getModuleContainer(smf);
return new IQuickFix(){
public SourcePosition applyFix(final int startLine, final int startColumn, CompilerMessageLogger messageLogger){
final ISourceManager sourceManager = moduleContainer.getSourceManager(moduleName);
if (sourceManager instanceof ISourceManager2){
ISourceManager2 sourceManager2 = (ISourceManager2) sourceManager;
sourceManager2.saveSource(moduleName, startIndex, endIndex, newText);
}
return null;
}
public String getDescription(){
return Messages.bind(Messages.quickFix_fullyQualifyName, newText);
}
};
}
/**
* Get quick fixes for the given compile error.
*
* TODO Fix this hack after Joseph fixes some back end stuff
* @param marker the marker of the problem to quickfix
* @param moduleName the module of the module where the quickfix resides
* @param smf SourceManagerFactory
* @return list of quick fixes appropriate at the marker
*/
public static IQuickFix[] getQuickFixes(IMarker marker, ModuleName moduleName, SourceManagerFactory smf) {
String errorMessage = marker.getAttribute(IMarker.MESSAGE, "");
SourceIdentifier.Category category = null;
if (errorMessage.indexOf("Attempt to use undefined type") != -1){
category = SourceIdentifier.Category.TYPE_CONSTRUCTOR;
}
else if (errorMessage.indexOf("Attempt to use undefined data constructor") != -1){
category = SourceIdentifier.Category.DATA_CONSTRUCTOR;
}
else if (errorMessage.indexOf("Attempt to use undefined class") != -1){
category = SourceIdentifier.Category.TYPE_CLASS;
}
else if (errorMessage.indexOf("Attempt to use undefined function") != -1){
category = SourceIdentifier.Category.TOP_LEVEL_FUNCTION_OR_CLASS_METHOD;
}
final int startCharFromOne = marker.getAttribute(IMarker.CHAR_START, -1) + 1;
final int endCharFromOne = marker.getAttribute(IMarker.CHAR_END, -1) + 1;
// The error lists multiple possible imports.
{
String searchKey = "Was one of these intended: ";
int beginIndex = errorMessage.indexOf(searchKey);
if (beginIndex != -1){
final String oldText = getSymbolName(errorMessage);
String rest = errorMessage.substring(beginIndex + searchKey.length());
rest = rest.replaceAll("\\?", "");
String alternatives[] = rest.split(", ");
IQuickFix[] qf = new IQuickFix[alternatives.length * 2];
for(int i = 0; i < alternatives.length; ++i){
QualifiedName qualifiedName = QualifiedName.makeFromCompoundName(alternatives[i]);
qf[i] = createImportQuickFix(qualifiedName, category, moduleName, smf);
qf[i + alternatives.length] = createQualifyNameQuickFixDirect(moduleName, oldText, qualifiedName.toSourceText(), startCharFromOne, endCharFromOne, smf);
}
return qf;
}
}
{
// The error looks like this:
// Attempt to use undefined function 'zip4'. Was 'Cal.Collections.List.zip4' intended?
final String searchKey1 = "Attempt to use undefined ";
final CALModelManager cmm = CALModelManager.getCALModelManager();
final ModuleTypeInfo moduleTypeInfo = cmm.getModuleTypeInfo(moduleName);
final UnqualifiedIfUsingOrSameModule namingProvider;
if (moduleTypeInfo != null) {
namingProvider = new UnqualifiedIfUsingOrSameModule(moduleTypeInfo);
} else {
namingProvider = null;
}
int beginIndex = errorMessage.indexOf(searchKey1);
if (beginIndex != -1){
final String oldText = getSymbolName(errorMessage);
String searchKey2 = "Was '";
beginIndex = errorMessage.indexOf("Was '", beginIndex);
if (beginIndex != -1){
beginIndex += searchKey2.length();
int endIndex = errorMessage.indexOf("'", beginIndex);
String importSymbol = errorMessage.substring(beginIndex, endIndex);
IQuickFix[] qf = new IQuickFix[2];
final QualifiedName qualifiedName = QualifiedName.makeFromCompoundName(importSymbol);
qf[0] = createImportQuickFix(qualifiedName, category, moduleName, smf);
String name = qualifiedName.toSourceText();
if (namingProvider != null){
ScopedEntity scopedEntity = getScopedEntity(qualifiedName, category);
if (scopedEntity != null){
name = namingProvider.getName(scopedEntity);
}
}
qf[1] = createQualifyNameQuickFixDirect(moduleName, oldText, name, startCharFromOne, endCharFromOne, smf);
return qf;
} else {
// no suggestions so look in the workspace for possible matches
final ProgramModelManager programModelManager = cmm.getProgramModelManager();
final ModuleName[] moduleNames = programModelManager.getModuleNamesInProgram();
final ArrayList<IQuickFix> quickFixes = new ArrayList<IQuickFix>();
for (int i = 0; i < moduleNames.length; ++i) {
final ModuleTypeInfo mti = cmm.getModuleTypeInfo(moduleNames[i]);
if (mti == null) {
continue;
}
List<ScopedEntity> choices = new ArrayList<ScopedEntity>();
// the category can be wrong for these kinds of errors
{
final ScopedEntity se = mti.getFunctionalAgent(oldText);
if (se != null &&
(category == null || category == SourceIdentifier.Category.TOP_LEVEL_FUNCTION_OR_CLASS_METHOD)
){
category = SourceIdentifier.Category.TOP_LEVEL_FUNCTION_OR_CLASS_METHOD;
final Scope scope = se.getScope();
if (scope.isPublic() || (scope.isProtected() && mti.hasFriendModule(moduleName))){
choices.add(se);
}
}
}
{
final ScopedEntity se = mti.getDataConstructor(oldText);
if (se != null &&
(category == null || category == SourceIdentifier.Category.DATA_CONSTRUCTOR)
){
category = SourceIdentifier.Category.DATA_CONSTRUCTOR;
final Scope scope = se.getScope();
if (scope.isPublic() || (scope.isProtected() && mti.hasFriendModule(moduleName))){
choices.add(se);
}
}
}
{
final ScopedEntity se = mti.getTypeConstructor(oldText);
if (se != null &&
(category == null || category == SourceIdentifier.Category.TYPE_CONSTRUCTOR)
){
category = SourceIdentifier.Category.TYPE_CONSTRUCTOR;
final Scope scope = se.getScope();
if (scope.isPublic() || (scope.isProtected() && mti.hasFriendModule(moduleName))){
choices.add(se);
}
}
}
{
final ScopedEntity se = mti.getTypeClass(oldText);
if (se != null &&
(category == null || category == SourceIdentifier.Category.TYPE_CLASS)
){
category = SourceIdentifier.Category.TYPE_CLASS;
final Scope scope = se.getScope();
if (scope.isPublic() || (scope.isProtected() && mti.hasFriendModule(moduleName))){
choices.add(se);
}
}
}
if (!choices.isEmpty()){
final ModuleName thisModuleName = moduleName;
// Should not return a jar entry because all jar entries are read-only.
// since we are doing quick-fixes here, should not be read-only.
// However, to be safe, check to make sure it is an IFile.
IStorage storage = cmm.getInputSourceFile(thisModuleName);
if (! (storage instanceof IFile)) {
Util.log("Attempt to get quick fixes for a read-only file: " + storage.getName());
return new IQuickFix[0];
}
final IFile thisFile = (IFile) storage;
IProject thisProject = null;
ArrayList<IProject> dependeeProjects = new ArrayList<IProject>();
if (thisFile != null){
thisProject = thisFile.getProject();
IProject[] projects = CALModelManager.getDependeeProjects(thisProject);
dependeeProjects.add(thisProject);
for (final IProject iProject : projects) {
dependeeProjects.add(iProject);
}
}
for (final ScopedEntity choice : choices) {
final ModuleName choiceModuleName = choice.getName().getModuleName();
// Only show quick fixes or symbols in projects that are referenced
{
// check that the current project depends on the project containing
// the import symbol.
if (thisProject != null){
// ADE get the storage object, not the file
IProject project = cmm.getInputSourceFileContainer(choiceModuleName)
.getPackageRoot().getJavaProject().getProject();
if (project == null || !dependeeProjects.contains(project)){
continue; // skip this one
}
}
// Make sure that a circular dependencies would not be introduced
{
ModuleTypeInfo moduleTypeInfoToCheck = cmm.getModuleTypeInfo(choiceModuleName);
if (null != moduleTypeInfoToCheck.getDependeeModuleTypeInfo(thisModuleName)){
// skip this otherwise there will be circular module dependencies.
continue;
}
}
}
QualifiedName qualifiedName = choice.getName();
quickFixes.add(createImportQuickFix(choice.getName(), category, moduleName, smf));
String name = qualifiedName.toSourceText();
if (namingProvider != null){
ScopedEntity scopedEntity = getScopedEntity(qualifiedName, category);
if (scopedEntity != null){
name = namingProvider.getName(scopedEntity);
}
else{
name = namingProvider.getName(choice);
}
}
quickFixes.add(createQualifyNameQuickFixDirect(moduleName, oldText, name, startCharFromOne, endCharFromOne, smf));
}
}
}
return quickFixes.toArray(new IQuickFix[quickFixes.size()]);
}
}
}
// Quick fix for when the module name is not qualified enough in the import statement
//
// Unresolved external module import Math. Did you mean Cal.Utilities.Math?
if (errorMessage.indexOf("Unresolved external module import ") != -1){
final String secondSentenceStart = "Did you mean ";
final int secondSentenceStartIndex = errorMessage.indexOf(secondSentenceStart);
if (secondSentenceStartIndex != -1){
final int questionMarkIndex = errorMessage.lastIndexOf('?');
if (questionMarkIndex != -1){
final int startOfQualifiedName = secondSentenceStartIndex + secondSentenceStart.length();
final String qualifiedName = errorMessage.substring(startOfQualifiedName, questionMarkIndex);
final int periodInFirstSentence = errorMessage.lastIndexOf('.', secondSentenceStartIndex);
final int startOfUnqualifiedNameIndex = errorMessage.lastIndexOf(' ', periodInFirstSentence) + 1;
final String problemName = errorMessage.substring(startOfUnqualifiedNameIndex, periodInFirstSentence);
IQuickFix[] qf = new IQuickFix[1];
qf[0] = createQualifyNameQuickFixDirect(moduleName, problemName, qualifiedName, startCharFromOne, endCharFromOne, smf);
return qf;
}
}
}
if (errorMessage.indexOf("Unexpected token '='. Was the equality operator ('==') intended?") != -1){
IQuickFix[] qf = new IQuickFix[1];
qf[0] = createQualifyNameQuickFixDirect(moduleName, "=", "==", startCharFromOne, endCharFromOne, smf);
return qf;
}
// Error looks like this
//
// The module Frnd has not been imported into T234.
{
String searchFor = " has not been imported into";
if (errorMessage.indexOf(searchFor) != -1){
final String prefix = "The module ";
final int startOfModuleName = prefix.length();
final int endOfModuleName = errorMessage.indexOf(searchFor);
final String missingModuleNameString = errorMessage.substring(startOfModuleName, endOfModuleName);
final ModuleName missingModuleName = ModuleName.make(missingModuleNameString);
// The module name in the error does not necessarily have the hierarchical
// components so I have to look for a match in the workspace. This is written
// to get around that the module has not compiled properly.
final Set<ModuleName> matches = getMatchingModules(missingModuleName);
final ArrayList<IQuickFix> quickFixes = new ArrayList<IQuickFix>(matches.size());
for(ModuleName match : matches){
quickFixes.add(createImportOnlyQuickFix(match, moduleName, smf));
}
return quickFixes.toArray(new IQuickFix[quickFixes.size()]);
}
}
return new IQuickFix[0];
}
/**
* Returns modules from the workspace the potentially match the given module name. This is used
* instead of the module name resolved because potentially the module is not compiled properly.
* @param moduleName
* @return Set of module names which potentially match.
*/
private static Set<ModuleName> getMatchingModules(ModuleName moduleName){
final Set<ModuleName> matches = new HashSet<ModuleName>();
final Set<ModuleName> allModules = CALModelManager.getCALModelManager().getModuleNames();
for(ModuleName targetModuleName : allModules){
final String[] targetComponents = targetModuleName.getComponents();
final String[] components = moduleName.getComponents();
if (components.length > targetComponents.length){
continue;
}
boolean itMatches = true;
int iTC = targetComponents.length - 1;
for(int i = components.length - 1; i >= 0; --i, --iTC){
if (!components[i].equals(targetComponents[iTC])){
itMatches = false;
break;
}
}
if (itMatches){
matches.add(targetModuleName);
}
}
if (matches.size() == 0){
// This is the best guess then
matches.add(moduleName);
}
return matches;
}
private static String getSymbolName(String errorMessage) {
// Attempt to use undefined data constructor 'Duder'
//
// Find the name in the single quotes
//
// An example error message is
//
// "Attempt to use undefined function 'arbitrary'. Was 'Cal.Utilities.QuickCheck.arbitrary' intended?"
//
final int startOfName = errorMessage.indexOf('\'', 0) + 1;
final int endOfName = errorMessage.indexOf('\'', startOfName);
return errorMessage.substring(startOfName, endOfName);
}
/**
* Get the scoped entity with the given name and type.
* @param qualifiedName the name of the scoped entity
* @param category the category of the scoped entity
* @return the scoped entity with the given name and category
*/
private static ScopedEntity getScopedEntity(QualifiedName qualifiedName, Category category){
final ModuleTypeInfo moduleTypeInfo = CALModelManager.getCALModelManager().getModuleTypeInfo(qualifiedName.getModuleName());
String unqualifiedName = qualifiedName.getUnqualifiedName();
if (category == SourceIdentifier.Category.TOP_LEVEL_FUNCTION_OR_CLASS_METHOD){
ScopedEntity scopedEntity = moduleTypeInfo.getFunction(unqualifiedName);
if (scopedEntity == null){
// maybe its a class
return moduleTypeInfo.getClassMethod(unqualifiedName);
}
return scopedEntity;
}
else if (category == SourceIdentifier.Category.TYPE_CLASS){
return moduleTypeInfo.getTypeClass(unqualifiedName);
}
else if (category == SourceIdentifier.Category.TYPE_CONSTRUCTOR){
return moduleTypeInfo.getTypeConstructor(unqualifiedName);
}
else if (category == SourceIdentifier.Category.DATA_CONSTRUCTOR){
return moduleTypeInfo.getDataConstructor(unqualifiedName);
}
else{
return null;
}
}
/**
* Get quick fixes for the given compile error.
*
* TODO Fix this hack after Joseph fixes some back end stuff
* @param marker the marker to examine
* @return true if this marker can be quick fixed, false otherwise
*/
public static boolean canFix(IMarker marker) {
String errorMessage = marker.getAttribute(IMarker.MESSAGE, "");
// The error lists multiple possible imports.
{
String searchKey = "Was one of these intended: ";
int beginIndex = errorMessage.indexOf(searchKey);
if (beginIndex != -1){
String rest = errorMessage.substring(beginIndex + searchKey.length());
rest = rest.replaceAll("\\?", "");
String alternatives[] = rest.split(", ");
return alternatives.length > 0;
}
}
{
// The error looks like this:
// Attempt to use undefined function 'zip4'. Was 'Cal.Collections.List.zip4' intended?
String searchKey1 = "Attempt to use undefined ";
int beginIndex = errorMessage.indexOf(searchKey1);
if (beginIndex != -1){
return true;
// String searchKey2 = "Was '";
// beginIndex = errorMessage.indexOf("Was '", beginIndex);
// if (beginIndex != -1){
// beginIndex += searchKey2.length();
// int endIndex = errorMessage.indexOf("'", beginIndex);
// String importSymbol = errorMessage.substring(beginIndex, endIndex);
// return importSymbol.length() > 0;
// }
}
}
// Error looks like this
//
// Unresolved external module import Math. Did you mean Cal.Utilities.Math?
{
if (errorMessage.indexOf("Unresolved external module import ") != -1){
final String secondSentenceStart = "Did you mean ";
final int secondSentenceStartIndex = errorMessage.indexOf(secondSentenceStart);
if (secondSentenceStartIndex != -1){
return true;
}
}
}
// Error looks like this
//
// Unexpected token '='. Was the equality operator ('==') intended?
if (errorMessage.indexOf("Unexpected token '='. Was the equality operator ('==') intended?") != -1){
return true;
}
// Error looks like this
//
// The module Frnd has not been imported into T234.
if (errorMessage.indexOf("has not been imported into") != -1){
return true;
}
return false;
}
}