/** * <copyright> * </copyright> * * */ package org.emftext.language.java.resource.java.ui; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Map; import org.eclipse.emf.ecore.EObject; import org.eclipse.emf.ecore.EStructuralFeature.Setting; import org.eclipse.emf.ecore.util.EcoreUtil; import org.eclipse.jface.text.BadLocationException; import org.eclipse.jface.text.IDocument; import org.eclipse.jface.text.ITextViewerExtension5; import org.eclipse.jface.text.Position; import org.eclipse.jface.text.Region; import org.eclipse.jface.text.rules.IToken; import org.eclipse.jface.text.source.Annotation; import org.eclipse.jface.text.source.IAnnotationModel; import org.eclipse.jface.text.source.ISourceViewer; import org.eclipse.jface.text.source.projection.ProjectionViewer; import org.eclipse.swt.custom.StyledText; /** * This class finds text positions to highlight and adds them to the document. */ public class JavaOccurrence { private static interface ITokenScannerConstraint { public boolean mustStop(org.emftext.language.java.resource.java.ui.IJavaTokenScanner tokenScanner); } public final static String OCCURRENCE_ANNOTATION_ID = "org.emftext.language.java.resource.java.ui.occurences"; public final static String DECLARATION_ANNOTATION_ID = "org.emftext.language.java.resource.java.ui.occurences.declaration"; private final static org.emftext.language.java.resource.java.ui.JavaPositionHelper positionHelper = new org.emftext.language.java.resource.java.ui.JavaPositionHelper(); /** * The viewer showing the document the we search occurrences for */ private ProjectionViewer projectionViewer; /** * The resource we operate on */ private org.emftext.language.java.resource.java.IJavaTextResource textResource; /** * The text of the token that was located at the caret position at the time * occurrence were computed last */ private String tokenText = ""; /** * The region of the token that was located at the caret position at the time * occurrence were computed last */ private Region tokenRegion; /** * <p> * Creates a JavaOccurrence object to find positions to highlight. * </p> * * @param textResource the text resource for location * @param projectionViewer the viewer for the text */ public JavaOccurrence(org.emftext.language.java.resource.java.IJavaTextResource textResource, ProjectionViewer projectionViewer) { super(); this.textResource = textResource; this.projectionViewer = projectionViewer; resetTokenRegion(); } protected EObject getResolvedEObject(EObject eObject) { return eObject.eIsProxy() ? EcoreUtil.resolve(eObject, textResource) : eObject; } /** * <p> * Tries to resolve the first proxy object in the given list. * </p> * * @param objects the <code>EObject</code>s located at the caret position * * @return the resolved <code>EObject</code> of the first proxy * <code>EObject</code> in a list. If resolving fails, <code>null</code> is * returned. */ public EObject tryToResolve(List<EObject> objects) { for (EObject object : objects) { if (object.eIsProxy()) { return getResolvedEObject(object); } } return null; } /** * Returns the EObject at the current caret position. */ public EObject getEObjectAtCurrentPosition() { if (textResource == null) { return null; } int caretOffset = getCaretOffset(); org.emftext.language.java.resource.java.IJavaLocationMap locationMap = textResource.getLocationMap(); List<EObject> elementsAtOffset = locationMap.getElementsAt(caretOffset); if (elementsAtOffset == null || elementsAtOffset.isEmpty()) { return null; } for (EObject candidate : elementsAtOffset) { if (candidate.eIsProxy()) { candidate = getResolvedEObject(candidate); } // Only accept elements that are actually contained in a resource. The location // map might reference elements that were removed by a post processor and which // are therefore not part of the resource anymore. if (candidate.eResource() != null) { return candidate; } } return null; } /** * <p> * Returns the text of the token that was found at the caret position at the time * occurrence we computed last. * </p> * * @return the token text */ protected String getTokenText() { return tokenText; } protected int getLength(org.emftext.language.java.resource.java.IJavaLocationMap locationMap, EObject eObject) { return locationMap.getCharEnd(eObject) - locationMap.getCharStart(eObject) + 1; } /** * Finds the positions of the occurrences and declarations which will be * highlighted. */ public void updateOccurrenceAnnotations() { if (textResource == null) { return; } final int caretOffset = getCaretOffset(); IDocument document = getSourceViewer().getDocument(); if (caretOffset < 0 || caretOffset >= document.getLength()) { // The caret is outside of the document. removeAnnotations(); return; } if (isContainedIn(tokenRegion, caretOffset)) { // The caret is still contained in the same token region. No need to update // occurrence annotations. return; } resetTokenRegion(); org.emftext.language.java.resource.java.IJavaLocationMap locationMap = textResource.getLocationMap(); List<EObject> elementsAtOffset = locationMap.getElementsAt(caretOffset); if (elementsAtOffset == null || elementsAtOffset.size() < 1) { // The document does not contain EObjects. Probably there is a syntax error. removeAnnotations(); return; } removeAnnotations(); EObject resolvedEObject = tryToResolve(elementsAtOffset); EObject referencedElement; EObject firstElementAtOffset = elementsAtOffset.get(0); if (resolvedEObject != null) { referencedElement = resolvedEObject; } else { referencedElement = firstElementAtOffset; } // Scan the region in which the referenced object is located. org.emftext.language.java.resource.java.ui.IJavaTokenScanner tokenScanner = scan(referencedElement, new ITokenScannerConstraint() { public boolean mustStop(org.emftext.language.java.resource.java.ui.IJavaTokenScanner tokenScanner) { int tokenOffset = tokenScanner.getTokenOffset(); int tokenLength = tokenScanner.getTokenLength(); // check whether the caret in this token return isContainedIn(tokenOffset, tokenLength, caretOffset); } }); if (tokenScanner != null) { // caret is located in referenced element removeAnnotations(); int tokenOffset = tokenScanner.getTokenOffset(); int tokenLength = tokenScanner.getTokenLength(); tokenText = tokenScanner.getTokenText(); tokenRegion = new Region(tokenOffset, tokenLength); } // The tokenScanner must always be not null if there was no proxy at the caret // position, but to prevent JDT from complaining about a potential null pointer // access, we check both conditions here. if (resolvedEObject == null && tokenScanner != null) { // caret is within definition int tokenOffset = tokenScanner.getTokenOffset(); // we pass null as 'definitionText' because we do not know whether the token at // the caret is actually the defining name addAnnotations(referencedElement, null, tokenOffset, caretOffset); } else { // caret is within reference int proxyOffset = locationMap.getCharStart(firstElementAtOffset); int proxyLength = getLength(locationMap, firstElementAtOffset); try { String proxyText = document.get(proxyOffset, proxyLength); int index = getIndexOf(referencedElement, proxyText); if (index >= 0) { addAnnotations(referencedElement, proxyText, index, caretOffset); } } catch (BadLocationException e) { // ignore } } } protected boolean isContainedIn(Region region, int offset) { int regionOffset = region.getOffset(); int regionEnd = regionOffset + region.getLength(); return offset >= regionOffset && offset <= regionEnd; } protected boolean isContainedIn(int regionOffset, int regionLength, int offset) { int regionEnd = regionOffset + regionLength; return regionOffset <= offset && offset < regionEnd; } protected void addAnnotations(EObject referencedElement, String definitionText, int definitionOffset, int caretOffset) { List<String> matchingNames = addAnnotationsForDefinition(referencedElement, definitionText, definitionOffset, caretOffset); addAnnotationsForReferences(referencedElement, matchingNames); } protected void addAnnotationsForReferences(EObject referencedElement, List<String> matchingNames) { IDocument document = getSourceViewer().getDocument(); // Determine all references to the EObject Map<EObject, Collection<Setting>> map = EcoreUtil.UsageCrossReferencer.find(Collections.singleton(textResource)); Collection<Setting> referencingObjects = map.get(referencedElement); if (referencingObjects == null) { // No references found return; } // Highlight the token in the text for the referencing objects for (Setting setting : referencingObjects) { EObject referencingElement = setting.getEObject(); // Search through all tokens in the elements that reference the element at the // caret position for (String name : matchingNames) { int index = getIndexOf(referencingElement, name); if (index > 0) { addAnnotation(document, org.emftext.language.java.resource.java.ui.JavaPositionCategory.PROXY, name, index, name.length()); } } } } protected List<String> addAnnotationsForDefinition(EObject referencedElement, String definitionText, int definitionOffset, final int caretOffset) { final IDocument document = getSourceViewer().getDocument(); final List<String> matchingNames = new ArrayList<String>(); if (definitionText == null) { // The object at the caret position is not referenced from within the resource. // Thus, we cannot highlight occurrences or declarations. final List<String> names = new org.emftext.language.java.resource.java.analysis.JavaDefaultNameProvider().getNames(referencedElement); scan(referencedElement, new ITokenScannerConstraint() { public boolean mustStop(org.emftext.language.java.resource.java.ui.IJavaTokenScanner tokenScanner) { int offset = tokenScanner.getTokenOffset(); int length = tokenScanner.getTokenLength(); String text = tokenScanner.getTokenText(); if (names.contains(text) && isContainedIn(offset, length, caretOffset)) { matchingNames.add(text); addAnnotation(document, org.emftext.language.java.resource.java.ui.JavaPositionCategory.DEFINITION, text, offset, text.length()); } return false; } }); } else { // Highlight the token in the text for the referenced object addAnnotation(document, org.emftext.language.java.resource.java.ui.JavaPositionCategory.DEFINITION, definitionText, definitionOffset, definitionText.length()); matchingNames.add(definitionText); } return matchingNames; } /** * Returns the index of the given text within the text that corresponds to the * EObject. */ protected int getIndexOf(EObject eObject, final String text) { org.emftext.language.java.resource.java.ui.IJavaTokenScanner tokenScanner = scan(eObject, new ITokenScannerConstraint() { public boolean mustStop(org.emftext.language.java.resource.java.ui.IJavaTokenScanner tokenScanner) { String tokenText = tokenScanner.getTokenText(); return tokenText.equals(text); } }); if (tokenScanner == null) { return -1; } else { return tokenScanner.getTokenOffset(); } } protected org.emftext.language.java.resource.java.ui.IJavaTokenScanner scan(EObject object, ITokenScannerConstraint constraint) { IDocument document = getSourceViewer().getDocument(); org.emftext.language.java.resource.java.IJavaLocationMap locationMap = textResource.getLocationMap(); org.emftext.language.java.resource.java.ui.IJavaTokenScanner tokenScanner = createTokenScanner(); int offset = locationMap.getCharStart(object); int length = getLength(locationMap, object); tokenScanner.setRange(document, offset, length); IToken token = tokenScanner.nextToken(); while (!token.isEOF()) { if (constraint.mustStop(tokenScanner)) { return tokenScanner; } token = tokenScanner.nextToken(); } return null; } protected org.emftext.language.java.resource.java.ui.IJavaTokenScanner createTokenScanner() { return new org.emftext.language.java.resource.java.ui.JavaUIMetaInformation().createTokenScanner(null, null); } protected void addAnnotation(IDocument document, org.emftext.language.java.resource.java.ui.JavaPositionCategory type, String text, int offset, int length) { // for declarations and occurrences we do not need to add the position to the // document Position position = positionHelper.createPosition(offset, length); // instead, an annotation is created Annotation annotation = new Annotation(false); if (type == org.emftext.language.java.resource.java.ui.JavaPositionCategory.DEFINITION) { annotation.setText("Declaration of " + text); annotation.setType(DECLARATION_ANNOTATION_ID); } else { annotation.setText("Occurrence of " + text); annotation.setType(OCCURRENCE_ANNOTATION_ID); } getSourceViewer().getAnnotationModel().addAnnotation(annotation, position); } protected void removeAnnotations() { removeAnnotations(org.emftext.language.java.resource.java.ui.JavaOccurrence.OCCURRENCE_ANNOTATION_ID); removeAnnotations(org.emftext.language.java.resource.java.ui.JavaOccurrence.DECLARATION_ANNOTATION_ID); } protected void removeAnnotations(String annotationTypeID) { List<Annotation> annotationsToRemove = new ArrayList<Annotation>(); IAnnotationModel annotationModel = getSourceViewer().getAnnotationModel(); Iterator<?> annotationIterator = annotationModel.getAnnotationIterator(); while (annotationIterator.hasNext()) { Object object = (Object) annotationIterator.next(); if (object instanceof Annotation) { Annotation annotation = (Annotation) object; if (annotationTypeID.equals(annotation.getType())) { annotationsToRemove.add(annotation); } } } for (Annotation annotation : annotationsToRemove) { annotationModel.removeAnnotation(annotation); } } /** * Resets the token region to enable remove highlighting if the text is changing. */ public void resetTokenRegion() { tokenRegion = new Region(-1, 0); } protected int getCaretOffset() { StyledText textWidget = getSourceViewer().getTextWidget(); if (textWidget == null) { return -1; } int widgetOffset = textWidget.getCaretOffset(); return getTextViewerExtension5().widgetOffset2ModelOffset(widgetOffset); } /** * Accessor method for the field <code>projectionViewer</code>. The accessor is * also used for unit testing to inject a custom source viewer by overriding this * method. */ protected ISourceViewer getSourceViewer() { return projectionViewer; } /** * Accessor method for the field <code>projectionViewer</code>. The accessor is * also used for unit testing to inject a custom text viewer extension by * overriding this method. */ protected ITextViewerExtension5 getTextViewerExtension5() { return projectionViewer; } }