/* * 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.engine.services.internal.refactoring; import com.google.common.base.Objects; import com.google.dart.engine.context.AnalysisContext; import com.google.dart.engine.element.Element; import com.google.dart.engine.element.ImportElement; import com.google.dart.engine.element.LocalElement; import com.google.dart.engine.element.PrefixElement; import com.google.dart.engine.internal.context.InstrumentedAnalysisContextImpl; import com.google.dart.engine.search.SearchEngine; import com.google.dart.engine.search.SearchMatch; import com.google.dart.engine.services.change.Edit; import com.google.dart.engine.services.change.SourceChange; import com.google.dart.engine.services.internal.correction.CorrectionUtils; import com.google.dart.engine.services.refactoring.ProgressMonitor; import com.google.dart.engine.services.refactoring.RenameRefactoring; import com.google.dart.engine.services.status.RefactoringStatus; import com.google.dart.engine.source.Source; import com.google.dart.engine.utilities.source.SourceRange; import static com.google.dart.engine.utilities.source.SourceRangeFactory.rangeElementName; import org.apache.commons.lang3.ArrayUtils; /** * Abstract implementation of {@link RenameRefactoring}. */ public abstract class RenameRefactoringImpl extends RefactoringImpl implements RenameRefactoring { /** * @return the {@link Edit} to replace the given {@link SearchMatch} reference. */ protected static Edit createReferenceEdit(SourceReference reference, String newText) { return new Edit(reference.range, newText); } /** * @return {@code true} if two given {@link Element}s are {@link LocalElement}s and have * intersecting with visibility ranges. */ protected static boolean haveIntersectingRanges(LocalElement localElement, Element element) { if (!(element instanceof LocalElement)) { return false; } LocalElement localElement2 = (LocalElement) element; Source localSource = localElement.getSource(); Source localSource2 = localElement2.getSource(); SourceRange localRange = localElement.getVisibleRange(); SourceRange localRange2 = localElement2.getVisibleRange(); return Objects.equal(localSource2, localSource) && localRange != null && localRange2 != null && localRange2.intersects(localRange); } /** * Check if the given {@link Element} is in the given {@link AnalysisContext}. */ protected static boolean isInContext(Element element, AnalysisContext context) { AnalysisContext elementContext = element.getContext(); if (elementContext != context) { if (context instanceof InstrumentedAnalysisContextImpl) { if (elementContext != ((InstrumentedAnalysisContextImpl) context).getBasis()) { return false; } } else { return false; } } return true; } /** * Check if the given {@link Element} is visible in the given {@link Source}. */ protected static boolean isInTheSameLibrary(Element element, AnalysisContext context, Source source) { // should be the same AnalysisContext if (!isInContext(element, context)) { return false; } // private elements are visible only in their library Source[] librarySourcesOfSource = context.getLibrariesContaining(source); Source librarySourceOfElement = element.getLibrary().getSource(); return ArrayUtils.contains(librarySourcesOfSource, librarySourceOfElement); } /** * Check if the given {@link Element} is visible in the given {@link Source}. */ protected static boolean isPublicOrInTheSameLibrary(Element element, AnalysisContext context, Source source) { // should be the same AnalysisContext if (!isInContext(element, context)) { return false; } // public elements are always visible if (element.isPublic()) { return true; } // private elements are visible only in their library return isInTheSameLibrary(element, context, source); } /** * @return if given unqualified {@link SearchMatch} intersects with visibility range of * {@link LocalElement}. */ protected static boolean isReferenceInLocalRange(LocalElement localElement, SearchMatch reference) { if (reference.isQualified()) { return false; } Source localSource = localElement.getSource(); Source referenceSource = reference.getElement().getSource(); SourceRange localRange = localElement.getVisibleRange(); SourceRange referenceRange = reference.getSourceRange(); return Objects.equal(referenceSource, localSource) && referenceRange.intersects(localRange); } private static String getDisplayName(Element element) { if (element instanceof ImportElement) { PrefixElement prefix = ((ImportElement) element).getPrefix(); if (prefix != null) { return prefix.getDisplayName(); } } return element.getDisplayName(); } protected final SearchEngine searchEngine; protected final Element element; protected final AnalysisContext context; protected final String oldName; protected String newName; public RenameRefactoringImpl(SearchEngine searchEngine, Element element) { this.searchEngine = searchEngine; this.element = element; this.context = element.getContext(); this.oldName = getDisplayName(element); } @Override public RefactoringStatus checkInitialConditions(ProgressMonitor pm) throws Exception { return new RefactoringStatus(); } @Override public RefactoringStatus checkNewName(String newName) { RefactoringStatus result = new RefactoringStatus(); if (Objects.equal(newName, getCurrentName())) { result.addFatalError("Choose another name."); } return result; } @Override public String getCurrentName() { return element.getDisplayName(); } @Override public String getNewName() { return newName; } @Override public void setNewName(String newName) { this.newName = newName; } @Override public boolean shouldReportUnsafeRefactoringSource(AnalysisContext context, Source source) { return isPublicOrInTheSameLibrary(element, context, source); } /** * Adds the "Update declaration" {@link Edit} to the {@link SourceChange}. */ protected final void addDeclarationEdit(SourceChange change, Element element) throws Exception { Edit edit = new Edit(rangeElementName(element), newName); addEdit(change, "Update declaration", edit); } /** * Adds the {@link Edit} that replaces {@link #oldName} to the {@link SourceChange}. */ protected final void addEdit(SourceChange sourceChange, String description, Edit edit) throws Exception { CorrectionUtils.addEdit(context, sourceChange, description, oldName, edit); } /** * Adds the "Update reference" {@link Edit} to the {@link SourceChange}. */ protected final void addReferenceEdit(SourceChange change, SourceReference reference) throws Exception { Edit edit = createReferenceEdit(reference, newName); addEdit(change, "Update reference", edit); } }