/******************************************************************************* * This file is part of the Symfony eclipse plugin. * * (c) Robert Gruendler <r.gruendler@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. ******************************************************************************/ package com.dubture.symfony.core.codeassist; import java.util.List; import org.eclipse.core.resources.IFile; import org.eclipse.core.runtime.CoreException; import org.eclipse.dltk.ast.ASTNode; import org.eclipse.dltk.ast.declarations.ModuleDeclaration; import org.eclipse.dltk.codeassist.ScriptSelectionEngine; import org.eclipse.dltk.compiler.env.IModuleSource; import org.eclipse.dltk.core.IMethod; import org.eclipse.dltk.core.IModelElement; import org.eclipse.dltk.core.IScriptProject; import org.eclipse.dltk.core.ISourceModule; import org.eclipse.dltk.core.IType; import org.eclipse.dltk.core.SourceParserUtil; import org.eclipse.jface.text.BadLocationException; import org.eclipse.php.core.compiler.ast.nodes.ClassDeclaration; import org.eclipse.php.core.compiler.ast.nodes.NamespaceDeclaration; import org.eclipse.php.core.compiler.ast.nodes.PHPDocBlock; import org.eclipse.php.core.compiler.ast.nodes.PHPMethodDeclaration; import org.eclipse.php.core.compiler.ast.visitor.PHPASTVisitor; import org.eclipse.php.internal.core.documentModel.parser.PHPRegionContext; import org.eclipse.php.internal.core.documentModel.parser.regions.IPHPScriptRegion; import org.eclipse.wst.sse.core.StructuredModelManager; import org.eclipse.wst.sse.core.internal.provisional.IStructuredModel; import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocument; import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocumentRegion; import org.eclipse.wst.sse.core.internal.provisional.text.ITextRegion; import org.eclipse.wst.sse.core.internal.provisional.text.ITextRegionCollection; import org.eclipse.wst.sse.core.internal.provisional.text.ITextRegionContainer; import com.dubture.doctrine.annotation.model.Annotation; import com.dubture.doctrine.core.AnnotationParserUtil; import com.dubture.doctrine.core.compiler.IAnnotationModuleDeclaration; import com.dubture.symfony.core.log.Logger; import com.dubture.symfony.core.model.Service; import com.dubture.symfony.core.model.SymfonyModelAccess; import com.dubture.symfony.core.model.ViewPath; import com.dubture.symfony.core.util.text.SymfonyTextSequenceUtilities; import com.dubture.symfony.index.model.Route; /** * * * The {@link SymfonySelectionEngine} helps DLTK to identify symfony model * elements for actions like "Open Declaration" - F3 and Hyperlinking. * * TODO: I think a cleaner way to implement is to actually provide the model * elements as native DLTK model elements somehow, so DLTK knows what a * Route/Viewpath etc. is and how to resolve it. * * * @see http://wiki.eclipse.org/DLTK_IDE_Guide:Step_3._Towards_an_IDE# * Open_declaration_feature. * @author Robert Gruendler <r.gruendler@gmail.com> * */ @SuppressWarnings("restriction") public class SymfonySelectionEngine extends ScriptSelectionEngine { private static final IModelElement[] NONE = {}; @Override public IModelElement[] select(IModuleSource sourceUnit, final int offset, int end) { ISourceModule sourceModule = (ISourceModule) sourceUnit.getModelElement(); String content = sourceUnit.getSourceContents(); if (content.length() <= offset) { return NONE; } IStructuredDocument document = null; IStructuredModel structuredModel = null; try { IFile file = (IFile) sourceUnit.getModelElement().getResource(); if (file != null) { if (file.exists()) { structuredModel = StructuredModelManager.getModelManager().getExistingModelForRead(file); if (structuredModel != null) { document = structuredModel.getStructuredDocument(); } else { document = StructuredModelManager.getModelManager().createStructuredDocumentFor(file); } } else { document = StructuredModelManager.getModelManager().createNewStructuredDocumentFor(file); document.set(sourceUnit.getSourceContents()); } } } catch (Exception e) { Logger.logException(e); } finally { if (structuredModel != null) { structuredModel.releaseFromRead(); } } if (document == null) { return NONE; } IStructuredDocumentRegion sRegion = document.getRegionAtCharacterOffset(offset); if (sRegion == null) { return NONE; } ITextRegion tRegion = sRegion.getRegionAtCharacterOffset(offset); ITextRegionCollection container = sRegion; if (tRegion instanceof ITextRegionContainer) { container = (ITextRegionContainer) tRegion; tRegion = container.getRegionAtCharacterOffset(offset); } if (tRegion != null && tRegion.getType() == PHPRegionContext.PHP_CONTENT) { IPHPScriptRegion phpScriptRegion = (IPHPScriptRegion) tRegion; try { tRegion = phpScriptRegion.getPHPToken(offset - container.getStartOffset() - phpScriptRegion.getStart()); } catch (BadLocationException e) { } if (tRegion == null) { return NONE; } } int startOffset = SymfonyTextSequenceUtilities.readLiteralStartIndex(content, offset); int endOffset = SymfonyTextSequenceUtilities.readLiteralEndIndex(content, offset); SymfonyModelAccess model = SymfonyModelAccess.getDefault(); IScriptProject project = sourceModule.getScriptProject(); if (startOffset >= 0 && endOffset != 0 && (endOffset > startOffset)) { String literal = content.substring(startOffset, endOffset); // viewpaths are linked using ViewpathHyperlinkDetector // // try to resolve a viewepath first // ViewPath viewPath = new ViewPath(literal); // // if (viewPath.isValid()) { // // IModelElement template = model.findTemplate(viewPath, project); // // if (template != null) { // return new IModelElement[] { template }; // } // } // nope, not a viewpath, check for a route Route route = model.findRoute(literal, project); if (route != null) { IMethod method = model.findAction(route, project); if (method != null) return new IModelElement[] { method }; } // next search for a service Service service = model.findService(literal, project.getPath()); if (service != null) { IType serviceType = model.findServiceType(service, project); if (serviceType != null) return new IModelElement[] { serviceType }; } } try { ModuleDeclaration parsedUnit = SourceParserUtil.getModuleDeclaration(sourceModule, null); AnnotationPathVisitor visitor = new AnnotationPathVisitor(sourceModule, offset); parsedUnit.traverse(visitor); if (visitor.getTemplate() != null) { return new IModelElement[] { visitor.getTemplate() }; } } catch (Exception e) { e.printStackTrace(); } // couldn't find anything return NONE; } /** * * Parses @Template annotations in controllers to link to the corresponding * template. * * * @author Robert Gruendler <r.gruendler@gmail.com> * */ private static class AnnotationPathVisitor extends PHPASTVisitor { private static final String TEMPLATE_CLASS_NAME = "Template"; private IScriptProject project; private NamespaceDeclaration namespaceDeclaration; private ClassDeclaration classDeclaration; private IModelElement template = null; private int offset; private IAnnotationModuleDeclaration annotationModule = null; public AnnotationPathVisitor(ISourceModule sourceModule, int offset) { this.project = sourceModule.getScriptProject(); try { this.annotationModule = AnnotationParserUtil.getModule(sourceModule); } catch (CoreException e) { // TODO Auto-generated catch block e.printStackTrace(); } this.offset = offset; } public IModelElement getTemplate() { return template; } @Override public boolean visit(NamespaceDeclaration namespaceDeclaration) throws Exception { this.namespaceDeclaration = namespaceDeclaration; return true; } @Override public boolean visit(ClassDeclaration classDeclaration) throws Exception { this.classDeclaration = classDeclaration; return true; } @Override public boolean visit(PHPMethodDeclaration methodDeclaration) throws Exception { if (namespaceDeclaration == null || !namespaceDeclaration.getName().endsWith("\\Controller")) { return false; } PHPDocBlock phpDoc = methodDeclaration.getPHPDoc(); if (phpDoc == null || (phpDoc.sourceStart() > offset || phpDoc.sourceEnd() < offset)) { return false; } if (annotationModule == null) { return false; } // FIXME check real name List<Annotation> annotations = annotationModule.readAnnotations((ASTNode) methodDeclaration).findAnnotations(TEMPLATE_CLASS_NAME); if (annotations.size() < 1) { return false; } // We found at least of annotation with class Template, create a // view path String bundle = namespaceDeclaration.getName().replace("\\Controller", "").replace("\\", ""); String controller = classDeclaration.getName().replace("Controller", ""); String action = methodDeclaration.getName().replace("Action", ""); ViewPath path = new ViewPath(String.format("%s:%s:%s", bundle, controller, action)); if (!path.isValid()) { return false; } IModelElement[] templates = SymfonyModelAccess.getDefault().findTemplates(bundle, controller, project); this.template = null; for (IModelElement template : templates) { if (template.getElementName().startsWith(action)) { // We found a matching template, set template and return // true this.template = template; return false; } } return false; } } }