/******************************************************************************* * Copyright (c) 2012 VMWare, 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: * VMWare, Inc. - initial API and implementation *******************************************************************************/ package org.grails.ide.eclipse.refactoring.test; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.HashSet; import java.util.List; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IFolder; import org.eclipse.core.resources.IResource; import org.eclipse.core.resources.IResourceVisitor; import org.eclipse.core.resources.IWorkspaceRoot; import org.eclipse.core.resources.ResourcesPlugin; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IPath; import org.eclipse.core.runtime.NullProgressMonitor; import org.eclipse.core.runtime.Path; import org.eclipse.jdt.core.IJavaProject; import org.eclipse.jdt.core.IType; import org.eclipse.jdt.core.JavaCore; import org.eclipse.jdt.core.JavaModelException; import org.eclipse.jdt.internal.corext.util.Strings; import org.eclipse.ltk.core.refactoring.RefactoringStatus; import org.grails.ide.eclipse.core.model.GrailsVersion; import org.grails.ide.eclipse.refactoring.rename.type.GrailsTypeRenameRefactoring; import org.grails.ide.eclipse.refactoring.rename.type.ITypeRenaming; import org.grails.ide.eclipse.ui.internal.importfixes.GrailsProjectVersionFixer; /** * @author Kris De Volder * @since 2.7 */ public class GrailsTypeRenameTest extends GrailsRefactoringTest { private static class GSPFile { final String name; final String contents; public GSPFile(IFile resource) throws IOException, CoreException { this.name = resource.getName(); this.contents = getContents(resource); } private GSPFile(String name, String contents) { this.name = name; this.contents = contents; } public GSPFile replace(String oldName, String newName) { return new GSPFile(name, contents.replace(oldName, newName)); } } @Override protected void setUp() throws Exception { super.setUp(); //clearGrailsState(); ensureDefaultGrailsVersion(GrailsVersion.MOST_RECENT); GrailsProjectVersionFixer.globalAskToConvertToGrailsProjectAnswer = true; } @Override protected void tearDown() throws Exception { super.tearDown(); } public void testImportGtunes() throws Exception { if (GrailsVersion.MOST_RECENT.isSnapshot()) { //Don't run for snapshots, too much work to create test projects for moving target return; } importZippedProject("gTunez"); ///////////////////////////////////////////////////////////////////////////////////////////////// //Check a few things about this test project checkImportedProject(); } public void testRelatedTypeDiscoveryFromDomainType() throws Exception { if (GrailsVersion.MOST_RECENT.isSnapshot()) { //Don't run for snapshots, too much work to create test projects for moving target return; } importZippedProject("gTunez"); checkImportedProject(); IType target = getType("gtunez.Song"); GrailsTypeRenameRefactoring refactoring = new GrailsTypeRenameRefactoring(target); Collection<ITypeRenaming> extras = refactoring.getExtraRenamingsComputer().getExtraRenamings(new NullProgressMonitor()); assertRenamings(extras, "gtunez.SongTests => SongTests", "gtunez.SongController => SongController", "gtunez.SongControllerTests => SongControllerTests", "gtunez.SongService => SongService", "gtunez.SongServiceTests => SongServiceTests", "gtunez.SongTagLib => SongTagLib", "gtunez.SongTagLibTests => SongTagLibTests" ); refactoring.setNewName("Banana"); extras = Arrays.asList(refactoring.getChosenAdditionalRenamings()); assertRenamings(extras, "gtunez.SongTests => BananaTests", "gtunez.SongController => BananaController", "gtunez.SongControllerTests => BananaControllerTests", "gtunez.SongService => BananaService", "gtunez.SongServiceTests => BananaServiceTests", "gtunez.SongTagLib => BananaTagLib", "gtunez.SongTagLibTests => BananaTagLibTests" ); assertTrue("update gsps", refactoring.getUpdateGSPs()); assertTrue("update services", refactoring.getUpdateServiceRefs()); deleteResource("/gTunez/test/unit/gtunez/SongTagLibTests.groovy"); refactoring.setNewName("Coconut"); extras = Arrays.asList(refactoring.getChosenAdditionalRenamings()); assertRenamings(extras, "gtunez.SongTests => CoconutTests", "gtunez.SongController => CoconutController", "gtunez.SongControllerTests => CoconutControllerTests", "gtunez.SongService => CoconutService", "gtunez.SongServiceTests => CoconutServiceTests", "gtunez.SongTagLib => CoconutTagLib" //"gtunez.SongTagLibTests => CoconutTagLibTests" should be DROPPED: it was deleted! ); } public void testPerformRefactoring() throws Exception { if (GrailsVersion.MOST_RECENT.isSnapshot()) { //Don't run for snapshots, too much work to create test projects for moving target return; } importZippedProject("gTunez"); checkImportedProject(); IType target = getType("gtunez.Song"); GrailsTypeRenameRefactoring refactoring = new GrailsTypeRenameRefactoring(target); refactoring.setNewName("Banana"); IFolder oldGspFolder = project.getFolder("grails-app/views/song"); assertTrue(oldGspFolder.exists()); //Exists before the refactoring List<GSPFile> gspFiles = getGSPFiles(oldGspFolder); assertFalse(gspFiles.isEmpty()); //Should be some there or its not much of a test! String extraControllerContents = "package gtunez\n" + "\n" + "class ExtraController {\n" + " def index() { \n" + " redirect(controller: \"song\", action: \"index\")\n" + " }\n" + "}"; createResource(project, "grails-app/controllers/gtunez/ExtraController.groovy", extraControllerContents); RefactoringStatus status = performRefactoring(refactoring, true, false); assertOK(status); // Now check whether the all the changes we think are supposed to happen did happen. //Check the files have moved and the types renamed. assertRenamingsPerformed("Song", "Banana", "gtunez.Song", "gtunez.SongTests", "gtunez.SongController", "gtunez.SongControllerTests", "gtunez.SongService", "gtunez.SongServiceTests", "gtunez.SongTagLib", "gtunez.SongTagLibTests" ); IFolder newGspFolder = project.getFolder("grails-app/views/banana"); assertRenamedGSPFiles(newGspFolder, gspFiles, "Song", "Banana" ); //Checking a few of the more interesting files... but not all of them assertFile("/gTunez/grails-app/domain/gtunez/Banana.groovy", "package gtunez\n" + "\n" + "class Banana {\n" + " \n" + " static Banana example = null\n" + " static BananaController myController\n" + " \n" + " def bananaService\n" + " \n" + " static constraints = {\n" + " }\n" + " \n" + " String title\n" + " String genre\n" + " \n" + " def play() {\n" + " bananaService.play(this)\n" + " } \n" + "}\n"); assertFile("/gTunez/grails-app/conf/BootStrap.groovy", "class BootStrap {\n" + "\n" + " def bananaService\n" + "\n" + " def init = { servletContext ->\n" + " bananaService.start()\n" + " }\n" + " def destroy = {\n" + " bananaService.stop()\n" + " }\n" + "}\n"); assertFile("/gTunez/grails-app/controllers/gtunez/ExtraController.groovy", extraControllerContents.replace( '"'+target.getElementName().toLowerCase()+'"', '"'+refactoring.getNewName().toLowerCase()+'"')); } private void assertRenamedGSPFiles(IFolder newGspFolder, List<GSPFile> oldGspFiles, String oldName, String newName) throws IOException, CoreException { for (GSPFile oldGspFile : oldGspFiles) { GSPFile expectedGspFile = oldGspFile.replace(oldName, newName) //Exceptions that we don't expect to be renamed: .replace("default: '"+newName+"'", "default: '"+oldName+"'"); GSPFile newGspFile = new GSPFile(newGspFolder.getFile(new Path(oldGspFile.name))); assertEqualLines(expectedGspFile.contents, newGspFile.contents); } } private List<GSPFile> getGSPFiles(final IFolder oldGspFolder) throws CoreException { final List<GSPFile> result = new ArrayList<GrailsTypeRenameTest.GSPFile>(); oldGspFolder.accept(new IResourceVisitor() { public boolean visit(IResource resource) throws CoreException { if (resource.getName().endsWith(".gsp")) { try { result.add(new GSPFile((IFile)resource)); } catch (IOException e) { throw new Error(e); } } return resource.equals(oldGspFolder); } }); return result; } /** * Checks whether Files and Types were renamed as expected. */ private void assertRenamingsPerformed(String oldName, String newName, String... fqOldNames) throws JavaModelException { final IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot(); IJavaProject jp = JavaCore.create(project); for (String fqOldName : fqOldNames) { assertTrue(fqOldName.contains(oldName)); String fqNewName = fqOldName.replace(oldName, newName); IType newType = jp.findType(fqNewName); assertNotNull(fqNewName, newType); assertNull(fqOldName, jp.findType(fqOldName)); IResource newRsrc = newType.getCompilationUnit().getCorrespondingResource(); IPath newPath = newRsrc.getFullPath(); assertTrue(newRsrc instanceof IFile); assertTrue(newRsrc.exists()); IPath oldPath = newPath.removeLastSegments(1).append(newPath.segment(newPath.segmentCount()-1).replace(newName, oldName)); IFile oldRsrc = root.getFile(oldPath); assertFalse(oldRsrc.exists()); } } private void assertOK(RefactoringStatus status) { if (!status.isOK()) { fail(status.getEntryWithHighestSeverity().getMessage()); } } private void assertRenamings(Collection<ITypeRenaming> extras, String... _expected) { HashSet<String> expected = new HashSet<String>(); for (String string : _expected) { expected.add(string); } StringBuffer unexpected = new StringBuffer(); for (ITypeRenaming renaming : extras) { String label = renaming.getTarget().getFullyQualifiedName() + " => "+renaming.getNewName(); if (expected.contains(label)) { expected.remove(label); //Seen it now! } else { unexpected.append(label+"\n"); } } assertEquals("Unexpected renamings", "", unexpected.toString()); if (!expected.isEmpty()) { StringBuffer missing = new StringBuffer(); for (String string : expected) { missing.append(string+"\n"); } fail("Missing renamings\n"+missing); } } /** * Line-based version of junit.framework.Assert.assertEquals(String, String) * without considering line delimiters. * @param expected the expected value * @param actual the actual value */ public static void assertEqualLines(String expected, String actual) { assertEqualLines("", expected, actual); } public static String getContents(IFile file) throws IOException, CoreException { return getContents(file.getContents()); } public static String getContents(InputStream in) throws IOException { BufferedReader br= new BufferedReader(new InputStreamReader(in)); StringBuffer sb= new StringBuffer(300); try { int read= 0; while ((read= br.read()) != -1) sb.append((char) read); } finally { br.close(); } return sb.toString(); } /** * Line-based version of junit.framework.Assert.assertEquals(String, String, String) * without considering line delimiters. * @param message the message * @param expected the expected value * @param actual the actual value */ public static void assertEqualLines(String message, String expected, String actual) { String[] expectedLines= Strings.convertIntoLines(expected); String[] actualLines= Strings.convertIntoLines(actual); String expected2= (expectedLines == null ? null : Strings.concatenate(expectedLines, "\n")); String actual2= (actualLines == null ? null : Strings.concatenate(actualLines, "\n")); assertEquals(message, expected2, actual2); } }