001    /*
002      Copyright (C) 2001-2002 Renaud Pawlak <renaud@aopsys.com>
003      Laurent Martelli <laurent@aopsys.com>
004      
005      This program is free software; you can redistribute it and/or modify
006      it under the terms of the GNU Lesser General Public License as
007      published by the Free Software Foundation; either version 2 of the
008      License, or (at your option) any later version.
009    
010      This program is distributed in the hope that it will be useful,
011      but WITHOUT ANY WARRANTY; without even the implied warranty of
012      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
013      GNU Lesser General Public License for more details.
014    
015      You should have received a copy of the GNU Lesser General Public License
016      along with this program; if not, write to the Free Software
017      Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */
018    
019    package org.objectweb.jac.core.rtti;
020    
021    import java.lang.reflect.*;
022    import java.util.*;
023    
024    /**
025     * This class provides some useful methods to get some information
026     * regarding the naming conventions.
027     *
028     * @author Renaud Pawlak
029     * @author Laurent Martelli
030     */
031    
032    public class NamingConventions {
033    
034       /** Constant to represent field modifiers. */
035       public static final int MODIFIER = 0;
036    
037       /** Constant to represent field getters. */
038       public static final int GETTER = 1;
039    
040       /** Constant to represent field setters. */
041       public static final int SETTER = 2;
042    
043       /** Constant to represent collection adders. */
044       public static final int ADDER = 3;
045    
046       /** Constant to represent collection removers. */
047       public static final int REMOVER = 4;
048    
049       /** Store default field getter prefixes (get...). */
050       public static final String[] getterPrefixes;
051    
052       /** Store default field setter prefixes (set...). */
053       public static final String[] setterPrefixes;
054    
055       /** Store default collection adder prefixes (add..., put...). */
056       public static final String[] adderPrefixes;
057    
058       /** Store default collection remover prefixes (rmv..., del...,
059           remove...). */
060       public static final String[] removerPrefixes;
061    
062       static {
063          getterPrefixes  = new String[] { "get", "is" };
064          setterPrefixes  = new String[] { "set" };
065          adderPrefixes   = new String[] { "add","put" };
066          removerPrefixes = new String[] { "rmv", "del", "remove", "clear" };
067       }
068    
069       /**
070        * Returns the short name of a class.
071        *
072        * <p>If it is a well-known java class or a well-known JAC class,
073        * truncates the packages to give only the class name. It is is an
074        * array, add [] after the primitive type.
075        *
076        * @param cli the class item
077        */
078       public static String getShortClassName(ClassItem cli) {
079          return getShortClassName(cli.getActualClass());
080       }
081    
082       /**
083        * Returns the short name of a class.
084        *
085        * <p>If it is a well-known java class or a well-known JAC class,
086        * truncates the packages to give only the class name. It is is an
087        * array, add [] after the primitive type.
088        *
089        * @param cl the class
090        */
091       public static String getShortClassName(Class cl) {
092    
093          String type = cl.getName();
094    
095          if (cl.isArray()) {
096             type = cl.getComponentType().getName();
097          }
098    
099          if (type.startsWith("java") || type.startsWith( "org.objectweb.jac")) {
100             type = type.substring(type.lastIndexOf( '.' )+1);
101          }
102    
103          if (cl.isArray()) {
104             type = type + "[]";
105          }
106    
107          return type;
108          
109       }
110    
111       public static String getStandardClassName(Class cl) {
112          String type = cl.getName();
113          if ( cl.isArray() ) {
114             type = cl.getComponentType().getName()+"[]";
115          }
116          return type;      
117       }
118    
119       /**
120        * Returns the short name of a given constructor.
121        *
122        * <p>By default, the name of a constructor contains the full path
123        * name of the constructor class. Thus, this method is equivalent
124        * to get the short name of the constructor class.
125        * 
126        * @param constructor a constructor 
127        * @return its short name
128        * @see #getShortClassName(Class) */
129    
130       public static String getShortConstructorName(Constructor constructor) {
131          String name = constructor.getName();
132          if ( name.lastIndexOf( '.' ) == -1 ) {
133             return name;
134          }
135          return name.substring(name.lastIndexOf( '.' ) + 1);
136       }
137    
138       /**
139        * Returns the short name of a given constructor.
140        *
141        * <p>By default, the name of a constructor contains the full path
142        * name of the constructor class. Thus, this method is equivalent
143        * to get the short name of the constructor class.
144        * 
145        * @param constructor a constructor 
146        * @return its short name
147        * @see #getShortClassName(ClassItem) */
148    
149       public static String getShortConstructorName(ConstructorItem constructor) {
150          return getShortConstructorName(constructor.getActualConstructor());
151       }
152    
153       /**
154        * Returns the package name of the given class.
155        * 
156        * @param cl the class to get the package of
157        * @return the name of the package where <code>cl</code> is defined
158        */
159    
160       public static String getPackageName(Class cl) {
161          String type = cl.getName();
162    
163          return type.substring(0,type.lastIndexOf("."));
164       }
165    
166       /**
167        * Returns a printable representation of the argument types of a
168        * method (or a constructor if needed).
169        *
170        * <p>For instance, for a method that takes one object, one string,
171        * and one string array, the result will look like "Object, String,
172        * String[]".
173        * 
174        * @param method the involved method
175        * @return a printable string
176        * @see #getPrintableParameterTypes(AbstractMethodItem) */
177    
178       public static String getPrintableParameterTypes(AccessibleObject method) {
179          String ret = "(";
180          
181          Class[] pts;
182          if ( method instanceof Constructor )
183             pts = ((Constructor)method).getParameterTypes();
184          else if ( method instanceof Method )
185             pts = ((Method)method).getParameterTypes();
186          else return "";
187          for ( int j = 0; j < pts.length; j++ ) {
188             ret = ret + getShortClassName( pts[j] );
189             if ( j < pts.length - 1 ) ret = ret + ",";
190          }
191          ret = ret + ")";
192          return ret;
193       }
194    
195       /**
196        * Returns a printable representation of the argument types of a
197        * method (or a constructor if needed).
198        *
199        * <p>Same as its homonym but uing RTTI meta item.
200        * 
201        * @param method the involved method
202        * @return a printable string
203        * @see #getPrintableParameterTypes(AccessibleObject) */
204    
205       public static String getPrintableParameterTypes(AbstractMethodItem method) {
206          String ret = "(";
207          
208          Class[] pts = method.getParameterTypes();
209          for ( int j = 0; j < pts.length; j++ ) {
210             ret = ret + getShortClassName( pts[j] );
211             if ( j < pts.length - 1 ) ret = ret + ",";
212          }
213          ret = ret + ")";
214          return ret;
215       }
216    
217       /**
218        * Returns a declared method of a class <code>c</code> only by
219        * knowing its name.  Returns null if not found (to be modified to
220        * raise an exception.
221        *
222        * @param c     the class.
223        * @param name  the name of the method to find.
224        * @return the method (null if not found).  */
225    
226       public static Method getDeclaredMethodByName(Class c, String name) {
227    
228          Method[] methods = c.getDeclaredMethods();
229    
230          for ( int i=0 ; i < methods.length ; i++) {
231             if ( name.equals(methods[i].getName()) ) {
232                return methods[i];
233             }
234          }
235    
236          return null;
237       }
238    
239       /**
240        * Tell if the string is prefixed with one of the given prefixes.
241        * 
242        * <p>Returns 0 if the candidate is not prefixed by any of the
243        * given prefixes and also if the candidate exactly equals one of
244        * the prefixes.
245        *
246        * @param candidate the string to test
247        * @param prefixes the tested prefixes
248        * 
249        * @return the length of the matching prefix, 0 if not prefixed */
250    
251       public static int isPrefixedWith(String candidate, String[] prefixes) {
252          for ( int i = 0; i < prefixes.length; i++ ) {
253             if ( prefixes[i].equals(candidate) ) 
254                return 0;
255             if ( candidate.startsWith(prefixes[i]) ) 
256                return prefixes[i].length();
257          }
258          return 0;
259       }
260    
261       /**
262        * Tells if the the method name is equal to one of the given
263        * prefixes.
264        *
265        * @return true if equals */
266    
267       public static boolean isInPrefixes(String candidate, String[] prefixes) {
268          for ( int i = 0; i < prefixes.length; i++ ) {
269             if ( prefixes[i].equals(candidate) ) return true;
270          }
271          return false;
272       }   
273    
274       /**
275        * Return true if the name matches a setter profile (i.e. set...).
276        * 
277        * @param name the string to test
278        * @return true if setter profile
279        */
280    
281       public static boolean isSetter(String name) {
282          return isPrefixedWith(name, setterPrefixes) != 0 ;
283       }  
284    
285       /**
286        * Return true if the name matches a getter profile (i.e. get...).
287        * 
288        * @param name the string to test
289        * @return true if getter profile */
290    
291       public static boolean isGetter(String name) {
292          return isPrefixedWith(name, getterPrefixes) != 0 ;
293       }  
294    
295       /**
296        * Return true if the name matches a adder profile (i.e. add...).
297        * 
298        * @param name the string to test
299        * @return true if adder profile */
300    
301       public static boolean isAdder(String name) {
302          return isPrefixedWith(name, adderPrefixes) != 0 ;
303       }
304    
305       /**
306        * Return true if the name matches a remover profile (i.e. rmv...,
307        * del..., remove...).
308        * 
309        * @param name the string to test
310        * @return true if remover profile */
311    
312       public static boolean isRemover(String name) {
313          return isPrefixedWith( name, removerPrefixes ) != 0 ;
314       }
315    
316       /**
317        * Returns true if the name matches a modifier profile
318        * (i.e. setter, adder, or remover profile).
319        * 
320        * @param name the string to test
321        * @return true if modifier profile */
322    
323       public static boolean isModifier(String name) {
324          return isSetter(name) || isAdder(name) || isRemover(name) ||
325             isInPrefixes(name, removerPrefixes) || isInPrefixes(name, adderPrefixes);
326       }
327    
328       /**
329        * Takes a string and returns a new capitalized one.
330        *
331        * @param str the original string
332        * @return a new capitalized version of <code>str</code> */
333       
334       public static String capitalize(String str) {
335          if ( str.length() == 0 ) 
336             return str;
337          StringBuffer sb = new StringBuffer(str);
338          sb.setCharAt(0, Character.toUpperCase(str.charAt(0)) );
339          return sb.toString();
340       }
341    
342       /**
343        * Returns the normalized name for a string.
344        * 
345        * <p>A normalized string is a blank-free word where each relevant
346        * substring starts with an upcase character.<br>
347        *
348        * <p>For instance:
349        * 
350        * <ul><pre>
351        * - one string --> OneString
352        * - one_string --> OneString
353        * - oneString  --> OneString
354        * - one.s-tring --> OneSTring
355        * </pre></ul>
356        *
357        * @param string the string to normalize
358        * @return the normalized string */
359    
360       public static String getNormalizedString(String string) {
361          if ( string.length() == 0 ) return string;
362          StringBuffer sb = new StringBuffer( string.length() );
363          sb.append ( Character.toUpperCase( string.charAt( 0 ) ) );
364          boolean wordSep = false;
365          for ( int i = 1; i < string.length(); i++ ) {
366             char c = string.charAt( i );
367             if ( c == '_' || c == '.' || c == ' ' || c == '-' ) {
368                wordSep = true;
369             } else {
370                if ( wordSep ) {
371                   wordSep = false;
372                   sb.append ( Character.toUpperCase( c ) );
373                } else {
374                   sb.append ( c );
375                }
376             }
377          }
378          return new String( sb );
379       }
380    
381       /**
382        * Returns the underscored name for a normalized string.
383        *
384        * <p>A normalized string is a blank-free word where each relevant
385        * substring starts with an upcase character.<br>
386        *
387        * <p>For instance:
388        * 
389        * <ul><pre>
390        * - OneString --> one_string
391        * </pre></ul>
392        *
393        * @param string a normalized string
394        * @return the underscored string
395        */
396    
397       public static String getUnderscoredString(String string) {
398          if (string.length() == 0) 
399             return string;
400          StringBuffer sb = new StringBuffer(string.length());
401          sb.append(Character.toLowerCase(string.charAt(0)));
402          for (int i=1; i<string.length(); i++) {
403             char c = string.charAt(i);
404             if (Character.isUpperCase(c)) {
405                sb.append('_');
406                sb.append(Character.toLowerCase(c));
407             } else {
408                sb.append(c);
409             }
410          }
411          return new String(sb);
412       }
413    
414       /**
415        * Lower case the first character of a string.
416        *
417        * @param string the string to transform
418        * @return the same string but with the first character lowered 
419        */
420       public static String lowerFirst(String string) {
421          if (string.equals("")) 
422             return string;
423          char[] chs = string.toCharArray();
424          chs[0] = Character.toLowerCase(chs[0]);
425          return String.copyValueOf(chs);      
426       }
427    
428       /**
429        * Lower case the first character of a string unless it starts with
430        * at least two upper case letters.
431        *
432        * @param string the string to transform
433        * @return the same string but with the first character lowered */
434       public static String maybeLowerFirst(String string) {
435          if (!(string.length()>1 && 
436                Character.isUpperCase(string.charAt(0)) && 
437                Character.isUpperCase(string.charAt(1))))
438             return lowerFirst(string);
439          else 
440             return string;
441       }
442    
443       /**
444        * Get an unprefixed string from a prefixed string.<br>
445        *
446        * <p><b>NOTE:</b> this method is not semantically indentical to
447        * <code>removePrefixFrom</code>.
448        *
449        * <p>For instance:
450        * 
451        * <ul><pre>
452        * - getName --> Name
453        * - addName --> Names
454        * - removeName --> Names
455        * </pre></ul>
456        *
457        * @param string a prefixed string
458        * @return the corresponding unprefixed string
459        * @see #removePrefixFrom(String) */
460    
461       public static String getUnprefixedString(String string) {
462          String ret = string;
463          int ps = 0; /* prefix size */
464          
465          if( (ps = isPrefixedWith( string, removerPrefixes )) != 0 ) {
466             ret = getPlural(string.substring(ps));
467          } else if( (ps = isPrefixedWith( string, adderPrefixes )) != 0 ) {
468             ret = getPlural(string.substring(ps));
469          } else if( (ps = isPrefixedWith( string, setterPrefixes )) != 0 ) {
470             ret = string.substring(ps);
471          } else if( (ps = isPrefixedWith( string, getterPrefixes )) != 0 ) {
472             ret = string.substring(ps);
473          }
474    
475          return ret;
476       }
477    
478       /**
479        * Returns the plural of a name
480        *
481        * <p></p>
482        *
483        * @param name the name
484        * @return the plural of name
485        */
486       public static String getPlural(String name) {
487          if (!name.endsWith("s")) {
488             if (name.endsWith("y")) {
489                return name.substring( 0, name.length()-1 )+"ie";
490             } else {
491                return name + "s";
492             }
493          } else {
494             return name + "es";
495          }
496       }
497    
498       /**
499        * Return the singular of a name. Handles english -ies plurals,
500        * and -s. If no plural is recognized, returns name.
501        */
502       public static String getSingular(String name) {
503          if (name.endsWith("ies")) {
504             return name.substring(0, name.length()-3)+"y";
505          } else if (name.endsWith("s")) {
506             return name.substring(0, name.length()-1);
507          } else {
508             return name;
509          }
510       }
511    
512       /**
513        * Removes the prefix from a prefixed string.
514        *
515        * <p><b>NOTE:</b> this method is not semantically indentical to
516        * <code>getUnprefixedString</code>.
517        *
518        * <p>For instance:
519        * 
520        * <ul><pre>
521        * - getName --> Name
522        * - addName --> Name
523        * - removeName --> Name
524        * </pre></ul>
525        *
526        * @param string a prefixed string
527        * @return the corresponding unprefixed string
528        * @see #getUnprefixedString(String) */
529    
530       public static String removePrefixFrom(String string) {
531          String ret = string;
532          int ps = 0; /* prefix size */
533          
534          if( (ps = isPrefixedWith( string, removerPrefixes )) != 0 ) {
535             ret = string.substring( ps );
536          } else if ( (ps = isPrefixedWith( string, adderPrefixes )) != 0 ) {
537             ret = string.substring( ps );
538          } else if ( (ps = isPrefixedWith( string, setterPrefixes )) != 0 ) {
539             ret = string.substring( ps );
540          } else if ( (ps = isPrefixedWith( string, getterPrefixes )) != 0 ) {
541             ret = string.substring( ps );
542          }
543    
544          return ret;
545       }
546    
547       /**
548        * Returns the field name for a given method (modifier or getter).
549        *
550        * @param cl the class where the method is supposed to be
551        * @param method the name of the method
552        * @return the field for this method if exist */
553    
554       public static String fieldForMethod(Class cl, String method) {
555          if ( (!isGetter(method)) && (!isModifier(method)) ) 
556             return null;
557          String fieldName1 = getUnderscoredString(
558             getUnprefixedString( method ) );
559          Hashtable fields = ClassRepository.getDirectFieldAccess(cl);
560          if (fields.containsKey(fieldName1)) 
561             return fieldName1;
562          String fieldName2 = lowerFirst(getUnprefixedString(method));
563          if (fields.containsKey(fieldName2))
564             return fieldName2;
565          return null;
566       }
567    
568       /**
569        * Returns the printable textual representation of a field or
570        * method name.
571        *
572        * @param name the field or method name
573        * @return a "natural-language-like" textual representation */
574    
575       public static String textForName(String name) {
576          if (name.length()==0) 
577             return name;
578          StringBuffer sb = new StringBuffer(name.length());
579          sb.append(Character.toUpperCase(name.charAt(0)) );
580          for (int i = 1; i<name.length(); i++) {
581             char c = name.charAt(i);
582             if ( Character.isUpperCase(c) && 
583                  ( (i>0) && Character.isLowerCase(name.charAt(i-1)) ) ) {
584                sb.append ( ' ' );
585                if ( (i<name.length()-1) && 
586                     Character.isUpperCase(name.charAt(i+1)) ) {
587                   sb.append(c);
588                } else {
589                   sb.append(Character.toLowerCase(c));
590                }
591             } else {
592                if ( c == '_' || c == '.' || c == '-' ) {
593                   sb.append(' ');
594                } else {
595                   sb.append(c);
596                }
597             }
598          }
599          return new String(sb);
600       }
601    
602       /**
603        * Returns the normalized name of an aspect regarding its class
604        * name.<p>
605        *
606        * E.g.:
607        *
608        * <ul>
609        * <li>AgendaPersistenceAC -> persistence
610        *
611        * @param name the aspect name to normalize
612        * @return the normalized name */
613    
614       public static String getNormalizedAspectName(String name) {
615          if ( name == null ) return null;
616          if ( name.length() == 0 ) 
617             return name;
618          String result = null;
619          StringBuffer sb = new StringBuffer( name.length() );
620          for ( int i = 1; i < name.length(); i++ ) {
621             char c = name.charAt( i );
622             if ( Character.isUpperCase( c ) ) { 
623                result = name.substring( i );
624                break;
625             }
626          }
627          if ( result == null ) return null;
628          result = result.substring( 0, result.length() - 2 );
629          return result.toLowerCase();
630       }
631    
632       /**
633        * Returns the normalized class name of an aspect regarding its
634        * normalized name and the program it belongs to.<p>
635        *
636        * <ul>
637        * <li>org.objectweb.jac.samples.agenda, persistence -> AgendaPersistenceAC
638        *
639        * @param programName name of the application the aspect belongs to
640        * @param aspectName the aspect name to normalize
641        * @return the normalized name */
642    
643       public static String getNormalizedAspectClassName(String programName, 
644                                                         String aspectName) {
645          if (aspectName == null || aspectName.length() == 0) 
646             return null;
647          String result = null;
648          String shortProgramName = programName.substring( programName.lastIndexOf( '.' ) );
649          StringBuffer sb1 = new StringBuffer( shortProgramName );
650          sb1.setCharAt( 1, Character.toUpperCase( sb1.charAt( 1 ) ) );
651          StringBuffer sb2 = new StringBuffer( aspectName );
652          sb2.setCharAt( 0, Character.toUpperCase( sb2.charAt( 0 ) ) );
653          result = programName + new String( sb1 ) + new String( sb2 ) + "AC";
654          return result;
655       }
656    
657    }