/*******************************************************************************
* Copyright (c) 2012 - 2013 GoPivotal, 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:
* GoPivotal, Inc. - initial API and implementation
*******************************************************************************/
package org.springframework.ide.eclipse.config.ui.actions;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.eclipse.core.resources.IFile;
import org.eclipse.jface.action.Action;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.ITextSelection;
import org.eclipse.jface.text.Position;
import org.eclipse.jface.text.source.Annotation;
import org.eclipse.jface.text.source.IAnnotationModel;
import org.eclipse.jface.text.source.IAnnotationModelExtension;
import org.eclipse.jface.util.IPropertyChangeListener;
import org.eclipse.jface.util.PropertyChangeEvent;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.SelectionChangedEvent;
import org.eclipse.swt.custom.StyledText;
import org.eclipse.swt.events.KeyEvent;
import org.eclipse.swt.events.KeyListener;
import org.eclipse.swt.events.MouseEvent;
import org.eclipse.swt.events.MouseListener;
import org.eclipse.wst.sse.core.internal.provisional.IndexedRegion;
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.IDOMNode;
import org.springframework.ide.eclipse.beans.core.BeansCorePlugin;
import org.springframework.ide.eclipse.beans.core.internal.model.validation.BeansValidationContext;
import org.springframework.ide.eclipse.beans.core.model.IBeansConfig;
import org.springframework.ide.eclipse.beans.core.namespaces.ToolAnnotationUtils.ToolAnnotationData;
import org.springframework.ide.eclipse.beans.ui.editor.util.BeansEditorUtils;
import org.springframework.ide.eclipse.config.core.schemas.BeansSchemaConstants;
import org.springframework.ide.eclipse.config.ui.ConfigUiPlugin;
import org.springframework.ide.eclipse.config.ui.editors.AbstractConfigEditor;
import org.springframework.ide.eclipse.core.SpringCore;
import org.springframework.ide.eclipse.core.internal.model.SpringProject;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
/**
* This action toggles the mark occurrences for bean references.
* @author Terry Denney
* @author Christian Dupuis
* @author Leo Dos Santos
*/
@SuppressWarnings("restriction")
public class ToggleMarkOccurrencesAction extends Action implements IPropertyChangeListener, KeyListener, MouseListener {
public static class OccurrenceLocation {
private final int offset;
private final int length;
public OccurrenceLocation(int offset, int length) {
this.offset = offset;
this.length = length;
}
public int getLength() {
return length;
}
public int getOffset() {
return offset;
}
}
private final IPreferenceStore prefStore;
private AbstractConfigEditor editor;
private final Set<Annotation> annotations;
public static String STS_MARK_OCCURRENCES = "stsMarkOccurrences"; //$NON-NLS-1$
private final String MARK_PATH = "icons/mark_occurrences.gif"; //$NON-NLS-1$
private final String MARK_DISABLED_PATH = "icons/mark_occurrences_disabled.gif"; //$NON-NLS-1$
private static final String[] ATTRIBUTES_TO_CHECK = new String[] { BeansSchemaConstants.ATTR_ID,
BeansSchemaConstants.ATTR_NAME, BeansSchemaConstants.ATTR_PARENT, BeansSchemaConstants.ATTR_REF,
BeansSchemaConstants.ATTR_DEPENDS_ON, BeansSchemaConstants.ATTR_BEAN, BeansSchemaConstants.ATTR_LOCAL,
BeansSchemaConstants.ATTR_KEY_REF, BeansSchemaConstants.ATTR_VALUE_REF };
public ToggleMarkOccurrencesAction() {
super(Messages.getString("ToggleMarkOccurrencesAction.ACTION_LABEL"), Action.AS_CHECK_BOX); //$NON-NLS-1$
ImageDescriptor imageDesc = ConfigUiPlugin.imageDescriptorFromPlugin(ConfigUiPlugin.PLUGIN_ID, MARK_PATH);
ImageDescriptor disabledDesc = ConfigUiPlugin.imageDescriptorFromPlugin(ConfigUiPlugin.PLUGIN_ID,
MARK_DISABLED_PATH);
setImageDescriptor(imageDesc);
setDisabledImageDescriptor(disabledDesc);
annotations = new HashSet<Annotation>();
prefStore = ConfigUiPlugin.getDefault().getPreferenceStore();
prefStore.addPropertyChangeListener(this);
setChecked(prefStore.getBoolean(STS_MARK_OCCURRENCES));
}
private Set<OccurrenceLocation> findBeanReference(String beanName, BeansValidationContext context)
throws BadLocationException {
Set<OccurrenceLocation> result = new HashSet<OccurrenceLocation>();
IDOMDocument document = editor.getDomDocument();
if (document != null) {
NodeList nodes = document.getDocumentElement().getChildNodes();
for (int i = 0; i < nodes.getLength(); i++) {
result.addAll(findBeanReference(beanName, nodes.item(i), context));
}
}
return result;
}
private Set<OccurrenceLocation> findBeanReference(String beanName, Node node, BeansValidationContext context)
throws BadLocationException {
Set<OccurrenceLocation> result = new HashSet<OccurrenceLocation>();
NamedNodeMap attributes = node.getAttributes();
if (attributes != null) {
for (int i = 0; i < attributes.getLength(); i++) {
AttrImpl attribute = (AttrImpl) attributes.item(i);
result.addAll(findBeanReferences(beanName, attribute, node, context));
}
}
NodeList nodes = node.getChildNodes();
if (nodes != null && nodes.getLength() > 0) {
for (int i = 0; i < nodes.getLength(); i++) {
Node child = nodes.item(i);
if (child.getNodeType() == Node.ELEMENT_NODE) {
result.addAll(findBeanReference(beanName, child, context));
}
}
}
return result;
}
private Set<OccurrenceLocation> findBeanReferences(String beanName, AttrImpl attribute, Node node,
BeansValidationContext context) {
Set<OccurrenceLocation> result = new HashSet<OccurrenceLocation>();
String attrName = attribute.getName();
if (attrName != null) {
boolean found = false;
for (String attributeName : ATTRIBUTES_TO_CHECK) {
if (attributeName.equals(attrName)) {
found = true;
break;
}
}
if (context != null) {
for (ToolAnnotationData annotationData : context.getToolAnnotation(node, attribute.getLocalName())) {
if ("ref".equals(annotationData.getKind())) { //$NON-NLS-1$
found = true;
}
}
}
if (found) {
if (beanName.equals(attribute.getValue())) {
try {
int offset = attribute.getValueRegionStartOffset();
int length = beanName.length();
// check if the value starts with quote
if (editor.getTextViewer().getDocument().getChar(offset) == '"') {
offset++;
}
result.add(new OccurrenceLocation(offset, length));
}
catch (BadLocationException e) {
}
}
}
}
return result;
}
private Set<OccurrenceLocation> findLocations(IndexedRegion region, int offset) {
if (region instanceof IDOMNode) {
IDOMNode node = (IDOMNode) region;
IFile file = editor.getResourceFile();
BeansValidationContext context = null;
IBeansConfig config = BeansCorePlugin.getModel().getConfig(file);
if (config != null) {
context = new BeansValidationContext(config,
new SpringProject(SpringCore.getModel(), file.getProject()));
}
String beanName = getBeanName(node, offset, context);
if (beanName != null) {
try {
return findBeanReference(beanName, context);
}
catch (BadLocationException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
return new HashSet<OccurrenceLocation>();
}
private String getBeanName(IDOMNode node, int offset, BeansValidationContext context) {
NamedNodeMap attributes = node.getAttributes();
if (attributes != null) {
for (int i = 0; i < attributes.getLength(); i++) {
AttrImpl attribute = (AttrImpl) attributes.item(i);
if (attribute != null && attribute.getStartOffset() <= offset && attribute.getEndOffset() >= offset) {
if (context != null) {
List<ToolAnnotationData> toolAnnotations = context.getToolAnnotation(node,
attribute.getLocalName());
for (ToolAnnotationData toolAnnotation : toolAnnotations) {
if ("ref".equals(toolAnnotation.getKind())) { //$NON-NLS-1$
return attribute.getNodeValue();
}
}
}
for (String attributeName : ATTRIBUTES_TO_CHECK) {
if (attributeName.equals(attribute.getNodeName())) {
return attribute.getNodeValue();
}
}
}
}
}
return null;
}
public void keyPressed(KeyEvent e) {
}
public void keyReleased(KeyEvent e) {
selectionChanged();
}
public void mouseDoubleClick(MouseEvent e) {
}
public void mouseDown(MouseEvent e) {
selectionChanged();
}
public void mouseUp(MouseEvent e) {
}
public void propertyChange(PropertyChangeEvent event) {
String property = event.getProperty();
if (STS_MARK_OCCURRENCES.equals(property)) {
setChecked(prefStore.getBoolean(STS_MARK_OCCURRENCES));
}
}
@Override
public void run() {
prefStore.setValue(STS_MARK_OCCURRENCES, isChecked());
selectionChanged();
}
private void selectionChanged() {
if (editor != null) {
if (prefStore.getBoolean(STS_MARK_OCCURRENCES)) {
ISelection selection = editor.getTextViewer().getSelection();
if (selection instanceof ITextSelection) {
ITextSelection textSelection = (ITextSelection) selection;
int offset = textSelection.getOffset();
IndexedRegion node = BeansEditorUtils.getNodeAt(editor.getTextViewer(), offset);
Set<OccurrenceLocation> locations = findLocations(node, offset);
updateAnnotations(locations);
}
}
else if (annotations.size() > 0) {
updateAnnotations(new HashSet<OccurrenceLocation>());
annotations.clear();
}
}
}
public void selectionChanged(SelectionChangedEvent event) {
selectionChanged();
}
public void setActiveEditor(AbstractConfigEditor targetEditor) {
if (this.editor != null && this.editor.getTextViewer() != null
&& this.editor.getTextViewer().getTextWidget() != null) {
StyledText textWidget = this.editor.getTextViewer().getTextWidget();
textWidget.removeMouseListener(this);
textWidget.removeKeyListener(this);
updateAnnotations(new HashSet<OccurrenceLocation>());
}
this.editor = targetEditor;
setChecked(prefStore.getBoolean(STS_MARK_OCCURRENCES));
if (this.editor != null && this.editor.getTextViewer() != null
&& this.editor.getTextViewer().getTextWidget() != null) {
StyledText textWidget = this.editor.getTextViewer().getTextWidget();
textWidget.addMouseListener(this);
textWidget.addKeyListener(this);
}
selectionChanged();
}
private void updateAnnotations(Set<OccurrenceLocation> locations) {
IAnnotationModel annotationModel = editor.getSourcePage().getDocumentProvider()
.getAnnotationModel(editor.getEditorInput());
if (annotationModel == null) {
return;
}
Map<Annotation, Position> newAnnotations = new HashMap<Annotation, Position>();
for (OccurrenceLocation location : locations) {
Annotation annotation = new Annotation("org.eclipse.jdt.ui.occurrences", false, ""); //$NON-NLS-1$ //$NON-NLS-2$
Position position = new Position(location.getOffset(), location.getLength());
newAnnotations.put(annotation, position);
}
if (annotationModel instanceof IAnnotationModelExtension) {
((IAnnotationModelExtension) annotationModel).replaceAnnotations(
annotations.toArray(new Annotation[annotations.size()]), newAnnotations);
}
else {
for (Annotation annotation : annotations) {
annotationModel.removeAnnotation(annotation);
}
for (Annotation annotation : newAnnotations.keySet()) {
annotationModel.addAnnotation(annotation, newAnnotations.get(annotation));
}
}
annotations.clear();
annotations.addAll(newAnnotations.keySet());
}
}