org.jbake.parser.Engines Maven / Gradle / Ivy
Show all versions of jbake-core Show documentation
package org.jbake.parser;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.net.URL;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
/**
* A singleton class giving access to markup engines. Markup engines are loaded based on classpath.
* New engines may be registered either at runtime (not recommanded) or by putting a descriptor file
* on classpath (recommanded).
*
* The descriptor file must be found in META-INF directory and named
* org.jbake.parser.MarkupEngines.properties. The format of the file is easy:
*
* org.jbake.parser.RawMarkupEngine=html
* org.jbake.parser.AsciidoctorEngine=ad,adoc,asciidoc
* org.jbake.parser.MarkdownEngine=md
*
* where the key is the class of the engine (must extend {@link org.jbake.parser.MarkupEngine} and have a no-arg
* constructor and the value is a comma-separated list of file extensions that this engine is capable of proceeding.
*
* Markup engines are singletons, so are typically used to initialize the underlying renderning engines. They
* must not store specific information of a currently processed file (use {@link ParserContext the parser context}
* for that).
*
* This class loads the engines only if they are found on classpath. If not, the engine is not registered. This allows
* JBake to support multiple rendering engines without the explicit need to have them on classpath. This is a better
* fit for embedding.
*
* @author Cédric Champeau
*
*/
public class Engines {
private static final Logger LOGGER = LoggerFactory.getLogger(Engines.class);
private static final Engines INSTANCE;
private final Map parsers;
static {
INSTANCE = new Engines();
loadEngines();
}
public static ParserEngine get(String fileExtension) {
return INSTANCE.getEngine(fileExtension);
}
public static void register(String fileExtension, ParserEngine engine) {
INSTANCE.registerEngine(fileExtension, engine);
}
public static Set getRecognizedExtensions() {
return Collections.unmodifiableSet(INSTANCE.parsers.keySet());
}
private Engines() {
parsers = new HashMap<>();
}
private void registerEngine(String fileExtension, ParserEngine markupEngine) {
ParserEngine old = parsers.put(fileExtension, markupEngine);
if (old != null) {
LOGGER.warn("Registered a markup engine for extension [.{}] but another one was already defined: {}", fileExtension, old);
}
}
private ParserEngine getEngine(String fileExtension) {
return parsers.get(fileExtension);
}
/**
* This method is used to search for a specific class, telling if loading the engine would succeed. This is
* typically used to avoid loading optional modules.
*
* @param engineClassName engine class, used both as a hint to find it and to create the engine itself.
* @return null if the engine is not available, an instance of the engine otherwise
*/
private static ParserEngine tryLoadEngine(String engineClassName) {
try {
@SuppressWarnings("unchecked")
Class extends ParserEngine> engineClass = (Class extends ParserEngine>) Class.forName(engineClassName, false, Engines.class.getClassLoader());
return engineClass.getDeclaredConstructor().newInstance();
} catch (ClassNotFoundException | NoClassDefFoundError | IllegalAccessException | InstantiationException e) {
return new ErrorEngine(engineClassName);
} catch (NoSuchMethodException | InvocationTargetException e) {
LOGGER.error("unable to instantiate ParserEngine {}", engineClassName);
}
return null;
}
/**
* This method is used internally to load markup engines. Markup engines are found using descriptor files on classpath, so
* adding an engine is as easy as adding a jar on classpath with the descriptor file included.
*/
private static void loadEngines() {
try {
ClassLoader cl = Engines.class.getClassLoader();
Enumeration resources = cl.getResources("META-INF/org.jbake.parser.MarkupEngines.properties");
while (resources.hasMoreElements()) {
URL url = resources.nextElement();
Properties props = new Properties();
props.load(url.openStream());
for (Map.Entry