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.
|
||||
*/
|
||||
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} */
|
||||
|
|
|
@ -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 <cstdio>
|
||||
//====================
|
||||
// RenamedClass.cpp
|
||||
//#include "RenamedClass.h"
|
||||
// renamed-class.cpp
|
||||
//#include "renamed-class.h"
|
||||
//
|
||||
//#include <cstdio>
|
||||
|
||||
// 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);
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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: <ul>
|
||||
* @param capitalization capitalization transformation applied to a name. Possible values: <ul>
|
||||
* <li>PreferenceConstants.NAME_STYLE_CAPITALIZATION_ORIGINAL,</li>
|
||||
* <li>PreferenceConstants.NAME_STYLE_CAPITALIZATION_UPPER_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
|
||||
* @return the composed name
|
||||
*/
|
||||
public String compose(String seedName) {
|
||||
public String compose(CharSequence seedName) {
|
||||
List<CharSequence> 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<CharSequence> splitIntoWords(CharSequence name) {
|
||||
List<CharSequence> words = new ArrayList<CharSequence>();
|
||||
public static List<CharSequence> splitIntoWords(CharSequence name) {
|
||||
List<CharSequence> 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<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