/******************************************************************************* * Copyright (c) 2012 VMWare, Inc. * 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: * VMWare, Inc. - initial API and implementation *******************************************************************************/ package org.grails.ide.eclipse.search; import java.io.IOException; import org.eclipse.core.resources.IFile; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IPath; import org.eclipse.jface.text.BadLocationException; import org.eclipse.jst.jsp.core.internal.domdocument.ElementImplForJSP; import org.eclipse.search.ui.text.Match; import org.eclipse.wst.sse.core.StructuredModelManager; import org.eclipse.wst.sse.core.internal.provisional.text.ITextRegion; import org.eclipse.wst.xml.core.internal.document.AttrImpl; import org.eclipse.wst.xml.core.internal.provisional.document.IDOMDocument; import org.grails.ide.eclipse.core.GrailsCoreActivator; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import org.grails.ide.eclipse.editor.gsp.model.GSPStructuredModel; /** * @author Kris De Volder * * @since 2.9 */ @SuppressWarnings("restriction") public abstract class GSPSearcher extends FileSearcher { protected abstract void visit(GLinkTag glink); /** * Helper class to represent and operate on GLink tags inside a .gsp file * @author Kris De Volder * * @since 2.9 */ protected class GLinkTag { private static final String CONTROLLER_ATTR_NAME = "controller"; private static final String ACTION_ATTR_NAME = "action"; public ElementImplForJSP node; //TODO: shouldn't be public. Instead provide some nicer abstract methods to work with the node. private IFile file; // The GSP file that contains this tag public GLinkTag(IFile file, Node item) { this.file = file; this.node = (ElementImplForJSP) item; } public String getActionName() { return getAttribute(ACTION_ATTR_NAME); } public String getControllerName() { String name = getAttribute(CONTROLLER_ATTR_NAME); if (name==null) { IPath path = file.getProjectRelativePath(); //Expected path: "grails-app/views/<controller-name>/<view-name>.gsp if (path.segmentCount()==4) { if (path.segment(0).equals("grails-app")) { if (path.segment(1).equals("views")) { name = path.segment(2); } } } } return name; } /** * Creates a search match object representing the text range where the action name is shown. * May return null if the action name can not be found verbatim in the g:link action attribute * attribute. */ public Match createActionMatch(String expectedValue) { return createAttributeMatch(ACTION_ATTR_NAME, expectedValue); } /** * Creates a search match object representing the text range where the controller name is shown. * May return null if the name can not be found verbatim in the g:link controller attribute. * This may happen, for example, if the controller attribute is not explicitly specified in the g:link * (so the controller is taken from the context). */ public Match createControllerMatch(String expectedValue) { return createAttributeMatch(CONTROLLER_ATTR_NAME, expectedValue); } /** * Creates a match that indicates the region of text corresponding to a specific attribute value. * <p> * This may return null if the expectedValue text can not be found verbatim in the node attribute * text. This may happen if the attribuet's value was derived somehow instead of explicitly present * in the node. */ private Match createAttributeMatch(String nodeAttributeName, String expectedValue) { Match match = null; try { AttrImpl attr = (AttrImpl) node.getAttributeNode(nodeAttributeName); if (attr!=null) { ITextRegion valueRegion = attr.getValueRegion(); if (valueRegion!=null) { int start = attr.getValueRegionStartOffset(); //IMPORTANT: don't use valueRegion.getStart because that is wrong! (not relative to document!) String docValueText = node.getStructuredDocument().get(start, valueRegion.getLength()); int relOffset = docValueText.indexOf(expectedValue); if (relOffset>=0) { match = createMatch(start+relOffset, expectedValue.length()); } } } } catch (BadLocationException e) { GrailsCoreActivator.log(e); } return match; } private String getAttribute(String name) { String value = node.getAttribute(name); if (value.equals("")) { return null; } return value; } @Override public String toString() { return node.toString(); } } public GSPSearcher(IFile gspFile) { super(gspFile); } private Match createMatch(int offset, int length) { return new Match(file, offset, length); } /** * Compute changes needed to replaces all references to a given action (identified by a controller and action name) inside a given gsp file. * The changes are added to the given TextChange object. */ public void perform() throws IOException, CoreException { GSPStructuredModel model = (GSPStructuredModel) StructuredModelManager.getModelManager().getModelForRead(file); try { IDOMDocument document = model.getDocument(); NodeList links = document.getElementsByTagName("g:link"); for (int i = 0; i < links.getLength(); i++) { GSPSearcher.GLinkTag link = new GSPSearcher.GLinkTag(file, links.item(i)); visit(link); } } finally { model.releaseFromRead(); } } }