From fb6cf9f4105b7054aa766c741aa670cd2ac00982 Mon Sep 17 00:00:00 2001 From: Sergey Prigogin Date: Tue, 30 Dec 2014 16:05:35 -0800 Subject: [PATCH] Bug 456099. More flexible file name matching algorithm. --- .../refactoring/RefactoringTestBase.java | 2 +- .../RenameMoveHeaderRefactoringTest.java | 40 +++---- .../rename/CRenameClassProcessor.java | 80 +++++++------- .../rename/HeaderFileReferenceAdjuster.java | 5 +- .../cdt/internal/ui/util/NameComposer.java | 100 ++++++++++++++++-- 5 files changed, 159 insertions(+), 68 deletions(-) diff --git a/core/org.eclipse.cdt.ui.tests/ui/org/eclipse/cdt/ui/tests/refactoring/RefactoringTestBase.java b/core/org.eclipse.cdt.ui.tests/ui/org/eclipse/cdt/ui/tests/refactoring/RefactoringTestBase.java index d7583cc6c8b..f7c1cc35dd7 100644 --- a/core/org.eclipse.cdt.ui.tests/ui/org/eclipse/cdt/ui/tests/refactoring/RefactoringTestBase.java +++ b/core/org.eclipse.cdt.ui.tests/ui/org/eclipse/cdt/ui/tests/refactoring/RefactoringTestBase.java @@ -58,7 +58,7 @@ import org.eclipse.cdt.internal.ui.refactoring.CRefactoringContext; * Common base for refactoring tests. */ public abstract class RefactoringTestBase extends BaseTestCase { - private static final Pattern FILENAME_PATTERN = Pattern.compile("^(\\w+/)*\\w+\\.\\w+$"); + private static final Pattern FILENAME_PATTERN = Pattern.compile("((\\w|_|-)+/)*(\\w|_|-)+\\.\\w+"); /** Allows empty files to be created during test setup. */ protected boolean createEmptyFiles = true; /** See {@link PreferenceConstants.CLASS_MEMBER_ASCENDING_VISIBILITY_ORDER} */ diff --git a/core/org.eclipse.cdt.ui.tests/ui/org/eclipse/cdt/ui/tests/refactoring/rename/RenameMoveHeaderRefactoringTest.java b/core/org.eclipse.cdt.ui.tests/ui/org/eclipse/cdt/ui/tests/refactoring/rename/RenameMoveHeaderRefactoringTest.java index cab94c97026..06a739be753 100644 --- a/core/org.eclipse.cdt.ui.tests/ui/org/eclipse/cdt/ui/tests/refactoring/rename/RenameMoveHeaderRefactoringTest.java +++ b/core/org.eclipse.cdt.ui.tests/ui/org/eclipse/cdt/ui/tests/refactoring/rename/RenameMoveHeaderRefactoringTest.java @@ -277,44 +277,44 @@ public class RenameMoveHeaderRefactoringTest extends RefactoringTestBase { compareFiles(); } - // OriginalClass.h - //#ifndef ORIGINALCLASS_H_ - //#define ORIGINALCLASS_H_ + // original-class.h + //#ifndef ORIGINAL_CLASS_H_ + //#define ORIGINAL_CLASS_H_ // //class OriginalClass {}; // - //#endif // ORIGINALCLASS_H_ + //#endif // ORIGINAL_CLASS_H_ //==================== - // RenamedClass.h - //#ifndef RENAMEDCLASS_H_ - //#define RENAMEDCLASS_H_ + // renamed-class.h + //#ifndef RENAMED_CLASS_H_ + //#define RENAMED_CLASS_H_ // //class RenamedClass {}; // - //#endif // RENAMEDCLASS_H_ + //#endif // RENAMED_CLASS_H_ - // OriginalClass.cpp - //#include "OriginalClass.h" + // original-class.cpp + //#include "original-class.h" // //#include //==================== - // RenamedClass.cpp - //#include "RenamedClass.h" + // renamed-class.cpp + //#include "renamed-class.h" // //#include - // OriginalClass_test.cpp - //#include "OriginalClass.h" + // original-class_test.cpp + //#include "original-class.h" //==================== - // RenamedClass_test.cpp - //#include "RenamedClass.h" + // renamed-class_test.cpp + //#include "renamed-class.h" - // SomeOtherFile.cpp - //#include "OriginalClass.h" + // some-other-file.cpp + //#include "original-class.h" ///*$*/OriginalClass/*$$*/ a; //==================== - // SomeOtherFile.cpp - //#include "RenamedClass.h" + // some-other-file.cpp + //#include "renamed-class.h" //RenamedClass a; public void testClassRename() throws Exception { executeRenameRefactoring("RenamedClass", true); diff --git a/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/ui/refactoring/rename/CRenameClassProcessor.java b/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/ui/refactoring/rename/CRenameClassProcessor.java index 229aeecbce7..54e67bc39bb 100644 --- a/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/ui/refactoring/rename/CRenameClassProcessor.java +++ b/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/ui/refactoring/rename/CRenameClassProcessor.java @@ -46,10 +46,9 @@ import org.eclipse.cdt.core.parser.util.ArrayUtil; import org.eclipse.cdt.ui.CUIPlugin; import org.eclipse.cdt.ui.PreferenceConstants; -import org.eclipse.cdt.internal.ui.editor.SourceHeaderPartnerFinder; import org.eclipse.cdt.internal.ui.refactoring.changes.CCompositeChange; import org.eclipse.cdt.internal.ui.refactoring.changes.RenameTranslationUnitChange; -import org.eclipse.cdt.internal.ui.wizards.filewizard.NewSourceFileGenerator; +import org.eclipse.cdt.internal.ui.util.NameComposer; /** * Processor adding constructor and destructor to the bindings to be renamed. @@ -130,51 +129,57 @@ public class CRenameClassProcessor extends CRenameTypeProcessor { if (fullPath == null) return; IPath headerPath = new Path(fullPath); - String className = binding.getName(); - String headerName = NewSourceFileGenerator.generateHeaderFileNameFromClass(className); - if (!headerPath.lastSegment().equals(headerName)) - return; IResource file = ResourcesPlugin.getWorkspace().getRoot().findMember(headerPath); if (file == null || file.getType() != IResource.FILE) return; - String newClassName = getReplacementText(); - String newHeaderName = NewSourceFileGenerator.generateHeaderFileNameFromClass(newClassName); + + IProject project = getProject(); + int headerCapitalization = PreferenceConstants.getPreference( + PreferenceConstants.NAME_STYLE_CPP_HEADER_CAPITALIZATION, project, + PreferenceConstants.NAME_STYLE_CAPITALIZATION_ORIGINAL); + String headerWordDelimiter = PreferenceConstants.getPreference( + PreferenceConstants.NAME_STYLE_CPP_HEADER_WORD_DELIMITER, project, ""); //$NON-NLS-1$ + int sourceCapitalization = PreferenceConstants.getPreference( + PreferenceConstants.NAME_STYLE_CPP_SOURCE_CAPITALIZATION, project, + PreferenceConstants.NAME_STYLE_CAPITALIZATION_ORIGINAL); + String sourceWordDelimiter = PreferenceConstants.getPreference( + PreferenceConstants.NAME_STYLE_CPP_SOURCE_WORD_DELIMITER, project, ""); //$NON-NLS-1$ + + String headerName = headerPath.lastSegment(); + String className = binding.getName(); + NameComposer nameComposer = NameComposer.createByExample(className, headerName, + headerCapitalization, headerWordDelimiter); + if (nameComposer == null) + return; + + String newClassName = getReplacementText(); + String newHeaderName = nameComposer.compose(newClassName); if (!newHeaderName.equals(headerName)) { renameTranslationUnit((IFile) file, newHeaderName); } - String sourceName = NewSourceFileGenerator.generateSourceFileNameFromClass(className); - String testName = NewSourceFileGenerator.generateTestFileNameFromClass(className); - String[] partnerFileSuffixes = getPartnerFileSuffixes(); + IIndexInclude[] includedBy = index.findIncludedBy(names[0].getFile()); for (IIndexInclude include : includedBy) { location = include.getIncludedByLocation(); fullPath = location.getFullPath(); if (fullPath == null) continue; - IPath sourcePath = new Path(fullPath); - file = ResourcesPlugin.getWorkspace().getRoot().findMember(sourcePath); + IPath filePath = new Path(fullPath); + file = ResourcesPlugin.getWorkspace().getRoot().findMember(filePath); if (file != null && file.getType() == IResource.FILE) { - if (sourcePath.lastSegment().equals(sourceName)) { - String newName = NewSourceFileGenerator.generateSourceFileNameFromClass(newClassName); - if (!newName.equals(sourceName)) { + String fileName = filePath.lastSegment(); + if (CoreModel.isValidHeaderUnitName(project, fileName)) { + nameComposer = NameComposer.createByExample(className, fileName, + headerCapitalization, headerWordDelimiter); + } else { + nameComposer = NameComposer.createByExample(className, fileName, + sourceCapitalization, sourceWordDelimiter); + } + if (nameComposer != null) { + String newName = nameComposer.compose(newClassName); + if (!newName.equals(fileName)) { renameTranslationUnit((IFile) file, newName); } - } else if (sourcePath.lastSegment().equals(testName)) { - String newName = NewSourceFileGenerator.generateTestFileNameFromClass(newClassName); - file = ResourcesPlugin.getWorkspace().getRoot().findMember(sourcePath); - if (!newName.equals(testName)) { - renameTranslationUnit((IFile) file, newName); - } - } else if (SourceHeaderPartnerFinder.isPartnerFile(sourcePath, headerPath, partnerFileSuffixes)) { - String name = sourcePath.lastSegment(); - String baseName = headerPath.removeFileExtension().lastSegment(); - if (name.startsWith(baseName)) { - String newBaseName = new Path(headerName).removeFileExtension().lastSegment(); - String newName = newBaseName + name.substring(baseName.length()); - if (!newName.equals(name)) { - renameTranslationUnit((IFile) file, newName); - } - } } } } @@ -196,11 +201,10 @@ public class CRenameClassProcessor extends CRenameTypeProcessor { } } - private String[] getPartnerFileSuffixes() { - IFile file= getArgument().getSourceFile(); - IProject project = file == null ? null : file.getProject(); - String value = PreferenceConstants.getPreference( - PreferenceConstants.INCLUDES_PARTNER_FILE_SUFFIXES, project, ""); //$NON-NLS-1$ - return value.split(","); //$NON-NLS-1$ + protected IProject getProject() { + IFile file= getArgument().getSourceFile(); + if (file == null) + return null; + return file.getProject(); } } diff --git a/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/ui/refactoring/rename/HeaderFileReferenceAdjuster.java b/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/ui/refactoring/rename/HeaderFileReferenceAdjuster.java index 388d72492c4..3e8b0bcb3d5 100644 --- a/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/ui/refactoring/rename/HeaderFileReferenceAdjuster.java +++ b/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/ui/refactoring/rename/HeaderFileReferenceAdjuster.java @@ -486,11 +486,12 @@ public class HeaderFileReferenceAdjuster { } private static boolean areIncludeGuardsAffected(IFile oldfile, IFile newFile) { + IProject project = oldfile.getProject(); String filename = oldfile.getLocation().lastSegment(); - if (!CoreModel.isValidHeaderUnitName(null, filename)) + if (!CoreModel.isValidHeaderUnitName(project, filename)) return false; IPreferencesService preferences = Platform.getPreferencesService(); - IScopeContext[] scopes = PreferenceConstants.getPreferenceScopes(oldfile.getProject()); + IScopeContext[] scopes = PreferenceConstants.getPreferenceScopes(project); int scheme = preferences.getInt(CUIPlugin.PLUGIN_ID, PreferenceConstants.CODE_TEMPLATES_INCLUDE_GUARD_SCHEME, PreferenceConstants.CODE_TEMPLATES_INCLUDE_GUARD_SCHEME_FILE_NAME, scopes); diff --git a/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/ui/util/NameComposer.java b/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/ui/util/NameComposer.java index 133d216cdbe..394ab247a2e 100644 --- a/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/ui/util/NameComposer.java +++ b/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/ui/util/NameComposer.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2011 Google, Inc and others. + * Copyright (c) 2011, 2014 Google, Inc and others. * 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 @@ -31,16 +31,23 @@ public class NameComposer { private static final int CAPITALIZATION_LOWER_CASE = PreferenceConstants.NAME_STYLE_CAPITALIZATION_LOWER_CASE; private static final int CAPITALIZATION_CAMEL_CASE = PreferenceConstants.NAME_STYLE_CAPITALIZATION_CAMEL_CASE; private static final int CAPITALIZATION_LOWER_CAMEL_CASE = PreferenceConstants.NAME_STYLE_CAPITALIZATION_LOWER_CAMEL_CASE; + private static final int[] ALL_CAPITALIZATIONS = { + CAPITALIZATION_ORIGINAL, + CAPITALIZATION_UPPER_CASE, + CAPITALIZATION_LOWER_CASE, + CAPITALIZATION_CAMEL_CASE, + CAPITALIZATION_LOWER_CAMEL_CASE, + }; - private final int capitalization; + private int capitalization; private final String wordDelimiter; private final String prefix; - private final String suffix; + private String suffix; /** * Creates a name composer for a given style. * - * @param capitalization capitalization transformation applied to name. Possible values:
    + * @param capitalization capitalization transformation applied to a name. Possible values:
      *
    • PreferenceConstants.NAME_STYLE_CAPITALIZATION_ORIGINAL,
    • *
    • PreferenceConstants.NAME_STYLE_CAPITALIZATION_UPPER_CASE,
    • *
    • PreferenceConstants.NAME_STYLE_CAPITALIZATION_LOWER_CASE,
    • @@ -64,7 +71,7 @@ public class NameComposer { * @param seedName the name used as an inspiration * @return the composed name */ - public String compose(String seedName) { + public String compose(CharSequence seedName) { List words = splitIntoWords(seedName); return compose(words); } @@ -112,8 +119,8 @@ public class NameComposer { /** * Splits a name into words at non-alphanumeric characters and camel case boundaries. */ - public List splitIntoWords(CharSequence name) { - List words = new ArrayList(); + public static List splitIntoWords(CharSequence name) { + List words = new ArrayList<>(); CBreakIterator iterator = new CBreakIterator(); iterator.setText(name); int end; @@ -146,4 +153,83 @@ public class NameComposer { Character.toUpperCase(word.charAt(i)) : Character.toLowerCase(word.charAt(i))); } } + + /** + * Creates a NameComposer such that it would compose {@code composedName} given {@code seedName} + * as a seed. + * + * @param seedName the seed name + * @param composedName the composed name + * @param defaultCapitalization used to disambiguate capitalization if it cannot be uniquely + * determined from the composed name + * @param defaultWordDelimiter used to disambiguate word delimiter if it cannot be uniquely + * determined from the composed name + * @return a name composer based on the composed name, or {@code null} if such name composer + * does not exist + */ + public static NameComposer createByExample(String seedName, String composedName, + int defaultCapitalization, String defaultWordDelimiter) { + List seedWords = splitIntoWords(seedName); + if (seedWords.isEmpty()) + return null; + List composedWords = splitIntoWords(composedName); + int numPrefixWords = indexOfSublistIgnoreCase(composedWords, seedWords); + if (numPrefixWords < 0) + return null; + String prefix = deducePrefix(composedName, numPrefixWords); + String delimiter = defaultWordDelimiter; + if (seedWords.size() > 1) { + delimiter = ""; //$NON-NLS-1$ + int start = prefix.length() + composedWords.get(0).length(); + for (int i = start; i < composedName.length(); i++) { + if (Character.isLetterOrDigit(composedName.charAt(i))) { + delimiter = composedName.substring(start, i); + break; + } + } + } + NameComposer composer = new NameComposer(defaultCapitalization, delimiter, prefix, ""); //$NON-NLS-1$ + for (int i = -1; i < ALL_CAPITALIZATIONS.length; i++) { + if (i >= 0) + composer.capitalization = ALL_CAPITALIZATIONS[i]; + String name = composer.compose(seedWords); + if (composedName.startsWith(name)) { + composer.suffix = composedName.substring(name.length()); + return composer; + } + } + return null; + } + + private static int indexOfSublistIgnoreCase(List list, List subList) { + int subListSize = subList.size(); + int limit = list.size() - subListSize; + + outer: + for (int k = 0; k <= limit; k++) { + for (int i = 0, j = k; i < subListSize; i++, j++) { + if (!subList.get(i).toString().equalsIgnoreCase(list.get(j).toString())) + continue outer; + } + return k; + } + return -1; + } + + private static String deducePrefix(CharSequence name, int numPrefixWords) { + CBreakIterator iterator = new CBreakIterator(); + iterator.setText(name); + int end; + int wordCount = 0; + for (int start = iterator.first(); (end = iterator.next()) != BreakIterator.DONE; start = end) { + if (Character.isLetterOrDigit(name.charAt(start))) { + if (wordCount == numPrefixWords) + return name.subSequence(0, start).toString(); + wordCount++; + } + } + if (wordCount == numPrefixWords) + return name.toString(); + throw new IllegalArgumentException(numPrefixWords + " is larger than the number of words in \"" + name + "\""); //$NON-NLS-1$//$NON-NLS-2$ + } }