/*******************************************************************************
* Copyright © 2008, 2013 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* IBM Corporation - initial API and implementation
*
*******************************************************************************/
package org.eclipse.edt.ide.core.internal.compiler.workingcopy;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.runtime.jobs.ILock;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.edt.compiler.PartEnvironmentStack;
import org.eclipse.edt.compiler.binding.FileBinding;
import org.eclipse.edt.compiler.binding.IPartBinding;
import org.eclipse.edt.compiler.binding.ITypeBinding;
import org.eclipse.edt.compiler.core.ast.File;
import org.eclipse.edt.compiler.core.ast.Handler;
import org.eclipse.edt.compiler.core.ast.Library;
import org.eclipse.edt.compiler.core.ast.Part;
import org.eclipse.edt.compiler.core.ast.Program;
import org.eclipse.edt.compiler.core.ast.Service;
import org.eclipse.edt.compiler.internal.core.builder.BuildException;
import org.eclipse.edt.compiler.internal.core.builder.CancelledException;
import org.eclipse.edt.compiler.internal.util.PackageAndPartName;
import org.eclipse.edt.ide.core.internal.builder.AbstractDuplicatePartManager.DuplicatePartList;
import org.eclipse.edt.ide.core.internal.builder.workingcopy.WorkingCopyDuplicatePartManager;
import org.eclipse.edt.ide.core.internal.builder.workingcopy.WorkingCopyUnsavedDuplicatePartRequestor;
import org.eclipse.edt.ide.core.internal.lookup.FileInfoDifferencer;
import org.eclipse.edt.ide.core.internal.lookup.IASTFileInfo;
import org.eclipse.edt.ide.core.internal.lookup.IFileInfo;
import org.eclipse.edt.ide.core.internal.lookup.IFileInfoDifferenceNotificationRequestor;
import org.eclipse.edt.ide.core.internal.lookup.ZipFileBuildPathEntryManager;
import org.eclipse.edt.ide.core.internal.lookup.workingcopy.WorkingCopyBuildNotifier;
import org.eclipse.edt.ide.core.internal.lookup.workingcopy.WorkingCopyCompilerResourceFileInfoCreator;
import org.eclipse.edt.ide.core.internal.lookup.workingcopy.WorkingCopyFileInfoCreator;
import org.eclipse.edt.ide.core.internal.lookup.workingcopy.WorkingCopyFileInfoManager;
import org.eclipse.edt.ide.core.internal.lookup.workingcopy.WorkingCopyProjectBuildPathEntryManager;
import org.eclipse.edt.ide.core.internal.lookup.workingcopy.WorkingCopyProjectBuildPathManager;
import org.eclipse.edt.ide.core.internal.lookup.workingcopy.WorkingCopyProjectEnvironmentManager;
import org.eclipse.edt.ide.core.internal.lookup.workingcopy.WorkingCopyProjectInfo;
import org.eclipse.edt.ide.core.internal.lookup.workingcopy.WorkingCopyProjectInfoManager;
import org.eclipse.edt.ide.core.internal.model.EGLFile;
import org.eclipse.edt.ide.core.internal.partinfo.IPartOrigin;
import org.eclipse.edt.ide.core.internal.utils.Util;
import org.eclipse.edt.ide.core.model.EGLCore;
import org.eclipse.edt.ide.core.model.EGLModelException;
import org.eclipse.edt.ide.core.model.IEGLElement;
import org.eclipse.edt.ide.core.model.IEGLFile;
import org.eclipse.edt.ide.core.model.IWorkingCopy;
import org.eclipse.edt.ide.core.search.ICompiledFileUnit;
import org.eclipse.edt.mof.serialization.Environment;
import org.eclipse.edt.mof.utils.NameUtile;
// Because our compilers require the ability to access other objects through singleton managers, we can only allow one working copy compile to run at a time.
//TODO - Make BuildException a more generic Compile Exception so that code being shared between this compiler and the builder are not always throwing BuildExceptions
public class WorkingCopyCompiler {
private class DupeFileinfo{
String packageName;
String partName;
IFile file;
IProject project;
public DupeFileinfo(IProject project,IFile file, String packageName,String partName) {
this.project = project;
this.file = file;
this.packageName = packageName;
this.partName = partName;
}
}
private static final WorkingCopyCompiler INSTANCE = new WorkingCopyCompiler();
public static final ILock lock = Job.getJobManager().newLock();
private WorkingCopyCompiler(){}
public static WorkingCopyCompiler getInstance(){
return INSTANCE;
}
/**
* Compile a single part in the file.
*
* The bound node that is returned to the requestor is only valid for the life of the requestor call. The binding should NOT be cached.
*
* To compile the "file part" (import declarations), pass in com.ibm.etools.edt.internal.core.ide.utils.getFilePartName() as the part name.
*
* It is possible that this method will not return any results. Users should not assume that the requestor method will be invoked. This can occur
* if the Index that the compiler depends on is in an invalid state. See WorkingCopyFileInfoManager.hasValidState() for more information on states.
*/
public synchronized void compilePart(IProject project, String packageName, IFile file, IWorkingCopy[] workingCopies, String partName, IWorkingCopyCompileRequestor requestor){
compilePart(project, packageName, file, workingCopies, partName, requestor, NullProblemRequestorFactory.getInstance());
}
/**
* Compile a single part in the file.
*
* The bound node that is returned to the requestor is only valid for the life of the requestor call. The binding should NOT be cached.
*
* To compile the "file part" (import declarations), pass in com.ibm.etools.edt.internal.core.ide.utils.getFilePartName() as the part name.
*
* It is possible that this method will not return any results. Users should not assume that the requestor method will be invoked. This can occur
* if the Index that the compiler depends on is in an invalid state. See WorkingCopyFileInfoManager.hasValidState() for more information on states.
*/
public synchronized void compilePart(IProject project, String packageName, IFile file, IWorkingCopy[] workingCopies, String partName, IWorkingCopyCompileRequestor requestor, IProblemRequestorFactory problemRequestorFactory){
if(!WorkingCopyFileInfoManager.getInstance().hasValidState()){
return;
}
WorkingCopyProcessingQueue queue = null;
try{
// Acquire the compiling lock
lock.acquire();
initialize();
processWorkingCopies(workingCopies, problemRequestorFactory);
queue = new WorkingCopyProcessingQueue(project, problemRequestorFactory);
WorkingCopyProjectInfo projectInfo = WorkingCopyProjectInfoManager.getInstance().getProjectInfo(project);
String internedPackageName = NameUtile.getAsName(packageName);
String internedPartName = NameUtile.getAsName(partName);
// Verify that the part is in the model in the requested package.
if(ITypeBinding.NOT_FOUND_BINDING != projectInfo.hasPart(internedPackageName, internedPartName)){
IFile declaringFile = projectInfo.getPartOrigin(internedPackageName, internedPartName).getEGLFile();
if(declaringFile.equals(file)){
try{
queue.setCompileRequestor(requestor);
PackageAndPartName ppName = projectInfo.getPackageAndPartName(internedPackageName, internedPartName);
queue.addPart(new PackageAndPartName(ppName.getCaseSensitivePackageName(), ppName.getCaseSensitivePartName(), internedPackageName));
queue.process();
}catch(CancelledException e){
throw e;
}catch(BuildException e){
throw e;
}catch(RuntimeException e){
throw new BuildException(e);
}finally {
cleanup();
}
}
}
}finally{
if (queue != null && queue.pushedEnvironment()) {
Environment.popEnv();
PartEnvironmentStack.popEnv();
}
lock.release(); // allow changes to be processed
}
}
/**
* Compile all parts in the file, including the "file part" (import statements).
*
* The bound node that is returned to the requestor is only valid for the life of the requestor call. The binding should NOT be cached.
*
* It is possible that this method will not return any results. Users should not assume that the requestor method will be invoked. This can occur
* if the Index that the compiler depends on is in an invalid state. See WorkingCopyFileInfoManager.hasValidState() for more information on states.
*/
public synchronized void compileAllParts(IProject project, String packageName, IFile file, IWorkingCopy[] workingCopies, IWorkingCopyCompileRequestor requestor){
compileAllParts(project, packageName, file, workingCopies, requestor, NullProblemRequestorFactory.getInstance());
}
/**
* Compile all parts in the file, including the "file part" (import statements).
*
* The bound node that is returned to the requestor is only valid for the life of the requestor call. The binding should NOT be cached.
*
* It is possible that this method will not return any results. Users should not assume that the requestor method will be invoked. This can occur
* if the Index that the compiler depends on is in an invalid state. See WorkingCopyFileInfoManager.hasValidState() for more information on states.
*
*/
public synchronized void compileAllParts(IProject project, String packageName, IFile file, IWorkingCopy[] workingCopies, IWorkingCopyCompileRequestor requestor, IProblemRequestorFactory problemRequestorFactory){
if(!WorkingCopyFileInfoManager.getInstance().hasValidState()){
return;
}
WorkingCopyProcessingQueue queue = null;
try{
// Acquire the compiling lock
lock.acquire();
initialize();
processWorkingCopies(workingCopies, problemRequestorFactory);
processBinaryReadOnlyFile(file,packageName,problemRequestorFactory);
queue = new WorkingCopyProcessingQueue(project, problemRequestorFactory);
WorkingCopyProjectInfo projectInfo = WorkingCopyProjectInfoManager.getInstance().getProjectInfo(project);
try{
String internedPackageName = NameUtile.getAsName(packageName);
// We only need to check the package because the parts will exist - we know this because we just parsed the file
if(projectInfo.hasPackage(internedPackageName)){
IFileInfo fileInfo = WorkingCopyFileInfoManager.getInstance().getFileInfo(project, file.getProjectRelativePath());
for (Iterator iter = fileInfo.getPartNames().iterator(); iter.hasNext();) {
String partName = (String) iter.next();
IFile declaringFile = projectInfo.getPartOrigin(internedPackageName, partName).getEGLFile();
if(declaringFile.equals(file)){
queue.addPart(new PackageAndPartName(fileInfo.getCaseSensitivePackageName(), fileInfo.getCaseSensitivePartName(partName), internedPackageName));
}
}
queue.setCompileRequestor(requestor);
queue.process();
}
}catch(CancelledException e){
throw e;
}catch(BuildException e){
throw e;
}catch(RuntimeException e){
throw new BuildException(e);
}finally {
cleanup();
}
}finally{
if (queue != null && queue.pushedEnvironment()) {
Environment.popEnv();
PartEnvironmentStack.popEnv();
}
lock.release(); // allow changes to be processed
}
}
/**
* @deprecated - USE compileParts or compilePart instead
*/
public synchronized ICompiledFileUnit compileGenPart(IFile file){
return compileFiles(new IFile[]{file},true);
}
/**
* @deprecated - USE compileParts or compilePart instead
*/
public synchronized ICompiledFileUnit compileFile(IFile file){
return compileFiles(new IFile[]{file});
}
protected synchronized ICompiledFileUnit compileFiles(IFile[] files,boolean compileGeneratable){
final CompiledFileUnit searchTarget = new CompiledFileUnit();
if(WorkingCopyFileInfoManager.getInstance().hasValidState()){
try{
// Acquire the compile lock
lock.acquire();
initialize();
WorkingCopyASTManager.getInstance().setPartASTRequestor(searchTarget);
try{
for(int i = 0; i < files.length; i++){
final IFile file = files[i];
IEGLFile eglFile = (IEGLFile)EGLCore.create(file);
if(eglFile != null && eglFile.exists()){
IProject project = file.getProject();
// No Working Copies used in this search
WorkingCopyProcessingQueue queue = null;
try {
queue = new WorkingCopyProcessingQueue(project, NullProblemRequestorFactory.getInstance());
WorkingCopyProjectInfo projectInfo = WorkingCopyProjectInfoManager.getInstance().getProjectInfo(project);
File fileAST = null;
fileAST = WorkingCopyASTManager.getInstance().getFileAST(file);
searchTarget.setFileAST(fileAST);
String internedPackageName = NameUtile.getAsName(Util.stringArrayToQualifiedName(((EGLFile)eglFile).getPackageName()));
// We only need to check the package because the parts will exist - we know this because we just parsed the file
if(projectInfo.hasPackage(internedPackageName)){
String caseSensitivePackageName = org.eclipse.edt.compiler.Util.createCaseSensitivePackageName(fileAST);
for (Iterator iter = fileAST.getParts().iterator(); iter.hasNext();) {
Part part = (Part) iter.next();
IPartOrigin partOrigin = projectInfo.getPartOrigin(internedPackageName, part.getIdentifier());
if(partOrigin != null){
IFile declaringFile = partOrigin.getEGLFile();
if(declaringFile.equals(file)){
if (searchTarget.getFileBinding() == null){
String fileName = Util.getFilePartName(declaringFile);
IPartBinding fileBinding = WorkingCopyProjectEnvironmentManager.getInstance().getProjectEnvironment(project).getPartBinding(internedPackageName, fileName);
searchTarget.setFileBinding((FileBinding)fileBinding);
}
if (compileGeneratable){
if (part instanceof Program ||
part instanceof Library ||
part instanceof Handler||
part instanceof Service){
queue.addPart(new PackageAndPartName(caseSensitivePackageName, part.getName().getCaseSensitiveIdentifier(), internedPackageName));
break;
}
}else queue.addPart(new PackageAndPartName(caseSensitivePackageName, part.getName().getCaseSensitiveIdentifier(), internedPackageName));
}
}
}
queue.setCompileRequestor(new IWorkingCopyCompileRequestor(){
public void acceptResult(WorkingCopyCompilationResult result){
if (result.getDeclaringFile().equals(file)){
Part part = (Part)result.getBoundPart();
searchTarget.addBoundPart(result.getDeclaringFile(),part);
}
}
});
queue.process();
}
}
finally {
if (queue != null && queue.pushedEnvironment()) {
Environment.popEnv();
PartEnvironmentStack.popEnv();
}
}
}
}
}catch(CancelledException e){
throw e;
}catch(BuildException e){
throw e;
}catch(RuntimeException e){
throw new BuildException(e);
}finally {
cleanup();
}
}finally{
lock.release(); // allow changes to be processed
}
}
searchTarget.indexASTs();
return searchTarget;
}
/**
* @deprecated - USE compileParts or compilePart instead
*/
public synchronized ICompiledFileUnit compileFiles(IFile[] files){
return compileFiles(files,false);
}
private void initialize(){
WorkingCopyProjectEnvironmentManager.getInstance().initialize();
}
private void cleanup(){
ZipFileBuildPathEntryManager.getWCCInstance().clear();
WorkingCopyProjectBuildPathManager.getInstance().clear();
WorkingCopyProjectEnvironmentManager.getInstance().clear();
WorkingCopyProjectBuildPathEntryManager.getInstance().clear();
WorkingCopyProjectInfoManager.getInstance().resetWorkingCopies();
WorkingCopyASTManager.getInstance().resetWorkingCopies();
WorkingCopyFileInfoManager.getInstance().resetWorkingCopies();
WorkingCopyDuplicatePartManager.getInstance().clearUnsavedDuplicateParts();
WorkingCopyBuildNotifier.getInstance().setCanceled(false);
}
private void processWorkingCopies(final IWorkingCopy[] workingCopies, IProblemRequestorFactory problemRequestorFactory) {
for (int i = 0; i < workingCopies.length; i++) {
IWorkingCopy copy = workingCopies[i];
if(((IEGLElement)copy).exists()){
final IProject project = (IProject)copy.getOriginalElement().getEGLProject().getResource();
final String packageName = NameUtile.getAsName(Util.stringArrayToQualifiedName(((EGLFile)copy.getOriginalElement()).getPackageName()));
addWorkingCopy(project, packageName, copy, problemRequestorFactory);
}
}
}
private void processBinaryReadOnlyFile(IFile file,String packageName,IProblemRequestorFactory problemRequestorFactory) {
if ("eglar".equalsIgnoreCase(file.getFullPath().getFileExtension())) {
final String packageNames = NameUtile.getAsName(packageName);
addFileInfoForBinaryReadOnlyFile(file.getProject(),file,packageNames,problemRequestorFactory);
}
}
private void addFileInfoForBinaryReadOnlyFile(final IProject project,IFile file,final String packageName,IProblemRequestorFactory problemRequestorFactory) {
File fileAST = WorkingCopyASTManager.getInstance().getFileAST(file);
final WorkingCopyProjectInfo projectInfo = WorkingCopyProjectInfoManager.getInstance().getProjectInfo(project);
final IASTFileInfo newFileInfo = new WorkingCopyFileInfoCreator(project, packageName, file, null, fileAST, new WorkingCopyUnsavedDuplicatePartRequestor(project, packageName, file)).getASTInfo();
// Report file related errors (duplicate parts, more than one generateable part per file)
newFileInfo.accept(problemRequestorFactory.getFileProblemRequestor(file));
// Perform Syntax Checking
fileAST.accept(problemRequestorFactory.getSyntaxErrorRequestor(file));
Set partNames = newFileInfo.getPartNames();
for (Iterator iter = partNames.iterator(); iter.hasNext();) {
String partName = (String) iter.next();
PackageAndPartName ppName = new PackageAndPartName(newFileInfo.getCaseSensitivePackageName(), newFileInfo.getCaseSensitivePartName(partName));
projectInfo.workingCopyPartAdded(packageName, partName, newFileInfo.getPartType(partName), file, ppName);
}
WorkingCopyFileInfoManager.getInstance().addFileInfo(project, file.getProjectRelativePath(), newFileInfo);
}
private void processDuplicateFiles(Set duplicateFilesToProcess, IProblemRequestorFactory problemRequestorFactory){
//TODO NEed to reparse working copy if one exists, otherwise use the saved version of the file
Iterator dupeIter = duplicateFilesToProcess.iterator();
while (dupeIter.hasNext()){
DupeFileinfo dupeFileInfo = (DupeFileinfo)dupeIter.next();
WorkingCopyProjectInfo projectInfo = WorkingCopyProjectInfoManager.getInstance().getProjectInfo(dupeFileInfo.project);
try {
String fileContents = Util.getFileContents(dupeFileInfo.file);
File fileAST = WorkingCopyASTManager.getInstance().getFileAST(dupeFileInfo.file, fileContents);
IASTFileInfo fileInfo = new WorkingCopyCompilerResourceFileInfoCreator(projectInfo, dupeFileInfo.packageName, dupeFileInfo.file, fileAST, fileContents, new WorkingCopyUnsavedDuplicatePartRequestor(dupeFileInfo.project, dupeFileInfo.packageName, dupeFileInfo.file), false).getASTInfo();
if (fileInfo != null){
Set partNames = fileInfo.getPartNames();
for (Iterator partIter = partNames.iterator(); partIter.hasNext();) {
String thisPartName = (String) partIter.next();
if (thisPartName.equalsIgnoreCase(dupeFileInfo.partName)){
PackageAndPartName ppName = new PackageAndPartName(fileInfo.getCaseSensitivePackageName(), fileInfo.getCaseSensitivePartName(thisPartName));
projectInfo.workingCopyPartAdded(dupeFileInfo.packageName, dupeFileInfo.partName, fileInfo.getPartType(dupeFileInfo.partName), dupeFileInfo.file, ppName);
break;
}
}
}
}catch(Exception e){
throw new RuntimeException("Error adding file: " + dupeFileInfo.file.getProjectRelativePath(), e);
}
}
}
private void addWorkingCopy(final IProject project, final String packageName, final IWorkingCopy copy, IProblemRequestorFactory problemRequestorFactory) {
final HashSet duplicateFilesToProcess = new HashSet();
final IEGLFile eglFile = (IEGLFile)copy.getOriginalElement();
try{
copy.reconcile(true, null);
}catch(EGLModelException e){
// Do nothing
}
File fileAST = WorkingCopyASTManager.getInstance().getFileAST(copy);
final WorkingCopyProjectInfo projectInfo = WorkingCopyProjectInfoManager.getInstance().getProjectInfo(project);
final IASTFileInfo newFileInfo = new WorkingCopyFileInfoCreator(project, packageName, (IFile)eglFile.getResource(), copy, fileAST, new WorkingCopyUnsavedDuplicatePartRequestor(project, packageName, (IFile)eglFile.getResource())).getASTInfo();
final IFileInfo cachedFileInfo = WorkingCopyFileInfoManager.getInstance().getFileInfo(project, eglFile.getResource().getProjectRelativePath());
// Report file related errors (duplicate parts, more than one generateable part per file)
newFileInfo.accept(problemRequestorFactory.getFileProblemRequestor((IFile)eglFile.getResource()));
// Perform Syntax Checking
fileAST.accept(problemRequestorFactory.getSyntaxErrorRequestor((IFile)eglFile.getResource()));
if(cachedFileInfo != null){
FileInfoDifferencer differencer = new FileInfoDifferencer(new IFileInfoDifferenceNotificationRequestor(){
public void partAdded(String partName) {
PackageAndPartName ppName = new PackageAndPartName(newFileInfo.getCaseSensitivePackageName(), newFileInfo.getCaseSensitivePartName(partName));
projectInfo.workingCopyPartAdded(packageName, partName, newFileInfo.getPartType(partName), (IFile)eglFile.getResource(), ppName);
}
public void partRemoved(String partName) {
PackageAndPartName ppName = new PackageAndPartName(cachedFileInfo.getCaseSensitivePackageName(), cachedFileInfo.getCaseSensitivePartName(partName));
projectInfo.workingCopyPartRemoved(packageName, partName, cachedFileInfo.getPartType(partName), (IFile)eglFile.getResource(), ppName);
locateDuplicateFile(duplicateFilesToProcess, project, packageName, partName, cachedFileInfo.getPartType(partName), (IFile)eglFile.getResource());
}
public void partChanged(String partName) {
PackageAndPartName ppName = new PackageAndPartName(newFileInfo.getCaseSensitivePackageName(), newFileInfo.getCaseSensitivePartName(partName));
projectInfo.workingCopyPartChanged(packageName, partName, newFileInfo.getPartType(partName), (IFile)eglFile.getResource(), ppName);
}});
differencer.findDifferences(cachedFileInfo, newFileInfo);
}else{
Set partNames = newFileInfo.getPartNames();
for (Iterator iter = partNames.iterator(); iter.hasNext();) {
String partName = (String) iter.next();
PackageAndPartName ppName = new PackageAndPartName(newFileInfo.getCaseSensitivePackageName(), newFileInfo.getCaseSensitivePartName(partName));
projectInfo.workingCopyPartAdded(packageName, partName, newFileInfo.getPartType(partName), (IFile)eglFile.getResource(), ppName);
}
}
WorkingCopyFileInfoManager.getInstance().addFileInfo(project, ((IFile)eglFile.getResource()).getProjectRelativePath(), newFileInfo);
processDuplicateFiles(duplicateFilesToProcess, problemRequestorFactory);
}
private void locateDuplicateFile(Set duplicateFilesToProcess, IProject project, String packageName, String partName, int partType, IFile file){
// TODO:
// Get the duplicate part list for this part
// Get the first file in the list Using the WC or Saved version
// process the file
// If the file does not have the part, go to the next part in the list
// otherwise we are done
DuplicatePartList dupeparts = WorkingCopyDuplicatePartManager.getInstance().getDuplicatePartList(project);
if (dupeparts.isDuplicatePart(packageName, partName)){
// Confirm that the removed part was the one that was indexed, and not a duplicate
if (WorkingCopyProjectInfoManager.getInstance().getProjectInfo(project).getPartOrigin(packageName,partName).getEGLFile().equals(file)){
//assume dupe part without message is removed. need to resurrect the dupe part with message
Set files = dupeparts.getFilesForDuplicatePart(packageName, partName);
for (Iterator iter = files.iterator(); iter.hasNext();) {
IFile dupeFile = (IFile) iter.next();
duplicateFilesToProcess.add(new DupeFileinfo(project, dupeFile, packageName, partName));
break;
}
}
}
}
}