blob: a62b5654ed848c7b3693a98d508d284475984407 [file] [log] [blame]
/*=============================================================================#
# 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;
}
}