--- /dev/null
+/*******************************************************************************
+ * Copyright (c) 2000, 2012 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.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.IFile;
+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.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.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.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;
+
+
+/**
+ * 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 http://bugs.eclipse.org/22376
+ * </p>
+ *
+ * @since 3.1
+ */
+public class PropertyKeyHyperlink implements IHyperlink {
+
+
+ static class KeyReference extends PlatformObject implements IWorkbenchAdapter, Comparable<KeyReference> {
+
+ 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)
+ */
+ @Override
+ 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), BasicElementLabels.getPathLabel(storage.getFullPath(), false) };
+ return Messages.format(PropertiesFileEditorMessages.OpenAction_SelectionDialog_elementLabel, args);
+ }
+ } finally {
+ manager.disconnect(storage.getFullPath(), LocationKind.NORMALIZE, null);
+ }
+ } catch (CoreException e) {
+ JavaPlugin.log(e.getStatus());
+ } catch (BadLocationException e) {
+ JavaPlugin.log(e);
+ }
+
+ return BasicElementLabels.getPathLabel(storage.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= storage.getFullPath().toString();
+ return otherRef.generated_8631208768201754532(KeyReference.this, thisPath);
+ }
+
+ public void generated_47542269901016625(PropertyKeyHyperlink propertykeyhyperlink) {
+ try {
+ IEditorPart part= EditorUtility.openInEditor(storage, true);
+ if (part != null)
+ EditorUtility.revealInEditor(part, offset, length);
+ } catch (PartInitException x) {
+
+ String message= null;
+
+ IWorkbenchAdapter wbAdapter= (IWorkbenchAdapter)((IAdaptable)this).getAdapter(IWorkbenchAdapter.class);
+ if (wbAdapter != null)
+ message= Messages.format(PropertiesFileEditorMessages.OpenAction_error_messageArgs,
+ new String[] { wbAdapter.getLabel(this), x.getLocalizedMessage() } );
+
+ if (message == null)
+ message= Messages.format(PropertiesFileEditorMessages.OpenAction_error_message, x.getLocalizedMessage());
+
+ MessageDialog.openError(propertykeyhyperlink.fShell,
+ PropertiesFileEditorMessages.OpenAction_error_messageProblems,
+ message);
+ }
+ }
+
+ public int generated_8631208768201754532(KeyReference keyreference, String thisPath) {
+ String otherPath= storage.getFullPath().toString();
+ int result= KeyReference.fgCollator.compare(thisPath, otherPath);
+ if (result != 0)
+ return result;
+ else
+ return keyreference.offset - offset;
+ }
+ }
+
+
+ private static class ResultCollector extends TextSearchRequestor {
+
+ private List<KeyReference> fResult;
+ private boolean fIsKeyDoubleQuoted;
+
+ public ResultCollector(List<KeyReference> result, boolean isKeyDoubleQuoted) {
+ fResult= result;
+ fIsKeyDoubleQuoted= isKeyDoubleQuoted;
+ }
+
+ @Override
+ 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.jdt.internal.ui.javaeditor.IHyperlink#getHyperlinkRegion()
+ */
+ public IRegion getHyperlinkRegion() {
+ return fRegion;
+ }
+
+ /*
+ * @see org.eclipse.jdt.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.
+ *
+ * @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;
+ return generated_4910017305724796316(keyReferences, length, input, keyRef);
+ }
+
+ public String generated_4910017305724796316(final KeyReference[] keyReferences, final int length, String input, KeyReference keyRef) {
+ 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[] { BasicElementLabels.getResourceName(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.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;
+
+ keyReference.generated_47542269901016625(this);
+ }
+
+ 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, 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.getType() == IResource.FILE)
+ result.add(new KeyReference((IFile)resource, match.getOffset(), match.getLength()));
+ }
+ }, 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);
+ engine.search(createScope(scope), 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);
+ }
+}