KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > eclipse > jdt > internal > corext > refactoring > rename > RenamingNameSuggestor


1 /*******************************************************************************
2  * Copyright (c) 2000, 2006 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  *******************************************************************************/

11 package org.eclipse.jdt.internal.corext.refactoring.rename;
12
13 import com.ibm.icu.text.BreakIterator;
14 import java.util.ArrayList JavaDoc;
15 import java.util.List JavaDoc;
16
17 import org.eclipse.core.runtime.Assert;
18
19 import org.eclipse.jdt.core.IJavaProject;
20 import org.eclipse.jdt.core.JavaCore;
21
22
23 import org.eclipse.jdt.internal.ui.text.JavaWordIterator;
24
25 /**
26  * This class contains methods for suggesting new names for variables or methods
27  * whose name consists at least partly of the name of their declaring type (or
28  * in case of methods, the return type or a parameter type).
29  *
30  * The methods return the newly suggested method or variable name in case of a
31  * match, or null in case nothing matched.
32  *
33  * In any case, prefixes and suffixes are removed from variable names. As method
34  * names have no configurable suffixes or prefixes, they are left unchanged. The
35  * remaining name is called "stripped element name".
36  *
37  * After the match according to the strategy, prefixes and suffixes are
38  * reapplied to the names.
39  *
40  * EXACT STRATEGY (always performed).
41  * ----------------------------------------------------------------
42  *
43  * The stripped element name is directly compared with the type name:
44  *
45  * a) the first character must match case-insensitive
46  *
47  * b) all other characters must match case-sensitive
48  *
49  * In case of a match, the new type name is returned (first character adapted,
50  * respectively). Suffixes/Prefixes are reapplied.
51  *
52  * Note that this also matches fields with names like "SomeField", "fsomeField",
53  * and method names like "JavaElement()".
54  *
55  * EMBEDDED STRATEGY (performed second if chosen by user).
56  * ----------------------------------------------------------------
57  *
58  * A search is performed in the stripped element name for the old type name:
59  *
60  * a) the first character must match case-insensitive
61  *
62  * b) all other characters must match case-sensitive
63  *
64  * c) the stripped element name must end after the type name, or the next
65  * character must be a non-letter, or the next character must be upper cased.
66  *
67  * In case of a match, the new type is inserted into the stripped element name,
68  * replacing the old type name, first character adapted to the correct case.
69  * Suffixes/Prefixes are reapplied.
70  *
71  * Note that this also matches methods with names like "createjavaElement()" or
72  * fields like "fjavaElementCache".
73  *
74  * SUFFIX STRATEGY (performed third if chosen by user)
75  * ----------------------------------------------------------------
76  *
77  * The new and old type names are analyzed for "camel case suffixes", that is,
78  * substrings which begin with an uppercased letter. For example,
79  * "SimpleJavaElement" is split into the three hunks "Simple",
80  * "Java", and "Element". If one type name has more suffixes than the
81  * other, both are stripped to the smaller size.
82  *
83  * Then, a search is performed in the stripped variable name hunks from back to
84  * front. At least the last hunk must be found, others may then extend the match.
85  * Each hunk must match like in the exact strategy, i.e.
86  *
87  * a) the first character must match case-insensitive
88  *
89  * b) all other characters must match case-sensitive
90  *
91  * In case of a match, the matched hunks of the new type replace
92  * the hunks of the old type. Suffixes/Prefixes are reapplied.
93  *
94  * Note that numbers and other non-letter characters belong to the previous
95  * camel case substring.
96  *
97  *
98  * @since 3.2
99  *
100  */

101 public class RenamingNameSuggestor {
102     
103     /*
104      * ADDITIONAL OPTIONS
105      * ----------------------------------------------------------------
106      *
107      * There are two additional flags which may be set in this class to allow
108      * better matching of special cases:
109      *
110      * a) Special treatment of leading "I"s in type names, i.e. interface names
111      * like "IJavaElement". If the corresponding flag is set, leading "I"s are
112      * stripped from type names if the second char is also uppercase to allow
113      * exact matching of variable names like "javaElement" for type
114      * "IJavaElement". Note that embedded matching already matches cases like
115      * this.
116      *
117      * b) Special treatment of all-uppercase type names or all-uppercase type
118      * name camel-case hunks, i.e. names like "AST" or "PersonalURL". If the
119      * corresponding flag is set, the type name hunks will be transformed such
120      * that variables like "fAst", "ast", "personalUrl", or "url" are found as
121      * well. The target name will be transformed too if it is an
122      * all-uppercase type name camel-case hunk as well.
123      *
124      * NOTE that in exact or embedded mode, the whole type name must be
125      * all-uppercase to allow matching custom-lowercased variable names, i.e.
126      * there are no attempts to "guess" which hunk of the new name should be lowercased
127      * to match a partly lowercased variable name. In suffix mode, hunks of the
128      * new type which are at the same position as in the old type will be
129      * lowercased if necessary.
130      *
131      * c) Support for (english) plural forms. If the corresponding flag is set, the
132      * suggestor will try to match variables which have plural forms of the
133      * type name, for example "handies" for "Handy" or "phones" for "MobilePhone".
134      * The target name will be transformed as well, i.e. conversion like
135      * "fHandies" -> "fPhones" are supported.
136      *
137      */

138
139     public static final int STRATEGY_EXACT= 1;
140     public static final int STRATEGY_EMBEDDED= 2;
141     public static final int STRATEGY_SUFFIX= 3;
142     
143     private static final String JavaDoc PLURAL_S= "s"; //$NON-NLS-1$
144
private static final String JavaDoc PLURAL_IES= "ies"; //$NON-NLS-1$
145
private static final String JavaDoc SINGULAR_Y= "y"; //$NON-NLS-1$
146

147     private int fStrategy;
148     private String JavaDoc[] fFieldPrefixes;
149     private String JavaDoc[] fFieldSuffixes;
150     private String JavaDoc[] fStaticFieldPrefixes;
151     private String JavaDoc[] fStaticFieldSuffixes;
152     private String JavaDoc[] fLocalPrefixes;
153     private String JavaDoc[] fLocalSuffixes;
154     private String JavaDoc[] fArgumentPrefixes;
155     private String JavaDoc[] fArgumentSuffixes;
156     
157     private boolean fExtendedInterfaceNameMatching;
158     private boolean fExtendedAllUpperCaseHunkMatching;
159     private boolean fExtendedPluralMatching;
160
161     public RenamingNameSuggestor() {
162         this(STRATEGY_SUFFIX);
163     }
164
165     public RenamingNameSuggestor(int strategy) {
166
167         Assert.isTrue(strategy >= 1 && strategy <= 3);
168
169         fStrategy= strategy;
170         fExtendedInterfaceNameMatching= true;
171         fExtendedAllUpperCaseHunkMatching= true;
172         fExtendedPluralMatching= true;
173
174         resetPrefixes();
175     }
176
177     public String JavaDoc suggestNewFieldName(IJavaProject project, String JavaDoc oldFieldName, boolean isStatic, String JavaDoc oldTypeName, String JavaDoc newTypeName) {
178
179         initializePrefixesAndSuffixes(project);
180
181         if (isStatic)
182             return suggestNewVariableName(fStaticFieldPrefixes, fStaticFieldSuffixes, oldFieldName, oldTypeName, newTypeName);
183         else
184             return suggestNewVariableName(fFieldPrefixes, fFieldSuffixes, oldFieldName, oldTypeName, newTypeName);
185     }
186
187     public String JavaDoc suggestNewLocalName(IJavaProject project, String JavaDoc oldLocalName, boolean isArgument, String JavaDoc oldTypeName, String JavaDoc newTypeName) {
188
189         initializePrefixesAndSuffixes(project);
190
191         if (isArgument)
192             return suggestNewVariableName(fArgumentPrefixes, fArgumentSuffixes, oldLocalName, oldTypeName, newTypeName);
193         else
194             return suggestNewVariableName(fLocalPrefixes, fLocalSuffixes, oldLocalName, oldTypeName, newTypeName);
195     }
196
197     public String JavaDoc suggestNewMethodName(String JavaDoc oldMethodName, String JavaDoc oldTypeName, String JavaDoc newTypeName) {
198
199         Assert.isNotNull(oldMethodName);
200         Assert.isNotNull(oldTypeName);
201         Assert.isNotNull(newTypeName);
202         Assert.isTrue(oldMethodName.length() > 0);
203         Assert.isTrue(oldTypeName.length() > 0);
204         Assert.isTrue(newTypeName.length() > 0);
205
206         resetPrefixes();
207
208         return match(oldTypeName, newTypeName, oldMethodName);
209     }
210
211     public String JavaDoc suggestNewVariableName(String JavaDoc[] prefixes, String JavaDoc[] suffixes, String JavaDoc oldVariableName, String JavaDoc oldTypeName, String JavaDoc newTypeName) {
212
213         Assert.isNotNull(prefixes);
214         Assert.isNotNull(suffixes);
215         Assert.isNotNull(oldVariableName);
216         Assert.isNotNull(oldTypeName);
217         Assert.isNotNull(newTypeName);
218         Assert.isTrue(oldVariableName.length() > 0);
219         Assert.isTrue(oldTypeName.length() > 0);
220         Assert.isTrue(newTypeName.length() > 0);
221
222         final String JavaDoc usedPrefix= findLongestPrefix(oldVariableName, prefixes);
223         final String JavaDoc usedSuffix= findLongestSuffix(oldVariableName, suffixes);
224         final String JavaDoc strippedVariableName= oldVariableName.substring(usedPrefix.length(), oldVariableName.length() - usedSuffix.length());
225
226         String JavaDoc newVariableName= match(oldTypeName, newTypeName, strippedVariableName);
227         return (newVariableName != null) ? usedPrefix + newVariableName + usedSuffix : null;
228     }
229
230     // -------------------------------------- Match methods
231

232     private String JavaDoc match(final String JavaDoc oldTypeName, final String JavaDoc newTypeName, final String JavaDoc strippedVariableName) {
233
234         String JavaDoc oldType= oldTypeName;
235         String JavaDoc newType= newTypeName;
236
237         if (fExtendedInterfaceNameMatching && isInterfaceName(oldType) && isInterfaceName(newType)) {
238             oldType= getInterfaceName(oldType);
239             newType= getInterfaceName(newType);
240         }
241
242         String JavaDoc newVariableName= matchDirect(oldType, newType, strippedVariableName);
243
244         if (fExtendedPluralMatching && newVariableName == null && canPluralize(oldType))
245             newVariableName= matchDirect(pluralize(oldType), pluralize(newType), strippedVariableName);
246
247         return newVariableName;
248     }
249
250     private String JavaDoc matchDirect(String JavaDoc oldType, String JavaDoc newType, final String JavaDoc strippedVariableName) {
251         /*
252          * Use all strategies applied by the user. Always start with exact
253          * matching.
254          *
255          * Note that suffix matching may not match the whole type name if the
256          * new type name has a smaller camel case chunk count.
257          */

258
259         String JavaDoc newVariableName= exactMatch(oldType, newType, strippedVariableName);
260         if (newVariableName == null && fStrategy >= STRATEGY_EMBEDDED)
261             newVariableName= embeddedMatch(oldType, newType, strippedVariableName);
262         if (newVariableName == null && fStrategy >= STRATEGY_SUFFIX)
263             newVariableName= suffixMatch(oldType, newType, strippedVariableName);
264
265         return newVariableName;
266     }
267
268     private String JavaDoc exactMatch(final String JavaDoc oldTypeName, final String JavaDoc newTypeName, final String JavaDoc strippedVariableName) {
269
270         String JavaDoc newName= exactDirectMatch(oldTypeName, newTypeName, strippedVariableName);
271         if (newName != null)
272             return newName;
273
274         if (fExtendedAllUpperCaseHunkMatching && isUpperCaseCamelCaseHunk(oldTypeName)) {
275             String JavaDoc oldTN= getFirstUpperRestLowerCased(oldTypeName);
276             String JavaDoc newTN= isUpperCaseCamelCaseHunk(newTypeName) ? getFirstUpperRestLowerCased(newTypeName) : newTypeName;
277             newName= exactDirectMatch(oldTN, newTN, strippedVariableName);
278         }
279
280         return newName;
281     }
282
283     private String JavaDoc exactDirectMatch(final String JavaDoc oldTypeName, final String JavaDoc newTypeName, final String JavaDoc strippedVariableName) {
284
285         if (strippedVariableName.equals(oldTypeName))
286             return newTypeName;
287
288         if (strippedVariableName.equals(getLowerCased(oldTypeName)))
289             return getLowerCased(newTypeName);
290
291         return null;
292     }
293
294     private String JavaDoc embeddedMatch(String JavaDoc oldTypeName, String JavaDoc newTypeName, String JavaDoc strippedVariableName) {
295
296         // possibility of a match?
297
final String JavaDoc lowerCaseVariable= strippedVariableName.toLowerCase();
298         final String JavaDoc lowerCaseOldTypeName= oldTypeName.toLowerCase();
299         int presumedIndex= lowerCaseVariable.indexOf(lowerCaseOldTypeName);
300
301         while (presumedIndex != -1) {
302             // it may be there
303
final String JavaDoc presumedTypeName= strippedVariableName.substring(presumedIndex, presumedIndex + oldTypeName.length());
304             final String JavaDoc prefix= strippedVariableName.substring(0, presumedIndex);
305             final String JavaDoc suffix= strippedVariableName.substring(presumedIndex + oldTypeName.length());
306
307             // can match at all? (depends on suffix)
308
if (startsNewHunk(suffix)) {
309
310                 String JavaDoc name= exactMatch(oldTypeName, newTypeName, presumedTypeName);
311                 if (name != null)
312                     return prefix + name + suffix;
313             }
314
315             // did not match -> find next occurrence
316
presumedIndex= lowerCaseVariable.indexOf(lowerCaseOldTypeName, presumedIndex + 1);
317         }
318
319         return null;
320     }
321     
322     private String JavaDoc suffixMatch(final String JavaDoc oldType, final String JavaDoc newType, final String JavaDoc strippedVariableName) {
323
324         // get an array of all camel-cased elements from both types + the
325
// variable
326
String JavaDoc[] suffixesOld= getSuffixes(oldType);
327         String JavaDoc[] suffixesNew= getSuffixes(newType);
328         String JavaDoc[] suffixesVar= getSuffixes(strippedVariableName);
329
330         // get an equal-sized array of the last n camel-cased elements
331
int min= Math.min(suffixesOld.length, suffixesNew.length);
332         String JavaDoc[] suffixesOldEqual= new String JavaDoc[min];
333         String JavaDoc[] suffixesNewEqual= new String JavaDoc[min];
334         System.arraycopy(suffixesOld, suffixesOld.length - min, suffixesOldEqual, 0, min);
335         System.arraycopy(suffixesNew, suffixesNew.length - min, suffixesNewEqual, 0, min);
336
337         // find endIndex. endIndex is the index of the last hunk of the old type
338
// name in the variable name.
339
int endIndex= -1;
340         for (int j= suffixesVar.length - 1; j >= 0; j--) {
341             String JavaDoc newHunkName= exactMatch(suffixesOldEqual[suffixesOldEqual.length - 1], suffixesNewEqual[suffixesNewEqual.length - 1], suffixesVar[j]);
342             if (newHunkName != null) {
343                 endIndex= j;
344                 break;
345             }
346         }
347
348         if (endIndex == -1)
349             return null; // last hunk not found -> no match
350

351         int stepBack= 0;
352         int lastSuffixMatched= -1;
353         int hunkInVarName= -1;
354         for (int i= suffixesOldEqual.length - 1; i >= 0; i--) {
355
356             hunkInVarName= endIndex - stepBack;
357             stepBack++;
358
359             if (hunkInVarName < 0) {
360                 // we have reached the beginning of the variable name
361
break;
362             }
363
364             // try to match this hunk:
365
String JavaDoc newHunkName= exactMatch(suffixesOldEqual[i], suffixesNewEqual[i], suffixesVar[hunkInVarName]);
366
367             if (newHunkName == null)
368                 break; // only match complete suffixes
369

370             suffixesVar[hunkInVarName]= newHunkName;
371             lastSuffixMatched= i;
372         }
373         
374         if (lastSuffixMatched == 0) {
375             // we have matched ALL type hunks in the variable name,
376
// insert any new prefixes of the new type name
377
int newPrefixes= suffixesNew.length - suffixesNewEqual.length;
378             if (newPrefixes > 0) {
379                 
380                 // Propagate lowercased start to the front
381
if (Character.isLowerCase(suffixesVar[hunkInVarName].charAt(0)) && Character.isUpperCase(suffixesOldEqual[lastSuffixMatched].charAt(0))) {
382                     suffixesVar[hunkInVarName]= getUpperCased(suffixesVar[hunkInVarName]);
383                     suffixesNew[0]= getLowerCased(suffixesNew[0]);
384                 }
385                 
386                 String JavaDoc[] newVariableName= new String JavaDoc[suffixesVar.length + newPrefixes];
387                 System.arraycopy(suffixesVar, 0, newVariableName, 0, hunkInVarName); // hunks before type name in variable name
388
System.arraycopy(suffixesNew, 0, newVariableName, hunkInVarName, newPrefixes); // new hunks in new type name
389
System.arraycopy(suffixesVar, hunkInVarName, newVariableName, hunkInVarName + newPrefixes, suffixesVar.length - hunkInVarName); // matched + rest hunks
390
suffixesVar= newVariableName;
391             }
392         }
393
394         String JavaDoc varName= concat(suffixesVar);
395         if (varName.equals(strippedVariableName))
396             return null; // no "silly suggestions"
397
else
398             return varName;
399     }
400
401
402     // ---------------- Helper methods
403

404     /**
405      * True if the string is the beginning of a new camel case hunk. False if it
406      * is not.
407      */

408     private boolean startsNewHunk(String JavaDoc string) {
409
410         if (string.length() == 0)
411             return true;
412
413         return isLegalChar(string.charAt(0));
414     }
415
416     /**
417      * True if hunk is longer than 1 character and all letters in the hunk are
418      * uppercase. False if not.
419      */

420     private boolean isUpperCaseCamelCaseHunk(String JavaDoc hunk) {
421         if (hunk.length() < 2)
422             return false;
423
424         for (int i= 0; i < hunk.length(); i++) {
425             if (!isLegalChar(hunk.charAt(i)))
426                 return false;
427         }
428         return true;
429     }
430     
431     /**
432      * False if the character is a letter and it is lowercase. True in all other
433      * cases.
434      */

435     private boolean isLegalChar(char c) {
436         if (Character.isLetter(c))
437             return Character.isUpperCase(c);
438         return true;
439     }
440
441     /**
442      * Grab a list of camelCase-separated suffixes from the typeName, for
443      * example:
444      *
445      * "JavaElementName" => { "Java", "Element", "Name }
446      *
447      * "ASTNode" => { "AST", "Node" }
448      *
449      */

450     private String JavaDoc[] getSuffixes(String JavaDoc typeName) {
451         List JavaDoc suffixes= new ArrayList JavaDoc();
452         JavaWordIterator iterator= new JavaWordIterator();
453         iterator.setText(typeName);
454         int lastmatch= 0;
455         int match;
456         while ( (match= iterator.next()) != BreakIterator.DONE) {
457             suffixes.add(typeName.substring(lastmatch, match));
458             lastmatch= match;
459         }
460         return (String JavaDoc[]) suffixes.toArray(new String JavaDoc[0]);
461     }
462
463     private String JavaDoc concat(String JavaDoc[] suffixesNewEqual) {
464         StringBuffer JavaDoc returner= new StringBuffer JavaDoc();
465         for (int j= 0; j < suffixesNewEqual.length; j++) {
466             returner.append(suffixesNewEqual[j]);
467         }
468         return returner.toString();
469     }
470
471     private String JavaDoc getLowerCased(String JavaDoc name) {
472         if (name.length() > 1)
473             return Character.toLowerCase(name.charAt(0)) + name.substring(1);
474         else
475             return name.toLowerCase();
476     }
477     
478     private String JavaDoc getUpperCased(String JavaDoc name) {
479         if (name.length() > 1)
480             return Character.toUpperCase(name.charAt(0)) + name.substring(1);
481         else
482             return name.toLowerCase();
483     }
484
485     private String JavaDoc getFirstUpperRestLowerCased(String JavaDoc name) {
486         if (name.length() > 1)
487             return Character.toUpperCase(name.charAt(0)) + name.substring(1).toLowerCase();
488         else
489             return name.toLowerCase();
490     }
491
492     private boolean isInterfaceName(String JavaDoc typeName) {
493         return ( (typeName.length() >= 2) && typeName.charAt(0) == 'I' && Character.isUpperCase(typeName.charAt(1)));
494     }
495
496     private String JavaDoc getInterfaceName(String JavaDoc typeName) {
497         return typeName.substring(1);
498     }
499
500     private String JavaDoc findLongestPrefix(String JavaDoc name, String JavaDoc[] prefixes) {
501         String JavaDoc usedPrefix= ""; //$NON-NLS-1$
502
int bestLen= 0;
503         for (int i= 0; i < prefixes.length; i++) {
504             if (name.startsWith(prefixes[i])) {
505                 if (prefixes[i].length() > bestLen) {
506                     bestLen= prefixes[i].length();
507                     usedPrefix= prefixes[i];
508                 }
509             }
510         }
511         return usedPrefix;
512     }
513
514     private String JavaDoc findLongestSuffix(String JavaDoc name, String JavaDoc[] suffixes) {
515         String JavaDoc usedPrefix= ""; //$NON-NLS-1$
516
int bestLen= 0;
517         for (int i= 0; i < suffixes.length; i++) {
518             if (name.endsWith(suffixes[i])) {
519                 if (suffixes[i].length() > bestLen) {
520                     bestLen= suffixes[i].length();
521                     usedPrefix= suffixes[i];
522                 }
523             }
524         }
525         return usedPrefix;
526     }
527     
528     /**
529      * Returns true if the type name can be pluralized by a string operation.
530      * This is always the case if it does not already end with an "s".
531      */

532     private boolean canPluralize(String JavaDoc typeName) {
533         return !typeName.endsWith(PLURAL_S);
534     }
535
536     private String JavaDoc pluralize(String JavaDoc typeName) {
537         if (typeName.endsWith(SINGULAR_Y))
538             typeName= typeName.substring(0, typeName.length() - 1).concat(PLURAL_IES);
539         else if (!typeName.endsWith(PLURAL_S))
540             typeName= typeName.concat(PLURAL_S);
541         return typeName;
542     }
543
544     private void resetPrefixes() {
545         String JavaDoc[] empty= new String JavaDoc[0];
546         fFieldPrefixes= empty;
547         fFieldSuffixes= empty;
548         fStaticFieldPrefixes= empty;
549         fStaticFieldSuffixes= empty;
550         fLocalPrefixes= empty;
551         fLocalSuffixes= empty;
552         fArgumentPrefixes= empty;
553         fArgumentSuffixes= empty;
554     }
555
556     private void initializePrefixesAndSuffixes(IJavaProject project) {
557         fFieldPrefixes= readCommaSeparatedPreference(project, JavaCore.CODEASSIST_FIELD_PREFIXES);
558         fFieldSuffixes= readCommaSeparatedPreference(project, JavaCore.CODEASSIST_FIELD_SUFFIXES);
559         fStaticFieldPrefixes= readCommaSeparatedPreference(project, JavaCore.CODEASSIST_STATIC_FIELD_PREFIXES);
560         fStaticFieldSuffixes= readCommaSeparatedPreference(project, JavaCore.CODEASSIST_STATIC_FIELD_SUFFIXES);
561         fLocalPrefixes= readCommaSeparatedPreference(project, JavaCore.CODEASSIST_LOCAL_PREFIXES);
562         fLocalSuffixes= readCommaSeparatedPreference(project, JavaCore.CODEASSIST_LOCAL_SUFFIXES);
563         fArgumentPrefixes= readCommaSeparatedPreference(project, JavaCore.CODEASSIST_ARGUMENT_PREFIXES);
564         fArgumentSuffixes= readCommaSeparatedPreference(project, JavaCore.CODEASSIST_ARGUMENT_SUFFIXES);
565     }
566
567     private String JavaDoc[] readCommaSeparatedPreference(IJavaProject project, String JavaDoc option) {
568         String JavaDoc list= project.getOption(option, true);
569         return list == null ? new String JavaDoc[0] : list.split(","); //$NON-NLS-1$
570
}
571
572 }
573
Popular Tags