/**
* Loads code to process a given custom macro.
*
* @author Roedy Green, Canadian Mind Products
* @version 1.0 2008-07-26 initial version. Extract and expandNoRef code in Include and Replacer.
* Now does cache and looks first in custom package.
* @since 2008-07-26
*/
package com.mindprod.htmlmacros;
import java.util.HashMap;
/**
* Loads code to process a given custom macro.
* <p/>
* Deals with loading the Class to process a macro, creating a fresh instance for each time a macro needs to be
* expanded.
* It maintains a cache of previously loaded Macro Classes, not Macro Instances.
* Used by Include and Replacer only.
* <p/>
* created with Intellij Idea
*
* @author Roedy Green, Canadian Mind Products
* @version 1.0 2008-07-26 initial version. Extract and expandNoRef code in Include and Replacer.
* Now does cache and looks first in custom package.
*/
class LoadCodeToProcessMacro
{
/**
* how many macros max we might load
*/
private static final int MACRO_CACHE_CAPACITY = 200;
/**
* cache of previously loaded Macro processing code classes. We create a fresh instance for each Macro processed.
* Look up Class object(not Macro instance) via unqualified macro name.
* We could have used the System's cache of loaded classes accessible via
* ClassLoader.findLoadedClass(String) but the code would be a tad more complicated.
*/
private static final HashMap<String, Class<? extends Macro>> macroClassCache = new HashMap<String,
Class<? extends Macro>>(
MACRO_CACHE_CAPACITY );
/**
* find class to process macro. Look in three places, cache, custom package and main package.
*
* @param macroName Single word Macro name. Same as class name to process macro.
*
* @return class handle to class to process the macro. Null if does not exist.
*/
private static Class<? extends Macro>
findMacroClass( String macroName )
{
Class<? extends Macro> macroClass = getCachedMacroClass( macroName );
if ( macroClass != null )
{
return macroClass;
}
return loadMacroClass( macroName, "com.mindprod.htmlmacros" );
}
/**
* get class to process macro from cache of previously loaded classes.
*
* @param macroName Single word Macro name. Same as class name to process macro.
*
* @return class handle to class to process the macro. Null if not in cache.
*/
private static Class<? extends Macro> getCachedMacroClass( String macroName )
{
return macroClassCache.get( macroName );
}
/**
* get fresh instance of Class to process this macro. May have to load the class dynamically.
*
* @param macroName Single word Macro name. Same as class name to process macro.
* Code may live in either com.mindprod.htmlmacros package or CUSTOM_MACROS_PACKAGE.
*
* @return interface handle to instance of the class to process the macro.
* @throws InstantiationException if macro class refuses to Instantiate.
* @throws IllegalAccessException if class does not have public access.
* @throws ClassNotFoundException if code for class cannot be found or if it does not implement Macro.
*/
static Macro getMacroProcessorInstance( String macroName ) throws InstantiationException, IllegalAccessException,
ClassNotFoundException
{
Class<? extends Macro> macroClass = findMacroClass( macroName );
if ( macroClass == null )
{
if ( !( macroName.length() > 0
&& Character.isUpperCase( macroName.charAt( 0 ) ) ) )
{
throw new IllegalArgumentException( "macro "
+
macroName
+
" should start with an upper case letter. Possible missing macro " +
"name." );
}
else
{
throw new ClassNotFoundException( "No such macro " + macroName + " or possible coding bug: The code " +
"that implements the Macro interface to process " + macroName + " " +
"could not be found." );
}
}
try
{
return macroClass.newInstance();
}
catch ( ClassCastException e )
{
throw new ClassNotFoundException( "Coding bug: The code to process macro " + macroName + " does not " +
"implement the Macro interface." );
}
catch ( InstantiationException e )
{
throw new InstantiationException( "Coding bug: The code to process macro " + macroName + " would not " +
"instantiate. It needs a public no-arg constructor." );
}
catch ( IllegalAccessException e )
{
throw new IllegalAccessException( "Coding bug: The code to process macro " + macroName + " refused access" +
". It needs a public no-arg constructor." );
}
}
/**
* load class to process macro.
*
* @param macroName Single word Macro name. Same as class name to process macro.
* @param packageName name of package where to look for code for this Macro class.
*
* @return class handle to class to process the macro. Null if does not exist.
*/
private static Class<? extends Macro> loadMacroClass( String macroName, String packageName )
{
try
{
final String binaryClassName = packageName + "." + macroName;
final Class<? extends Macro> macroClass = Class.forName( binaryClassName ).asSubclass( Macro.class );
if ( macroClass != null )
{
macroClassCache.put( macroName, macroClass );
}
return macroClass;
}
catch ( ClassCastException e )
{
throw new ClassCastException( "Coding bug: The code to process macro " + macroName + " refused access. It needs a public no-arg constructor." );
}
catch ( Exception e )
{
return null;
}
}
}