blob: b12ab02025fa06f4c37a62501101453eea999ebe [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2000, 2008 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
*******************************************************************************/
package org.eclipse.wst.jsdt.internal.ui.propertiesfileeditor;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.regex.Pattern;
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.core.resources.IResource;
import org.eclipse.core.resources.IStorage;
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.jface.dialogs.ErrorDialog;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.operation.IRunnableWithProgress;
import org.eclipse.jface.resource.ImageDescriptor;
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.jface.viewers.ILabelProvider;
import org.eclipse.jface.window.Window;
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.swt.widgets.Shell;
import org.eclipse.ui.IEditorPart;
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.wst.jsdt.core.IJavaScriptElement;
import org.eclipse.wst.jsdt.core.JavaScriptCore;
import org.eclipse.wst.jsdt.core.JavaScriptModelException;
import org.eclipse.wst.jsdt.core.search.IJavaScriptSearchConstants;
import org.eclipse.wst.jsdt.core.search.IJavaScriptSearchScope;
import org.eclipse.wst.jsdt.core.search.SearchEngine;
import org.eclipse.wst.jsdt.core.search.SearchMatch;
import org.eclipse.wst.jsdt.core.search.SearchPattern;
import org.eclipse.wst.jsdt.core.search.SearchRequestor;
import org.eclipse.wst.jsdt.internal.corext.util.Messages;
import org.eclipse.wst.jsdt.internal.corext.util.SearchUtils;
import org.eclipse.wst.jsdt.internal.ui.IJavaStatusConstants;
import org.eclipse.wst.jsdt.internal.ui.JavaScriptPlugin;
import org.eclipse.wst.jsdt.internal.ui.javaeditor.EditorUtility;
import org.eclipse.wst.jsdt.internal.ui.util.ExceptionHandler;
import org.eclipse.wst.jsdt.internal.ui.util.PatternConstructor;
import org.eclipse.wst.jsdt.ui.JavaScriptUI;
import com.ibm.icu.text.Collator;
/**
* Properties key hyperlink.
* <p>
* XXX: This does not work for properties files coming from a JAR due to
* missing J Core functionality. For details see:
* https://bugs.eclipse.org/bugs/show_bug.cgi?id=22376
* </p>
*
* @since 3.1
*/
public class PropertyKeyHyperlink implements IHyperlink {
private static class KeyReference extends PlatformObject implements IWorkbenchAdapter, Comparable {
private static final Collator fgCollator= Collator.getInstance();
private IStorage storage;
private int offset;
private int length;
private KeyReference(IStorage storage, int offset, int length) {
Assert.isNotNull(storage);
this.storage= storage;
this.offset= offset;
this.length= length;
}
/*
* @see org.eclipse.core.runtime.IAdaptable#getAdapter(java.lang.Class)
*/
public Object getAdapter(Class adapter) {
if (adapter == IWorkbenchAdapter.class)
return 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= (IWorkbenchAdapter)storage.getAdapter(IWorkbenchAdapter.class);
if (wbAdapter != null)
return wbAdapter.getImageDescriptor(storage);
return null;
}
/*
* @see org.eclipse.ui.model.IWorkbenchAdapter#getLabel(java.lang.Object)
*/
public String getLabel(Object o) {
ITextFileBufferManager manager= FileBuffers.getTextFileBufferManager();
try {
manager.connect(storage.getFullPath(), LocationKind.NORMALIZE, null);
try {
ITextFileBuffer buffer= manager.getTextFileBuffer(storage.getFullPath(), LocationKind.NORMALIZE);
IDocument document= buffer.getDocument();
if (document != null) {
int line= document.getLineOfOffset(offset) + 1;
Object[] args= new Object[] { new Integer(line), storage.getFullPath() };
return Messages.format(PropertiesFileEditorMessages.OpenAction_SelectionDialog_elementLabel, args);
}
} finally {
manager.disconnect(storage.getFullPath(), LocationKind.NORMALIZE, null);
}
} catch (CoreException e) {
JavaScriptPlugin.log(e.getStatus());
} catch (BadLocationException e) {
JavaScriptPlugin.log(e);
}
return storage.getFullPath().toString();
}
/*
* @see org.eclipse.ui.model.IWorkbenchAdapter#getParent(java.lang.Object)
*/
public Object getParent(Object o) {
return null;
}
public int compareTo(Object o) {
KeyReference otherRef= (KeyReference)o;
String thisPath= storage.getFullPath().toString();
String otherPath= otherRef.storage.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 List fResult;
private boolean fIsKeyDoubleQuoted;
public ResultCollector(List result, boolean isKeyDoubleQuoted) {
fResult= result;
fIsKeyDoubleQuoted= isKeyDoubleQuoted;
}
public boolean acceptPatternMatch(TextSearchMatchAccess matchAccess) throws CoreException {
int start= matchAccess.getMatchOffset();
int length= matchAccess.getMatchLength();
if (fIsKeyDoubleQuoted) {
start= start + 1;
length= length - 2;
}
fResult.add(new KeyReference(matchAccess.getFile(), start, length));
return true;
}
}
private IRegion fRegion;
private String fPropertiesKey;
private Shell fShell;
private IStorage fStorage;
private ITextEditor fEditor;
/**
* 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;
IStorageEditorInput storageEditorInput= (IStorageEditorInput)fEditor.getEditorInput();
fShell= fEditor.getEditorSite().getShell();
try {
fStorage= storageEditorInput.getStorage();
} catch (CoreException e) {
fStorage= null;
}
}
/*
* @see org.eclipse.wst.jsdt.internal.ui.javaeditor.IHyperlink#getHyperlinkRegion()
*/
public IRegion getHyperlinkRegion() {
return fRegion;
}
/*
* @see org.eclipse.wst.jsdt.internal.ui.javaeditor.IHyperlink#open()
*/
public void open() {
if (!checkEnabled())
return;
// Search the key
IResource resource= (IResource)fStorage;
KeyReference[] references= null;
if (resource != null)
references= search(resource.getProject(), 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 boolean checkEnabled() {
// XXX: Can be removed once support for JARs is available (see class Javadoc for details)
return fStorage instanceof IResource;
}
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.
* <p>
* FIXME: The lower pane is currently not sorted due to https://bugs.eclipse.org/bugs/show_bug.cgi?id=84220
* </p>
*
* @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() {
public String decorateText(String input, Object element) {
KeyReference keyRef= (KeyReference)element;
IStorage storage= keyRef.storage;
String name= storage.getName();
if (name == null)
return input;
int count= 0;
for (int i= 0; i < length; i++) {
if (keyReferences[i].storage.equals(storage))
count++;
}
if (count > 1) {
Object[] args= new Object[] { name, new Integer(count) };
name= Messages.format(PropertiesFileEditorMessages.OpenAction_SelectionDialog_elementLabelWithMatchCount, args);
}
return name;
}
};
TwoPaneElementSelector dialog= new TwoPaneElementSelector(fShell, labelProvider, new WorkbenchLabelProvider());
dialog.setLowerListLabel(PropertiesFileEditorMessages.OpenAction_SelectionDialog_details);
dialog.setMultipleSelection(false);
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= EditorUtility.openInEditor(keyReference.storage, true);
EditorUtility.revealInEditor(part, keyReference.offset, keyReference.length);
} catch (JavaScriptModelException e) {
JavaScriptPlugin.log(new Status(IStatus.ERROR, JavaScriptPlugin.getPluginId(),
IJavaStatusConstants.INTERNAL_ERROR, PropertiesFileEditorMessages.OpenAction_error_message, e));
ErrorDialog.openError(fShell,
getErrorDialogTitle(),
PropertiesFileEditorMessages.OpenAction_error_messageProblems,
e.getStatus());
} catch (PartInitException x) {
String message= null;
IWorkbenchAdapter wbAdapter= (IWorkbenchAdapter)((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= (IEditorStatusLine)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 scope the 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 IResource scope, String key) {
if (key == null)
return new KeyReference[0];
final List result= new ArrayList(5);
final String searchString;
// XXX: This is a hack to improve the accuracy of matches, see https://bugs.eclipse.org/bugs/show_bug.cgi?id=81140
final boolean useDoubleQuotedKey= useDoubleQuotedKey();
if (useDoubleQuotedKey) {
StringBuffer buf= new StringBuffer("\""); //$NON-NLS-1$
buf.append(fPropertiesKey);
buf.append('"');
searchString= buf.toString();
} else
searchString= fPropertiesKey;
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 {
ResultCollector collector= new ResultCollector(result, useDoubleQuotedKey);
TextSearchEngine engine= TextSearchEngine.create();
Pattern searchPattern= PatternConstructor.createPattern(searchString, true, false);
engine.search(createScope(scope), collector, searchPattern, new SubProgressMonitor(monitor, 4));
if (result.size() == 0 && useDoubleQuotedKey) {
//Try without, maybe an eclipse style NLS string
IJavaScriptElement element= JavaScriptCore.create(scope);
if (element == null)
return;
int includeMask = IJavaScriptSearchScope.SOURCES | IJavaScriptSearchScope.APPLICATION_LIBRARIES | IJavaScriptSearchScope.REFERENCED_PROJECTS;
IJavaScriptSearchScope javaSearchScope= SearchEngine.createJavaSearchScope(new IJavaScriptElement[] { element }, includeMask);
SearchPattern pattern= SearchPattern.createPattern(fPropertiesKey, IJavaScriptSearchConstants.FIELD, IJavaScriptSearchConstants.REFERENCES, SearchPattern.R_PATTERN_MATCH | SearchPattern.R_CASE_SENSITIVE);
try {
new SearchEngine().search(pattern, SearchUtils.getDefaultSearchParticipants(), javaSearchScope, new SearchRequestor() {
public void acceptSearchMatch(SearchMatch match) throws CoreException {
result.add(new KeyReference((IStorage)match.getResource(), match.getOffset(), match.getLength()));
}
}, new SubProgressMonitor(monitor, 1));
} catch (CoreException e) {
throw new InvocationTargetException(e);
}
} else {
monitor.worked(1);
}
} finally {
monitor.done();
}
}
}
);
} catch (InvocationTargetException ex) {
String message= PropertiesFileEditorMessages.OpenAction_error_messageErrorSearchingKey;
showError(new CoreException(new Status(IStatus.ERROR, JavaScriptUI.ID_PLUGIN, IStatus.OK, message, ex.getTargetException())));
} catch (InterruptedException ex) {
return null; // canceled
}
return (KeyReference[])result.toArray(new KeyReference[result.size()]);
}
private static TextSearchScope createScope(IResource scope) {
ArrayList fileNamePatternStrings= new ArrayList();
// XXX: Should be configurable via preference, see https://bugs.eclipse.org/bugs/show_bug.cgi?id=81117
String[] javaExtensions= JavaScriptCore.getJavaScriptLikeExtensions();
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= (String[]) fileNamePatternStrings.toArray(new String[fileNamePatternStrings.size()]);
Pattern fileNamePattern= PatternConstructor.createPattern(allPatternStrings, false, false);
return TextSearchScope.newSearchScope(new IResource[] { scope }, fileNamePattern, false);
}
/*
* @see org.eclipse.wst.jsdt.internal.ui.javaeditor.IHyperlink#getTypeLabel()
*/
public String getTypeLabel() {
return null;
}
/*
* @see org.eclipse.wst.jsdt.internal.ui.javaeditor.IHyperlink#getHyperlinkText()
*/
public String getHyperlinkText() {
return null;
}
}