| /*=============================================================================# |
| # Copyright (c) 2008, 2019 Stephan Wahlbrink and others. |
| # |
| # This program and the accompanying materials are made available under the |
| # terms of the Eclipse Public License 2.0 which is available at |
| # https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 |
| # which is available at https://www.apache.org/licenses/LICENSE-2.0. |
| # |
| # SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 |
| # |
| # Contributors: |
| # Stephan Wahlbrink <sw@wahlbrink.eu> - initial API and implementation |
| #=============================================================================*/ |
| |
| package org.eclipse.statet.r.core.refactoring; |
| |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.HashSet; |
| import java.util.List; |
| import java.util.Set; |
| |
| import org.eclipse.core.resources.IProject; |
| import org.eclipse.core.resources.IResource; |
| import org.eclipse.core.runtime.CoreException; |
| import org.eclipse.core.runtime.IStatus; |
| import org.eclipse.core.runtime.Status; |
| import org.eclipse.core.runtime.SubMonitor; |
| import org.eclipse.jface.text.BadLocationException; |
| import org.eclipse.osgi.util.NLS; |
| |
| import org.eclipse.statet.jcommons.collections.ImCollections; |
| import org.eclipse.statet.jcommons.collections.ImList; |
| |
| import org.eclipse.statet.internal.r.core.refactoring.Messages; |
| import org.eclipse.statet.ltk.core.LTK; |
| import org.eclipse.statet.ltk.model.core.IModelManager; |
| import org.eclipse.statet.ltk.model.core.ISourceUnitManager; |
| import org.eclipse.statet.ltk.model.core.elements.ISourceUnit; |
| import org.eclipse.statet.r.core.RCore; |
| import org.eclipse.statet.r.core.RProject; |
| import org.eclipse.statet.r.core.RProjects; |
| import org.eclipse.statet.r.core.model.IRFrame; |
| import org.eclipse.statet.r.core.model.IRFrameInSource; |
| import org.eclipse.statet.r.core.model.IRModelInfo; |
| import org.eclipse.statet.r.core.model.IRSourceUnit; |
| import org.eclipse.statet.r.core.model.RElementAccess; |
| import org.eclipse.statet.r.core.model.RElementName; |
| import org.eclipse.statet.r.core.model.RModel; |
| |
| |
| public class RElementSearchProcessor { |
| |
| public static final int WARN_NO_DEFINITION= 0x10000; |
| public static final int WARN_MULTIPLE_DEFINITION= 0x20000; |
| |
| public static final int ALLOW_SUB_NAMEDPART= 0x01000; |
| |
| |
| public static enum Mode { |
| |
| WORKSPACE (Messages.SearchScope_Workspace_label), |
| CURRENT_AND_REFERENCING_PROJECTS (Messages.SearchScope_CurrentAndReferencingProjects_label), |
| CURRENT_PROJECT (Messages.SearchScope_CurrentProject_label), |
| CURRENT_FILE (Messages.SearchScope_CurrentFile_label), |
| LOCAL_FRAME (Messages.SearchScope_LocalFrame_label); |
| |
| |
| private final String label; |
| |
| |
| private Mode(final String label) { |
| this.label= label; |
| } |
| |
| |
| public String getLabel() { |
| return this.label; |
| } |
| |
| } |
| |
| |
| protected static final String createPackageFrameId(final String packageName) { |
| return "package:" + packageName; //$NON-NLS-1$ |
| } |
| |
| |
| private static ImList<Mode> MODES_LOCAL= ImCollections.newList( |
| Mode.LOCAL_FRAME ); |
| |
| private static ImList<Mode> MODES_GLOBAL= ImCollections.newList( |
| Mode.WORKSPACE, |
| Mode.CURRENT_AND_REFERENCING_PROJECTS, |
| Mode.CURRENT_PROJECT, |
| Mode.CURRENT_FILE ); |
| |
| private static ImList<Mode> MODES_FILE= ImCollections.newList( |
| Mode.CURRENT_FILE ); |
| |
| |
| private final IRSourceUnit initialSourceUnit; |
| |
| private List<Mode> availableModes; |
| private Mode mode; |
| |
| protected final RElementName name; |
| protected final RElementName mainName; |
| protected final RElementName scope; |
| protected final String scopeFrameId; |
| |
| private final int flags; |
| |
| private final List<RProject> allProjects= new ArrayList<>(); |
| private final List<List<ISourceUnit>> allProjectsSourceUnits= new ArrayList<>(); |
| protected final List<RProject> definitionProjects= new ArrayList<>(); |
| protected final Set<String> definitionFrameIds= new HashSet<>(); |
| protected final List<RProject> matchProjects= new ArrayList<>(); |
| |
| private IStatus status; |
| |
| |
| /** |
| * Creates a search processor initialized by the specified element access |
| * |
| * @param sourceUnit the source unit of the element access |
| * @param name the name of the element to search |
| * @param mainAccess the access element for the element to search (must match the name) |
| * @param mode |
| * @param flags |
| */ |
| public RElementSearchProcessor(final RElementName name, |
| final IRSourceUnit sourceUnit, final RElementAccess mainAccess, |
| final Mode mode, final int flags) { |
| this.initialSourceUnit= sourceUnit; |
| this.mode= mode; |
| this.flags= flags; |
| |
| this.name= name; |
| this.status= Status.OK_STATUS; |
| |
| validateName(); |
| |
| if (this.status.getSeverity() < IStatus.ERROR) { |
| init(mainAccess); |
| } |
| |
| if (this.status.getSeverity() < IStatus.ERROR) { |
| this.mainName= RElementName.cloneSegment(mainAccess); |
| this.scope= (mainAccess.getScope() != null) ? |
| RElementName.cloneSegment(mainAccess.getScope()) : |
| null; |
| this.scopeFrameId= (this.scope != null |
| && RElementName.isPackageFacetScopeType(this.scope.getType())) ? |
| createPackageFrameId(this.scope.getSegmentName()) : null; |
| } |
| else { |
| this.mainName= null; |
| this.scope= null; |
| this.scopeFrameId= null; |
| } |
| } |
| |
| protected void validateName() { |
| if (this.name == null) { |
| addStatus(IStatus.ERROR, "The operation is unavailable on the current selection."); |
| return; |
| } |
| |
| RElementName nameSegment= this.name; |
| ITER_SEGMENTS: do { |
| if (nameSegment.getSegmentName() == null) { |
| addStatus(IStatus.ERROR, "The operation is unavailable on the current selection (invalid name)."); |
| break ITER_SEGMENTS; |
| } |
| nameSegment= nameSegment.getNextSegment(); |
| } while (nameSegment != null); |
| |
| nameSegment= this.name.getNextSegment(); |
| ITER_SEGMENTS: while (nameSegment != null) { |
| switch (nameSegment.getType()) { |
| case RElementName.SUB_NAMEDPART: |
| if ((this.flags & ALLOW_SUB_NAMEDPART) == 0) { |
| addStatus(IStatus.ERROR, "The operation is unavailable on the current selection (sub element)."); |
| break ITER_SEGMENTS; |
| } |
| break; |
| default: |
| addStatus(IStatus.ERROR, "The operation is unavailable on the current selection (unsupported sub element)."); |
| break ITER_SEGMENTS; |
| } |
| nameSegment= nameSegment.getNextSegment(); |
| } |
| } |
| |
| protected void init(final RElementAccess access) { |
| if (access == null) { |
| throw new NullPointerException("elementAccess"); //$NON-NLS-1$ |
| } |
| { RElementName subName= this.name; |
| RElementAccess subAccess= access; |
| do { |
| if (subAccess == null |
| || !subName.getSegmentName().equals(subAccess.getSegmentName())) { |
| throw new IllegalArgumentException("elementAccess does not match to elementName"); //$NON-NLS-1$ |
| } |
| subName= subName.getNextSegment(); |
| subAccess= subAccess.getNextSegment(); |
| } while (subName != null); |
| } |
| if (access.getType() != RElementName.MAIN_DEFAULT) { |
| addStatus(IStatus.ERROR, "The operation is unavailable on the current selection."); |
| return; |
| } |
| if (access.getSegmentName() == null || access.getSegmentName().isEmpty()) { |
| addStatus(IStatus.ERROR, "The operation is unavailable on the current selection."); |
| return; |
| } |
| |
| this.availableModes= getAvailableModes(access); |
| if (this.mode != null) { |
| if (!this.availableModes.contains(this.mode)) { |
| this.mode= this.availableModes.get(0); |
| this.status= new Status(IStatus.WARNING, RCore.BUNDLE_ID, |
| NLS.bind("Scope changed to: ''{0}''.", this.mode) ); |
| } |
| } |
| else { |
| this.mode= this.availableModes.get(0); |
| } |
| } |
| |
| public IRSourceUnit getInitialSourceUnit() { |
| return this.initialSourceUnit; |
| } |
| |
| public IStatus getStatus() { |
| return this.status; |
| } |
| |
| protected List<Mode> getAvailableModes(final RElementAccess access) { |
| final IRFrame frame= access.getFrame(); |
| if (frame == null || (frame.getFrameType() != IRFrame.PACKAGE && frame.getFrameType() != IRFrame.PROJECT)) { |
| return MODES_LOCAL; |
| } |
| else if (getInitialRProject() == null) { |
| return MODES_FILE; |
| } |
| else { |
| return MODES_GLOBAL; |
| } |
| } |
| |
| public List<Mode> getAvailableModes() { |
| return this.availableModes; |
| } |
| |
| public void setMode(final Mode mode) { |
| if (!getAvailableModes().contains(mode)) { |
| throw new IllegalArgumentException("mode"); //$NON-NLS-1$ |
| } |
| this.mode= mode; |
| } |
| |
| public Mode getMode() { |
| return this.mode; |
| } |
| |
| public String getModeLabel() { |
| switch (this.mode) { |
| case WORKSPACE: |
| return "workspace"; |
| case CURRENT_AND_REFERENCING_PROJECTS: |
| return NLS.bind("project ''{0}'' and referencing projects", getInitialProjectName()); |
| case CURRENT_PROJECT: |
| return NLS.bind("project ''{0}''", getInitialProjectName()); |
| case CURRENT_FILE: |
| return NLS.bind("file ''{0}''", this.initialSourceUnit.getElementName().getDisplayName()); |
| case LOCAL_FRAME: |
| return "local frame"; |
| default: |
| return ""; //$NON-NLS-1$ |
| } |
| } |
| |
| private IProject getInitialProject() { |
| final Object resource= this.initialSourceUnit.getResource(); |
| return (resource instanceof IResource) ? |
| ((IResource) resource).getProject() : |
| null; |
| } |
| |
| private String getInitialProjectName() { |
| final IProject project= getInitialProject(); |
| return (project != null) ? project.getName() : "-"; //$NON-NLS-1$ |
| } |
| |
| private RProject getInitialRProject() { |
| final IProject project= getInitialProject(); |
| return (project != null) ? RProjects.getRProject(project) : null; |
| } |
| |
| |
| protected void addStatus(final int severity, final String message) { |
| this.status= new Status(severity, RCore.BUNDLE_ID, message); |
| } |
| |
| public RElementName getElementName() { |
| return this.name; |
| } |
| |
| |
| protected void clear() { |
| this.allProjects.clear(); |
| this.allProjectsSourceUnits.clear(); |
| this.definitionProjects.clear(); |
| this.definitionFrameIds.clear(); |
| this.matchProjects.clear(); |
| } |
| |
| public void run(final SubMonitor m) throws CoreException { |
| if (this.status.getSeverity() >= IStatus.ERROR) { |
| throw new IllegalStateException(); |
| } |
| |
| m.beginTask(getTaskName(), 100); |
| clear(); |
| begin(m); |
| |
| try { |
| this.definitionFrameIds.add(null); |
| if (this.scopeFrameId != null) { |
| // search for specified package |
| this.definitionFrameIds.add(this.scopeFrameId); |
| } |
| |
| { // start with current project |
| final RProject initialProject= getInitialRProject(); |
| this.allProjects.add(initialProject); |
| if (initialProject != null && getMode().compareTo(Mode.CURRENT_PROJECT) <= 0) { |
| final List<ISourceUnit> sus= loadSus(initialProject, this.allProjectsSourceUnits, true, |
| m.newChild(2) ); |
| sus.add(this.initialSourceUnit); |
| } |
| else { |
| this.initialSourceUnit.connect(m.newChild(1)); |
| this.allProjectsSourceUnits.add(Collections.<ISourceUnit>singletonList(this.initialSourceUnit)); |
| } |
| m.worked(1); |
| } |
| |
| if (m.isCanceled()) { |
| throw new CoreException(Status.CANCEL_STATUS); |
| } |
| m.setWorkRemaining(90); |
| if (getMode().compareTo(Mode.CURRENT_PROJECT) <= 0) { // referenced projects |
| final SubMonitor m1= m.newChild(40); |
| for (int i= 0; i < this.allProjects.size(); i++) { |
| m1.setWorkRemaining((this.allProjects.size() - i) * 4); |
| |
| final RProject project= this.allProjects.get(i); |
| if (project == null) { |
| continue; |
| } |
| |
| if (m1.isCanceled()) { |
| throw new CoreException(Status.CANCEL_STATUS); |
| } |
| |
| List<ISourceUnit> sus; |
| if (i < this.allProjectsSourceUnits.size()) { |
| sus= this.allProjectsSourceUnits.get(i); |
| m1.worked(1); |
| } |
| else { |
| sus= loadSus(project, this.allProjectsSourceUnits, false, m1.newChild(2)); |
| } |
| if (sus != null) { |
| final int found= searchDefinition(project, sus, m1.newChild(2)); |
| if (found > 0) { |
| this.definitionProjects.add(project); |
| if (this.scopeFrameId == null) { |
| final String packageName= project.getPkgName(); |
| if (packageName != null) { |
| this.definitionFrameIds.add(createPackageFrameId(packageName)); |
| } |
| } |
| if (found == 2) { // || specificPackage == null |
| continue; |
| } |
| } |
| if (getMode() == Mode.WORKSPACE) { |
| addReferencedProjects(project, this.allProjects); |
| } |
| } |
| } |
| } |
| |
| if (getMode() == Mode.WORKSPACE) { |
| if (this.definitionProjects.isEmpty()) { |
| if ((this.flags & WARN_NO_DEFINITION) != 0) { |
| addStatus(IStatus.WARNING, Messages.RenameInWorkspace_warning_NoDefinition_message); |
| } |
| } |
| else if (this.definitionProjects.size() > 1) { |
| if ((this.flags & WARN_NO_DEFINITION) != 0) { |
| addStatus(IStatus.WARNING, Messages.RenameInWorkspace_warning_MultipleDefinitions_message); |
| } |
| } |
| } |
| |
| // definitions? |
| if (!this.definitionProjects.isEmpty()) { |
| this.matchProjects.addAll(this.definitionProjects); |
| } |
| else { |
| for (int i= 0; i < this.allProjects.size(); i++) { |
| final RProject project; |
| if (this.allProjectsSourceUnits.get(i) != null |
| && (project= this.allProjects.get(i)) != null) { |
| this.matchProjects.add(project); |
| } |
| } |
| } |
| m.worked(4); |
| if (m.isCanceled()) { |
| throw new CoreException(Status.CANCEL_STATUS); |
| } |
| |
| beginFinalProcessing(m); |
| |
| // referencing occurrences - create text changes |
| if (this.matchProjects.isEmpty() && getMode() == Mode.CURRENT_FILE) { |
| final List<ISourceUnit> sus= new ArrayList<>(); |
| sus.add(this.initialSourceUnit); |
| process(null, sus, m.newChild(40)); |
| } |
| else{ |
| final SubMonitor m1= m.newChild(40); |
| for (int i= 0; i < this.matchProjects.size(); i++) { |
| m1.setWorkRemaining((this.matchProjects.size() - i) * 11); |
| |
| final RProject project= this.matchProjects.get(i); |
| int idx= this.allProjects.indexOf(project); |
| |
| if (m1.isCanceled()) { |
| throw new CoreException(Status.CANCEL_STATUS); |
| } |
| |
| List<ISourceUnit> sus; |
| if (idx >= 0) { |
| sus= this.allProjectsSourceUnits.get(idx); |
| } |
| else { |
| this.allProjects.add(project); |
| idx= this.allProjectsSourceUnits.size(); |
| sus= loadSus(project, this.allProjectsSourceUnits, false, m1.newChild(2)); |
| } |
| process(project, sus, m1.newChild(8)); |
| switch (getMode()) { |
| case WORKSPACE: |
| case CURRENT_AND_REFERENCING_PROJECTS: |
| addReferencingProjects(project, this.matchProjects); |
| break; |
| default: |
| break; |
| } |
| |
| if (sus != null) { |
| this.allProjectsSourceUnits.set(idx, null); |
| closeSus(sus, m1.newChild(1)); |
| } |
| } |
| } |
| } |
| catch (final BadLocationException e) { |
| throw new CoreException(new Status(IStatus.ERROR, RCore.BUNDLE_ID, |
| "Unexpected error (concurrent change?)", e )); |
| } |
| finally { |
| for (int i= 0; i < this.allProjectsSourceUnits.size(); i++) { |
| final List<ISourceUnit> sus= this.allProjectsSourceUnits.get(i); |
| if (sus != null) { |
| m.setWorkRemaining(this.allProjectsSourceUnits.size() - i); |
| closeSus(sus, m.newChild(1)); |
| } |
| } |
| } |
| } |
| |
| private void closeSus(final List<ISourceUnit> sus, final SubMonitor m) { |
| m.setWorkRemaining(sus.size()); |
| for (final ISourceUnit su : sus) { |
| try { |
| su.disconnect(m.newChild(1)); |
| } |
| catch (final Exception e) {} |
| } |
| } |
| |
| private void addReferencedProjects(final RProject initialProject, final List<RProject> projects) |
| throws CoreException { |
| final IProject[] referencedProjects= initialProject.getProject().getReferencedProjects(); |
| for (final IProject referencedProject : referencedProjects) { |
| if (referencedProject.isOpen()) { |
| final RProject project= RProjects.getRProject(referencedProject); |
| if (project != null && !projects.contains(project)) { |
| projects.add(project); |
| } |
| } |
| } |
| } |
| |
| private void addReferencingProjects(final RProject initialProject, final List<RProject> projects) |
| throws CoreException { |
| final IProject[] referencedProjects= initialProject.getProject().getReferencingProjects(); |
| for (final IProject referencedProject : referencedProjects) { |
| if (referencedProject.isOpen()) { |
| final RProject project= RProjects.getRProject(referencedProject); |
| if (project != null && !projects.contains(project)) { |
| projects.add(project); |
| } |
| } |
| } |
| } |
| |
| private List<ISourceUnit> loadSus(final RProject project, final List<List<ISourceUnit>> projectsSus, |
| final boolean force, final SubMonitor m) throws CoreException { |
| m.setWorkRemaining(10 + 10); |
| |
| final ISourceUnitManager suManager= LTK.getSourceUnitManager(); |
| List<ISourceUnit> sourceUnits= RModel.getRModelManager().findReferencingSourceUnits( |
| project, this.mainName, m.newChild(10) ); |
| if (sourceUnits == null && force) { |
| sourceUnits= new ArrayList<>(1); |
| } |
| if (sourceUnits != null) { |
| projectsSus.add(sourceUnits); |
| |
| int workRemaining= sourceUnits.size(); |
| for (int i= 0; i < sourceUnits.size(); i++) { |
| m.setWorkRemaining(workRemaining--); |
| |
| final ISourceUnit sourceUnit= sourceUnits.get(i); |
| if (sourceUnit.getId().equals(this.initialSourceUnit.getId())) { |
| sourceUnits.remove(i--); |
| } |
| else { |
| try { |
| final ISourceUnit editUnit= suManager.getSourceUnit( |
| LTK.EDITOR_CONTEXT, sourceUnit, null, true, m.newChild(1) ); |
| if (editUnit != null) { |
| sourceUnits.set(i, editUnit); |
| } |
| } |
| catch (final Throwable e) { |
| throw new CoreException(new Status(IStatus.ERROR, RCore.BUNDLE_ID, 1, |
| NLS.bind("An error occurred when looking for ''{0}''.", sourceUnit.getId()), |
| e )); |
| } |
| } |
| } |
| |
| return sourceUnits; |
| } |
| else { |
| projectsSus.add(null); |
| return null; |
| } |
| } |
| |
| private int searchDefinition(final RProject project, final List<ISourceUnit> sus, |
| final SubMonitor m) { |
| final String packageName= project.getPkgName(); |
| if (this.scopeFrameId != null && packageName != null |
| && this.scopeFrameId.equals(createPackageFrameId(packageName))) { |
| m.setWorkRemaining(sus.size() * 2); |
| for (final ISourceUnit su : sus) { |
| searchDefinition(su, null, m.newChild(1)); |
| searchDefinition(su, this.scopeFrameId, m.newChild(1)); |
| } |
| return 2; |
| } |
| else { |
| m.setWorkRemaining(sus.size()); |
| boolean found= false; |
| for (final ISourceUnit su : sus) { |
| found |= searchDefinition(su, this.scopeFrameId, m.newChild(1)); |
| } |
| return (found) ? 1 : 0; |
| } |
| } |
| |
| private boolean searchDefinition(final ISourceUnit su, final String specificFrameId, |
| final SubMonitor m) { |
| m.setWorkRemaining(2 + 4); |
| |
| su.connect(m.newChild(1)); |
| try { |
| final IRModelInfo modelInfo= (IRModelInfo) su.getModelInfo(RModel.R_TYPE_ID, |
| IModelManager.MODEL_FILE, m.newChild(2) ); |
| if (modelInfo == null) { |
| return false; |
| } |
| final IRFrame frame; |
| if (specificFrameId == null) { |
| frame= modelInfo.getTopFrame(); |
| } |
| else { |
| frame= modelInfo.getReferencedFrames().get(specificFrameId); |
| } |
| if (frame instanceof IRFrameInSource) { |
| final List<? extends RElementAccess> allAccess= ((IRFrameInSource) frame).getAllAccessOf( |
| this.mainName.getSegmentName(), false ); |
| if (allAccess != null) { |
| for (final RElementAccess access : allAccess) { |
| if (access.isWriteAccess() && access.getNextSegment() == null) { |
| return true; |
| } |
| } |
| } |
| } |
| return false; |
| } |
| finally { |
| m.setWorkRemaining(1); |
| su.disconnect(m.newChild(1)); |
| } |
| } |
| |
| |
| protected String getTaskName() { |
| return NLS.bind(Messages.SearchProcessor_label, this.name.getDisplayName()); |
| } |
| |
| protected void begin(final SubMonitor m) { |
| } |
| |
| protected void beginFinalProcessing(final SubMonitor m) { |
| } |
| |
| protected void process(final RProject project, final List<ISourceUnit> sus, |
| final SubMonitor m) throws BadLocationException { |
| } |
| |
| protected RElementAccess searchMatch(RElementAccess access) { |
| RElementName nameSegment= this.name.getNextSegment(); |
| while (nameSegment != null) { |
| access= access.getNextSegment(); |
| if (access == null |
| || nameSegment.getType() != access.getType() |
| || !nameSegment.getSegmentName().equals(access.getSegmentName()) ) { |
| return null; |
| } |
| nameSegment= nameSegment.getNextSegment(); |
| } |
| return access; |
| } |
| |
| } |