/******************************************************************************* * Copyright (c) 2008-2010 Sonatype, Inc. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Sonatype, Inc. - initial API and implementation *******************************************************************************/ package org.eclipse.m2e.refactoring.rename; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import org.eclipse.core.resources.IFile; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.OperationCanceledException; import org.eclipse.emf.common.command.CompoundCommand; import org.eclipse.emf.ecore.EObject; import org.eclipse.emf.ecore.EStructuralFeature; import org.eclipse.emf.edit.command.SetCommand; import org.eclipse.emf.edit.domain.AdapterFactoryEditingDomain; import org.eclipse.ltk.core.refactoring.RefactoringStatus; import org.eclipse.osgi.util.NLS; import org.eclipse.m2e.model.edit.pom.Model; import org.eclipse.m2e.model.edit.pom.util.PomResourceImpl; import org.eclipse.m2e.refactoring.AbstractPomRefactoring; import org.eclipse.m2e.refactoring.Messages; import org.eclipse.m2e.refactoring.PomVisitor; import org.eclipse.m2e.refactoring.RefactoringModelResources; import org.eclipse.m2e.refactoring.RefactoringModelResources.PropertyInfo; /** * Rename artifact refactoring implementation * * @author Anton Kraev */ @SuppressWarnings("rawtypes") public class RenameRefactoring extends AbstractPomRefactoring { private static final Object[] EMPTY_OBJECT_ARRAY = new Object[] {}; private static final String VERSION = "version"; //$NON-NLS-1$ private static final String GETVERSION = Messages.RenameRefactoring_1; private static final String ARTIFACT_ID = "artifactId"; //$NON-NLS-1$ private static final String GETARTIFACT_ID = "getArtifactId"; //$NON-NLS-1$ private static final String GROUP_ID = "groupId"; //$NON-NLS-1$ private static final String GETGROUP_ID = "getGroupId"; //$NON-NLS-1$ // this page contains new values MavenRenameWizardPage page; // old values String oldGroupId; String oldArtifactId; String oldVersion; public RenameRefactoring(IFile file, MavenRenameWizardPage page) { super(file); this.page = page; } // gets element from effective model based on path private Object getElement(Object root, Path path) { if(path == null || path.path.size() == 0) { return root; } PathElement current = path.path.remove(0); String getterName = "get" + current.element; //$NON-NLS-1$ try { Method getter = root.getClass().getMethod(getterName, new Class[] {}); root = getElement(getter.invoke(root, EMPTY_OBJECT_ARRAY), path); if(root instanceof List) { List children = (List) root; for(int i = 0; i < children.size(); i++ ) { Object child = children.get(i); Method artifact = child.getClass().getMethod(GETARTIFACT_ID, new Class[] {}); String artifactId = (String) artifact.invoke(child, EMPTY_OBJECT_ARRAY); if(current.artifactId != null && !current.artifactId.equals(artifactId)) continue; //found, names are correct return getElement(child, path); } } else { return getElement(root, path); } return null; } catch(Exception ex) { return null; } } /** * Finds all potential matched objects in model */ private List<EObjectWithPath> scanModel(Model model, String groupId, String artifactId, String version, boolean processRoot) { List<EObjectWithPath> res = new ArrayList<EObjectWithPath>(); Path path = new Path(); if(processRoot) { scanObject(path, model, groupId, artifactId, version, res); } else { scanChildren(path, model, groupId, artifactId, version, res); } return res; } // add candidate objects with same artifactId private List<EObjectWithPath> scanObject(Path current, EObject obj, String groupId, String artifactId, String version, List<EObjectWithPath> res) { if(scanFeature(obj, ARTIFACT_ID, artifactId)) { // System.out.println("found object " + obj + " : " + current); res.add(new EObjectWithPath(obj, current)); } scanChildren(current, obj, groupId, artifactId, version, res); return res; } private List<EObjectWithPath> scanChildren(Path current, EObject obj, String groupId, String artifactId, String version, List<EObjectWithPath> res) { Iterator<EObject> it = obj.eContents().iterator(); while(it.hasNext()) { obj = it.next(); Path child = current.clone(); String element = obj.eContainingFeature().getName(); element = element.substring(0, 1).toUpperCase() + element.substring(1); child.addElement(element, artifactId); scanObject(child, obj, groupId, artifactId, version, res); } return res; } private boolean scanFeature(EObject obj, String featureName, String value) { //not searching on this if(value == null) { return false; } EStructuralFeature feature = obj.eClass().getEStructuralFeature(featureName); if(feature == null) { return false; } String val = obj.eGet(feature) == null ? null : obj.eGet(feature).toString(); if(value.equals(val)) { return true; } return false; } private String getValue(EObject obj, String featureName) { EStructuralFeature feature = obj.eClass().getEStructuralFeature(featureName); if(feature == null) { return null; } return obj.eGet(feature) == null ? null : obj.eGet(feature).toString(); } public String getNewProjectName() { return page.getRenameEclipseProject() ? page.getNewArtifactId() : null; } /** * Applies new values in model * * @param editingDomain * @param renameProject * @throws NoSuchMethodException * @throws Exception */ public CompoundCommand applyModel(RefactoringModelResources model, String newGroupId, String newArtifactId, String newVersion, boolean processRoot) throws Exception { // find all affected objects in EMF model List<EObjectWithPath> affected = scanModel(model.getTmpModel(), this.oldGroupId, this.oldArtifactId, this.oldVersion, processRoot); // go through all affected objects, check in effective model Iterator<EObjectWithPath> i = affected.iterator(); CompoundCommand command = new CompoundCommand(); while(i.hasNext()) { EObjectWithPath obj = i.next(); Object effectiveObj = getElement(model.getEffective(), obj.path.clone()); if(effectiveObj == null) { // System.out.println("cannot find effective for: " + obj.object); continue; } Method method = effectiveObj.getClass().getMethod(GETVERSION, new Class[] {}); String effectiveVersion = (String) method.invoke(effectiveObj, EMPTY_OBJECT_ARRAY); method = effectiveObj.getClass().getMethod(GETGROUP_ID, new Class[] {}); String effectiveGroupId = (String) method.invoke(effectiveObj, EMPTY_OBJECT_ARRAY); // if version from effective POM is different from old version, skip it if(this.oldVersion != null && !this.oldVersion.equals(effectiveVersion)) { continue; } // only set groupId if effective group id is the same as old group id if(oldGroupId != null && oldGroupId.equals(effectiveGroupId)) applyFeature(editingDomain, model, GROUP_ID, newGroupId, command, obj); // set artifact id unconditionally applyFeature(editingDomain, model, ARTIFACT_ID, newArtifactId, command, obj); // only set version if effective version is the same (already checked by the above) // and new version is not empty if(!"".equals(newVersion)) { //$NON-NLS-1$ applyFeature(editingDomain, model, VERSION, newVersion, command, obj); } } return command.isEmpty() ? null : command; } // apply the value, considering properties private void applyFeature(AdapterFactoryEditingDomain editingDomain, RefactoringModelResources model, String feature, String newValue, CompoundCommand command, EObjectWithPath obj) { PropertyInfo info = null; String old = getValue(obj.object, feature); if(old != null && old.startsWith("${")) { //$NON-NLS-1$ // this is a property, go find it String pName = old.substring(2); pName = pName.substring(0, pName.length() - 1).trim(); info = model.getProperties().get(pName); } if(info != null) info.setNewValue(new SetCommand(editingDomain, info.getPair(), info.getPair().eClass() .getEStructuralFeature("value"), newValue)); //$NON-NLS-1$ else applyObject(editingDomain, command, obj.object, feature, newValue); } private void applyObject(AdapterFactoryEditingDomain editingDomain, CompoundCommand command, EObject obj, String featureName, String value) { EStructuralFeature feature = obj.eClass().getEStructuralFeature(featureName); if(feature == null) { return; } Object old = obj.eGet(feature); if(old == null || old.equals(value)) { return; } command.append(new SetCommand(editingDomain, obj, feature, value)); } @Override public RefactoringStatus checkInitialConditions(IProgressMonitor pm) throws CoreException, OperationCanceledException { PomResourceImpl resource = AbstractPomRefactoring.loadResource(file); try { Model model = (Model) resource.getContents().get(0); this.oldArtifactId = model.getArtifactId(); this.oldGroupId = model.getGroupId(); this.oldVersion = model.getVersion(); } finally { resource.unload(); } RefactoringStatus res = new RefactoringStatus(); return res; } @Override public String getName() { return Messages.RenameRefactoring_name; } @Override public PomVisitor getVisitor() { return new PomVisitor() { public CompoundCommand applyChanges(RefactoringModelResources current, IProgressMonitor pm) throws Exception { //process <project> element only for the refactored file itself boolean processRoot = current.getPomFile().equals(file); return RenameRefactoring.this.applyModel(current, page.getNewGroupId(), page.getNewArtifactId(), page.getNewVersion(), processRoot); } }; } static class Path { List<PathElement> path = new ArrayList<PathElement>(); public void addElement(String element, String artifactId) { path.add(new PathElement(element, artifactId)); } public String toString() { return path.toString(); } public Path clone() { Path res = new Path(); res.path = new ArrayList<PathElement>(this.path); return res; } } // path (built during traversal of EMF model, used to find in effective model) static class PathElement { String element; String artifactId; public PathElement(String element, String artifactId) { this.element = element; this.artifactId = artifactId; } public String toString() { return "/" + element + "[artifactId=" + artifactId + "]"; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ } } static class EObjectWithPath { public EObject object; public Path path; public EObjectWithPath(EObject object, Path path) { this.object = object; this.path = path; } } // XXX move stuff UP after implementing another refactoring // after moving up, use this interface ScanVisitor { public boolean interested(EObject obj); } public boolean scanAllArtifacts() { return true; } public String getTitle() { return NLS.bind(Messages.RenameRefactoring_title, file.getParent().getName()); } }