package com.redhat.ceylon.eclipse.code.parse;
import static com.redhat.ceylon.cmr.ceylon.CeylonUtils.repoManager;
import static com.redhat.ceylon.common.config.DefaultToolOptions.DEFAULTS_OFFLINE;
import static com.redhat.ceylon.compiler.typechecker.TypeChecker.LANGUAGE_MODULE_VERSION;
import static com.redhat.ceylon.eclipse.code.parse.TreeLifecycleListener.Stage.FOR_OUTLINE;
import static com.redhat.ceylon.eclipse.code.parse.TreeLifecycleListener.Stage.LEXICAL_ANALYSIS;
import static com.redhat.ceylon.eclipse.code.parse.TreeLifecycleListener.Stage.SYNTACTIC_ANALYSIS;
import static com.redhat.ceylon.eclipse.code.parse.TreeLifecycleListener.Stage.TYPE_ANALYSIS;
import static com.redhat.ceylon.eclipse.core.builder.CeylonBuilder.allClasspathContainersInitialized;
import static com.redhat.ceylon.eclipse.core.builder.CeylonBuilder.doWithSourceModel;
import static com.redhat.ceylon.eclipse.core.builder.CeylonBuilder.getInterpolatedCeylonSystemRepo;
import static com.redhat.ceylon.eclipse.core.builder.CeylonBuilder.getProjectExternalModules;
import static com.redhat.ceylon.eclipse.core.builder.CeylonBuilder.getProjectTypeChecker;
import static com.redhat.ceylon.eclipse.core.builder.CeylonBuilder.getProjects;
import static com.redhat.ceylon.eclipse.core.builder.CeylonBuilder.getReferencedProjectsOutputRepositories;
import static com.redhat.ceylon.eclipse.core.builder.CeylonBuilder.getSourceFolders;
import static com.redhat.ceylon.eclipse.core.builder.CeylonBuilder.getSuppressedWarnings;
import static com.redhat.ceylon.eclipse.core.builder.CeylonBuilder.isModelTypeChecked;
import static com.redhat.ceylon.eclipse.core.builder.CeylonBuilder.showWarnings;
import static com.redhat.ceylon.eclipse.core.external.CeylonArchiveFileSystem.JAR_SUFFIX;
import static com.redhat.ceylon.eclipse.core.external.ExternalSourceArchiveManager.isTheSourceArchiveProject;
import static com.redhat.ceylon.eclipse.core.external.ExternalSourceArchiveManager.toFullPath;
import static com.redhat.ceylon.eclipse.java2ceylon.Java2CeylonProxies.modelJ2C;
import static com.redhat.ceylon.eclipse.java2ceylon.Java2CeylonProxies.vfsJ2C;
import static com.redhat.ceylon.eclipse.util.InteropUtils.toJavaString;
import static com.redhat.ceylon.eclipse.util.PathUtils.toCommonPath;
import static java.util.Arrays.asList;
import static org.eclipse.core.resources.ResourcesPlugin.getWorkspace;
import static org.eclipse.core.runtime.jobs.Job.getJobManager;
import java.io.File;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import org.antlr.runtime.CommonToken;
import org.antlr.runtime.CommonTokenStream;
import org.antlr.runtime.RecognitionException;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IFolder;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IWorkspaceRoot;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.OperationCanceledException;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.jface.text.IDocument;
import com.redhat.ceylon.cmr.api.RepositoryManager;
import com.redhat.ceylon.common.config.CeylonConfig;
import com.redhat.ceylon.compiler.java.loader.UnknownTypeCollector;
import com.redhat.ceylon.compiler.java.runtime.model.TypeDescriptor;
import com.redhat.ceylon.compiler.typechecker.TypeChecker;
import com.redhat.ceylon.compiler.typechecker.TypeCheckerBuilder;
import com.redhat.ceylon.compiler.typechecker.analyzer.ModuleSourceMapper;
import com.redhat.ceylon.compiler.typechecker.analyzer.ModuleValidator;
import com.redhat.ceylon.compiler.typechecker.analyzer.Warning;
import com.redhat.ceylon.compiler.typechecker.context.Context;
import com.redhat.ceylon.compiler.typechecker.context.PhasedUnit;
import com.redhat.ceylon.compiler.typechecker.context.PhasedUnits;
import com.redhat.ceylon.compiler.typechecker.io.VirtualFile;
import com.redhat.ceylon.compiler.typechecker.parser.CeylonLexer;
import com.redhat.ceylon.compiler.typechecker.parser.CeylonParser;
import com.redhat.ceylon.compiler.typechecker.parser.LexError;
import com.redhat.ceylon.compiler.typechecker.parser.ParseError;
import com.redhat.ceylon.compiler.typechecker.parser.RecognitionError;
import com.redhat.ceylon.compiler.typechecker.tree.Message;
import com.redhat.ceylon.compiler.typechecker.tree.Node;
import com.redhat.ceylon.compiler.typechecker.tree.Tree;
import com.redhat.ceylon.compiler.typechecker.tree.Visitor;
import com.redhat.ceylon.compiler.typechecker.util.ModuleManagerFactory;
import com.redhat.ceylon.compiler.typechecker.util.NewlineFixingStringStream;
import com.redhat.ceylon.compiler.typechecker.util.WarningSuppressionVisitor;
import com.redhat.ceylon.eclipse.code.correct.correctJ2C;
import com.redhat.ceylon.eclipse.code.editor.AnnotationCreator;
import com.redhat.ceylon.eclipse.code.parse.CeylonParserScheduler.Stager;
import com.redhat.ceylon.eclipse.code.parse.TreeLifecycleListener.Stage;
import com.redhat.ceylon.eclipse.core.builder.CeylonNature;
import com.redhat.ceylon.eclipse.ui.CeylonPlugin;
import com.redhat.ceylon.eclipse.util.EclipseLogger;
import com.redhat.ceylon.ide.common.model.BaseCeylonProject;
import com.redhat.ceylon.ide.common.model.BaseIdeModelLoader;
import com.redhat.ceylon.ide.common.model.BaseIdeModule;
import com.redhat.ceylon.ide.common.model.BaseIdeModuleManager;
import com.redhat.ceylon.ide.common.model.BaseIdeModuleSourceMapper;
import com.redhat.ceylon.ide.common.model.CeylonProject;
import com.redhat.ceylon.ide.common.model.IdeModuleManager;
import com.redhat.ceylon.ide.common.platform.CommonDocument;
import com.redhat.ceylon.ide.common.typechecker.AnalysisResult$impl;
import com.redhat.ceylon.ide.common.typechecker.EditedPhasedUnit;
import com.redhat.ceylon.ide.common.typechecker.IdePhasedUnit;
import com.redhat.ceylon.ide.common.typechecker.LocalAnalysisResult;
import com.redhat.ceylon.ide.common.typechecker.LocalAnalysisResult$impl;
import com.redhat.ceylon.ide.common.typechecker.ProjectPhasedUnit;
import com.redhat.ceylon.ide.common.util.Path;
import com.redhat.ceylon.ide.common.util.SingleSourceUnitPackage;
import com.redhat.ceylon.ide.common.vfs.DummyFolder;
import com.redhat.ceylon.ide.common.vfs.FileVirtualFile;
import com.redhat.ceylon.ide.common.vfs.FolderVirtualFile;
import com.redhat.ceylon.ide.common.vfs.SourceCodeVirtualFile;
import com.redhat.ceylon.ide.common.vfs.VirtualFileSystem;
import com.redhat.ceylon.model.typechecker.model.Cancellable;
import com.redhat.ceylon.model.typechecker.model.Module;
import com.redhat.ceylon.model.typechecker.model.Modules;
import com.redhat.ceylon.model.typechecker.model.Package;
import com.redhat.ceylon.model.typechecker.util.ModuleManager;
public class CeylonParseController
implements LocalAnalysisResult {
/**
* The project containing the source being parsed. May be
* null if the source isn't actually part of an Eclipse
* project (e.g., a random bit of source text living
* outside the workspace).
*/
protected IProject project;
/**
* The path to the file containing the source being parsed.
*/
protected IPath filePath;
/**
* The current AST (if any) produced by the most recent
* successful parse.<br>
* N.B.: "Successful" may mean that there were syntax
* errors, but the parser managed to perform error recovery
* and still produce an AST.
*/
protected Tree.CompilationUnit rootNode;
/**
* The EditedPhasedUnit associated with the most recent typecheck.
* May be null if this parse controller has never parsed or
* successfully typechecked anything.
*/
private PhasedUnit phasedUnit;
/**
* The most-recently parsed source document. May be null
* if this parse controller has never parsed anything.
*/
protected IDocument document;
/**
* The most-recently parsed token stream. May be null if
* this parse controller has never parsed anything.
*/
private List<CommonToken> tokens;
/**
* The type checker associated with the most recent parse.
* May be null if this parse controller has never parsed
* anything.
*/
private TypeChecker typeChecker;
private Stage stage = Stage.NONE;
private boolean dirty = true;
/**
* @param filePath the project-relative path of file
* @param project the project that contains the file
* @param handler a message handler to receive error
* messages/warnings
*/
public void initialize(IPath filePath, IProject project,
AnnotationCreator handler) {
if (isTheSourceArchiveProject(project)) {
IResource archiveEntry =
project.findMember(filePath);
if (archiveEntry instanceof IFile
&& archiveEntry.exists()) {
IPath entryPath =
toFullPath((IFile) archiveEntry);
if (entryPath != null) {
filePath = entryPath;
}
project = null;
}
}
this.project = project;
this.filePath = filePath;
}
public Stage getStage() {
return stage;
}
public PhasedUnit getLastPhasedUnit() {
return phasedUnit;
}
private boolean isCanceling(IProgressMonitor monitor) {
boolean isCanceling = false;
if (monitor!=null) {
isCanceling = monitor.isCanceled();
}
CeylonParserScheduler scheduler = getScheduler();
if (scheduler!=null && scheduler.isCanceling()) {
if (monitor!=null && !monitor.isCanceled()) {
monitor.setCanceled(true);
}
isCanceling = true;
}
return isCanceling;
}
private CeylonParserScheduler getScheduler() {
final Job parsingJob = getJobManager().currentJob();
if (parsingJob instanceof CeylonParserScheduler) {
return (CeylonParserScheduler) parsingJob;
}
return null;
}
private FileVirtualFile<IProject,IResource,IFolder,IFile>
createSourceCodeVirtualFile(String contents,
IPath path) {
return new SourceCodeVirtualFile<IProject,IResource,IFolder,IFile>(
TypeDescriptor.klass(IProject.class),
TypeDescriptor.klass(IResource.class),
TypeDescriptor.klass(IFolder.class),
TypeDescriptor.klass(IFile.class),
contents,
path==null ? null : toCommonPath(path),
project, getFile(),
ceylon.language.String.instance(getCharset()));
}
private IFile getFile() {
return project==null || filePath==null ? null :
project.getFile(filePath);
}
private String getCharset() {
IFile file = getFile();
try {
if (file==null) {
if (project==null) {
return getWorkspace().getRoot()
.getDefaultCharset();
}
else {
return project.getDefaultCharset();
}
}
else {
return file.getCharset();
}
} catch (CoreException e) {
e.printStackTrace();
return System.getProperty("file.encoding");
}
}
private FolderVirtualFile<IProject,IResource,IFolder,IFile>
inferSrcDir(IPath path) {
String pathString = path.toString();
int lastBangIdx = pathString.lastIndexOf('!');
if (lastBangIdx>0) {
String srcArchivePath =
pathString.substring(0, lastBangIdx);
return new DummyFolder<IProject,IResource,IFolder,IFile>(
TypeDescriptor.klass(IProject.class),
TypeDescriptor.klass(IResource.class),
TypeDescriptor.klass(IFolder.class),
TypeDescriptor.klass(IFile.class),
srcArchivePath+'!');
}
else {
return null;
}
}
private void collectLexAndParseErrors(CeylonLexer lexer,
CeylonParser parser, Tree.CompilationUnit cu) {
List<LexError> lexerErrors = lexer.getErrors();
for (LexError le : lexerErrors) {
cu.addLexError(le);
}
lexerErrors.clear();
List<ParseError> parserErrors = parser.getErrors();
for (ParseError pe : parserErrors) {
cu.addParseError(pe);
}
parserErrors.clear();
}
private EditedPhasedUnit<IProject,IResource,IFolder,IFile>
newEditedPhasedUnit(
FileVirtualFile<IProject,IResource,IFolder,IFile> file,
FolderVirtualFile<IProject,IResource,IFolder,IFile> srcDir,
Tree.CompilationUnit cu, Package pkg,
ModuleManager moduleManager,
BaseIdeModuleSourceMapper moduleSourceMapper,
TypeChecker typeChecker, List<CommonToken> tokens,
ProjectPhasedUnit<IProject,IResource,IFolder,IFile> savedPhasedUnit) {
EditedPhasedUnit<IProject, IResource, IFolder, IFile> editedPhasedUnit =
new EditedPhasedUnit<IProject,IResource,IFolder,IFile>(
TypeDescriptor.klass(IProject.class),
TypeDescriptor.klass(IResource.class),
TypeDescriptor.klass(IFolder.class),
TypeDescriptor.klass(IFile.class),
file, srcDir,
cu, pkg,
moduleManager, moduleSourceMapper,
typeChecker, tokens, savedPhasedUnit,
project,
project==null || filePath==null ? null :
project.getFile(filePath));
if (savedPhasedUnit != null) {
savedPhasedUnit.addWorkingCopy(editedPhasedUnit);
}
return editedPhasedUnit;
}
private PhasedUnit typecheck(IPath path,
FileVirtualFile<IProject,IResource,IFolder,IFile> file,
Tree.CompilationUnit rootNode,
FolderVirtualFile<IProject,IResource,IFolder,IFile> srcDir,
final boolean showWarnings,
final PhasedUnit builtPhasedUnit,
final IProgressMonitor monitor) {
if (isExternalPath(path) && builtPhasedUnit!=null) {
// reuse the existing AST
phasedUnit = builtPhasedUnit;
useTypechecker(builtPhasedUnit, new Runnable() {
@Override
public void run() {
builtPhasedUnit.analyseTypes(Cancellable.ALWAYS_CANCELLED);
if (showWarnings) {
builtPhasedUnit.analyseUsage();
}
}
});
return builtPhasedUnit;
}
Package pkg;
if (srcDir==null) {
srcDir = new DummyFolder<IProject,IResource,IFolder,IFile>(
TypeDescriptor.klass(IProject.class),
TypeDescriptor.klass(IResource.class),
TypeDescriptor.klass(IFolder.class),
TypeDescriptor.klass(IFile.class));
//put it in the default module
pkg = typeChecker
.getContext()
.getModules()
.getDefaultModule()
.getPackages()
.get(0);
}
else {
pkg = getPackage(file, srcDir, builtPhasedUnit);
}
if (isCanceling(monitor)) {
throw new OperationCanceledException();
}
final PhasedUnit newPhasedUnit =
createPhasedUnit(file, rootNode, srcDir,
builtPhasedUnit, pkg);
useTypechecker(newPhasedUnit, new Runnable() {
@Override
public void run() {
if (isCanceling(monitor)) {
throw new OperationCanceledException();
}
newPhasedUnit.validateTree();
if (isCanceling(monitor)) {
throw new OperationCanceledException();
}
newPhasedUnit.visitSrcModulePhase();
if (isCanceling(monitor)) {
throw new OperationCanceledException();
}
newPhasedUnit.visitRemainingModulePhase();
if (isCanceling(monitor)) {
throw new OperationCanceledException();
}
newPhasedUnit.scanDeclarations();
if (isCanceling(monitor)) {
throw new OperationCanceledException();
}
newPhasedUnit.scanTypeDeclarations(Cancellable.ALWAYS_CANCELLED);
if (isCanceling(monitor)) {
throw new OperationCanceledException();
}
newPhasedUnit.validateRefinement();
if (isCanceling(monitor)) {
throw new OperationCanceledException();
}
newPhasedUnit.analyseTypes(Cancellable.ALWAYS_CANCELLED);
if (isCanceling(monitor)) {
throw new OperationCanceledException();
}
if (showWarnings) {
newPhasedUnit.analyseUsage();
}
if (isCanceling(monitor)) {
throw new OperationCanceledException();
}
newPhasedUnit.analyseFlow();
if (isCanceling(monitor)) {
throw new OperationCanceledException();
}
newPhasedUnit.getCompilationUnit()
.visit(new UnknownTypeCollector());
if (isCanceling(monitor)) {
throw new OperationCanceledException();
}
newPhasedUnit.getCompilationUnit()
.visit(new WarningSuppressionVisitor<Warning>(
Warning.class,
getSuppressedWarnings(project)));
}
});
return newPhasedUnit;
}
public PhasedUnit createPhasedUnit(
FileVirtualFile<IProject, IResource, IFolder, IFile> file,
Tree.CompilationUnit rootNode,
FolderVirtualFile<IProject, IResource, IFolder, IFile> srcDir,
final PhasedUnit builtPhasedUnit,
Package pkg) {
final PhasedUnit newPhasedUnit;
PhasedUnits phasedUnits =
typeChecker.getPhasedUnits();
if (builtPhasedUnit instanceof ProjectPhasedUnit) {
newPhasedUnit =
newEditedPhasedUnit(file, srcDir,
rootNode, pkg,
phasedUnits.getModuleManager(),
(BaseIdeModuleSourceMapper)
phasedUnits.getModuleSourceMapper(),
typeChecker, tokens,
(ProjectPhasedUnit)
builtPhasedUnit);
}
else {
newPhasedUnit =
newEditedPhasedUnit(file, srcDir,
rootNode, pkg,
phasedUnits.getModuleManager(),
(BaseIdeModuleSourceMapper)
phasedUnits.getModuleSourceMapper(),
typeChecker, tokens,
null);
IdeModuleManager moduleManager =
(IdeModuleManager)
phasedUnits.getModuleManager();
moduleManager.getModelLoader()
.setupSourceFileObjects(
asList(newPhasedUnit));
}
return newPhasedUnit;
}
private void useTypechecker(
PhasedUnit phasedUnitToTypeCheck,
final Runnable typecheckSteps) {
Job typecheckJob =
new Job("Typechecking the working copy of " +
phasedUnitToTypeCheck.getPathRelativeToSrcDir()) {
@Override
protected IStatus run(IProgressMonitor monitor) {
try {
typecheckSteps.run();
} catch(OperationCanceledException e) {
return Status.CANCEL_STATUS;
}
return Status.OK_STATUS;
}
};
CeylonParserScheduler scheduler = getScheduler();
if (scheduler!=null) {
typecheckJob.setPriority(scheduler.getPriority());
}
typecheckJob.setSystem(true);
typecheckJob.schedule();
try {
typecheckJob.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
if (typecheckJob.getResult() == Status.CANCEL_STATUS) {
throw new OperationCanceledException();
}
}
private static TypeChecker createTypeChecker(
IProject project, boolean showWarnings)
throws CoreException {
TypeChecker typeChecker =
buildTypeChecker(project, showWarnings);
List<PhasedUnit> dependencies =
getDependencies(typeChecker);
for (PhasedUnit pu: dependencies) {
pu.scanDeclarations();
}
for (PhasedUnit pu: dependencies) {
pu.scanTypeDeclarations(Cancellable.ALWAYS_CANCELLED);
}
for (PhasedUnit pu: dependencies) {
pu.validateRefinement(); //TODO: only needed for type hierarchy view in IDE!
}
for (PhasedUnit pu: dependencies) {
pu.analyseTypes(Cancellable.ALWAYS_CANCELLED); //TODO: Needed to have the right values in the Value.trans field (set in Expression visitor)
// which in turn is important for debugging !
}
return typeChecker;
}
private static List<PhasedUnit> getDependencies(
TypeChecker typeChecker) {
PhasedUnits phasedUnits =
typeChecker.getPhasedUnits();
BaseIdeModuleManager moduleManager =
(BaseIdeModuleManager)
phasedUnits.getModuleManager();
moduleManager.setTypeChecker(typeChecker);
BaseIdeModuleSourceMapper moduleSourceMapper =
(BaseIdeModuleSourceMapper)
phasedUnits.getModuleSourceMapper();
moduleSourceMapper.setTypeChecker(typeChecker);
Context context = typeChecker.getContext();
BaseIdeModelLoader modelLoader =
moduleManager.getModelLoader();
moduleManager.prepareForTypeChecking();
phasedUnits.visitModules();
//By now the language module version should be known
//(as local) or we should use the default one.
Module languageModule =
context.getModules()
.getLanguageModule();
if (languageModule.getVersion() == null) {
languageModule.setVersion(LANGUAGE_MODULE_VERSION);
}
final ModuleValidator moduleValidator =
new ModuleValidator(context, phasedUnits) {
@Override
protected void executeExternalModulePhases() {}
};
moduleValidator.verifyModuleDependencyTree();
typeChecker.setPhasedUnitsOfDependencies(
moduleValidator.getPhasedUnitsOfDependencies());
List<PhasedUnit> dependencies =
new ArrayList<PhasedUnit>();
for (PhasedUnits dependencyPhasedUnits:
typeChecker.getPhasedUnitsOfDependencies()) {
List<PhasedUnit> units =
dependencyPhasedUnits.getPhasedUnits();
modelLoader.addSourceArchivePhasedUnits(units);
for (PhasedUnit phasedUnit: units) {
dependencies.add(phasedUnit);
}
}
return dependencies;
}
private static TypeChecker buildTypeChecker(
IProject project, boolean showWarnings)
throws CoreException {
final CeylonProject<IProject,IResource,IFolder,IFile>
ceylonProject =
modelJ2C().ceylonModel()
.getProject(project);
VirtualFileSystem vfs =
modelJ2C().ceylonModel()
.getVfs();
TypeCheckerBuilder tcb =
new TypeCheckerBuilder(vfs)
.verbose(false)
.moduleManagerFactory(new ModuleManagerFactory() {
@Override
public ModuleManager createModuleManager(
Context context) {
return modelJ2C()
.newModuleManager(context,
ceylonProject);
}
@Override
public ModuleSourceMapper createModuleManagerUtil(
Context context,
ModuleManager moduleManager) {
return modelJ2C()
.newModuleSourceMapper(context,
(IdeModuleManager)
moduleManager);
}
})
.usageWarnings(showWarnings);
File cwd;
String systemRepo;
boolean offline;
if (ceylonProject==null) {
cwd = null;
systemRepo =
CeylonPlugin.getInstance()
.getCeylonRepository()
.getAbsolutePath();
offline = CeylonConfig.get()
.getBoolOption(DEFAULTS_OFFLINE, false);
}
else {
cwd = project.getLocation().toFile();
systemRepo =
getInterpolatedCeylonSystemRepo(project);
offline = modelJ2C().ceylonModel()
.getProject(project)
.getConfiguration()
.getOffline();
}
List<String> userRepos =
getReferencedProjectsOutputRepositories(project);
RepositoryManager repositoryManager =
repoManager()
.offline(offline)
.cwd(cwd)
.systemRepo(systemRepo)
.extraUserRepos(userRepos)
.logger(new EclipseLogger())
.isJDKIncluded(true)
.buildManager();
tcb.setRepositoryManager(repositoryManager);
return tcb.getTypeChecker();
}
private IProject findProject(IPath path) {
//search for the project by iterating all
//projects in the workspace
//TODO: should we use CeylonBuilder.getProjects()?
IProject[] projects =
getWorkspace()
.getRoot()
.getProjects();
for (IProject p: projects) {
if (p.getLocation().isPrefixOf(path)) {
return p;
}
}
for (IProject p: getProjects()) {
TypeChecker typeChecker =
getProjectTypeChecker(p);
List<PhasedUnit> projPhasedUnits =
typeChecker.getPhasedUnits()
.getPhasedUnits();
for (PhasedUnit unit: projPhasedUnits) {
if (unit.getUnit().getFullPath()
.equals(path.toString())) {
return p;
}
}
List<PhasedUnits> depPhasedUnits =
typeChecker.getPhasedUnitsOfDependencies();
for (PhasedUnits units: depPhasedUnits) {
for (PhasedUnit unit: units.getPhasedUnits()) {
if (unit.getUnit().getFullPath()
.equals(path.toString())) {
return p;
}
}
}
if (path.toString().contains(JAR_SUFFIX)) {
for (Module m: getProjectExternalModules(p)) {
if (m instanceof BaseIdeModule) {
BaseIdeModule ibm = (BaseIdeModule) m;
String sourceArchivePath =
toJavaString(ibm.getSourceArchivePath());
if (sourceArchivePath!=null
&& path.toOSString()
.contains(sourceArchivePath)) {
return p;
}
}
}
}
}
return null;
}
private Package getPackage(VirtualFile file,
VirtualFile srcDir,
PhasedUnit builtPhasedUnit) {
Package pkg = null;
if (builtPhasedUnit!=null) {
// Editing an already built file
pkg = builtPhasedUnit.getPackage();
}
else {
// Editing a new file
Modules modules =
typeChecker.getContext()
.getModules();
// Retrieve the target package from the file
// src-relative path
//TODO: this is very fragile!
String packageName =
constructPackageName(file, srcDir);
for (Module module: modules.getListOfModules()) {
for (Package p: module.getPackages()) {
if (p.getQualifiedNameString()
.equals(packageName)) {
pkg = p;
break;
}
}
if (pkg!=null) {
break;
}
}
if (pkg==null) {
// assume the default package
pkg = modules.getDefaultModule()
.getPackages()
.get(0);
// TODO : iterate through parents to get the sub-package
// in which the package has been created, until we find the module
// Then the package can be created.
// However this should preferably be done on notification of the
// resource creation
// A more global/systematic integration between the model element
// (modules, packages, Units) and the IResourceModel should
// maybe be considered. But for now it is not required.
}
}
return new SingleSourceUnitPackage(pkg,
file.getPath());
}
public boolean isExternalPath(IPath path) {
IWorkspaceRoot wsRoot = getWorkspace().getRoot();
// If the path is outside the workspace, or pointing inside the workspace,
// but is still file-system-absolute.
return path!=null && path.isAbsolute() &&
(wsRoot.getLocation().isPrefixOf(path) ||
!wsRoot.exists(path));
}
private String constructPackageName(
VirtualFile file, VirtualFile srcDir) {
return file.getPath()
.substring(srcDir.getPath().length()+1)
.replace("/" + file.getName(), "")
.replace('/', '.');
}
private FolderVirtualFile getSourceFolder(
IProject project, IPath resolvedPath) {
for (IFolder folder: getSourceFolders(project)) {
if (folder.getFullPath()
.isPrefixOf(resolvedPath)) {
return vfsJ2C()
.createVirtualFolder(project,
folder.getProjectRelativePath());
}
}
return null;
}
public List<CommonToken> getTokens() {
return tokens;
}
public TypeChecker getTypeChecker() {
return typeChecker;
}
/*
* returns the last fully-typechecked AST.
* It might be different from the most recently parsed AST,
* and thus inconsistent with the source code.
*/
public Tree.CompilationUnit getLastCompilationUnit() {
return phasedUnit == null ? null :
phasedUnit.getCompilationUnit();
}
/*
* returns the most recently parsed AST.
*
* Be careful it can be returned *before* the typechecking or
* *during* the typechecking (in case of cancellation)
* So *never* use this from places that need a fully typechecked AST
* (with model elements such as declarations or units).
*/
public Tree.CompilationUnit getParsedRootNode() {
return rootNode;
}
/*
* returns the last parsed AST only if it is fully typechecked.
*
* Returns null if the last parsed AST could not be fully
* typechecked
* (cancellation, source model read lock not obtained,
* running typechecking ...)
*/
public Tree.CompilationUnit getTypecheckedRootNode() {
return analysisResult$impl.getTypecheckedRootNode();
}
public void dirty() {
dirty = true;
}
@Override
public Future<? extends PhasedUnit> getPhasedUnitWhenTypechecked() {
return new Future<PhasedUnit>() {
@Override
public boolean cancel(boolean mayInterruptIfRunning) {
return true;
}
@Override
public boolean isCancelled() {
return true;
}
@Override
public boolean isDone() {
return true;
}
@Override
public PhasedUnit get()
throws InterruptedException, ExecutionException {
return null;
}
@Override
public PhasedUnit get(long timeout, TimeUnit unit)
throws InterruptedException, ExecutionException,
TimeoutException {
return null;
}
};
}
/*
* The typechecked PhasedUnit built on the most-recently parsed AST
* or null if the most-recently parsed AST could not be fully
* typechecked
* (cancellation, source model read lock not obtained,
* running typechecking ...)
*/
@Override
public PhasedUnit getTypecheckedPhasedUnit() {
if (phasedUnit != null &&
phasedUnit.getCompilationUnit() == rootNode) {
return phasedUnit;
}
return null;
}
/*
* returns true is the the last AST was parsed *and*
* typechecked until the end (=> stage == TYPE_ANALYSIS)
*/
public PhasedUnit parseAndTypecheck(
final IDocument doc,
long waitForModelInSeconds,
final IProgressMonitor monitor,
final Stager stager) {
document = doc;
if (!dirty) {
return this.phasedUnit;
}
IPath path = this.filePath;
IProject project = this.project;
IPath resolvedPath = path;
stage = Stage.NONE;
if (path!=null) {
String ext = path.getFileExtension();
if (ext==null || !ext.equals("ceylon")) {
return null;
}
if (!path.isAbsolute() && project!=null) {
resolvedPath =
project.getFullPath()
.append(filePath);
//TODO: do we need to add in the source folder???
IWorkspaceRoot root =
project.getWorkspace()
.getRoot();
if (!root.exists(resolvedPath)) {
// file has been deleted for example
path = null;
project = null;
}
}
if (path.isAbsolute()) {
IdePhasedUnit builtPhasedUnit = null;
List<IProject> projects =
new ArrayList<IProject>
(getProjects());
for (IProject p: projects) {
if (project!=null && project!=p) {
continue;
}
Path commonPath = toCommonPath(path);
BaseIdeModule module =
getModuleManager(p)
.getArchiveModuleFromSourcePath(
commonPath);
if (module!=null) {
builtPhasedUnit =
module.getPhasedUnit(commonPath);
if (builtPhasedUnit!=null) {
if (project == p) {
break;
}
if (module.getIsCeylonBinaryArchive()) {
project = p;
break;
}
}
}
}
if (builtPhasedUnit != null) {
phasedUnit = builtPhasedUnit;
typeChecker =
builtPhasedUnit.getTypeChecker();
rootNode =
builtPhasedUnit.getCompilationUnit();
tokens =
builtPhasedUnit.getTokens();
stage = SYNTACTIC_ANALYSIS;
if (stager!=null) {
stager.afterStage(LEXICAL_ANALYSIS, monitor);
stager.afterStage(SYNTACTIC_ANALYSIS, monitor);
}
final IProject finalProject = project;
useTypechecker(phasedUnit, new Runnable() {
@Override
public void run() {
phasedUnit.analyseTypes(Cancellable.ALWAYS_CANCELLED);
if (showWarnings(finalProject)) {
phasedUnit.analyseUsage();
}
}
});
stage = TYPE_ANALYSIS;
if (stager!=null) {
stager.afterStage(FOR_OUTLINE, monitor);
stager.afterStage(TYPE_ANALYSIS, monitor);
}
return phasedUnit;
}
}
}
if (isCanceling(monitor)) {
return null;
}
final String code = doc.get();
NewlineFixingStringStream stream =
new NewlineFixingStringStream(code);
CeylonLexer lexer = new CeylonLexer(stream);
CommonTokenStream tokenStream =
new CommonTokenStream(lexer);
tokenStream.fill();
tokens = tokenStream.getTokens();
stage = LEXICAL_ANALYSIS;
if (stager!=null) {
stager.afterStage(LEXICAL_ANALYSIS, monitor);
}
if (isCanceling(monitor)) {
return null;
}
CeylonParser parser = new CeylonParser(tokenStream);
Tree.CompilationUnit cu;
try {
cu = parser.compilationUnit();
}
catch (RecognitionException e) {
throw new RuntimeException(e);
}
//TODO: make the AST available now, so that
// services like FoldingUpdater can
// make use of it in the callback
rootNode = cu;
collectLexAndParseErrors(lexer, parser, cu);
stage = SYNTACTIC_ANALYSIS;
if (stager!=null) {
stager.afterStage(SYNTACTIC_ANALYSIS, monitor);
}
if (isCanceling(monitor)) {
return null;
}
FolderVirtualFile srcDir = null;
if (project!=null) {
srcDir = getSourceFolder(project, resolvedPath);
}
else if (path!=null) { //path==null in structured compare editor
srcDir = inferSrcDir(path);
project = findProject(path);
}
final IProject finalProject = project;
final boolean ceylonEnabled =
CeylonNature.isEnabled(finalProject);
if (!allClasspathContainersInitialized() ||
ceylonEnabled && !isModelTypeChecked(project)) {
// Ceylon projects have not been setup,
// so don't try to typecheck
//
// or
//
// TypeChecking has not been performed
// on the main model, so don't do it
// on the editor's tree
stage = FOR_OUTLINE;
if (stager!=null) {
stager.afterStage(FOR_OUTLINE, monitor);
}
return null;
}
final IPath finalPath = path;
final FolderVirtualFile finalSrcDir = srcDir;
try {
return doWithSourceModel(project, true,
waitForModelInSeconds,
new Callable<PhasedUnit>() {
@Override
public PhasedUnit call()
throws Exception {
if (ceylonEnabled) {
typeChecker =
getProjectTypeChecker(
finalProject);
}
boolean showWarnings =
showWarnings(finalProject);
if (isCanceling(monitor)) {
return null;
}
if (typeChecker==null) {
try {
typeChecker =
createTypeChecker(
finalProject,
showWarnings);
}
catch (CoreException e) {
return null;
}
}
if (isCanceling(monitor)) {
return null;
}
FileVirtualFile file =
createSourceCodeVirtualFile(
code, finalPath);
IdePhasedUnit builtPhasedUnit =
(IdePhasedUnit)
typeChecker.getPhasedUnit(file); // TODO : refactor !
phasedUnit =
typecheck(finalPath, file,
rootNode, finalSrcDir,
showWarnings,
builtPhasedUnit,
monitor);
rootNode =
phasedUnit.getCompilationUnit();
if (doc.get().equals(code)) {
dirty = false;
}
if (finalProject!=null
&& !ceylonEnabled) {
rootNode.visit(new Visitor() {
@Override
public void visitAny(Node node) {
super.visitAny(node);
for (Iterator<Message> i
= node.getErrors()
.iterator();
i.hasNext();) {
if (!(i.next()
instanceof RecognitionError)) {
i.remove();
}
}
}
});
}
stage = TYPE_ANALYSIS;
if (stager!=null) {
stager.afterStage(FOR_OUTLINE, monitor);
stager.afterStage(TYPE_ANALYSIS, monitor);
}
return phasedUnit;
}
});
}
catch (OperationCanceledException e) {
if (monitor!=null) {
// Sets the current monitor to canceled,
// so that the scheduler will reschedule
// it later
monitor.setCanceled(true);
}
// Consider that the previous steps of the
// anaysis are OK, and still notify the
// related model listeners
stage = FOR_OUTLINE;
if (stager!=null) {
stager.afterStage(FOR_OUTLINE, monitor);
}
return null;
}
}
private static BaseIdeModuleManager getModuleManager(
IProject project) {
return (BaseIdeModuleManager)
getProjectTypeChecker(project)
.getPhasedUnits()
.getModuleManager();
}
public IProject getProject() {
return project;
}
public IPath getPath() {
return filePath;
}
public IDocument getDocument() {
return document;
}
public void resetStage() {
stage = Stage.NONE;
}
@Override
public BaseCeylonProject getCeylonProject() {
return modelJ2C().ceylonModel()
.getProject(project);
}
@Override
public CommonDocument getCommonDocument() {
return new correctJ2C().newDocument(getDocument());
}
LocalAnalysisResult$impl localAnalysisResult$impl = new LocalAnalysisResult$impl(this);
AnalysisResult$impl analysisResult$impl = new AnalysisResult$impl(this);
@Override
public LocalAnalysisResult$impl $com$redhat$ceylon$ide$common$typechecker$LocalAnalysisResult$impl() {
return localAnalysisResult$impl;
}
@Override
public AnalysisResult$impl $com$redhat$ceylon$ide$common$typechecker$AnalysisResult$impl() {
return analysisResult$impl;
}
@Override
public boolean getUpToDate() {
return analysisResult$impl.getUpToDate();
}
}