package com.redhat.ceylon.eclipse.code.correct; /******************************************************************************* * 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 *******************************************************************************/ import static com.redhat.ceylon.eclipse.ui.CeylonResources.MINOR_CHANGE; import static com.redhat.ceylon.eclipse.util.EditorUtil.getCurrentEditor; import static com.redhat.ceylon.eclipse.util.EditorUtil.performChange; import static com.redhat.ceylon.eclipse.util.Highlights.styleProposal; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.NullProgressMonitor; import org.eclipse.jface.text.IDocument; import org.eclipse.jface.text.Region; import org.eclipse.jface.text.contentassist.ICompletionProposal; import org.eclipse.jface.text.contentassist.ICompletionProposalExtension5; import org.eclipse.jface.text.contentassist.ICompletionProposalExtension6; import org.eclipse.jface.text.contentassist.IContextInformation; import org.eclipse.jface.util.Util; import org.eclipse.jface.viewers.StyledString; import org.eclipse.ltk.core.refactoring.Change; import org.eclipse.ltk.core.refactoring.NullChange; import org.eclipse.swt.graphics.Image; import org.eclipse.swt.graphics.Point; import org.eclipse.swt.widgets.Display; /** * A quick fix/assist proposal based on a {@link Change}. */ class CorrectionProposal implements ICompletionProposal, ICompletionProposalExtension5, ICompletionProposalExtension6 { private static final NullChange COMPUTING_CHANGE = new NullChange("ChangeCorrectionProposal computing..."); private Change change; private final String name; private final Image image; private final Region selection; private boolean qualifiedNameIsPath; /** * Constructs a change correction proposal. * * @param name the name that is displayed in the proposal selection dialog * @param change the change that is executed when the proposal is applied or <code>null</code> * if the change will be created by implementors of {@link #createChange()} * @param relevance the relevance of this proposal * @param image the image that is displayed for this proposal or <code>null</code> if no image * is desired */ public CorrectionProposal(String name, Change change, Region selection, Image image) { if (name == null) { throw new IllegalArgumentException("Name must not be null"); } this.name= name; this.change= change; this.image= image; this.selection= selection; } /** * Constructs a change correction proposal. Uses the default image for this proposal. * * @param name The name that is displayed in the proposal selection dialog. * @param change The change that is executed when the proposal is applied or <code>null</code> * if the change will be created by implementors of {@link #createChange()}. * @param relevance The relevance of this proposal. */ public CorrectionProposal(String name, Change change, Region selection) { this(name, change, selection, MINOR_CHANGE); } public CorrectionProposal(String name, Change change, Region selection, boolean qualifiedNameIsPath) { this(name, change, selection, MINOR_CHANGE, qualifiedNameIsPath); } public CorrectionProposal(String name, Change change, Region selection, Image image, boolean qualifiedNameIsPath) { this(name, change, selection, image); this.qualifiedNameIsPath = qualifiedNameIsPath; } @Override public void apply(IDocument document) { try { performChange(getCurrentEditor(), document, getChange(), getName()); } catch (CoreException e) { e.printStackTrace(); } } @Override public String getAdditionalProposalInfo() { Object info= getAdditionalProposalInfo(new NullProgressMonitor()); return info == null ? null : info.toString(); } @Override public Object getAdditionalProposalInfo(IProgressMonitor monitor) { StringBuffer buf= new StringBuffer(); buf.append("<p>"); try { Change change= getChange(); if (change != null) { String name= change.getName(); if (name.length() == 0) { return null; } buf.append(name); } else { return null; } } catch (CoreException e) { buf.append("Unexpected error when accessing this proposal:<p><pre>"); buf.append(e.getLocalizedMessage()); buf.append("</pre>"); } buf.append("</p>"); return buf.toString(); } @Override public IContextInformation getContextInformation() { return null; } @Override public String getDisplayString() { return getName(); } @Override public StyledString getStyledDisplayString() { return styleProposal(getDisplayString(), qualifiedNameIsPath); } /** * Returns the name of the proposal. * * @return the name of the proposal */ public String getName() { return name; } public Image getImage() { return image; } public Point getSelection(IDocument document) { if (selection==null) { return null; } else { return new Point( selection.getOffset(), selection.getLength()); } } /** * Returns the change that will be executed when the proposal is applied. * This method calls {@link #createChange()} to compute the change. * * @return the change for this proposal, can be <code>null</code> in rare cases if creation of * the change failed * @throws CoreException when the change could not be created */ public final Change getChange() throws CoreException { if (Util.isGtk()) { // workaround for https://bugs.eclipse.org/bugs/show_bug.cgi?id=293995 : // [Widgets] Deadlock while UI thread displaying/computing a change proposal and non-UI thread creating image // Solution is to create the change outside a 'synchronized' block. // Synchronization is achieved by polling fChange, using "fChange == COMPUTING_CHANGE" as barrier. // Timeout of 10s for safety reasons (should not be reached). long end= System.currentTimeMillis() + 10000; do { boolean computing; synchronized (this) { computing= change == COMPUTING_CHANGE; } if (computing) { try { Display display= Display.getCurrent(); if (display != null) { while (!display.isDisposed() && display.readAndDispatch()) { // empty the display loop } display.sleep(); } else { Thread.sleep(100); } } catch (InterruptedException e) { //continue } } else { synchronized (this) { if (change == COMPUTING_CHANGE) { continue; } else if (change != null) { return change; } else { change= COMPUTING_CHANGE; } } Change change= createChange(); synchronized (this) { this.change= change; } return change; } } while (System.currentTimeMillis() < end); synchronized (this) { if (change == COMPUTING_CHANGE) { return null; //failed } } } else { synchronized (this) { if (change == null) { change= createChange(); } } } return change; } /** * Creates the change for this proposal. * This method is only called once and only when no change has been passed in * {@link #ChangeCorrectionProposal(String, Change, int, Image)}. * * Subclasses may override. * * @return the created change * @throws CoreException if the creation of the change failed */ protected Change createChange() throws CoreException { return new NullChange(); } }