package com.redhat.ceylon.eclipse.util; import static com.redhat.ceylon.eclipse.core.builder.CeylonBuilder.getModelLoader; import static com.redhat.ceylon.eclipse.core.builder.CeylonBuilder.getProjectTypeChecker; import static com.redhat.ceylon.eclipse.core.builder.CeylonBuilder.getTypeCheckers; import static com.redhat.ceylon.eclipse.util.EditorUtil.getCurrentEditor; import static com.redhat.ceylon.eclipse.util.InteropUtils.toJavaString; import java.lang.ref.SoftReference; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IFolder; import org.eclipse.core.resources.IProject; import org.eclipse.ui.IEditorPart; import com.redhat.ceylon.compiler.typechecker.TypeChecker; import com.redhat.ceylon.compiler.typechecker.tree.Tree; import com.redhat.ceylon.eclipse.code.editor.CeylonEditor; import com.redhat.ceylon.eclipse.code.parse.CeylonParseController; import com.redhat.ceylon.ide.common.model.BaseIdeModelLoader; import com.redhat.ceylon.ide.common.model.CeylonBinaryUnit; import com.redhat.ceylon.ide.common.model.ExternalSourceFile; import com.redhat.ceylon.ide.common.model.IProjectAware; import com.redhat.ceylon.ide.common.model.IResourceAware; import com.redhat.ceylon.model.typechecker.model.Declaration; import com.redhat.ceylon.model.typechecker.model.Module; import com.redhat.ceylon.model.typechecker.model.Package; import com.redhat.ceylon.model.typechecker.model.Unit; public class ModelProxy { private final String qualifiedName; private final String unitName; private final String packageName; private final String moduleName; private final String moduleVersion; private final String name; private final IProject project; private SoftReference<Declaration> declaration; private Declaration toModelDeclaration(Declaration declaration) { Unit unit = declaration.getUnit(); if (unit instanceof ExternalSourceFile) { ExternalSourceFile externalSourceFile = (ExternalSourceFile) unit; if (externalSourceFile.getBinaryDeclarationSource()) { Declaration binaryDeclaration = externalSourceFile.retrieveBinaryDeclaration(declaration); if (binaryDeclaration != null) { return binaryDeclaration; } } } return declaration; } public ModelProxy(Declaration declaration) { declaration = toModelDeclaration(declaration); Unit unit = declaration.getUnit(); this.name = declaration.getName(); this.qualifiedName = declaration.getQualifiedNameString(); //TODO: persist the signature somehow, to handle overloads this.unitName = unit.getFilename(); Package pack = unit.getPackage(); this.packageName = pack.getNameAsString(); this.moduleName = pack.getModule().getNameAsString(); this.moduleVersion = pack.getModule().getVersion(); this.declaration = new SoftReference<Declaration>(declaration); if (unit instanceof IResourceAware) { project = ((IResourceAware<IProject, IFolder, IFile>) unit).getResourceProject(); // TODO In case of a cross project dependency (ICrossProjectReference-derived classes), // it will return the original project containing the source declaration. // Is is intentional ? In this case I wonder whether we should'nt also add a reference to // the original source declaration in the original source unit of original project : // which means search the equivalent declaration in ((ICrossProjectReference)unit).getOriginalSourceFile(). // I wonder whether it's not better to keep per-project ModelProxys. } else if (unit instanceof IProjectAware) { project = ((IProjectAware<IProject>) unit).getProject(); } else { project = null; } } public Declaration get() { Declaration dec = declaration.get(); if (dec!=null) return dec; //first handle the case of new declarations //defined in a dirty editor, and local declarations //in an external source file IEditorPart editor = getCurrentEditor(); if (editor instanceof CeylonEditor /*&& part.isDirty()*/) { CeylonParseController controller = ((CeylonEditor) editor).getParseController(); Tree.CompilationUnit rootNode = controller.getLastCompilationUnit(); if (rootNode!=null) { Unit unit = rootNode.getUnit(); if (unit!=null) { Package p = unit.getPackage(); String pname = p.getNameAsString(); String mname = p.getModule().getNameAsString(); String mversion = p.getModule().getVersion(); if (pname.equals(packageName) && mname.equals(moduleName) && mversion.equals(moduleVersion)) { Declaration result = getDeclarationInUnit(qualifiedName, unit); if (result!=null) { result = toModelDeclaration(result); declaration = new SoftReference<Declaration>(result); return result; } } } } } TypeChecker typeChecker = getTypeChecker(moduleName, moduleVersion, project); if (typeChecker!=null) { Package pack = getModelLoader(typeChecker) .getLoadedModule(moduleName, moduleVersion) .getPackage(packageName); boolean searchForCeylonSourceFileUnit = unitName.endsWith(".ceylon"); for (Unit unit: pack.getUnits()) { boolean foundTheUnit = false; if (unit.getFilename().equals(unitName)) { foundTheUnit = true; } else if (searchForCeylonSourceFileUnit && unit instanceof CeylonBinaryUnit) { // This is only to accommodate for cases when // the source file name of a binary unit has // been stored in the 'fileName' field of the // proxy (in the serialized ModelProxy objects // of the history for example) String ceylonSourceFileName = toJavaString(((CeylonBinaryUnit) unit).getCeylonFileName()); if (ceylonSourceFileName != null) { if (ceylonSourceFileName.equals(unitName)) { foundTheUnit = true; } } } if (foundTheUnit) { Declaration result = getDeclarationInUnit(qualifiedName, unit); if (result!=null) { declaration = new SoftReference<Declaration>(result); return result; } } } } return null; } public String getName() { return name; } public String getQualifiedName() { return qualifiedName; } @Override public boolean equals(Object obj) { if (this==obj) { return true; } else if (obj==null) { return false; } else if (obj instanceof ModelProxy) { ModelProxy proxy = (ModelProxy) obj; return qualifiedName.equals(proxy.qualifiedName) && moduleName.equals(proxy.moduleName) && moduleVersion.equals(proxy.moduleVersion); } else { return false; } } @Override public int hashCode() { return qualifiedName.hashCode(); } @Override public String toString() { return qualifiedName; } private static TypeChecker getTypeChecker(String moduleName, String moduleVersion, /*optional*/ IProject project) { if (project==null) { for (TypeChecker typeChecker: getTypeCheckers()) { BaseIdeModelLoader modelLoader = getModelLoader(typeChecker); Module module = modelLoader.getLoadedModule(moduleName, moduleVersion); if (module!=null) { return typeChecker; } } return null; } else { return getProjectTypeChecker(project); } } public static Declaration getDeclarationInUnit(String qualifiedName, Unit unit) { for (Declaration d: unit.getDeclarations()) { String qn = d.getQualifiedNameString(); if (qn.equals(qualifiedName)) { return d; } else if (qualifiedName.startsWith(qn)) { //TODO: I have to do this because of the // shortcut refinement syntax, but // I guess that's really a bug in // the typechecker! for (Declaration m: d.getMembers()) { if (m.getQualifiedNameString().equals(qualifiedName)) { return m; } } } } return null; } }