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:
parent
6de0d0163b
commit
fb6cf9f410
5 changed files with 159 additions and 68 deletions
|
@ -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} */
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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$
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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$
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue