/*
* 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.context.AnalysisContext;
import com.google.dart.engine.services.change.Change;
import com.google.dart.engine.services.change.CompositeChange;
import com.google.dart.engine.services.change.SourceChange;
import com.google.dart.engine.services.refactoring.ProgressMonitor;
import com.google.dart.engine.services.refactoring.Refactoring;
import com.google.dart.engine.services.status.RefactoringStatus;
import com.google.dart.engine.source.Source;
import com.google.dart.tools.core.DartCore;
import com.google.dart.tools.core.analysis.model.Project;
import com.google.dart.tools.core.analysis.model.PubFolder;
import com.google.dart.tools.internal.corext.refactoring.base.StringStatusContext;
import static com.google.dart.tools.ui.internal.refactoring.ServiceUtils_OLD.createCoreException;
import static com.google.dart.tools.ui.internal.refactoring.ServiceUtils_OLD.toLTK;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.OperationCanceledException;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
/**
* LTK wrapper around Engine Services {@link Refactoring}.
*
* @coverage dart.editor.ui.refactoring.ui
*/
public class ServiceRefactoring extends org.eclipse.ltk.core.refactoring.Refactoring {
private final Refactoring refactoring;
private final Set<Source> unsafeSources = Sets.newHashSet();
public ServiceRefactoring(Refactoring refactoring) {
this.refactoring = refactoring;
}
@Override
public org.eclipse.ltk.core.refactoring.RefactoringStatus checkFinalConditions(IProgressMonitor pm)
throws CoreException, OperationCanceledException {
try {
ProgressMonitor spm = new ServiceProgressMonitor(pm);
RefactoringStatus status = refactoring.checkFinalConditions(spm);
return toLTK(status);
} catch (Throwable e) {
DartCore.logError("Exception in ServiceRefactoring.checkFinalConditions", e);
return toLTK(e);
}
}
@Override
public org.eclipse.ltk.core.refactoring.RefactoringStatus checkInitialConditions(
IProgressMonitor pm) throws CoreException, OperationCanceledException {
try {
ProgressMonitor spm = new ServiceProgressMonitor(pm);
RefactoringStatus status = refactoring.checkInitialConditions(spm);
org.eclipse.ltk.core.refactoring.RefactoringStatus ltkStatus = toLTK(status);
checkUnsafeSources(ltkStatus);
return ltkStatus;
} catch (Throwable e) {
DartCore.logError("Exception in ServiceRefactoring.checkInitialConditions", e);
return toLTK(e);
}
}
@Override
public org.eclipse.ltk.core.refactoring.Change createChange(IProgressMonitor pm)
throws CoreException, OperationCanceledException {
try {
ProgressMonitor spm = new ServiceProgressMonitor(pm);
Change change = refactoring.createChange(spm);
change = removeChangesForUnsafeSources(change);
return toLTK(change);
} catch (Throwable e) {
DartCore.logError("Exception in ServiceRefactoring.createChange", e);
throw createCoreException(e);
}
}
@Override
public String getName() {
return refactoring.getRefactoringName();
}
/**
* @return {@code true} if the {@link Change} created by refactoring may be unsafe, so we want
* user to review the change to ensure that he understand it.
*/
public boolean requiresPreview() {
return refactoring.requiresPreview();
}
/**
* @return {@code true} if given {@link Source} may be affected by this refactoring, so we should
* warn user about it.
*/
protected boolean shouldReportUnsafeRefactoringSource(AnalysisContext context, Source source) {
return true;
}
/**
* Checks if all {@link AnalysisContext} are actually fully analyzed, so it is safe to perform
* refactoring. Otherwise updates given LTK refactoring status.
*/
private void checkUnsafeSources(org.eclipse.ltk.core.refactoring.RefactoringStatus ltkStatus) {
unsafeSources.clear();
// prepare contexts
Map<AnalysisContext, Project> contextToProject = Maps.newHashMap();
for (Project project : DartCore.getProjectManager().getProjects()) {
// default context
AnalysisContext defaultContext = project.getDefaultContext();
contextToProject.put(defaultContext, project);
// separate Pub folders
for (PubFolder pubFolder : project.getPubFolders()) {
AnalysisContext context = pubFolder.getContext();
if (context != defaultContext) {
contextToProject.put(context, project);
}
}
}
// prepare description for unsafe sources
StringBuilder sb = new StringBuilder();
for (Entry<AnalysisContext, Project> entry : contextToProject.entrySet()) {
AnalysisContext context = entry.getKey();
Project project = entry.getValue();
Source[] sources = context.getRefactoringUnsafeSources();
// all sources are ready
if (sources.length == 0) {
continue;
}
// remember
Collections.addAll(unsafeSources, sources);
// append project and its sources
boolean firstSource = true;
for (Source source : sources) {
if (!shouldReportUnsafeRefactoringSource(context, source)) {
continue;
}
if (firstSource) {
sb.append(project.getResource().getName() + "=[");
firstSource = false;
}
sb.append(source.getFullName());
sb.append(" ");
}
if (sb.length() != 0) {
sb.setLength(sb.length() - 1);
sb.append("]\n");
}
}
// show unsafe sources
if (sb.length() != 0) {
String sourcesText = sb.toString();
String msg = "Analysis of these sources is not up to date and they will be\n"
+ "excluded from the refactoring:\n\n" + sourcesText;
DartCore.logInformation(sourcesText);
ltkStatus.addWarning(
unsafeSources.size() + " source(s) have not been analyzed",
new StringStatusContext("Sources that have not been analyzed:", msg));
}
}
/**
* Checks if given {@link Change} is for one of the unsafe {@link Source}s and returns empty
* {@link Change} or contains such {@link Change} and should be recreated without it.
*/
private Change removeChangesForUnsafeSources(Change change) {
// SourceChange - may be exclude it
if (change instanceof SourceChange) {
Source source = ((SourceChange) change).getSource();
if (unsafeSources.contains(source)) {
return new SourceChange(change.getName(), source);
}
return change;
}
// CompositeChange - may be exclude some of its children
if (change instanceof CompositeChange) {
CompositeChange compositeChange = (CompositeChange) change;
List<Change> newChildren = Lists.newArrayList();
for (Change child : compositeChange.getChildren()) {
Change newChild = removeChangesForUnsafeSources(child);
if (newChild != null) {
newChildren.add(newChild);
}
}
return new CompositeChange(compositeChange.getName(), newChildren);
}
// some other Change
return change;
}
}