/*******************************************************************************
* 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.springframework.ide.eclipse.internal.bestpractices.quickfix;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IMarker;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.jface.text.contentassist.ICompletionProposal;
import org.eclipse.ui.IEditorPart;
import org.eclipse.ui.IMarkerResolution;
import org.eclipse.ui.IMarkerResolutionGenerator2;
import org.eclipse.wst.sse.core.StructuredModelManager;
import org.eclipse.wst.xml.core.internal.document.AttrImpl;
import org.eclipse.wst.xml.core.internal.provisional.document.IDOMDocument;
import org.eclipse.wst.xml.core.internal.provisional.document.IDOMModel;
import org.eclipse.wst.xml.core.internal.provisional.document.IDOMNode;
import org.springframework.ide.eclipse.beans.ui.editor.contentassist.bean.FactoryMethodContentAssistCalculator;
import org.springframework.ide.eclipse.beans.ui.editor.contentassist.bean.InitDestroyMethodContentAssistCalculator;
import org.springframework.ide.eclipse.config.core.schemas.BeansSchemaConstants;
import org.springframework.ide.eclipse.quickfix.processors.BeanReferenceQuickAssistProcessor;
import org.springframework.ide.eclipse.quickfix.processors.ClassAttributeQuickAssistProcessor;
import org.springframework.ide.eclipse.quickfix.processors.ConstructorArgQuickAssistProcessor;
import org.springframework.ide.eclipse.quickfix.processors.MethodAttributeQuickAssistProcessor;
import org.springframework.ide.eclipse.quickfix.processors.PropertyAttributeQuickAssistProcessor;
import org.springframework.ide.eclipse.quickfix.processors.RequiredPropertyQuickAssistProcessor;
import org.springsource.ide.eclipse.commons.core.StatusHandler;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
/**
* Produces an IMarkerResolution instance for a given marker, if applicable.
* This Generator currently supports only one resolution per rule.
* @author Wesley Coelho
* @author Christian Dupuis
* @author Leo Dos Santos
* @author Terry Denney
*/
public class MarkerResolutionGenerator implements IMarkerResolutionGenerator2 {
private class NodeInfo {
private final IDOMNode node;
private final int offset;
private final int length;
private NodeInfo(IDOMNode node, int offset, int length) {
this.node = node;
this.offset = offset;
this.length = length;
}
}
private static final String ERROR_ID_CLASS_NOT_FOUND = "CLASS_NOT_FOUND";
private static final String ERROR_ID_UNDEFINED_REFERENCED_BEAN = "UNDEFINED_REFERENCED_BEAN";
private static final String ERROR_ID_NO_SETTER = "NO_SETTER";
private static final String ERROR_ID_NO_GETTER = "NO_GETTER";
private static final String ERROR_ID_KEY = "errorId";
private static final String ERROR_ID_UNDEFINED_INIT = "UNDEFINED_INIT_METHOD";
private static final String ERROR_ID_UNDEFINED_DESTROY = "UNDEFINED_DESTROY_METHOD";
private static final String ERROR_ID_REQUIRED_PROPERTY_MISSING = "REQUIRED_PROPERTY_MISSING";
private static final String ERROR_ID_NO_CONSTRUCTOR = "NO_CONSTRUCTOR";
private static final String ERROR_ID_UNDEFINED_FACTORY_BEAN_METHOD = "UNDEFINED_FACTORY_BEAN_METHOD";
private IDOMNode findMatchedBean(NodeList nodes, String beanName) {
if (nodes == null) {
return null;
}
for (int i = 0; i < nodes.getLength(); i++) {
Node item = nodes.item(i);
if (BeansSchemaConstants.ELEM_BEAN.equals(item.getNodeName())) {
if (item.getAttributes() != null) {
AttrImpl attr = (AttrImpl) item.getAttributes().getNamedItem(BeansSchemaConstants.ATTR_NAME);
if (attr != null) {
if (beanName.equals(attr.getNodeValue())) {
return (IDOMNode) item;
}
}
attr = (AttrImpl) item.getAttributes().getNamedItem(BeansSchemaConstants.ATTR_ID);
if (attr != null) {
if (beanName.equals(attr.getNodeValue())) {
return (IDOMNode) item;
}
}
}
}
IDOMNode childNode = findMatchedBean(item.getChildNodes(), beanName);
if (childNode != null) {
return childNode;
}
}
return null;
}
private NodeInfo findNodeInfo(IDOMNode node, String attrName, String attrValue) {
if (node != null) {
NamedNodeMap attributes = node.getAttributes();
if (attributes != null) {
for (int i = 0; i < attributes.getLength(); i++) {
AttrImpl attribute = (AttrImpl) attributes.item(i);
if (attrName == null || attrName.equals(attribute.getNodeName())) {
if (attrValue != null && attrValue.equals(attribute.getNodeValue())) {
int offset = attribute.getValueRegionStartOffset();
// increase offset if value starts with
// "
if (attribute.getValueRegion().getLength() > attrValue.length()) {
offset++;
}
int length = attribute.getNodeValue() == null ? 0 : attribute.getNodeValue().length();
return new NodeInfo(node, offset, length);
}
}
}
}
NodeList children = node.getChildNodes();
for (int i = 0; i < children.getLength(); i++) {
NodeInfo info = findNodeInfo((IDOMNode) children.item(i), attrName, attrValue);
if (info != null) {
return info;
}
}
}
return null;
}
protected NodeInfo findNodeInfo(IMarker marker, String attrName, String attrValue) {
IDOMModel model = null;
try {
String beanName = (String) marker.getAttribute("BEAN_NAME");
if (beanName == null) {
return null;
}
IFile file = (IFile) marker.getResource();
model = (IDOMModel) StructuredModelManager.getModelManager().getModelForRead(file);
if (model != null) {
IDOMDocument document = model.getDocument();
if (document != null) {
NodeList nodes = document.getChildNodes();
IDOMNode node = findMatchedBean(nodes, beanName);
return findNodeInfo(node, attrName, attrValue);
}
}
}
catch (CoreException e) {
}
catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
finally {
if (model != null) {
model.releaseFromRead();
}
}
return null;
}
/**
* Return the resolution(s) for the given marker. Currently supports only
* one resolution per marker.
*/
public IMarkerResolution[] getResolutions(IMarker marker) {
ICompletionProposal[] proposals = null;
// Do not offer resolutions if the editor is dirty because the marker
// position may be incorrect
IEditorPart editor = XmlQuickFixUtil.getMarkedEditor(marker);
if (editor != null && editor.isDirty()) {
return new IMarkerResolution[0];
}
try {
String errorId = (String) marker.getAttribute(ERROR_ID_KEY);
String className = marker.getAttribute("CLASS", null);
IFile file = marker.getResource() instanceof IFile ? (IFile) marker.getResource() : null;
if (ERROR_ID_NO_CONSTRUCTOR.equals(errorId)) {
int numArgument = marker.getAttribute("NUM_ARGUMENT", 0);
if (className != null) {
NodeInfo nodeInfo = findNodeInfo(marker, BeansSchemaConstants.ATTR_CLASS, className);
if (nodeInfo != null && file != null) {
List<String> argClassNames = new ArrayList<String>();
for (int i = 0; i < numArgument; i++) {
// TODO: narrow this down to a type
argClassNames.add("Object");
}
proposals = new ConstructorArgQuickAssistProcessor(nodeInfo.offset, nodeInfo.length, className,
file.getProject(), false, argClassNames, nodeInfo.node)
.computeQuickAssistProposals(null);
}
}
}
else if (ERROR_ID_REQUIRED_PROPERTY_MISSING.equals(errorId)) {
List<String> requiredProperties = new ArrayList<String>();
String requiredProperty = null;
int counter = 1;
do {
requiredProperty = marker.getAttribute("MISSING_PROPERTIES" + counter, null);
if (requiredProperty != null) {
requiredProperties.add(requiredProperty);
}
counter++;
} while (requiredProperty != null);
if (requiredProperties.size() > 0 && className != null) {
NodeInfo nodeInfo = findNodeInfo(marker, BeansSchemaConstants.ATTR_CLASS, className);
if (nodeInfo != null) {
proposals = new RequiredPropertyQuickAssistProcessor(nodeInfo.offset, nodeInfo.length,
className, false, requiredProperties, nodeInfo.node).computeQuickAssistProposals(null);
}
}
}
else if (ERROR_ID_UNDEFINED_DESTROY.equals(errorId)) {
String methodName = marker.getAttribute("METHOD", null);
if (methodName != null && className != null) {
NodeInfo nodeInfo = findNodeInfo(marker, BeansSchemaConstants.ATTR_DESTROY_METHOD, methodName);
if (nodeInfo != null && file != null) {
proposals = new MethodAttributeQuickAssistProcessor(nodeInfo.offset, nodeInfo.length,
className, methodName, true, nodeInfo.node, BeansSchemaConstants.ATTR_DESTROY_METHOD,
file.getProject(), false, new InitDestroyMethodContentAssistCalculator(), file)
.computeQuickAssistProposals(null);
}
}
}
else if (ERROR_ID_UNDEFINED_INIT.equals(errorId)) {
String methodName = marker.getAttribute("METHOD", null);
if (methodName != null && className != null) {
NodeInfo nodeInfo = findNodeInfo(marker, BeansSchemaConstants.ATTR_INIT_METHOD, methodName);
if (nodeInfo != null && file != null) {
proposals = new MethodAttributeQuickAssistProcessor(nodeInfo.offset, nodeInfo.length,
className, methodName, true, nodeInfo.node, BeansSchemaConstants.ATTR_INIT_METHOD,
file.getProject(), false, new InitDestroyMethodContentAssistCalculator(), file)
.computeQuickAssistProposals(null);
}
}
}
else if (ERROR_ID_UNDEFINED_FACTORY_BEAN_METHOD.equals(errorId)) {
String methodName = marker.getAttribute("METHOD", null);
if (methodName != null && className != null) {
NodeInfo nodeInfo = findNodeInfo(marker, BeansSchemaConstants.ATTR_FACTORY_METHOD, methodName);
if (nodeInfo != null && file != null) {
proposals = new MethodAttributeQuickAssistProcessor(nodeInfo.offset, nodeInfo.length,
className, methodName, true, nodeInfo.node, BeansSchemaConstants.ATTR_FACTORY_METHOD,
file.getProject(), false, new FactoryMethodContentAssistCalculator(), file)
.computeQuickAssistProposals(null);
}
}
}
else if (ERROR_ID_NO_SETTER.equals(errorId)) {
String propertyName = marker.getAttribute("PROPERTY", null);
if (className != null && propertyName != null && file != null) {
NodeInfo nodeInfo = findNodeInfo(marker, BeansSchemaConstants.ATTR_NAME, propertyName);
if (nodeInfo != null) {
proposals = new PropertyAttributeQuickAssistProcessor(nodeInfo.offset, nodeInfo.length,
className, propertyName, file.getProject(), false,
PropertyAttributeQuickAssistProcessor.Type.SETTER).computeQuickAssistProposals(null);
}
}
}
else if (ERROR_ID_NO_GETTER.equals(errorId)) {
String propertyName = marker.getAttribute("PROPERTY", null);
if (className != null && propertyName != null && file != null) {
NodeInfo nodeInfo = findNodeInfo(marker, BeansSchemaConstants.ATTR_NAME, propertyName);
if (nodeInfo != null) {
proposals = new PropertyAttributeQuickAssistProcessor(nodeInfo.offset, nodeInfo.length,
className, propertyName, file.getProject(), false,
PropertyAttributeQuickAssistProcessor.Type.GETTER).computeQuickAssistProposals(null);
}
}
}
else if (ERROR_ID_CLASS_NOT_FOUND.equals(errorId)) {
if (className != null) {
NodeInfo nodeInfo = findNodeInfo(marker, BeansSchemaConstants.ATTR_CLASS, className);
if (nodeInfo != null && file != null) {
proposals = new ClassAttributeQuickAssistProcessor(nodeInfo.offset, nodeInfo.length, className,
file.getProject(), false, new HashSet<String>(), 0).computeQuickAssistProposals(null);
}
}
}
else if (ERROR_ID_UNDEFINED_REFERENCED_BEAN.equals(errorId)) {
String beanName = marker.getAttribute("BEAN", null);
if (beanName != null) {
NodeInfo nodeInfo = findNodeInfo(marker, BeansSchemaConstants.ATTR_REF, beanName);
if (nodeInfo == null) {
nodeInfo = findNodeInfo(marker, BeansSchemaConstants.ATTR_PARENT, beanName);
}
if (nodeInfo == null) {
nodeInfo = findNodeInfo(marker, BeansSchemaConstants.ATTR_FACTORY_BEAN, beanName);
}
if (nodeInfo != null && file != null) {
proposals = new BeanReferenceQuickAssistProcessor(nodeInfo.offset, nodeInfo.length, beanName,
false, nodeInfo.node, BeansSchemaConstants.ATTR_REF, beanName, file)
.computeQuickAssistProposals(null);
}
}
}
}
catch (CoreException e) {
StatusHandler.log(e.getStatus());
}
if (proposals != null) {
List<IMarkerResolution> resolutions = new ArrayList<IMarkerResolution>();
for (ICompletionProposal proposal : proposals) {
if (proposal instanceof IMarkerResolution) {
resolutions.add((IMarkerResolution) proposal);
}
}
return resolutions.toArray(new IMarkerResolution[resolutions.size()]);
}
return new IMarkerResolution[0];
}
public boolean hasResolutions(IMarker marker) {
String errorId = marker.getAttribute(ERROR_ID_KEY, null);
String className = marker.getAttribute("CLASS", null);
if (errorId == null) {
return false;
}
if (ERROR_ID_NO_CONSTRUCTOR.equals(errorId)) {
return className != null;
}
else if (ERROR_ID_REQUIRED_PROPERTY_MISSING.equals(errorId)) {
return true;
}
else if (ERROR_ID_UNDEFINED_DESTROY.equals(errorId)) {
String methodName = marker.getAttribute("METHOD", null);
return methodName != null && className != null;
}
else if (ERROR_ID_UNDEFINED_INIT.equals(errorId)) {
String methodName = marker.getAttribute("METHOD", null);
return methodName != null && className != null;
}
else if (ERROR_ID_UNDEFINED_FACTORY_BEAN_METHOD.equals(errorId)) {
String methodName = marker.getAttribute("METHOD", null);
return methodName != null && className != null;
}
else if (ERROR_ID_NO_SETTER.equals(errorId)) {
String propertyName = marker.getAttribute("PROPERTY", null);
return propertyName != null && className != null;
}
else if (ERROR_ID_NO_GETTER.equals(errorId)) {
String propertyName = marker.getAttribute("PROPERTY", null);
return propertyName != null && className != null;
}
else if (ERROR_ID_CLASS_NOT_FOUND.equals(errorId)) {
return className != null;
}
else if (ERROR_ID_UNDEFINED_REFERENCED_BEAN.equals(errorId)) {
String beanName = marker.getAttribute("BEAN", null);
return beanName != null;
}
return false;
}
}