blob: 6d0c5f0571230ba6d13abb9f134ca23bc02d1868 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2008, 2019 Sonatype, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Sonatype, Inc. - initial API and implementation
*******************************************************************************/
package org.eclipse.m2e.refactoring;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.OperationCanceledException;
import org.eclipse.core.runtime.Status;
import org.eclipse.emf.common.command.BasicCommandStack;
import org.eclipse.emf.common.command.CompoundCommand;
import org.eclipse.emf.common.notify.impl.AdapterFactoryImpl;
import org.eclipse.emf.common.util.EList;
import org.eclipse.emf.common.util.URI;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.emf.edit.domain.AdapterFactoryEditingDomain;
import org.eclipse.emf.edit.provider.ComposedAdapterFactory;
import org.eclipse.emf.edit.provider.ReflectiveItemProviderAdapterFactory;
import org.eclipse.emf.edit.provider.resource.ResourceItemProviderAdapterFactory;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.internal.corext.refactoring.rename.RenameJavaProjectProcessor;
import org.eclipse.ltk.core.refactoring.Change;
import org.eclipse.ltk.core.refactoring.CompositeChange;
import org.eclipse.ltk.core.refactoring.Refactoring;
import org.eclipse.ltk.core.refactoring.RefactoringStatus;
import org.eclipse.ltk.core.refactoring.TextFileChange;
import org.eclipse.ltk.core.refactoring.participants.RenameRefactoring;
import org.eclipse.osgi.util.NLS;
import org.apache.maven.project.MavenProject;
import org.eclipse.m2e.core.MavenPlugin;
import org.eclipse.m2e.core.project.IMavenProjectFacade;
import org.eclipse.m2e.core.project.IMavenProjectRegistry;
import org.eclipse.m2e.model.edit.pom.Model;
import org.eclipse.m2e.model.edit.pom.PropertyElement;
import org.eclipse.m2e.model.edit.pom.util.PomResourceFactoryImpl;
import org.eclipse.m2e.model.edit.pom.util.PomResourceImpl;
import org.eclipse.m2e.refactoring.RefactoringModelResources.PropertyInfo;
import org.eclipse.m2e.refactoring.internal.Activator;
/**
* Base class for all pom.xml refactorings in workspace
*
* @author Anton Kraev
*/
@SuppressWarnings("restriction")
public abstract class AbstractPomRefactoring extends Refactoring {
private static final Logger log = LoggerFactory.getLogger(AbstractPomRefactoring.class);
protected static final String PROBLEMS_DURING_REFACTORING = Messages.AbstractPomRefactoring_error;
// main file that is being refactored
protected IFile file;
// editing domain
protected AdapterFactoryEditingDomain editingDomain;
private HashMap<String, RefactoringModelResources> models;
public AbstractPomRefactoring(IFile file) {
this.file = file;
List<AdapterFactoryImpl> factories = new ArrayList<>();
factories.add(new ResourceItemProviderAdapterFactory());
factories.add(new ReflectiveItemProviderAdapterFactory());
ComposedAdapterFactory adapterFactory = new ComposedAdapterFactory(factories);
BasicCommandStack commandStack = new BasicCommandStack();
this.editingDomain = new AdapterFactoryEditingDomain(adapterFactory, //
commandStack, new HashMap<Resource, Boolean>());
}
// this gets actual refactoring visitor
public abstract PomVisitor getVisitor();
@Override
public RefactoringStatus checkFinalConditions(IProgressMonitor pm) throws OperationCanceledException {
return new RefactoringStatus();
}
@Override
public Change createChange(IProgressMonitor pm) throws CoreException, OperationCanceledException {
CompositeChange res = new CompositeChange(getTitle());
IMavenProjectFacade[] projects = MavenPlugin.getMavenProjectRegistry().getProjects();
pm.beginTask(Messages.AbstractPomRefactoring_task, projects.length);
models = new HashMap<>();
try {
// load all models
// XXX: assumption: artifactId is unique within workspace
for(IMavenProjectFacade projectFacade : projects) {
// skip "other" projects if not requested
if(!scanAllArtifacts() && !projectFacade.getPom().equals(file)) {
continue;
}
// skip closed projects
if(!projectFacade.getProject().isAccessible() || !projectFacade.getPom().isAccessible()) {
continue;
}
loadModel(projectFacade, pm);
}
// construct properties for all models
for(IMavenProjectFacade projectFacade : projects) {
RefactoringModelResources model = models.get(projectFacade.getArtifactKey().getArtifactId());
if(model == null) {
continue;
}
Map<String, PropertyInfo> properties = new HashMap<>();
// find all workspace parents
List<RefactoringModelResources> workspaceParents = new ArrayList<>();
MavenProject current = model.getProject();
// add itself
workspaceParents.add(model);
for(MavenProject parentProject = getParentProject(projectFacade, current, pm); parentProject != null;) {
String id = parentProject.getArtifactId();
RefactoringModelResources parent = models.get(id);
if(parent != null) {
workspaceParents.add(parent);
} else {
break;
}
parentProject = getParentProject(projectFacade, parentProject, pm);
}
//fill properties (from the root)
for(int i = workspaceParents.size() - 1; i >= 0; i-- ) {
RefactoringModelResources resource = workspaceParents.get(i);
EList<PropertyElement> props = resource.getTmpModel().getProperties();
if(props == null)
continue;
Iterator<?> it = props.iterator();
while(it.hasNext()) {
PropertyElement pair = (PropertyElement) it.next();
String pName = pair.getName();
PropertyInfo info = properties.get(pName);
if(info == null) {
info = new PropertyInfo();
properties.put(pName, info);
}
info.setPair(pair);
info.setResource(resource);
}
}
model.setProperties(properties);
}
// calculate the list of affected models
for(String artifact : models.keySet()) {
RefactoringModelResources model = models.get(artifact);
model.setCommand(getVisitor().applyChanges(model, pm));
}
// process all refactored properties, creating more commands
for(String artifact : models.keySet()) {
RefactoringModelResources model = models.get(artifact);
if(model.getProperties() == null) {
continue;
}
for(String pName : model.getProperties().keySet()) {
PropertyInfo info = model.getProperties().get(pName);
if(info.getNewValue() != null) {
CompoundCommand command = info.getResource().getCommand();
if(command == null) {
command = new CompoundCommand();
info.getResource().setCommand(command);
}
command.append(info.getNewValue());
}
}
}
// process the file itself first
for(String artifact : models.keySet()) {
RefactoringModelResources model = models.get(artifact);
if(model.getPomFile().equals(file)) {
processCommand(model, res);
model.releaseAllResources();
models.remove(artifact);
break;
}
}
// process others
for(String artifact : models.keySet()) {
processCommand(models.get(artifact), res);
}
// rename project if required
// TODO probably should copy relevant classes from internal packages
String newName = getNewProjectName();
if(newName != null) {
RenameJavaProjectProcessor processor = new RenameJavaProjectProcessor(JavaCore.create(file.getProject()));
RenameRefactoring refactoring = new RenameRefactoring(processor);
processor.setNewElementName(newName);
RefactoringStatus tmp = new RefactoringStatus();
tmp.merge(refactoring.checkInitialConditions(pm));
if(!tmp.hasFatalError()) {
tmp.merge(refactoring.checkFinalConditions(pm));
if(!tmp.hasFatalError()) {
res.add(refactoring.createChange(pm));
}
}
}
} catch(final PomRefactoringException ex) {
return new Change() {
@Override
public RefactoringStatus isValid(IProgressMonitor pm) throws OperationCanceledException {
return RefactoringStatus.createFatalErrorStatus(ex.getStatus().getMessage());
}
@Override
public Object getModifiedElement() {
return null;
}
@Override
public String getName() {
return ex.getStatus().getMessage();
}
@Override
public void initializeValidationData(IProgressMonitor pm) {
}
@Override
public Change perform(IProgressMonitor pm) {
return null;
}
@Override
public boolean isEnabled() {
return false;
}
};
} catch(Exception ex) {
throw new CoreException(new Status(IStatus.ERROR, Activator.PLUGIN_ID, PROBLEMS_DURING_REFACTORING, ex));
} finally {
for(String artifact : models.keySet()) {
models.get(artifact).releaseAllResources();
}
RefactoringModelResources.cleanupTmpProject();
}
return res;
}
protected MavenProject getParentProject(IMavenProjectFacade project, final MavenProject current,
final IProgressMonitor monitor) throws CoreException {
IMavenProjectRegistry projectManager = MavenPlugin.getMavenProjectRegistry();
return projectManager.execute(project, (context, monitor1) -> MavenPlugin.getMaven().resolveParentProject(current, monitor1), monitor);
}
// title for a composite change
public abstract String getTitle();
protected RefactoringModelResources loadModel(IMavenProjectFacade projectFacade, IProgressMonitor pm)
throws CoreException, IOException {
pm.setTaskName(NLS.bind(Messages.AbstractPomRefactoring_loading, projectFacade.getProject().getName()));
RefactoringModelResources current = new RefactoringModelResources(projectFacade);
models.put(current.effective.getArtifactId(), current);
pm.worked(1);
return current;
}
// this method determines whether all artifacts will be sent to visitor or only main one
public abstract boolean scanAllArtifacts();
protected void processCommand(RefactoringModelResources model, CompositeChange res) throws Exception {
CompoundCommand command = model.getCommand();
if(command == null) {
return;
}
if(command.canExecute()) {
// apply changes to temp file
editingDomain.getCommandStack().execute(command);
// create text change comparing temp file and real file
TextFileChange change = new ChangeCreator(model.getPomFile(), model.getPomBuffer().getDocument(),
model.getTmpBuffer().getDocument(), file.getParent().getName()).createChange();
res.add(change);
}
}
// returns new eclipse project name or null if no change
public String getNewProjectName() {
return null;
}
public Model createModel() {
try {
Resource resource = loadResource(file);
return (Model) resource.getContents().get(0);
} catch(CoreException ex) {
log.error(PROBLEMS_DURING_REFACTORING, ex);
return null;
}
}
public static PomResourceImpl loadResource(IFile pomFile) throws CoreException {
String path = pomFile.getFullPath().toOSString();
URI uri = URI.createPlatformResourceURI(path, true);
try {
Resource resource = new PomResourceFactoryImpl().createResource(uri);
resource.load(new HashMap<>());
return (PomResourceImpl) resource;
} catch(Exception ex) {
String msg = NLS.bind("Can't load model {0}", pomFile);
log.error(msg, ex);
throw new CoreException(new Status(IStatus.ERROR, Activator.PLUGIN_ID, -1, msg, ex));
}
}
}