]> git.uio.no Git - ifi-stolz-refaktor.git/blame - case-study/jdt-after/core refactoring/org/eclipse/jdt/internal/corext/refactoring/rename/RenamingNameSuggestor.java
Case Study: adding data and statistics
[ifi-stolz-refaktor.git] / case-study / jdt-after / core refactoring / org / eclipse / jdt / internal / corext / refactoring / rename / RenamingNameSuggestor.java
CommitLineData
1b2798f6
EK
1/*******************************************************************************
2 * Copyright (c) 2000, 2011 IBM Corporation and others.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the Eclipse Public License v1.0
5 * which accompanies this distribution, and is available at
6 * http://www.eclipse.org/legal/epl-v10.html
7 *
8 * Contributors:
9 * IBM Corporation - initial API and implementation
10 *******************************************************************************/
11package org.eclipse.jdt.internal.corext.refactoring.rename;
12
13import java.util.ArrayList;
14import java.util.List;
15
16import org.eclipse.core.runtime.Assert;
17
18import org.eclipse.jdt.core.IJavaProject;
19import org.eclipse.jdt.core.JavaCore;
20
21import org.eclipse.jdt.internal.ui.text.JavaWordIterator;
22
23/**
24 * This class contains methods for suggesting new names for variables or methods
25 * whose name consists at least partly of the name of their declaring type (or
26 * in case of methods, the return type or a parameter type).
27 *
28 * The methods return the newly suggested method or variable name in case of a
29 * match, or null in case nothing matched.
30 *
31 * In any case, prefixes and suffixes are removed from variable names. As method
32 * names have no configurable suffixes or prefixes, they are left unchanged. The
33 * remaining name is called "stripped element name".
34 *
35 * After the match according to the strategy, prefixes and suffixes are
36 * reapplied to the names.
37 *
38 * EXACT STRATEGY (always performed).
39 * ----------------------------------------------------------------
40 *
41 * The stripped element name is directly compared with the type name:
42 *
43 * a) the first character must match case-insensitive
44 *
45 * b) all other characters must match case-sensitive
46 *
47 * In case of a match, the new type name is returned (first character adapted,
48 * respectively). Suffixes/Prefixes are reapplied.
49 *
50 * Note that this also matches fields with names like "SomeField", "fsomeField",
51 * and method names like "JavaElement()".
52 *
53 * EMBEDDED STRATEGY (performed second if chosen by user).
54 * ----------------------------------------------------------------
55 *
56 * A search is performed in the stripped element name for the old type name:
57 *
58 * a) the first character must match case-insensitive
59 *
60 * b) all other characters must match case-sensitive
61 *
62 * c) the stripped element name must end after the type name, or the next
63 * character must be a non-letter, or the next character must be upper cased.
64 *
65 * In case of a match, the new type is inserted into the stripped element name,
66 * replacing the old type name, first character adapted to the correct case.
67 * Suffixes/Prefixes are reapplied.
68 *
69 * Note that this also matches methods with names like "createjavaElement()" or
70 * fields like "fjavaElementCache".
71 *
72 * SUFFIX STRATEGY (performed third if chosen by user)
73 * ----------------------------------------------------------------
74 *
75 * The new and old type names are analyzed for "camel case suffixes", that is,
76 * substrings which begin with an uppercased letter. For example,
77 * "SimpleJavaElement" is split into the three hunks "Simple",
78 * "Java", and "Element". If one type name has more suffixes than the
79 * other, both are stripped to the smaller size.
80 *
81 * Then, a search is performed in the stripped variable name hunks from back to
82 * front. At least the last hunk must be found, others may then extend the match.
83 * Each hunk must match like in the exact strategy, i.e.
84 *
85 * a) the first character must match case-insensitive
86 *
87 * b) all other characters must match case-sensitive
88 *
89 * In case of a match, the matched hunks of the new type replace
90 * the hunks of the old type. Suffixes/Prefixes are reapplied.
91 *
92 * Note that numbers and other non-letter characters belong to the previous
93 * camel case substring.
94 *
95 *
96 * @since 3.2
97 *
98 */
99public class RenamingNameSuggestor {
100
101 /*
102 * ADDITIONAL OPTIONS
103 * ----------------------------------------------------------------
104 *
105 * There are two additional flags which may be set in this class to allow
106 * better matching of special cases:
107 *
108 * a) Special treatment of leading "I"s in type names, i.e. interface names
109 * like "IJavaElement". If the corresponding flag is set, leading "I"s are
110 * stripped from type names if the second char is also uppercase to allow
111 * exact matching of variable names like "javaElement" for type
112 * "IJavaElement". Note that embedded matching already matches cases like
113 * this.
114 *
115 * b) Special treatment of all-uppercase type names or all-uppercase type
116 * name camel-case hunks, i.e. names like "AST" or "PersonalURL". If the
117 * corresponding flag is set, the type name hunks will be transformed such
118 * that variables like "fAst", "ast", "personalUrl", or "url" are found as
119 * well. The target name will be transformed too if it is an
120 * all-uppercase type name camel-case hunk as well.
121 *
122 * NOTE that in exact or embedded mode, the whole type name must be
123 * all-uppercase to allow matching custom-lowercased variable names, i.e.
124 * there are no attempts to "guess" which hunk of the new name should be lowercased
125 * to match a partly lowercased variable name. In suffix mode, hunks of the
126 * new type which are at the same position as in the old type will be
127 * lowercased if necessary.
128 *
129 * c) Support for (english) plural forms. If the corresponding flag is set, the
130 * suggestor will try to match variables which have plural forms of the
131 * type name, for example "handies" for "Handy" or "phones" for "MobilePhone".
132 * The target name will be transformed as well, i.e. conversion like
133 * "fHandies" -> "fPhones" are supported.
134 *
135 */
136
137 public static final int STRATEGY_EXACT= 1;
138 public static final int STRATEGY_EMBEDDED= 2;
139 public static final int STRATEGY_SUFFIX= 3;
140
141 private static final String PLURAL_S= "s"; //$NON-NLS-1$
142 private static final String PLURAL_IES= "ies"; //$NON-NLS-1$
143 private static final String SINGULAR_Y= "y"; //$NON-NLS-1$
144
145 private int fStrategy;
146 private String[] fFieldPrefixes;
147 private String[] fFieldSuffixes;
148 private String[] fStaticFieldPrefixes;
149 private String[] fStaticFieldSuffixes;
150 private String[] fLocalPrefixes;
151 private String[] fLocalSuffixes;
152 private String[] fArgumentPrefixes;
153 private String[] fArgumentSuffixes;
154
155 private boolean fExtendedInterfaceNameMatching;
156 private boolean fExtendedAllUpperCaseHunkMatching;
157 private boolean fExtendedPluralMatching;
158
159 public RenamingNameSuggestor() {
160 this(STRATEGY_SUFFIX);
161 }
162
163 public RenamingNameSuggestor(int strategy) {
164
165 Assert.isTrue(strategy >= 1 && strategy <= 3);
166
167 fStrategy= strategy;
168 fExtendedInterfaceNameMatching= true;
169 fExtendedAllUpperCaseHunkMatching= true;
170 fExtendedPluralMatching= true;
171
172 resetPrefixes();
173 }
174
175 public String suggestNewFieldName(IJavaProject project, String oldFieldName, boolean isStatic, String oldTypeName, String newTypeName) {
176
177 initializePrefixesAndSuffixes(project);
178
179 if (isStatic)
180 return suggestNewVariableName(fStaticFieldPrefixes, fStaticFieldSuffixes, oldFieldName, oldTypeName, newTypeName);
181 else
182 return suggestNewVariableName(fFieldPrefixes, fFieldSuffixes, oldFieldName, oldTypeName, newTypeName);
183 }
184
185 public String suggestNewLocalName(IJavaProject project, String oldLocalName, boolean isArgument, String oldTypeName, String newTypeName) {
186
187 initializePrefixesAndSuffixes(project);
188
189 if (isArgument)
190 return suggestNewVariableName(fArgumentPrefixes, fArgumentSuffixes, oldLocalName, oldTypeName, newTypeName);
191 else
192 return suggestNewVariableName(fLocalPrefixes, fLocalSuffixes, oldLocalName, oldTypeName, newTypeName);
193 }
194
195 public String suggestNewMethodName(String oldMethodName, String oldTypeName, String newTypeName) {
196
197 Assert.isNotNull(oldMethodName);
198 Assert.isNotNull(oldTypeName);
199 Assert.isNotNull(newTypeName);
200 Assert.isTrue(oldMethodName.length() > 0);
201 Assert.isTrue(oldTypeName.length() > 0);
202 Assert.isTrue(newTypeName.length() > 0);
203
204 resetPrefixes();
205
206 return match(oldTypeName, newTypeName, oldMethodName);
207 }
208
209 public String suggestNewVariableName(String[] prefixes, String[] suffixes, String oldVariableName, String oldTypeName, String newTypeName) {
210
211 Assert.isNotNull(prefixes);
212 Assert.isNotNull(suffixes);
213 Assert.isNotNull(oldVariableName);
214 Assert.isNotNull(oldTypeName);
215 Assert.isNotNull(newTypeName);
216 Assert.isTrue(oldVariableName.length() > 0);
217 Assert.isTrue(oldTypeName.length() > 0);
218 Assert.isTrue(newTypeName.length() > 0);
219
220 final String usedPrefix= findLongestPrefix(oldVariableName, prefixes);
221 final String usedSuffix= findLongestSuffix(oldVariableName, suffixes);
222 final String strippedVariableName= oldVariableName.substring(usedPrefix.length(), oldVariableName.length() - usedSuffix.length());
223
224 String newVariableName= match(oldTypeName, newTypeName, strippedVariableName);
225 return (newVariableName != null) ? usedPrefix + newVariableName + usedSuffix : null;
226 }
227
228 // -------------------------------------- Match methods
229
230 private String match(final String oldTypeName, final String newTypeName, final String strippedVariableName) {
231
232 String oldType= oldTypeName;
233 String newType= newTypeName;
234
235 if (fExtendedInterfaceNameMatching && isInterfaceName(oldType) && isInterfaceName(newType)) {
236 oldType= getInterfaceName(oldType);
237 newType= getInterfaceName(newType);
238 }
239
240 String newVariableName= matchDirect(oldType, newType, strippedVariableName);
241
242 if (fExtendedPluralMatching && newVariableName == null && canPluralize(oldType))
243 newVariableName= matchDirect(pluralize(oldType), pluralize(newType), strippedVariableName);
244
245 return newVariableName;
246 }
247
248 private String matchDirect(String oldType, String newType, final String strippedVariableName) {
249 /*
250 * Use all strategies applied by the user. Always start with exact
251 * matching.
252 *
253 * Note that suffix matching may not match the whole type name if the
254 * new type name has a smaller camel case chunk count.
255 */
256
257 String newVariableName= exactMatch(oldType, newType, strippedVariableName);
258 if (newVariableName == null && fStrategy >= STRATEGY_EMBEDDED)
259 newVariableName= embeddedMatch(oldType, newType, strippedVariableName);
260 if (newVariableName == null && fStrategy >= STRATEGY_SUFFIX)
261 newVariableName= suffixMatch(oldType, newType, strippedVariableName);
262
263 return newVariableName;
264 }
265
266 private String exactMatch(final String oldTypeName, final String newTypeName, final String strippedVariableName) {
267
268 String newName= exactDirectMatch(oldTypeName, newTypeName, strippedVariableName);
269 if (newName != null)
270 return newName;
271
272 if (fExtendedAllUpperCaseHunkMatching && isUpperCaseCamelCaseHunk(oldTypeName)) {
273 String oldTN= getFirstUpperRestLowerCased(oldTypeName);
274 String newTN= isUpperCaseCamelCaseHunk(newTypeName) ? getFirstUpperRestLowerCased(newTypeName) : newTypeName;
275 newName= exactDirectMatch(oldTN, newTN, strippedVariableName);
276 }
277
278 return newName;
279 }
280
281 private String exactDirectMatch(final String oldTypeName, final String newTypeName, final String strippedVariableName) {
282
283 if (strippedVariableName.equals(oldTypeName))
284 return newTypeName;
285
286 if (strippedVariableName.equals(getLowerCased(oldTypeName)))
287 return getLowerCased(newTypeName);
288
289 return null;
290 }
291
292 private String embeddedMatch(String oldTypeName, String newTypeName, String strippedVariableName) {
293
294 // possibility of a match?
295 final String lowerCaseVariable= strippedVariableName.toLowerCase();
296 final String lowerCaseOldTypeName= oldTypeName.toLowerCase();
297 int presumedIndex= lowerCaseVariable.indexOf(lowerCaseOldTypeName);
298
299 while (presumedIndex != -1) {
300 // it may be there
301 final String presumedTypeName= strippedVariableName.substring(presumedIndex, presumedIndex + oldTypeName.length());
302 final String prefix= strippedVariableName.substring(0, presumedIndex);
303 final String suffix= strippedVariableName.substring(presumedIndex + oldTypeName.length());
304
305 // can match at all? (depends on suffix)
306 if (startsNewHunk(suffix)) {
307
308 String name= exactMatch(oldTypeName, newTypeName, presumedTypeName);
309 if (name != null)
310 return prefix + name + suffix;
311 }
312
313 // did not match -> find next occurrence
314 presumedIndex= lowerCaseVariable.indexOf(lowerCaseOldTypeName, presumedIndex + 1);
315 }
316
317 return null;
318 }
319
320 private String suffixMatch(final String oldType, final String newType, final String strippedVariableName) {
321
322 // get an array of all camel-cased elements from both types + the
323 // variable
324 String[] suffixesOld= getSuffixes(oldType);
325 String[] suffixesNew= getSuffixes(newType);
326 String[] suffixesVar= getSuffixes(strippedVariableName);
327
328 // get an equal-sized array of the last n camel-cased elements
329 int min= Math.min(suffixesOld.length, suffixesNew.length);
330 String[] suffixesOldEqual= new String[min];
331 String[] suffixesNewEqual= new String[min];
332 System.arraycopy(suffixesOld, suffixesOld.length - min, suffixesOldEqual, 0, min);
333 System.arraycopy(suffixesNew, suffixesNew.length - min, suffixesNewEqual, 0, min);
334
335 // find endIndex. endIndex is the index of the last hunk of the old type
336 // name in the variable name.
337 int endIndex= -1;
338 for (int j= suffixesVar.length - 1; j >= 0; j--) {
339 String newHunkName= exactMatch(suffixesOldEqual[suffixesOldEqual.length - 1], suffixesNewEqual[suffixesNewEqual.length - 1], suffixesVar[j]);
340 if (newHunkName != null) {
341 endIndex= j;
342 break;
343 }
344 }
345
346 if (endIndex == -1)
347 return null; // last hunk not found -> no match
348
349 int stepBack= 0;
350 int lastSuffixMatched= -1;
351 int hunkInVarName= -1;
352 for (int i= suffixesOldEqual.length - 1; i >= 0; i--) {
353
354 hunkInVarName= endIndex - stepBack;
355 stepBack++;
356
357 if (hunkInVarName < 0) {
358 // we have reached the beginning of the variable name
359 break;
360 }
361
362 // try to match this hunk:
363 String newHunkName= exactMatch(suffixesOldEqual[i], suffixesNewEqual[i], suffixesVar[hunkInVarName]);
364
365 if (newHunkName == null)
366 break; // only match complete suffixes
367
368 suffixesVar[hunkInVarName]= newHunkName;
369 lastSuffixMatched= i;
370 }
371
372 if (lastSuffixMatched == 0) {
373 // we have matched ALL type hunks in the variable name,
374 // insert any new prefixes of the new type name
375 int newPrefixes= suffixesNew.length - suffixesNewEqual.length;
376 if (newPrefixes > 0) {
377
378 // Propagate lowercased start to the front
379 if (Character.isLowerCase(suffixesVar[hunkInVarName].charAt(0)) && Character.isUpperCase(suffixesOldEqual[lastSuffixMatched].charAt(0))) {
380 suffixesVar[hunkInVarName]= getUpperCased(suffixesVar[hunkInVarName]);
381 suffixesNew[0]= getLowerCased(suffixesNew[0]);
382 }
383
384 String[] newVariableName= new String[suffixesVar.length + newPrefixes];
385 System.arraycopy(suffixesVar, 0, newVariableName, 0, hunkInVarName); // hunks before type name in variable name
386 System.arraycopy(suffixesNew, 0, newVariableName, hunkInVarName, newPrefixes); // new hunks in new type name
387 System.arraycopy(suffixesVar, hunkInVarName, newVariableName, hunkInVarName + newPrefixes, suffixesVar.length - hunkInVarName); // matched + rest hunks
388 suffixesVar= newVariableName;
389 }
390 }
391
392 String varName= concat(suffixesVar);
393 if (varName.equals(strippedVariableName))
394 return null; // no "silly suggestions"
395 else
396 return varName;
397 }
398
399
400 // ---------------- Helper methods
401
402 /**
403 * True if the string is the beginning of a new camel case hunk. False if it
404 * is not.
405 */
406 private boolean startsNewHunk(String string) {
407
408 if (string.length() == 0)
409 return true;
410
411 return isLegalChar(string.charAt(0));
412 }
413
414 /**
415 * True if hunk is longer than 1 character and all letters in the hunk are
416 * uppercase. False if not.
417 */
418 private boolean isUpperCaseCamelCaseHunk(String hunk) {
419 if (hunk.length() < 2)
420 return false;
421
422 for (int i= 0; i < hunk.length(); i++) {
423 if (!isLegalChar(hunk.charAt(i)))
424 return false;
425 }
426 return true;
427 }
428
429 /**
430 * False if the character is a letter and it is lowercase. True in all other
431 * cases.
432 */
433 private boolean isLegalChar(char c) {
434 if (Character.isLetter(c))
435 return Character.isUpperCase(c);
436 return true;
437 }
438
439 /**
440 * Grab a list of camelCase-separated suffixes from the typeName, for
441 * example:
442 *
443 * "JavaElementName" => { "Java", "Element", "Name }
444 *
445 * "ASTNode" => { "AST", "Node" }
446 *
447 */
448 private String[] getSuffixes(String typeName) {
449 List<String> suffixes= new ArrayList<String>();
450 JavaWordIterator iterator= new JavaWordIterator();
451 iterator.generated_1405158282578834748(typeName, suffixes);
452 return suffixes.toArray(new String[0]);
453 }
454
455 private String concat(String[] suffixesNewEqual) {
456 StringBuffer returner= new StringBuffer();
457 for (int j= 0; j < suffixesNewEqual.length; j++) {
458 returner.append(suffixesNewEqual[j]);
459 }
460 return returner.toString();
461 }
462
463 private String getLowerCased(String name) {
464 if (name.length() > 1)
465 return Character.toLowerCase(name.charAt(0)) + name.substring(1);
466 else
467 return name.toLowerCase();
468 }
469
470 private String getUpperCased(String name) {
471 if (name.length() > 1)
472 return Character.toUpperCase(name.charAt(0)) + name.substring(1);
473 else
474 return name.toLowerCase();
475 }
476
477 private String getFirstUpperRestLowerCased(String name) {
478 if (name.length() > 1)
479 return Character.toUpperCase(name.charAt(0)) + name.substring(1).toLowerCase();
480 else
481 return name.toLowerCase();
482 }
483
484 private boolean isInterfaceName(String typeName) {
485 return ( (typeName.length() >= 2) && typeName.charAt(0) == 'I' && Character.isUpperCase(typeName.charAt(1)));
486 }
487
488 private String getInterfaceName(String typeName) {
489 return typeName.substring(1);
490 }
491
492 private String findLongestPrefix(String name, String[] prefixes) {
493 String usedPrefix= ""; //$NON-NLS-1$
494 int bestLen= 0;
495 for (int i= 0; i < prefixes.length; i++) {
496 if (name.startsWith(prefixes[i])) {
497 if (prefixes[i].length() > bestLen) {
498 bestLen= prefixes[i].length();
499 usedPrefix= prefixes[i];
500 }
501 }
502 }
503 return usedPrefix;
504 }
505
506 private String findLongestSuffix(String name, String[] suffixes) {
507 String usedPrefix= ""; //$NON-NLS-1$
508 int bestLen= 0;
509 for (int i= 0; i < suffixes.length; i++) {
510 if (name.endsWith(suffixes[i])) {
511 if (suffixes[i].length() > bestLen) {
512 bestLen= suffixes[i].length();
513 usedPrefix= suffixes[i];
514 }
515 }
516 }
517 return usedPrefix;
518 }
519
520 /**
521 * Returns true if the type name can be pluralized by a string operation.
522 * This is always the case if it does not already end with an "s".
523 */
524 private boolean canPluralize(String typeName) {
525 return !typeName.endsWith(PLURAL_S);
526 }
527
528 private String pluralize(String typeName) {
529 if (typeName.endsWith(SINGULAR_Y))
530 typeName= typeName.substring(0, typeName.length() - 1).concat(PLURAL_IES);
531 else if (!typeName.endsWith(PLURAL_S))
532 typeName= typeName.concat(PLURAL_S);
533 return typeName;
534 }
535
536 private void resetPrefixes() {
537 String[] empty= new String[0];
538 fFieldPrefixes= empty;
539 fFieldSuffixes= empty;
540 fStaticFieldPrefixes= empty;
541 fStaticFieldSuffixes= empty;
542 fLocalPrefixes= empty;
543 fLocalSuffixes= empty;
544 fArgumentPrefixes= empty;
545 fArgumentSuffixes= empty;
546 }
547
548 private void initializePrefixesAndSuffixes(IJavaProject project) {
549 fFieldPrefixes= readCommaSeparatedPreference(project, JavaCore.CODEASSIST_FIELD_PREFIXES);
550 fFieldSuffixes= readCommaSeparatedPreference(project, JavaCore.CODEASSIST_FIELD_SUFFIXES);
551 fStaticFieldPrefixes= readCommaSeparatedPreference(project, JavaCore.CODEASSIST_STATIC_FIELD_PREFIXES);
552 fStaticFieldSuffixes= readCommaSeparatedPreference(project, JavaCore.CODEASSIST_STATIC_FIELD_SUFFIXES);
553 fLocalPrefixes= readCommaSeparatedPreference(project, JavaCore.CODEASSIST_LOCAL_PREFIXES);
554 fLocalSuffixes= readCommaSeparatedPreference(project, JavaCore.CODEASSIST_LOCAL_SUFFIXES);
555 fArgumentPrefixes= readCommaSeparatedPreference(project, JavaCore.CODEASSIST_ARGUMENT_PREFIXES);
556 fArgumentSuffixes= readCommaSeparatedPreference(project, JavaCore.CODEASSIST_ARGUMENT_SUFFIXES);
557 }
558
559 private String[] readCommaSeparatedPreference(IJavaProject project, String option) {
560 String list= project.getOption(option, true);
561 return list == null ? new String[0] : list.split(","); //$NON-NLS-1$
562 }
563
564}