/*
* [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