blob: 5a2de6a081fa34cc449805adfc31eb79a7019224 [file] [log] [blame]
/*
* 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.common.collect.Lists;
import com.google.common.collect.Sets;
import com.google.dart.engine.context.AnalysisContext;
import com.google.dart.engine.element.ClassElement;
import com.google.dart.engine.element.Element;
import com.google.dart.engine.element.ElementKind;
import com.google.dart.engine.element.LocalElement;
import com.google.dart.engine.search.SearchEngine;
import com.google.dart.engine.search.SearchMatch;
import com.google.dart.engine.services.internal.correction.CorrectionUtils;
import com.google.dart.engine.services.refactoring.ProgressMonitor;
import com.google.dart.engine.services.status.RefactoringStatus;
import com.google.dart.engine.services.status.RefactoringStatusContext;
import com.google.dart.engine.services.util.HierarchyUtils;
import com.google.dart.engine.source.Source;
import com.google.dart.engine.source.SourceFactory;
import static com.google.dart.engine.services.internal.correction.CorrectionUtils.getChildren;
import static com.google.dart.engine.services.internal.correction.CorrectionUtils.getElementKindName;
import static com.google.dart.engine.services.internal.correction.CorrectionUtils.getElementQualifiedName;
import java.text.MessageFormat;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
/**
* Helper to check if renaming or creating {@link Element} with given name will cause any problems.
*/
class RenameClassMemberValidator {
private final SearchEngine searchEngine;
private final AnalysisContext activeContext;
private final ElementKind elementKind;
private final ClassElement elementClass;
private final String oldName;
private final String newName;
private Set<ClassElement> superClasses;
private Set<ClassElement> subClasses;
boolean hasIgnoredElements = false;
Set<Element> renameElements = Sets.newHashSet();
List<SearchMatch> renameElementsReferences = Lists.newArrayList();
public RenameClassMemberValidator(SearchEngine searchEngine, ElementKind elementKind,
ClassElement elementClass, String oldName, String newName) {
this.searchEngine = searchEngine;
this.activeContext = elementClass.getContext();
this.elementKind = elementKind;
this.elementClass = elementClass;
this.oldName = oldName;
this.newName = newName;
}
RefactoringStatus validate(ProgressMonitor pm, boolean isRename) {
pm.beginTask("Analyze possible conflicts", 4);
try {
final RefactoringStatus result = new RefactoringStatus();
// prepare
prepareHierarchyClasses(result);
// check if there are members with "newName" in the same ClassElement
for (Element newNameMember : getChildren(elementClass, newName)) {
String message = MessageFormat.format(
"Class ''{0}'' already declares {1} with name ''{2}''.",
elementClass.getDisplayName(),
getElementKindName(newNameMember),
newName);
result.addError(message, new RefactoringStatusContext(newNameMember));
}
pm.worked(1);
// check shadowing in hierarchy
List<SearchMatch> nameDeclarations = searchEngine.searchDeclarations(newName, null, null);
{
for (SearchMatch nameDeclaration : nameDeclarations) {
Element member = nameDeclaration.getElement();
member = HierarchyUtils.getSyntheticAccessorVariable(member);
Element memberDeclClass = member.getEnclosingElement();
// renamed Element shadows member of super-class
if (superClasses.contains(memberDeclClass)) {
String message = MessageFormat.format(
isRename ? "Renamed {0} will shadow {1} ''{2}''."
: "Created {0} will shadow {1} ''{2}''.",
getElementKindName(elementKind),
getElementKindName(member),
getElementQualifiedName(member));
result.addError(message, new RefactoringStatusContext(member));
}
// renamed Element is shadowed by member of sub-class
if (isRename && subClasses.contains(memberDeclClass)) {
String message = MessageFormat.format(
"Renamed {0} will be shadowed by {1} ''{2}''.",
getElementKindName(elementKind),
getElementKindName(member),
getElementQualifiedName(member));
result.addError(message, new RefactoringStatusContext(member));
}
}
pm.worked(1);
}
// check if shadowed by local
{
for (SearchMatch nameDeclaration : nameDeclarations) {
Element nameElement = nameDeclaration.getElement();
if (nameElement instanceof LocalElement) {
LocalElement localElement = (LocalElement) nameElement;
ClassElement enclosingClass = nameElement.getAncestor(ClassElement.class);
if (Objects.equal(enclosingClass, elementClass) || subClasses.contains(enclosingClass)) {
for (SearchMatch reference : renameElementsReferences) {
if (RenameRefactoringImpl.isReferenceInLocalRange(localElement, reference)) {
String message = MessageFormat.format(
"Usage of renamed {0} will be shadowed by {1} ''{2}''.",
getElementKindName(elementKind),
getElementKindName(localElement),
localElement.getDisplayName());
result.addError(message, RefactoringStatusContext.create(reference));
}
}
}
}
}
pm.worked(1);
}
// TODO(scheglov) may be top-level shadows reference
{
pm.worked(1);
}
// done
return result;
} finally {
pm.done();
}
}
/**
* Fills {@link #hierarchyClasses} with super- and sub- {@link ClassElement}s; and
* {@link #renameElements} with all {@link Element}s which should be renamed, i.e. overridden in
* super- and overrides in sub-classes.
*/
private void prepareHierarchyClasses(RefactoringStatus status) {
// prepare elements to rename
superClasses = HierarchyUtils.getSuperClasses(elementClass);
subClasses = Sets.newHashSet();
renameElements.clear();
// process super-classes with "oldName" and their sub-classes
Set<ClassElement> processed = Sets.newHashSet();
LinkedList<ClassElement> toProcess = Lists.newLinkedList();
toProcess.addAll(superClasses);
toProcess.add(elementClass);
while (!toProcess.isEmpty()) {
ClassElement classElement = toProcess.removeFirst();
// maybe already processed
if (!processed.add(classElement)) {
continue;
}
// add "oldName" children
List<Element> children = CorrectionUtils.getChildren(classElement, oldName);
for (Element child : children) {
// ignore if Source cannot be updated
Source source = child.getSource();
SourceFactory activeSourceFactory = activeContext.getSourceFactory();
if (!activeSourceFactory.isLocalSource(source)) {
hasIgnoredElements = true;
continue;
}
// add element to rename
renameElements.add(child);
// process sub-classes if this class has an "oldName" child
toProcess.addAll(HierarchyUtils.getSubClasses(searchEngine, classElement));
}
// add sub-class
if (!superClasses.contains(classElement)) {
subClasses.add(classElement);
}
}
// prepare references
renameElementsReferences.clear();
for (Element renameElement : renameElements) {
List<SearchMatch> references = searchEngine.searchReferences(renameElement, null, null);
renameElementsReferences.addAll(references);
}
}
}