blob: 761f1154920c805265b6d2e08ba2829a44d7a839 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2000, 2015 IBM Corporation and others.
* 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:
* IBM Corporation - initial API and implementation
* Terry Parker <tparker@google.com> (Google Inc.) - Bug 458852 - Speed up JDT text searches by supporting parallelism in its TextSearchRequestors
*******************************************************************************/
package org.eclipse.jdt.internal.ui.propertiesfileeditor;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.regex.Pattern;
import com.ibm.icu.text.Collator;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.core.runtime.Assert;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IAdaptable;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.PlatformObject;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.SubProgressMonitor;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IStorage;
import org.eclipse.core.filebuffers.FileBuffers;
import org.eclipse.core.filebuffers.ITextFileBuffer;
import org.eclipse.core.filebuffers.ITextFileBufferManager;
import org.eclipse.core.filebuffers.LocationKind;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.operation.IRunnableWithProgress;
import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.jface.viewers.ILabelProvider;
import org.eclipse.jface.window.Window;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IRegion;
import org.eclipse.jface.text.hyperlink.IHyperlink;
import org.eclipse.ui.IEditorPart;
import org.eclipse.ui.IFileEditorInput;
import org.eclipse.ui.IStorageEditorInput;
import org.eclipse.ui.PartInitException;
import org.eclipse.ui.dialogs.TwoPaneElementSelector;
import org.eclipse.ui.model.IWorkbenchAdapter;
import org.eclipse.ui.model.WorkbenchLabelProvider;
import org.eclipse.ui.texteditor.IEditorStatusLine;
import org.eclipse.ui.texteditor.ITextEditor;
import org.eclipse.search.core.text.TextSearchEngine;
import org.eclipse.search.core.text.TextSearchMatchAccess;
import org.eclipse.search.core.text.TextSearchRequestor;
import org.eclipse.search.core.text.TextSearchScope;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.core.search.IJavaSearchConstants;
import org.eclipse.jdt.core.search.SearchEngine;
import org.eclipse.jdt.core.search.SearchMatch;
import org.eclipse.jdt.core.search.SearchPattern;
import org.eclipse.jdt.core.search.SearchRequestor;
import org.eclipse.jdt.internal.corext.util.Messages;
import org.eclipse.jdt.internal.corext.util.SearchUtils;
import org.eclipse.jdt.ui.JavaElementLabels;
import org.eclipse.jdt.ui.JavaUI;
import org.eclipse.jdt.internal.ui.JavaPlugin;
import org.eclipse.jdt.internal.ui.javaeditor.EditorUtility;
import org.eclipse.jdt.internal.ui.util.ExceptionHandler;
import org.eclipse.jdt.internal.ui.util.PatternConstructor;
import org.eclipse.jdt.internal.ui.viewsupport.BasicElementLabels;
import org.eclipse.jdt.internal.ui.viewsupport.JavaElementImageProvider;
/**
* Properties key hyperlink.
*
* @since 3.1
*/
public class PropertyKeyHyperlink implements IHyperlink {
private static class KeyReference extends PlatformObject implements IWorkbenchAdapter, Comparable<KeyReference> {
private static final Collator fgCollator= Collator.getInstance();
private IResource resource;
private IJavaElement element;
private int offset;
private int length;
private final boolean showLineNumber;
private KeyReference(IResource resource, IJavaElement element, int offset, int length, boolean showLineNumber) {
Assert.isNotNull(resource);
this.resource= resource;
this.offset= offset;
this.length= length;
this.element= element;
this.showLineNumber= showLineNumber;
}
@SuppressWarnings("unchecked")
@Override
public <T> T getAdapter(Class<T> adapter) {
if (adapter == IWorkbenchAdapter.class)
return (T) this;
else
return super.getAdapter(adapter);
}
/*
* @see org.eclipse.ui.model.IWorkbenchAdapter#getChildren(java.lang.Object)
*/
public Object[] getChildren(Object o) {
return null;
}
/*
* @see org.eclipse.ui.model.IWorkbenchAdapter#getImageDescriptor(java.lang.Object)
*/
public ImageDescriptor getImageDescriptor(Object object) {
IWorkbenchAdapter wbAdapter= resource.getAdapter(IWorkbenchAdapter.class);
if (wbAdapter != null)
return wbAdapter.getImageDescriptor(resource);
return null;
}
/*
* @see org.eclipse.ui.model.IWorkbenchAdapter#getLabel(java.lang.Object)
*/
public String getLabel(Object o) {
ITextFileBufferManager manager= FileBuffers.getTextFileBufferManager();
try {
manager.connect(resource.getFullPath(), LocationKind.NORMALIZE, null);
try {
ITextFileBuffer buffer= manager.getTextFileBuffer(resource.getFullPath(), LocationKind.NORMALIZE);
IDocument document= buffer.getDocument();
if (document != null) {
int line= document.getLineOfOffset(offset) + 1;
String pathLabel= BasicElementLabels.getPathLabel(resource.getFullPath(), false);
Object[] args= new Object[] { new Integer(line), pathLabel };
return showLineNumber ? Messages.format(PropertiesFileEditorMessages.OpenAction_SelectionDialog_elementLabel, args) : pathLabel;
}
} finally {
manager.disconnect(resource.getFullPath(), LocationKind.NORMALIZE, null);
}
} catch (CoreException e) {
JavaPlugin.log(e.getStatus());
} catch (BadLocationException e) {
JavaPlugin.log(e);
}
return BasicElementLabels.getPathLabel(resource.getFullPath(), false);
}
/*
* @see org.eclipse.ui.model.IWorkbenchAdapter#getParent(java.lang.Object)
*/
public Object getParent(Object o) {
return null;
}
public int compareTo(KeyReference otherRef) {
String thisPath= resource.getFullPath().toString();
String otherPath= otherRef.resource.getFullPath().toString();
int result= fgCollator.compare(thisPath, otherPath);
if (result != 0)
return result;
else
return offset - otherRef.offset;
}
}
private static class ResultCollector extends TextSearchRequestor {
private final List<KeyReference> fResult;
private final boolean fIsKeyDoubleQuoted;
public ResultCollector(List<KeyReference> result, boolean isKeyDoubleQuoted) {
fResult= result;
fIsKeyDoubleQuoted= isKeyDoubleQuoted;
}
@Override
public boolean canRunInParallel() {
return true;
}
@Override
public boolean acceptPatternMatch(TextSearchMatchAccess matchAccess) throws CoreException {
int start= matchAccess.getMatchOffset();
int length= matchAccess.getMatchLength();
if (fIsKeyDoubleQuoted) {
start= start + 1;
length= length - 2;
}
synchronized(fResult) {
fResult.add(new KeyReference(matchAccess.getFile(), null, start, length, true));
}
return true;
}
}
private IRegion fRegion;
private String fPropertiesKey;
private Shell fShell;
private IStorage fStorage;
private ITextEditor fEditor;
private boolean fIsFileEditorInput;
/**
* Creates a new properties key hyperlink.
*
* @param region the region
* @param key the properties key
* @param editor the text editor
*/
public PropertyKeyHyperlink(IRegion region, String key, ITextEditor editor) {
Assert.isNotNull(region);
Assert.isNotNull(key);
Assert.isNotNull(editor);
fRegion= region;
fPropertiesKey= key;
fEditor= editor;
fIsFileEditorInput= fEditor.getEditorInput() instanceof IFileEditorInput;
IStorageEditorInput storageEditorInput= (IStorageEditorInput)fEditor.getEditorInput();
fShell= fEditor.getEditorSite().getShell();
try {
fStorage= storageEditorInput.getStorage();
} catch (CoreException e) {
fStorage= null;
}
}
/*
* @see org.eclipse.jdt.internal.ui.javaeditor.IHyperlink#getHyperlinkRegion()
*/
public IRegion getHyperlinkRegion() {
return fRegion;
}
/*
* @see org.eclipse.jdt.internal.ui.javaeditor.IHyperlink#open()
*/
public void open() {
// Search the key
KeyReference[] references= search(fPropertiesKey);
if (references == null)
return; // canceled by the user
if (references.length == 0) {
String message= PropertiesFileEditorMessages.OpenAction_error_messageNoResult;
showErrorInStatusLine(message);
return;
}
open(references);
}
private void open(KeyReference[] keyReferences) {
Assert.isLegal(keyReferences != null && keyReferences.length > 0);
if (keyReferences.length == 1)
open(keyReferences[0]);
else
open(select(keyReferences));
}
/**
* Opens a dialog which allows to select a key reference.
*
* @param keyReferences the array of key references
* @return the selected key reference or <code>null</code> if canceled by the user
*/
private KeyReference select(final KeyReference[] keyReferences) {
Arrays.sort(keyReferences);
final int length= keyReferences.length;
ILabelProvider labelProvider= new WorkbenchLabelProvider() {
@Override
public String decorateText(String input, Object element) {
KeyReference keyRef= (KeyReference)element;
IJavaElement javaElement= keyRef.element;
IResource resource= keyRef.resource;
String name= javaElement != null ? JavaElementLabels.getElementLabel(javaElement, JavaElementLabels.ALL_DEFAULT | JavaElementLabels.ALL_FULLY_QUALIFIED
| JavaElementLabels.USE_RESOLVED | JavaElementLabels.P_COMPRESSED) : resource.getName();
if (name == null)
return input;
int count= 0;
for (int i= 0; i < length; i++) {
if (javaElement != null) {
if (keyReferences[i].element.equals(javaElement))
count++;
} else {
if (keyReferences[i].resource.equals(resource))
count++;
}
}
if (count > 1) {
Object[] args= new Object[] { BasicElementLabels.getResourceName(name), new Integer(count) };
name= Messages.format(PropertiesFileEditorMessages.OpenAction_SelectionDialog_elementLabelWithMatchCount, args);
}
return name;
}
@Override
protected ImageDescriptor decorateImage(ImageDescriptor input, Object element) {
KeyReference keyRef= (KeyReference)element;
IJavaElement javaElement= keyRef.element;
return javaElement != null ? new JavaElementImageProvider().getJavaImageDescriptor(javaElement, JavaElementImageProvider.SMALL_ICONS) : super.decorateImage(input, element);
}
};
TwoPaneElementSelector dialog= new TwoPaneElementSelector(fShell, labelProvider, new WorkbenchLabelProvider());
dialog.setLowerListLabel(PropertiesFileEditorMessages.OpenAction_SelectionDialog_details);
dialog.setLowerListComparator(new Comparator<Object>() {
public int compare(Object o1, Object o2) {
return 0; // don't sort
}
});
dialog.setTitle(PropertiesFileEditorMessages.OpenAction_SelectionDialog_title);
dialog.setMessage(PropertiesFileEditorMessages.OpenAction_SelectionDialog_message);
dialog.setElements(keyReferences);
if (dialog.open() == Window.OK) {
Object[] result= dialog.getResult();
if (result != null && result.length == 1)
return (KeyReference)result[0];
}
return null;
}
private void open(KeyReference keyReference) {
if (keyReference == null)
return;
try {
IEditorPart part= keyReference.element != null ? EditorUtility.openInEditor(keyReference.element, true) : EditorUtility.openInEditor(keyReference.resource, true);
if (part != null)
EditorUtility.revealInEditor(part, keyReference.offset, keyReference.length);
} catch (PartInitException x) {
String message= null;
IWorkbenchAdapter wbAdapter= ((IAdaptable)keyReference).getAdapter(IWorkbenchAdapter.class);
if (wbAdapter != null)
message= Messages.format(PropertiesFileEditorMessages.OpenAction_error_messageArgs,
new String[] { wbAdapter.getLabel(keyReference), x.getLocalizedMessage() } );
if (message == null)
message= Messages.format(PropertiesFileEditorMessages.OpenAction_error_message, x.getLocalizedMessage());
MessageDialog.openError(fShell,
PropertiesFileEditorMessages.OpenAction_error_messageProblems,
message);
}
}
private String getErrorDialogTitle() {
return PropertiesFileEditorMessages.OpenAction_error_title;
}
private void showError(CoreException e) {
ExceptionHandler.handle(e, fShell, getErrorDialogTitle(), PropertiesFileEditorMessages.OpenAction_error_message);
}
private void showErrorInStatusLine(final String message) {
fShell.getDisplay().beep();
final IEditorStatusLine statusLine= fEditor.getAdapter(IEditorStatusLine.class);
if (statusLine != null) {
fShell.getDisplay().asyncExec(new Runnable() {
/*
* @see java.lang.Runnable#run()
*/
public void run() {
statusLine.setMessage(true, message, null);
}
});
}
}
/**
* Returns whether we search the key in double-quotes or not.
* <p>
* XXX: This is a hack to improve the accuracy of matches, see https://bugs.eclipse.org/bugs/show_bug.cgi?id=81140
* </p>
*
* @return <code>true</code> if we search for double-quoted key
*/
private boolean useDoubleQuotedKey() {
if (fStorage == null)
return false;
String name= fStorage.getName();
return name != null && !"about.properties".equals(name) && !"feature.properties".equals(name) && !"plugin.properties".equals(name); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
}
/**
* Searches references to the given key in the given scope.
*
* @param key the properties key
* @return the references or <code>null</code> if the search has been canceled by the user
*/
private KeyReference[] search(final String key) {
if (key == null)
return new KeyReference[0];
final List<KeyReference> result= new ArrayList<KeyReference>(5);
try {
fEditor.getEditorSite().getWorkbenchWindow().getWorkbench().getProgressService().busyCursorWhile(
new IRunnableWithProgress() {
public void run(IProgressMonitor monitor) throws InvocationTargetException, InterruptedException {
if (monitor == null)
monitor= new NullProgressMonitor();
monitor.beginTask("", 5); //$NON-NLS-1$
try {
// XXX: This is a hack to improve the accuracy of matches, see https://bugs.eclipse.org/bugs/show_bug.cgi?id=81140
boolean useDoubleQuotedKey= useDoubleQuotedKey();
if (useDoubleQuotedKey) {
SearchPattern pattern= SearchPattern.createPattern(key, IJavaSearchConstants.FIELD, IJavaSearchConstants.REFERENCES, SearchPattern.R_PATTERN_MATCH
| SearchPattern.R_CASE_SENSITIVE);
if (pattern == null)
return;
try {
new SearchEngine().search(pattern, SearchUtils.getDefaultSearchParticipants(), SearchEngine.createWorkspaceScope(), new SearchRequestor() {
@Override
public void acceptSearchMatch(SearchMatch match) throws CoreException {
IResource resource= match.getResource();
if (resource != null)
result.add(new KeyReference(resource, (IJavaElement) match.getElement(), match.getOffset(), match.getLength(), fIsFileEditorInput));
}
}, new SubProgressMonitor(monitor, 1));
} catch (CoreException e) {
throw new InvocationTargetException(e);
}
}
if (result.size() == 0) {
//maybe not an eclipse style NLS string
String searchString;
if (useDoubleQuotedKey) {
StringBuffer buf= new StringBuffer("\""); //$NON-NLS-1$
buf.append(key);
buf.append('"');
searchString= buf.toString();
} else
searchString= key;
ResultCollector collector= new ResultCollector(result, useDoubleQuotedKey);
TextSearchEngine engine= TextSearchEngine.create();
Pattern searchPattern= PatternConstructor.createPattern(searchString, true, false);
/* <p>
* XXX: This does not work for properties files coming from a JAR.
* For details see https://bugs.eclipse.org/bugs/show_bug.cgi?id=23341
* </p>
*/
if (fStorage instanceof IResource) {
engine.search(createScope(((IResource)fStorage).getProject()), collector, searchPattern, new SubProgressMonitor(monitor, 4));
}
} else {
monitor.worked(1);
}
} finally {
monitor.done();
}
}
}
);
} catch (InvocationTargetException ex) {
String message= PropertiesFileEditorMessages.OpenAction_error_messageErrorSearchingKey;
showError(new CoreException(new Status(IStatus.ERROR, JavaUI.ID_PLUGIN, IStatus.OK, message, ex.getTargetException())));
} catch (InterruptedException ex) {
return null; // canceled
}
return result.toArray(new KeyReference[result.size()]);
}
private static TextSearchScope createScope(IResource scope) {
ArrayList<String> fileNamePatternStrings= new ArrayList<String>();
// XXX: Should be configurable via preference, see https://bugs.eclipse.org/bugs/show_bug.cgi?id=81117
String[] javaExtensions= JavaCore.getJavaLikeExtensions();
for (int i= 0; i < javaExtensions.length; i++)
fileNamePatternStrings.add("*." + javaExtensions[i]); //$NON-NLS-1$
fileNamePatternStrings.add("*.xml"); //$NON-NLS-1$
fileNamePatternStrings.add("*.ini"); //$NON-NLS-1$
String[] allPatternStrings= fileNamePatternStrings.toArray(new String[fileNamePatternStrings.size()]);
Pattern fileNamePattern= PatternConstructor.createPattern(allPatternStrings, false, false);
return TextSearchScope.newSearchScope(new IResource[] { scope }, fileNamePattern, false);
}
/*
* @see org.eclipse.jdt.internal.ui.javaeditor.IHyperlink#getTypeLabel()
*/
public String getTypeLabel() {
return null;
}
/*
* @see org.eclipse.jdt.internal.ui.javaeditor.IHyperlink#getHyperlinkText()
*/
public String getHyperlinkText() {
return Messages.format(PropertiesFileEditorMessages.OpenAction_hyperlinkText, fPropertiesKey);
}
}