| /* |
| * 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); |
| } |
| } |
| } |