1
0
Fork 0
mirror of https://github.com/eclipse-cdt/cdt synced 2025-04-29 19:45:01 +02:00

Bug 456099. More flexible file name matching algorithm.

This commit is contained in:
Sergey Prigogin 2014-12-30 16:05:35 -08:00
parent 6de0d0163b
commit fb6cf9f410
5 changed files with 159 additions and 68 deletions

View file

@ -58,7 +58,7 @@ import org.eclipse.cdt.internal.ui.refactoring.CRefactoringContext;
* Common base for refactoring tests. * Common base for refactoring tests.
*/ */
public abstract class RefactoringTestBase extends BaseTestCase { 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. */ /** Allows empty files to be created during test setup. */
protected boolean createEmptyFiles = true; protected boolean createEmptyFiles = true;
/** See {@link PreferenceConstants.CLASS_MEMBER_ASCENDING_VISIBILITY_ORDER} */ /** See {@link PreferenceConstants.CLASS_MEMBER_ASCENDING_VISIBILITY_ORDER} */

View file

@ -277,44 +277,44 @@ public class RenameMoveHeaderRefactoringTest extends RefactoringTestBase {
compareFiles(); compareFiles();
} }
// OriginalClass.h // original-class.h
//#ifndef ORIGINALCLASS_H_ //#ifndef ORIGINAL_CLASS_H_
//#define ORIGINALCLASS_H_ //#define ORIGINAL_CLASS_H_
// //
//class OriginalClass {}; //class OriginalClass {};
// //
//#endif // ORIGINALCLASS_H_ //#endif // ORIGINAL_CLASS_H_
//==================== //====================
// RenamedClass.h // renamed-class.h
//#ifndef RENAMEDCLASS_H_ //#ifndef RENAMED_CLASS_H_
//#define RENAMEDCLASS_H_ //#define RENAMED_CLASS_H_
// //
//class RenamedClass {}; //class RenamedClass {};
// //
//#endif // RENAMEDCLASS_H_ //#endif // RENAMED_CLASS_H_
// OriginalClass.cpp // original-class.cpp
//#include "OriginalClass.h" //#include "original-class.h"
// //
//#include <cstdio> //#include <cstdio>
//==================== //====================
// RenamedClass.cpp // renamed-class.cpp
//#include "RenamedClass.h" //#include "renamed-class.h"
// //
//#include <cstdio> //#include <cstdio>
// OriginalClass_test.cpp // original-class_test.cpp
//#include "OriginalClass.h" //#include "original-class.h"
//==================== //====================
// RenamedClass_test.cpp // renamed-class_test.cpp
//#include "RenamedClass.h" //#include "renamed-class.h"
// SomeOtherFile.cpp // some-other-file.cpp
//#include "OriginalClass.h" //#include "original-class.h"
///*$*/OriginalClass/*$$*/ a; ///*$*/OriginalClass/*$$*/ a;
//==================== //====================
// SomeOtherFile.cpp // some-other-file.cpp
//#include "RenamedClass.h" //#include "renamed-class.h"
//RenamedClass a; //RenamedClass a;
public void testClassRename() throws Exception { public void testClassRename() throws Exception {
executeRenameRefactoring("RenamedClass", true); executeRenameRefactoring("RenamedClass", true);

View file

@ -46,10 +46,9 @@ import org.eclipse.cdt.core.parser.util.ArrayUtil;
import org.eclipse.cdt.ui.CUIPlugin; import org.eclipse.cdt.ui.CUIPlugin;
import org.eclipse.cdt.ui.PreferenceConstants; 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.CCompositeChange;
import org.eclipse.cdt.internal.ui.refactoring.changes.RenameTranslationUnitChange; 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. * Processor adding constructor and destructor to the bindings to be renamed.
@ -130,51 +129,57 @@ public class CRenameClassProcessor extends CRenameTypeProcessor {
if (fullPath == null) if (fullPath == null)
return; return;
IPath headerPath = new Path(fullPath); 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); IResource file = ResourcesPlugin.getWorkspace().getRoot().findMember(headerPath);
if (file == null || file.getType() != IResource.FILE) if (file == null || file.getType() != IResource.FILE)
return; 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)) { if (!newHeaderName.equals(headerName)) {
renameTranslationUnit((IFile) file, newHeaderName); renameTranslationUnit((IFile) file, newHeaderName);
} }
String sourceName = NewSourceFileGenerator.generateSourceFileNameFromClass(className);
String testName = NewSourceFileGenerator.generateTestFileNameFromClass(className);
String[] partnerFileSuffixes = getPartnerFileSuffixes();
IIndexInclude[] includedBy = index.findIncludedBy(names[0].getFile()); IIndexInclude[] includedBy = index.findIncludedBy(names[0].getFile());
for (IIndexInclude include : includedBy) { for (IIndexInclude include : includedBy) {
location = include.getIncludedByLocation(); location = include.getIncludedByLocation();
fullPath = location.getFullPath(); fullPath = location.getFullPath();
if (fullPath == null) if (fullPath == null)
continue; continue;
IPath sourcePath = new Path(fullPath); IPath filePath = new Path(fullPath);
file = ResourcesPlugin.getWorkspace().getRoot().findMember(sourcePath); file = ResourcesPlugin.getWorkspace().getRoot().findMember(filePath);
if (file != null && file.getType() == IResource.FILE) { if (file != null && file.getType() == IResource.FILE) {
if (sourcePath.lastSegment().equals(sourceName)) { String fileName = filePath.lastSegment();
String newName = NewSourceFileGenerator.generateSourceFileNameFromClass(newClassName); if (CoreModel.isValidHeaderUnitName(project, fileName)) {
if (!newName.equals(sourceName)) { 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); 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() { protected IProject getProject() {
IFile file= getArgument().getSourceFile(); IFile file= getArgument().getSourceFile();
IProject project = file == null ? null : file.getProject(); if (file == null)
String value = PreferenceConstants.getPreference( return null;
PreferenceConstants.INCLUDES_PARTNER_FILE_SUFFIXES, project, ""); //$NON-NLS-1$ return file.getProject();
return value.split(","); //$NON-NLS-1$
} }
} }

View file

@ -486,11 +486,12 @@ public class HeaderFileReferenceAdjuster {
} }
private static boolean areIncludeGuardsAffected(IFile oldfile, IFile newFile) { private static boolean areIncludeGuardsAffected(IFile oldfile, IFile newFile) {
IProject project = oldfile.getProject();
String filename = oldfile.getLocation().lastSegment(); String filename = oldfile.getLocation().lastSegment();
if (!CoreModel.isValidHeaderUnitName(null, filename)) if (!CoreModel.isValidHeaderUnitName(project, filename))
return false; return false;
IPreferencesService preferences = Platform.getPreferencesService(); IPreferencesService preferences = Platform.getPreferencesService();
IScopeContext[] scopes = PreferenceConstants.getPreferenceScopes(oldfile.getProject()); IScopeContext[] scopes = PreferenceConstants.getPreferenceScopes(project);
int scheme = preferences.getInt(CUIPlugin.PLUGIN_ID, int scheme = preferences.getInt(CUIPlugin.PLUGIN_ID,
PreferenceConstants.CODE_TEMPLATES_INCLUDE_GUARD_SCHEME, PreferenceConstants.CODE_TEMPLATES_INCLUDE_GUARD_SCHEME,
PreferenceConstants.CODE_TEMPLATES_INCLUDE_GUARD_SCHEME_FILE_NAME, scopes); PreferenceConstants.CODE_TEMPLATES_INCLUDE_GUARD_SCHEME_FILE_NAME, scopes);

View file

@ -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 * All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0 * are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at * 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_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_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 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 wordDelimiter;
private final String prefix; private final String prefix;
private final String suffix; private String suffix;
/** /**
* Creates a name composer for a given style. * Creates a name composer for a given style.
* *
* @param capitalization capitalization transformation applied to name. Possible values: <ul> * @param capitalization capitalization transformation applied to a name. Possible values: <ul>
* <li>PreferenceConstants.NAME_STYLE_CAPITALIZATION_ORIGINAL,</li> * <li>PreferenceConstants.NAME_STYLE_CAPITALIZATION_ORIGINAL,</li>
* <li>PreferenceConstants.NAME_STYLE_CAPITALIZATION_UPPER_CASE,</li> * <li>PreferenceConstants.NAME_STYLE_CAPITALIZATION_UPPER_CASE,</li>
* <li>PreferenceConstants.NAME_STYLE_CAPITALIZATION_LOWER_CASE,</li> * <li>PreferenceConstants.NAME_STYLE_CAPITALIZATION_LOWER_CASE,</li>
@ -64,7 +71,7 @@ public class NameComposer {
* @param seedName the name used as an inspiration * @param seedName the name used as an inspiration
* @return the composed name * @return the composed name
*/ */
public String compose(String seedName) { public String compose(CharSequence seedName) {
List<CharSequence> words = splitIntoWords(seedName); List<CharSequence> words = splitIntoWords(seedName);
return compose(words); return compose(words);
} }
@ -112,8 +119,8 @@ public class NameComposer {
/** /**
* Splits a name into words at non-alphanumeric characters and camel case boundaries. * Splits a name into words at non-alphanumeric characters and camel case boundaries.
*/ */
public List<CharSequence> splitIntoWords(CharSequence name) { public static List<CharSequence> splitIntoWords(CharSequence name) {
List<CharSequence> words = new ArrayList<CharSequence>(); List<CharSequence> words = new ArrayList<>();
CBreakIterator iterator = new CBreakIterator(); CBreakIterator iterator = new CBreakIterator();
iterator.setText(name); iterator.setText(name);
int end; int end;
@ -146,4 +153,83 @@ public class NameComposer {
Character.toUpperCase(word.charAt(i)) : Character.toLowerCase(word.charAt(i))); 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<CharSequence> seedWords = splitIntoWords(seedName);
if (seedWords.isEmpty())
return null;
List<CharSequence> 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<CharSequence> list, List<CharSequence> 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$
}
} }