/*
* Copyright (c) 2013, the Dart project authors.
*
* Licensed under the Eclipse Public License v1.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at
*
* http://www.eclipse.org/legal/epl-v10.html
*
* Unless required by applicable law or agreed to in writing, software distributed under the License
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
* or implied. See the License for the specific language governing permissions and limitations under
* the License.
*/
package com.google.dart.tools.ui.internal.refactoring;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.google.dart.engine.services.change.Change;
import com.google.dart.engine.services.change.CompositeChange;
import com.google.dart.engine.services.change.CreateFileChange;
import com.google.dart.engine.services.change.Edit;
import com.google.dart.engine.services.change.MergeCompositeChange;
import com.google.dart.engine.services.change.SourceChange;
import com.google.dart.engine.services.correction.AddDependencyCorrectionProposal;
import com.google.dart.engine.services.correction.ChangeCorrectionProposal;
import com.google.dart.engine.services.correction.CorrectionImage;
import com.google.dart.engine.services.correction.CorrectionKind;
import com.google.dart.engine.services.correction.CorrectionProposal;
import com.google.dart.engine.services.correction.CreateFileCorrectionProposal;
import com.google.dart.engine.services.correction.LinkedPositionProposal;
import com.google.dart.engine.services.correction.SourceCorrectionProposal;
import com.google.dart.engine.services.status.RefactoringStatus;
import com.google.dart.engine.services.status.RefactoringStatusContext;
import com.google.dart.engine.services.status.RefactoringStatusEntry;
import com.google.dart.engine.services.status.RefactoringStatusSeverity;
import com.google.dart.engine.source.Source;
import com.google.dart.engine.utilities.source.SourceRange;
import com.google.dart.tools.core.DartCore;
import com.google.dart.tools.core.refactoring.CompilationUnitChange;
import com.google.dart.tools.internal.corext.refactoring.base.DartStatusContext_OLD;
import com.google.dart.tools.internal.corext.refactoring.changes.TextChangeCompatibility;
import com.google.dart.tools.internal.corext.refactoring.util.ExecutionUtils;
import com.google.dart.tools.internal.corext.refactoring.util.RunnableObjectEx;
import com.google.dart.tools.ui.DartPluginImages;
import com.google.dart.tools.ui.DartToolsPlugin;
import com.google.dart.tools.ui.internal.text.correction.proposals.LinkedCorrectionProposal_OLD;
import com.google.dart.tools.ui.internal.text.correction.proposals.TrackedPositions;
import com.google.dart.tools.ui.text.dart.IDartCompletionProposal;
import org.apache.commons.lang3.StringUtils;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.jface.text.contentassist.ICompletionProposal;
import org.eclipse.ltk.core.refactoring.TextChange;
import org.eclipse.ltk.core.refactoring.TextEditChangeGroup;
import org.eclipse.ltk.core.refactoring.TextFileChange;
import org.eclipse.swt.graphics.Image;
import org.eclipse.text.edits.MalformedTreeException;
import org.eclipse.text.edits.MultiTextEdit;
import org.eclipse.text.edits.ReplaceEdit;
import org.eclipse.text.edits.TextEdit;
import org.eclipse.text.edits.TextEditGroup;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
/**
* Utilities to create LTK wrapper around Engine Services objects.
*
* @coverage dart.editor.ui.refactoring.ui
*/
public class ServiceUtils_OLD {
/**
* @return the {@link CoreException} wrapper around given {@link Throwable}.
*/
public static CoreException createCoreException(Throwable e) {
IStatus status = createRuntimeStatus(e);
return new CoreException(status);
}
/**
* @return the LTK change for the given Services {@link Change}.
*/
public static org.eclipse.ltk.core.refactoring.Change toLTK(Change change) {
// leaf SourceChange
if (change instanceof SourceChange) {
SourceChange sourceChange = (SourceChange) change;
return toLTK(sourceChange);
}
// leaf CreateFileChange
if (change instanceof CreateFileChange) {
CreateFileChange fileChange = (CreateFileChange) change;
return new com.google.dart.tools.ui.internal.text.correction.proposals.CreateFileChange(
fileChange.getName(),
fileChange.getFile(),
fileChange.getContent());
}
// may be MergeCompositeChange
if (change instanceof MergeCompositeChange) {
MergeCompositeChange mergeChange = (MergeCompositeChange) change;
return toLTK(mergeChange);
}
// should be CompositeChange
CompositeChange compositeChange = (CompositeChange) change;
return toLTK(compositeChange);
}
/**
* @return the LTK change for the given Services {@link CompositeChange}.
*/
public static org.eclipse.ltk.core.refactoring.CompositeChange toLTK(
CompositeChange compositeChange) {
org.eclipse.ltk.core.refactoring.CompositeChange ltkChange = new org.eclipse.ltk.core.refactoring.CompositeChange(
compositeChange.getName());
for (Change child : compositeChange.getChildren()) {
ltkChange.add(toLTK(child));
}
return ltkChange;
}
/**
* @return the Editor specific {@link Image} to given {@link CorrectionImage} identifier.
*/
public static Image toLTK(final CorrectionImage imageId) {
if (imageId != null) {
return ExecutionUtils.runObjectUI(new RunnableObjectEx<Image>() {
@Override
public Image runObject() throws Exception {
switch (imageId) {
case IMG_CORRECTION_CHANGE:
return DartPluginImages.get(DartPluginImages.IMG_CORRECTION_CHANGE);
case IMG_CORRECTION_CLASS:
return DartPluginImages.get(DartPluginImages.IMG_OBJS_CLASS);
}
return null;
}
});
}
return null;
}
/**
* @return the LTK change for the given Services {@link CompositeChange}.
*/
public static org.eclipse.ltk.core.refactoring.Change toLTK(MergeCompositeChange mergeChange) {
String mergedName = mergeChange.getName();
CompositeChange pChange = mergeChange.getPreviewChange();
CompositeChange eChange = mergeChange.getExecuteChange();
final org.eclipse.ltk.core.refactoring.CompositeChange previewChange = toLTK(pChange);
final org.eclipse.ltk.core.refactoring.CompositeChange executeChange = toLTK(eChange);
// May be no preview changes, for example because all these changes are applied to external
// files (in the Pub cache) and we ignored them.
if (previewChange.getChildren().length == 0) {
return getRenamedChange(mergedName, executeChange);
}
// OK, create wrapper LTK CompositeChange
return new org.eclipse.ltk.core.refactoring.CompositeChange(
mergedName,
new org.eclipse.ltk.core.refactoring.Change[] {previewChange, executeChange}) {
@Override
public org.eclipse.ltk.core.refactoring.Change perform(IProgressMonitor pm)
throws CoreException {
mergeTextChanges(executeChange, previewChange);
return executeChange.perform(pm);
}
};
}
/**
* @return the LTK status for the given Services {@link RefactoringStatus}.
*/
public static org.eclipse.ltk.core.refactoring.RefactoringStatus toLTK(RefactoringStatus status) {
org.eclipse.ltk.core.refactoring.RefactoringStatus result = new org.eclipse.ltk.core.refactoring.RefactoringStatus();
for (RefactoringStatusEntry entry : status.getEntries()) {
result.addEntry(
toLTK(entry.getSeverity()),
entry.getMessage(),
toLTK(entry.getContext()),
null,
org.eclipse.ltk.core.refactoring.RefactoringStatusEntry.NO_CODE);
}
return result;
}
/**
* @return the Dart status context for the given Services {@link RefactoringStatusContext}.
*/
public static org.eclipse.ltk.core.refactoring.RefactoringStatusContext toLTK(
RefactoringStatusContext context) {
if (context == null) {
return null;
}
return new DartStatusContext_OLD(context.getContext(), context.getSource(), context.getRange());
}
/**
* @return the LTK {@link TextFileChange} for the given services {@link SourceChange}.
*/
public static TextFileChange toLTK(SourceChange change) {
Source source = change.getSource();
// prepare IFile
IFile file = getFile(source);
if (file == null) {
return null;
}
// prepare CompilationUnitChange
CompilationUnitChange ltkChange = new CompilationUnitChange(change.getName(), file);
ltkChange.setEdit(new MultiTextEdit());
Map<String, List<Edit>> editGroups = change.getEditGroups();
for (Entry<String, List<Edit>> entry : editGroups.entrySet()) {
List<Edit> edits = entry.getValue();
// add edits
TextEdit ltkEdits[] = toLTK(edits);
try {
for (TextEdit ltkEdit : ltkEdits) {
ltkChange.addEdit(ltkEdit);
}
} catch (MalformedTreeException e) {
throw new Error(source + " " + StringUtils.join(ltkEdits, " "), e);
}
// add group
String groupName = entry.getKey();
if (StringUtils.isNotEmpty(groupName)) {
ltkChange.addTextEditGroup(new TextEditGroup(groupName, ltkEdits));
}
}
return ltkChange;
}
/**
* @return the error status for given {@link Throwable}.
*/
public static org.eclipse.ltk.core.refactoring.RefactoringStatus toLTK(Throwable e) {
IStatus status = createRuntimeStatus(e);
return org.eclipse.ltk.core.refactoring.RefactoringStatus.create(status);
}
/**
* @return the {@link IDartCompletionProposal} for the given {@link CreateFileCorrectionProposal}.
*/
public static IDartCompletionProposal toUI(AddDependencyCorrectionProposal proposal) {
return new com.google.dart.tools.ui.internal.text.correction.proposals.AddDependencyCorrectionProposal(
proposal.getKind().getRelevance(),
proposal.getName(),
proposal.getFile(),
proposal.getPackageName());
}
/**
* @return the Eclipse {@link ICompletionProposal} for the given {@link CorrectionProposal}.
*/
public static ICompletionProposal toUI(CorrectionProposal serviceProposal) {
if (serviceProposal instanceof ChangeCorrectionProposal) {
ChangeCorrectionProposal changeProposal = (ChangeCorrectionProposal) serviceProposal;
org.eclipse.ltk.core.refactoring.Change ltkChange = toLTK(changeProposal.getChange());
if (ltkChange == null) {
return null;
}
return new com.google.dart.tools.ui.internal.text.correction.proposals.ChangeCorrectionProposal(
changeProposal.getName(),
ltkChange,
changeProposal.getKind().getRelevance(),
toLTK(changeProposal.getKind().getImage()));
}
if (serviceProposal instanceof CreateFileCorrectionProposal) {
CreateFileCorrectionProposal fileProposal = (CreateFileCorrectionProposal) serviceProposal;
return toUI(fileProposal);
}
if (serviceProposal instanceof AddDependencyCorrectionProposal) {
AddDependencyCorrectionProposal proposal = (AddDependencyCorrectionProposal) serviceProposal;
return toUI(proposal);
}
if (serviceProposal instanceof SourceCorrectionProposal) {
SourceCorrectionProposal sourceProposal = (SourceCorrectionProposal) serviceProposal;
return toUI(sourceProposal);
}
return null;
}
/**
* @return the {@link IDartCompletionProposal} for the given {@link CreateFileCorrectionProposal}.
*/
public static IDartCompletionProposal toUI(CreateFileCorrectionProposal fileProposal) {
return new com.google.dart.tools.ui.internal.text.correction.proposals.CreateFileCorrectionProposal(
fileProposal.getKind().getRelevance(),
fileProposal.getName(),
fileProposal.getFile(),
fileProposal.getContent());
}
/**
* @return the {@link LinkedCorrectionProposal_OLD} for the given {@link SourceCorrectionProposal}.
*/
public static LinkedCorrectionProposal_OLD toUI(SourceCorrectionProposal sourceProposal) {
// prepare TextChange
SourceChange sourceChange = sourceProposal.getChange();
TextChange textChange = ServiceUtils_OLD.toLTK(sourceChange);
if (textChange == null) {
return null;
}
// prepare UI proposal
CorrectionKind kind = sourceProposal.getKind();
Image image = ServiceUtils_OLD.toLTK(kind.getImage());
LinkedCorrectionProposal_OLD uiProposal = new LinkedCorrectionProposal_OLD(
sourceProposal.getName(),
sourceChange.getSource(),
textChange,
kind.getRelevance(),
image);
// add linked positions
for (Entry<String, List<SourceRange>> entry : sourceProposal.getLinkedPositions().entrySet()) {
String group = entry.getKey();
for (SourceRange position : entry.getValue()) {
uiProposal.addLinkedPosition(TrackedPositions.forRange(position), false, group);
}
}
// add proposals
for (Entry<String, List<LinkedPositionProposal>> entry : sourceProposal.getLinkedPositionProposals().entrySet()) {
String group = entry.getKey();
for (LinkedPositionProposal proposal : entry.getValue()) {
uiProposal.addLinkedPositionProposal(group, proposal.getText(), toLTK(proposal.getIcon()));
}
}
// set end position
{
SourceRange endRange = sourceProposal.getEndRange();
if (endRange != null) {
uiProposal.setEndPosition(TrackedPositions.forRange(endRange));
}
}
// done
return uiProposal;
}
/**
* @return the error {@link IStatus} for the given {@link Throwable}.
*/
private static IStatus createRuntimeStatus(Throwable e) {
return new Status(IStatus.ERROR, DartToolsPlugin.getPluginId(), e.getMessage(), e);
}
/**
* @return the {@link IFile} of the given {@link Source}, may be {@code null} if external.
*/
private static IFile getFile(Source source) {
return (IFile) DartCore.getProjectManager().getResource(source);
}
private static org.eclipse.ltk.core.refactoring.CompositeChange getRenamedChange(String newName,
final org.eclipse.ltk.core.refactoring.CompositeChange executeChange) {
org.eclipse.ltk.core.refactoring.CompositeChange renamedExecuteChange;
renamedExecuteChange = new org.eclipse.ltk.core.refactoring.CompositeChange(newName);
renamedExecuteChange.merge(executeChange);
return renamedExecuteChange;
}
/**
* Merges {@link TextChange}s from "newCompositeChange" into "existingCompositeChange".
*/
private static void mergeTextChanges(
org.eclipse.ltk.core.refactoring.CompositeChange existingCompositeChange,
org.eclipse.ltk.core.refactoring.CompositeChange newCompositeChange) {
// [element -> Change map] in CompositeChange
Map<Object, org.eclipse.ltk.core.refactoring.Change> elementChanges = Maps.newHashMap();
for (org.eclipse.ltk.core.refactoring.Change change : existingCompositeChange.getChildren()) {
Object modifiedElement = change.getModifiedElement();
elementChanges.put(modifiedElement, change);
}
// merge new changes into CompositeChange
for (org.eclipse.ltk.core.refactoring.Change newChange : newCompositeChange.getChildren()) {
// ignore if disabled (in preview UI)
if (!newChange.isEnabled()) {
continue;
}
// prepare existing TextChange
Object modifiedElement = newChange.getModifiedElement();
org.eclipse.ltk.core.refactoring.Change existingChange = elementChanges.get(modifiedElement);
// add TextEditChangeGroup from new TextChange
if (existingChange instanceof TextChange && newChange instanceof TextChange) {
TextChange existingTextChange = (TextChange) existingChange;
TextEdit existingTextEdit = existingTextChange.getEdit();
TextChange newTextChange = (TextChange) newChange;
// remember TextEdit(s) disabled in preview UI
Set<TextEdit> disabledTextEdits = Sets.newHashSet();
for (TextEditChangeGroup group : newTextChange.getTextEditChangeGroups()) {
if (!group.isEnabled()) {
Collections.addAll(disabledTextEdits, group.getTextEdits());
}
}
// merge not disabled TextEdit(s)
TextEdit newTextEdit = newTextChange.getEdit();
if (newTextEdit != null) {
for (TextEdit childTextEdit : newTextEdit.getChildren()) {
if (disabledTextEdits.contains(childTextEdit)) {
continue;
}
childTextEdit.getParent().removeChild(childTextEdit);
TextChangeCompatibility.insert(existingTextEdit, childTextEdit);
}
}
} else {
newCompositeChange.remove(newChange);
existingCompositeChange.add(newChange);
}
}
}
private static TextEdit toLTK(Edit edit) {
return new ReplaceEdit(edit.getOffset(), edit.getLength(), edit.getReplacement());
}
private static TextEdit[] toLTK(List<Edit> edits) {
// NB(scheglov) It is a bad idea to ensure uniqueness of Edit(s) here.
List<TextEdit> ltkEdits = Lists.newArrayList();
for (Edit edit : edits) {
TextEdit ltkEdit = toLTK(edit);
ltkEdits.add(ltkEdit);
}
return ltkEdits.toArray(new TextEdit[ltkEdits.size()]);
}
/**
* @return the LTK status severity for the given Service {@link RefactoringStatusSeverity}.
*/
private static int toLTK(RefactoringStatusSeverity severity) {
switch (severity) {
case OK:
return org.eclipse.ltk.core.refactoring.RefactoringStatus.OK;
case INFO:
return org.eclipse.ltk.core.refactoring.RefactoringStatus.INFO;
case WARNING:
return org.eclipse.ltk.core.refactoring.RefactoringStatus.WARNING;
case ERROR:
return org.eclipse.ltk.core.refactoring.RefactoringStatus.ERROR;
case FATAL:
return org.eclipse.ltk.core.refactoring.RefactoringStatus.FATAL;
default:
throw new IllegalArgumentException("severity: " + severity);
}
}
}