/*******************************************************************************
* Copyright (c) 2012 Pivotal Software, 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:
* Pivotal Software, Inc. - initial API and implementation
*******************************************************************************/
package org.grails.ide.eclipse.editor.gsp.controllers;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.Path;
import org.eclipse.jst.jsp.core.internal.domdocument.AttrImplForJSP;
import org.eclipse.jst.jsp.core.internal.domdocument.ElementImplForJSP;
import org.eclipse.wst.sse.core.StructuredModelManager;
import org.eclipse.wst.sse.core.internal.provisional.IStructuredModel;
import org.eclipse.wst.sse.core.internal.provisional.IndexedRegion;
import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocument;
import org.eclipse.wst.sse.core.internal.provisional.text.ITextRegion;
import org.grails.ide.eclipse.core.internal.plugins.GrailsCore;
import org.grails.ide.eclipse.editor.groovy.controllers.ActionTarget;
import org.grails.ide.eclipse.editor.groovy.controllers.ControllerTarget;
import org.grails.ide.eclipse.editor.groovy.controllers.ITarget;
import org.grails.ide.eclipse.editor.groovy.controllers.PerProjectControllerCache;
/**
*
* @author Andrew Eisenberg
* @created Jul 14, 2011
*/
public class TargetFinder {
private final static String NO_PREFIX = "";
String prefix = NO_PREFIX;
private final boolean usePrefix;
public TargetFinder(boolean usePrefix) {
this.usePrefix = usePrefix;
}
/**
* Finds controller and action targets in the given document at the
* specified location. If usePrefix is specified, then the prefix
* is calculated
* @param document
* @param offset
* @param usePrefix
* @return
*/
public List<ITarget> findTargets(IStructuredDocument document, int offset) {
IStructuredModel model = StructuredModelManager.getModelManager().getExistingModelForRead(document);
try {
if (model != null) {
IndexedRegion indexedRegion = model.getIndexedRegion(offset);
if (indexedRegion instanceof ElementImplForJSP) {
ElementImplForJSP element = (ElementImplForJSP) indexedRegion;
if (element.getNodeName().equals("g:link")) {
// first check to see if content assist inside of controller attribute
// else grab the controller name
AttrImplForJSP controllerAttr = (AttrImplForJSP) element.getAttributeNode("controller");
int nodeRelativeOffset = offset - indexedRegion.getStartOffset();
String controllerName = null;
if (isPositionInsideRegion(controllerAttr, nodeRelativeOffset)) {
prefix = usePrefix ? findPrefix(controllerAttr, nodeRelativeOffset) : controllerAttr.getValue();
return findControllers(prefix, offset, model);
} else if (controllerAttr != null) {
controllerName = controllerAttr.getValue();
} else {
// try to get the controller name from the base location
controllerName = extractControllerName(model);
}
// check to see if content assist in action attribute
if (controllerName != null) {
// can't do anything unless there is already a controller name
AttrImplForJSP actionAttr = (AttrImplForJSP) element.getAttributeNode("action");
if (isPositionInsideRegion(actionAttr, nodeRelativeOffset)) {
// content assist inside of action region
prefix = usePrefix ? findPrefix(actionAttr, nodeRelativeOffset) : actionAttr.getValue();
return findActions(controllerName, prefix, offset, model);
}
}
}
}
}
} finally {
if (model != null) {
model.releaseFromRead();
}
}
return Collections.emptyList();
}
/**
* Guess the controller name from the base location
* @param model
* @return
*/
private String extractControllerName(IStructuredModel model) {
String baseLocation = model.getBaseLocation();
if (baseLocation != null) {
String[] segments = baseLocation.split("/");
if (segments.length > 2) {
return segments[segments.length-2];
}
}
return null;
}
private List<ITarget> findControllers(String prefix,
int invocationOffset, IStructuredModel model) {
IProject project = findProject(model);
if (project != null) {
PerProjectControllerCache controllerCache = GrailsCore.get().connect(project, PerProjectControllerCache.class);
if (controllerCache != null) {
Set<ControllerTarget> controllerTargets = controllerCache.getAllControllerTargets();
if (controllerTargets != null) {
List<ITarget> completions = new ArrayList<ITarget>(controllerTargets.size());
for (ControllerTarget controllerTarget : controllerTargets) {
if (controllerTarget.getName().startsWith(prefix)) {
completions.add(controllerTarget);
}
}
return completions;
}
}
}
return Collections.emptyList();
}
private List<ITarget> findActions(String controllerName, String prefix,
int invocationOffset, IStructuredModel model) {
IProject project = findProject(model);
if (project != null) {
PerProjectControllerCache controllerCache = GrailsCore.get().connect(project, PerProjectControllerCache.class);
if (controllerCache != null) {
Set<ActionTarget> actions = controllerCache.getActionsForController(controllerName);
if (actions != null) {
List<ITarget> completions = new ArrayList<ITarget>(actions.size());
for (ActionTarget action : actions) {
if (action.getName().startsWith(prefix)) {
completions.add(action);
}
}
return completions;
}
}
}
return Collections.emptyList();
}
private IProject findProject(IStructuredModel model) {
if (model.getBaseLocation() == null) {
return null;
}
IPath p = new Path(model.getBaseLocation());
if (p.segmentCount() == 0) {
return null;
}
IProject project = ResourcesPlugin.getWorkspace().getRoot().getProject(p.segment(0));
if (!project.isAccessible()) {
return null;
}
return project;
}
private String findPrefix(AttrImplForJSP attr, int nodeRelativeOffset) {
int start = attr.getValueRegion().getStart();
String fullText = attr.getValueSource();
return fullText.substring(0, nodeRelativeOffset-start-1);
}
private boolean isPositionInsideRegion(AttrImplForJSP attr, int nodeRelativeOffset) {
if (attr == null) {
return false;
}
ITextRegion valueRegion = attr.getValueRegion();
if (valueRegion == null) {
return false;
}
return nodeRelativeOffset > valueRegion.getStart() && nodeRelativeOffset < valueRegion.getEnd();
}
}