/*
* [Qualified.java]
*
* Summary: Displays a java Package or package.Class, package.Class.method or Class.method all one one line.
*
* Copyright: (c) 2009-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 2009-01-14
*/
package com.mindprod.htmlmacros.macro;
import com.mindprod.common18.EIO;
import com.mindprod.common18.FNV1a64;
import com.mindprod.common18.ST;
import com.mindprod.csv.CSVReader;
import com.mindprod.entities.EntifyStrings;
import com.mindprod.fastcat.FastCat;
import java.io.EOFException;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.util.Arrays;
import java.util.HashSet;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import static java.lang.System.*;
/**
* classify various legs of a reference to be displayed by Qualified
*/
enum Kind
{
PACKAGE, CLASS, INTERFACE, METHOD, CONSTANT, VAR, KEYWORD;
}
/**
* Displays a java Package or package.Class, package.Class.method or Class.method all one one line.
*
* Use macro Path for filenames.
*
* @author Roedy Green, Canadian Mind Products
* @version 1.0 2009-09-21
* @see Path
* @since 2009-09-21
*/
public final class Qualified extends Macro
{
// declarations
/**
* how to use the macro
*/
private static final String USAGE = "\nQualified macro needs [package].[Class][].[var].[method][()] then [interface]/[class] marker";
/**
* where website files are kept
*/
private static final File webrootDir = new File( Global.configuration.getLocalWebrootWithSlashes() );
private static final HashSet COMMON_CLASSES = new HashSet( Arrays.asList(
"EIO",
"HTTP",
"ST",
"URI",
"URL" )
);
private static final HashSet COMMON_METHODS = new HashSet( Arrays.asList(
"abs",
"add",
"addall",
"append",
"arraycopy",
"asList",
"audioClip",
"beep",
"binarySearch",
"browse",
"cancel",
"canRead",
"canWrite",
"ceil",
"channels",
"charAt",
"charset",
"charWidth",
"chopLeadingString",
"chopTrailingString",
"clear",
"close",
"compare",
"compareTo",
"compareToIgnoreCase",
"compile",
"condense",
"connect",
"contains",
"copyOf",
"copyRangeOf",
"copyTo",
"cos",
"countLeading",
"countTrailing",
"countTrainling",
"cp_p",
"createFont",
"createImage",
"createNewFile",
"createTempFile",
"currentThread",
"currentTimeMillis",
"decode",
"deepEquals",
"defineClass",
"delete",
"deleteOnExit",
"deleteOnReboot",
"doClick",
"doStartTag",
"doTag",
"doubleToLongBits",
"drawGlyphVector",
"drawImage",
"edit",
"elements",
"emitDetailsAndManual",
"encode",
"endsWith",
"exists",
"exp",
"fill",
"find",
"findLoadedClass",
"flip",
"forName",
"get",
"group",
"hashCode",
"identityHashCode",
"indexOf",
"insert",
"insertComponent",
"insertIcon",
"insertLogicalStyle",
"invalidate",
"invokeLater",
"jTable",
"keyPress",
"keystore",
"lastModified",
"length",
"list",
"listRoots",
"load",
"loadLibrary",
"longBitsToDouble",
"lookingAt",
"mail",
"matcher",
"matches",
"method",
"mkdirs",
"nanoTime",
"newInstance",
"next",
"nextInt",
"open",
"openConnection",
"order",
"paint",
"paintComponent",
"play",
"pow",
"prepareImage",
"print",
"printf",
"printHexBinary",
"println",
"propertyNames",
"put",
"quote",
"read",
"reflect",
"remove",
"renameTo",
"repaint",
"replace",
"replaceAll",
"replaceRange",
"replaceSelection",
"reset",
"revalidate",
"reverseBytes",
"reverseOrder",
"rightPad",
"rm",
"run",
"scrollRectToVisible",
"set",
"showOpenDialog",
"sin",
"someMethod",
"sort",
"squish",
"substring",
"tagext",
"toHexString",
"toLZHexString",
"toString",
"toUpperCase",
"toURL",
"trim",
"trimLeading",
"trimToSize",
"trimTrailing",
"unmodifiableCollection",
"unmodifiableList",
"unmodifiableMap",
"update",
"updateImage",
"validate",
"waitFor",
"write",
"writeObject",
"writeUTF",
"yield"
) );
private static final HashSet COMMON_PACKAGE_LEG = new HashSet( Arrays.asList(
"awt",
"com",
"de",
"io",
"lang",
"net",
"util",
"java",
"javax",
"org",
"sun"
) );
private static final HashSet COMMON_VARS = new HashSet( Arrays.asList(
"err",
"gr",
"gr2",
"graphics",
"height",
"length", /* ambiguous. use () to mean the method */
"out",
"var",
"width",
"x",
"y",
"z"
) );
/**
* keep track of all the interfaces in the JDK. Track just hashes, not the fully qualified names themselves.
*/
private static final HashSet extantInterfaces = new HashSet<>( 15_000 );
// used to split qualified names at the dots.
private static final Pattern SPLIT_ON_DOT = Pattern.compile( "\\." );
private static boolean DEBUGGING = false;
/**
* in processing chunks, was a previous chunk a package leg?
*/
private static boolean haveSeenPackage = false;
/**
* looks for xxx.xxx.interfacename in reference.
*/
private static Pattern FULLY_QUALIFIED_INTERFACE_FINDER = Pattern.compile( "([_a-z0-9.]+[A-Z][_a-z0-9]+[<.]?+)" );
// /declarations
// methods
/**
* in processing chunks, was a previous chunk a Class/Interface leg?
*/
private static boolean haveSeenClass = false;
/**
* categorise what his chunk is, part of package,Class,method,var,constant?
*
* @param chunk chunk between dots( may includeor( ).)
* @param isInterface true if we have been told this is an interface.
* @param reference the fully qualified name
*
* @return Kind category of chunk
*/
private static Kind categorise( final String chunk, final boolean isInterface, final String reference, final File fileBeingProcessed )
{
if ( isPackage( chunk ) )
{
return Kind.PACKAGE;
}
else if ( isClass( chunk ) )
{
return isInterface ? Kind.INTERFACE : Kind.CLASS;
}
else if ( chunk.equals( "this" ) || chunk.equals( "class" ) )
{
return Kind.KEYWORD;
}
else if ( isMethod( chunk ) )
{
return Kind.METHOD;
}
else if ( isVar( chunk ) )
{
return Kind.VAR;
}
else if ( isConstant( chunk ) )
{
return Kind.CONSTANT;
}
else
{
// guess that short names are vars and long names are methods.
final Kind result = chunk.length() <= 3 ? Kind.VAR : Kind.METHOD;
out.println( "Guessing that " + chunk + " is a " + result.name() + " in " + reference + " in file " +
EIO.getCanOrAbsPath( fileBeingProcessed ) );
return result;
}
}// /method
/**
* guts of Qualified macro expansion
*
* @param reference the qualified package.class.method name to format.
* @param isInterface if the reference is to an interface rather than a class.
* You can't tell just by looking at the name, unless you had a list
* of all possible references.
*
* @return html expansion for the macro
*/
static String expandReference( final String reference, final boolean isInterface, final File fileBeingProcessed )
{
if ( reference.startsWith( "." ) )
{
throw new IllegalArgumentException( "Qualified reference must not start with dot." );
}
if ( reference.endsWith( "." ) )
{
throw new IllegalArgumentException( "Qualified reference must not end with dot." );
}
/* make sure user marked this name as interface/class correctly */
verifyInterface( reference, isInterface );
// split up at the dots.
String[] chunks = SPLIT_ON_DOT.split( reference );
final FastCat sb = new FastCat( chunks.length * 8 + 1 );
if ( DEBUGGING )
{
out.println( "Qualified processing: " + reference );
}
haveSeenPackage = false;
haveSeenClass = false;
for ( String chunk : chunks )
{
if ( sb.length() == 0 )
{
sb.append( "\n" );
}
else
{
// prefix except first leg,
sb.append( "." );
}
final Kind kind = categorise( chunk, isInterface, reference, fileBeingProcessed );
if ( DEBUGGING )
{
out.println( chunk + " -> " + kind.name() );
}
chunk = EntifyStrings.entifyHTML( chunk );
switch ( kind )
{
case PACKAGE:
sb.append( "", chunk, "" );
break;
case CLASS:
sb.append( "", chunk, "" );
break;
case INTERFACE:
sb.append( "", chunk, "" );
break;
case METHOD:
if ( chunk.endsWith( "()" ) )
{
sb.append( "", ST.chopTrailingString( chunk, "()" ), "" );
sb.append( "()" );
}
else if ( chunk.endsWith( ")" ) )
{
final int place = chunk.indexOf( '(' );
if ( place < 0 )
{
throw new IllegalArgumentException( "unbalanced () in " + chunk );
}
sb.append( "", chunk.substring( 0, place ), "" );
sb.append( "(" );
sb.append( "", chunk.substring( place + 1, chunk.length() - 1 ), "" );
sb.append( ")" );
}
else
{
sb.append( "", chunk, "" );
}
break;
case KEYWORD:
sb.append( "", chunk, "" );
break;
case VAR:
sb.append( "", chunk, "" );
break;
case CONSTANT:
sb.append( "", chunk, "" );
break;
default:
throw new IllegalArgumentException( "unexpected Kind" );
}
} // end loop
sb.append( "" );
return sb.toString();
}// /method
/**
* is this chunk a Class name?
*
* @param chunk chunk between dots in qualified name
*
* @return true if is a Class name
*/
private static boolean isClass( String chunk )
{
if ( COMMON_CLASSES.contains( chunk ) )
{
haveSeenClass = true;
// implied
haveSeenPackage = true;
return true;
}
else if ( Character.isUpperCase( chunk.charAt( 0 ) ) & !chunk.toUpperCase().equals( chunk ) )
{
// not all upper case
haveSeenClass = true;
// implied
haveSeenPackage = true;
return true;
}
else
{
return chunk.contains( "<" ) && chunk.contains( ">" );
}
}// /method
/**
* is this chunk a constant name?
*
* @param chunk chunk between dots in qualified name
*
* @return true if is a method name
*/
private static boolean isConstant( String chunk )
{
if ( chunk.toUpperCase().equals( chunk ) )
{
if ( COMMON_CLASSES.contains( chunk ) )
{
return false;
}
// implied have seen package and class
haveSeenPackage = true;
haveSeenClass = true;
return true;
}
else
{
return false;
}
}// /method
/**
* is this chunk a method name?
*
* @param chunk chunk between dots in qualified name
*
* @return true if is a method name
*/
private static boolean isMethod( final String chunk )
{
if ( ST.isEmpty( chunk ) )
{
return false;
}
if ( Character.isUpperCase( chunk.charAt( 0 ) ) )
{
return false;
}
if ( chunk.endsWith( ")" ) )
{
return true;
}
if ( COMMON_METHODS.contains( chunk ) )
{
return true;
}
return chunk.startsWith( "get" )
|| chunk.startsWith( "set" )
|| chunk.startsWith( "is" );
}// /method
/**
* is this chunk a leg of a package name?
*
* @param chunk chunk between dots in qualified name
*
* @return true if is a package name
*/
private static boolean isPackage( String chunk )
{
if ( ST.isEmpty( chunk ) )
{
return false;
}
if ( !chunk.toLowerCase().equals( chunk ) )
{
return false;
}
if ( haveSeenClass )
{
return false;
}
if ( COMMON_PACKAGE_LEG.contains( chunk ) )
{
haveSeenPackage = true;
return true;
}
return haveSeenPackage;
}// /method
/**
* is this chunk a variable name?
*
* @param chunk chenk between dots in qualified name
*
* @return true if is a variable name
*/
private static boolean isVar( final String chunk )
{
if ( ST.isEmpty( chunk ) )
{
return false;
}
if ( Character.isUpperCase( chunk.charAt( 0 ) ) )
{
return false;
}
return COMMON_VARS.contains( chunk );
}// /method
/**
* verify user hint as class on interface
*
* @param reference whe fully qualified name
* @param isInterface what user told us about this being an interface
*/
private static void verifyInterface( String reference, boolean isInterface )
{
// we can catch interfaces marked as classes, but not the reverse.
// if it is already marked as interface, all is ok.
// If it is marked as class, we want to make sure it not an interface.
if ( isInterface )
{
return;
}
final Matcher m = FULLY_QUALIFIED_INTERFACE_FINDER.matcher( reference );
if ( m.lookingAt() )
{
final String possInterfaceName = m.group( 1 );
if ( DEBUGGING )
{
out.println( "Checking class [" + possInterfaceName + "] to see if it is an interface." );
}
long hash = FNV1a64.computeHash( possInterfaceName );
// only hashes in ram, not fully qualified interface names.
if ( extantInterfaces.contains( hash ) )
{
// user declared this name a class, but we found it in the list of official interfaces.
throw new IllegalArgumentException( "Qualified: " + possInterfaceName + " is an interface. You have it marked as a class." );
}
}
}// /method
/**
* load tables to make Qualified work
*/
public static void fireup()
{
// load a giant list of all interface sames used in the JDK. Put them in digest form in a HashSet so
// we can test if a class is likely really an Interface.
// Unfortunately I have forgotten how I created it. Presumably I used extract on the entire Javadoc.
try
{
final CSVReader r = new CSVReader( new FileReader( new File( webrootDir, "embellishment/allinterfaces.csv" ) ) );
try
{
while ( true )
{
final String interfaceName = r.get();
r.skipToNextLine();
if ( ST.isEmpty( interfaceName ) )
{
continue;
}
final long hash = FNV1a64.computeHash( interfaceName );
extantInterfaces.add( hash );
}
}
catch ( EOFException e )
{
r.close();
}
}
catch ( IOException e )
{
err.println( "Qualifiedcomm failure to load allinterfaces.csv" );
System.exit( 2 );
}
}// /method
/**
* Qualified java.lang.Long.parseLong expands to put css classes around each piece on one line
*
* @param parms parm to keep together.
* @param quiet true if want output suppressed.
* @param verbose @return expanded macro text
*/
public String expandMacro( String[] parms,
final boolean quiet, final boolean verbose )
{
if ( !quiet )
{
out.print( "Q" );
}
if ( !( 1 <= parms.length && parms.length <= 2 ) )
{
throw new IllegalArgumentException( USAGE + "\nWrong number of parameters specified" );
}
final String reference = parms[ 0 ].trim();
// is a class or interface
final boolean isInterface;
if ( parms.length < 2 )
{
isInterface = false;
}
else
{
switch ( parms[ 1 ].trim() )
{
case "interface":
isInterface = true;
break;
case "class":
isInterface = false;
break;
default:
throw new IllegalArgumentException( USAGE + "\nYou must specify either class or interface or leave it out which defaults to class." );
}
}
return expandReference( reference, isInterface, fileBeingProcessed );
}// /method
// /methods
}