/* * [LoadCodeToProcessMacro.java] * * Summary: Loads code to process a given custom macro. * * Copyright: (c) 2008-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 2008-07-26 initial version. Extract and expand code in Include and Replacer. * Now does cache and looks first in custom package. */ /** * 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 cacheFIle and looks first in custom package. * @since 2008-07-26 */ package com.mindprod.htmlmacros.support; import com.mindprod.htmlmacros.macro.Macro; import java.util.HashMap; /** * Loads code to process a given custom macro. *

* 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 cacheFIle of previously loaded Macro Classes, not Macro Instances. * Used by Include and Replacer only. * * @author Roedy Green, Canadian Mind Products * @version 1.0 2008-07-26 initial version. Extract and expandNoRef code in Include and Replacer. * Now does cacheFIle and looks first in custom package. * @since 2008-07-26 */ public class LoadCodeToProcessMacro { /** * how many macros max we might load */ private static final int MACRO_CACHE_CAPACITY = 200; /** * cacheFIle 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 cacheFIle of loaded classes accessible via * ClassLoader.findLoadedClass(String) but the code would be a tad more complicated. */ private static final HashMap> macroClassCache = new HashMap<>( MACRO_CACHE_CAPACITY ); /** * find class to process macro. Look in three places, cacheFIle, 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 findMacroClass( String macroName ) { Class macroClass = getCachedMacroClass( macroName ); if ( macroClass != null ) { return macroClass; } // not in custom package, look in main package. return loadMacroClass( macroName, "com.mindprod.htmlmacros.macro" ); // return with possibly null result. } /** * get class to process macro from cacheFIle 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 cacheFIle. */ private static Class getCachedMacroClass( String macroName ) { return macroClassCache.get( macroName ); } /** * 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 loadMacroClass( String macroName, String packageName ) { try { // e.g. parm to Class.forName looks like: "Measure" final String binaryClassName = packageName + "." + macroName; // Make sure the class we load extends Macro. final Class macroClass = Class.forName( binaryClassName ).asSubclass( Macro.class ); if ( macroClass != null ) { // save copy of class object for future use. macroClassCache.put( macroName, macroClass ); } return macroClass; } catch ( ClassCastException e ) { // macro is screwed up, but the code exists. throw new ClassCastException( "Coding bug: The code to process macro " + macroName + " refused access. It" + " needs a public no-arg constructor, and extend com.mindprod.htmlmacros" + ".macro.Macro and live in package com.mindprod.htmlmacros.macro." ); } catch ( Exception e ) { // might have been ClassNotFoundException, NoClassDefFoundException // Any problem is a failure. return null; } } /** * 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.macro 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. */ public static Macro getMacroProcessorInstance( String macroName ) throws InstantiationException, IllegalAccessException, ClassNotFoundException { Class 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 { // This cast will fail if the loaded Macro code does not implement Macro. 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 ) { // macro is screwed up if it won't instantiate. throw new InstantiationException( "Coding bug: The code to process macro " + macroName + " would not " + "instantiate. It needs a public no-arg constructor." ); } catch ( IllegalAccessException e ) { // macro is screwed up if if does not have no-arg public constructor. throw new IllegalAccessException( "Coding bug: The code to process macro " + macroName + " refused access" + ". It needs a public no-arg constructor." ); } } }