/* * [SortClasses.java] * * Summary: Sort Classes in order in Java source code. * * Copyright: (c) 2014-2017 Roedy Green, Canadian Mind Products, http://mindprod.com * * Licence: This software may be copied and used freely for any purpose but military. * http://mindprod.com/contact/nonmil.html * * Requires: JDK 1.8+ * * Created with: JetBrains IntelliJ IDEA IDE http://www.jetbrains.com/idea/ * * Version History: * 1.0 2014-04-30 initial version */ package com.mindprod.sortcode; import com.mindprod.common18.ST; import com.mindprod.fastcat.FastCat; import java.util.Comparator; import java.util.regex.Matcher; import java.util.regex.Pattern; import static java.lang.System.*; /** * Sort Classes in order in Java source code. *

* compare two Java classes/interfaces, possibly with lead JavaDoc comment, with trailing // /methods * For this to work, keywords much be in canonical order * see http://mindprod.com/jgloss/keyword.html * handles classes and interfaces, but not inner classes or anonymous classes * * @author Roedy Green, Canadian Mind Products * @version 1.0 2014-04-30 initial version * @since 2014-04-30 */ public class SortClasses implements Comparator { /** * true if want extra debugging output */ private static final boolean DEBUGGING = false; /** * parse Java declaration * keywords that apply to classes * public | protected | private ] * abstract, static, final, strictfp, class. * keywords that apply to interfaces * . * [public], [abstract], strictfp, interface */ private static final Pattern CLASS_GRABBER = Pattern.compile( "(private|public|protected)?\\s*" + "(abstract)?\\s*" + "(static)?\\s*" + "(final)?\\s*" + "class\\s*\\(" ); private static final Pattern INTERFACE_GRABBER = Pattern.compile( "(public)?\\s*" + "(abstract)?\\s*" + "interface\\s*\\(" ); /** * put abstract first, part of contract with outside world. * * @param abstr "abstract" or "" * * @return sort order */ private static char calcAbstrOrder( final String abstr ) { if ( ST.isEmpty( abstr ) ) { return '1'; } else { return '0'; } } // /method private static char calcInstanceOrder( final String instance ) { if ( ST.isEmpty( instance ) ) { return '1'; } switch ( instance ) { case "static": return '0'; case "": /*instance */ return '1'; default: err.println( "unrecognised or misplaced static/instance " + instance ); return 'z'; } } // /method private static char calcMethodOrder( final String methodName, final boolean isStatic ) { if ( ST.isEmpty( methodName ) ) { return 'z'; } else if ( methodName.equals( "main" ) ) { return '0'; } else if ( Character.isUpperCase( methodName.charAt( 0 ) ) ) { // first letter is cap err.println( "method name name should start with lower case letter " + methodName ); return '1'; } else { // first letter is lower case, as it should be. return '1'; } } // /method private static char calcNativityOrder( final String nativity ) { if ( ST.isEmpty( nativity ) ) { return '1'; } else { return '0'; } } // /method private static char calcScopeOrder( final String scope ) { if ( ST.isEmpty( scope ) ) { return '1'; } switch ( scope ) { case "public": return '0'; case "": /* package default */ return '1'; case "protected": return '2'; case "private": return '3'; default: err.println( "unrecognised or misplaced scope " + scope ); return 'z'; } } // /method private static char calcTypeOrder( final String typeName ) { if ( ST.isEmpty( typeName ) ) { return 'z'; } switch ( typeName ) { case "": return 'z'; case "void": return '0'; case "boolean": return '1'; case "byte": return '2'; case "char": return '3'; case "short": return '4'; case "int": return '5'; case "long": return '6'; case "float": return '7'; case "double": return '8'; case "Boolean": return '9'; case "Byte": return 'A'; case "Character": return 'B'; case "Short": return 'C'; case "Integer": return 'D'; case "Long": return 'E'; case "Float": return 'F'; case "Double": return 'G'; case "String": return 'H'; default: // we should have a class name of some sort e.g. Double, HashMap if ( Character.isUpperCase( typeName.charAt( 0 ) ) ) { return 'I'; } else { err.println( "Class name should start with a capital letter: " + typeName ); return 'J'; } } // end switch } // /method private static String[] computeKey( String s ) { // use regex to pluck info useful for sort. // The declaration we might parse looks like: // private static final int q; // or public String = "abc"; // or private static final ArrayList COLLECTION = new ArrayList<>( COUNT_OF_FILES_TO_COLLECT ); final String clean = stripLeadComment( s ); if ( DEBUGGING ) { int place = clean.indexOf( '\n' ); out.println( clean.substring( 0, place ) ); } final Matcher m = CLASS_GRABBER.matcher( clean ); if ( m.lookingAt() ) { /** * parse Java declaration * keywords that apply to methods * public | protected | private ] * abstract * static * final * synchronized * native * strictfp * int | long | String | etc. */ int g = 1; final String scope = m.group( g++ ); final char scopeOrder = calcScopeOrder( scope ); final String abstr = m.group( g++ ); final char abstrOrder = calcAbstrOrder( abstr ); final String instance = m.group( g++ ); final char instanceOrder = calcInstanceOrder( instance ); final boolean isStatic = "static".equals( instance ); // not currently used final String finality = m.group( g++ ); // not currently used final String sync = m.group( g++ ); final String nativity = m.group( g++ ); final char nativityOrder = calcNativityOrder( nativity ); // not currently used final String strict = m.group( g++ ); final String typeName = m.group( g++ ); final char typeOrder = calcTypeOrder( typeName ); String methodName = m.group( g++ ); final char methodOrder = calcMethodOrder( methodName, isStatic ); // sort by scope, instance, nativity, method final FastCat sb = new FastCat( 5 ); sb.append( abstrOrder ); sb.append( scopeOrder ); sb.append( instanceOrder ); sb.append( nativityOrder ); sb.append( methodOrder ); // for declarations, type in more important than name final String condensedKey = sb.toString(); final String[] result = new String[] { condensedKey, methodName, String.valueOf( typeOrder ), typeName }; if ( DEBUGGING ) { for ( String chunk : result ) { out.print( chunk + "|" ); } out.println(); } return result; } else { err.println( "SortMethods parse failed." ); err.println( "[-- " + clean + " --]\n" ); return new String[] { "zzzzz", clean, "z", "" }; } } // /method /** * remove lead comment. * * @param s block of code. pretrimmed. * * @return block of code with any lead javadoc removed. */ private static String stripLeadComment( final String s ) { // s has already been trimmed final String clean; if ( s.startsWith( "/**" ) ) { final int end = s.indexOf( "*/", "/**".length() ); if ( end < 0 ) { // malformed comment err.println( "malformed JavaDoc comment: [-- " + s + " --]\n" ); return s; } else { return s.substring( end + "*/".length() ).trim(); } } else { // no comment present. return s; } } // /method /** * Compare two String Objects each containing a method body * Informally, returns (a-b), or +ve if a comes after b. * * @param a first String to compare * @param b second String to compare * * @return +ve if a>b, 0 if a==b, -ve if a<b */ public final int compare( String a, String b ) { final String[] extracta = computeKey( a ); final String[] extractb = computeKey( b ); // sort on synthetic key that orders keywords int diff = 0; for ( int i = 0; i < extracta.length; i++ ) { diff = extracta[ i ].compareTo( extractb[ i ] ); if ( diff != 0 ) { return diff; } } return 0; } // /method } // /methods