All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.thisptr.jackson.jq.BuiltinFunctionLoader Maven / Gradle / Ivy

There is a newer version: 1.2.0
Show newest version
package net.thisptr.jackson.jq;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.ServiceLoader;

import com.fasterxml.jackson.databind.MappingIterator;
import com.fasterxml.jackson.databind.ObjectMapper;

import net.thisptr.jackson.jq.internal.IsolatedScopeQuery;
import net.thisptr.jackson.jq.internal.JqJson;
import net.thisptr.jackson.jq.internal.JsonQueryFunction;
import net.thisptr.jackson.jq.internal.javacc.ExpressionParser;

/**
 * Use {@code BuiltinFunctionLoader.getInstance()} to obtain the instance.
 */
public class BuiltinFunctionLoader {
	private static BuiltinFunctionLoader INSTANCE = new BuiltinFunctionLoader();

	public static BuiltinFunctionLoader getInstance() {
		return INSTANCE;
	}

	private BuiltinFunctionLoader() {}

	private static final ObjectMapper DEFAULT_MAPPER = new ObjectMapper();

	private static final String CONFIG_PATH = resolvePath(Scope.class, "jq.json");

	/**
	 * Dynamically resolve the path for a resource as packages may be relocated, e.g. by
	 * the maven-shade-plugin.
	 */
	private static String resolvePath(final Class clazz, final String name) {
		final String base = clazz.getName();
		return base.substring(0, base.lastIndexOf('.')).replace('.', '/') + '/' + name;
	}

	/**
	 * Load function definitions from the default resource
	 * from an arbitrary {@link ClassLoader}.
	 * E.g. in an OSGi context this may be the Bundle's {@link ClassLoader}.
	 */
	public Map listFunctions(final ClassLoader classLoader, final Version version, final Scope closureScope) {
		final Map functions = new HashMap<>();
		functions.putAll(loadFunctionsFromJsonJq(classLoader, version, closureScope));
		functions.putAll(loadFunctionsFromServiceLoader(classLoader, version));
		return functions;
	}

	public Map listFunctions(final Version version, final Scope closureScope) {
		return listFunctions(BuiltinFunctionLoader.class.getClassLoader(), version, closureScope);
	}

	public void loadFunctions(final Version version, final Scope closureScope) {
		listFunctions(version, closureScope).forEach(closureScope::addFunction);
	}

	public void loadFunctions(final ClassLoader classLoader, final Version version, final Scope closureScope) {
		listFunctions(classLoader, version, closureScope).forEach(closureScope::addFunction);
	}

	private static List loadConfig(final ClassLoader loader, final String path) throws IOException {
		final List result = new ArrayList<>();
		final Enumeration iter = loader.getResources(path);
		while (iter.hasMoreElements()) {
			final StringBuilder buffer = new StringBuilder();
			try (final BufferedReader reader = new BufferedReader(new InputStreamReader(iter.nextElement().openStream(), StandardCharsets.UTF_8))) {
				while (true) {
					final String line = reader.readLine();
					if (line == null)
						break;
					if (line.startsWith("#"))
						continue;
					buffer.append(line);
					buffer.append('\n');
				}
			}
			final MappingIterator iter2 = DEFAULT_MAPPER.readValues(DEFAULT_MAPPER.getFactory().createParser(buffer.toString()), JqJson.class);
			while (iter2.hasNext()) {
				result.add(iter2.next());
			}
		}
		return result;
	}

	private static String[] extractFunctionNamesFromAnnotationIfVersionMatch(Function fn, final Version version) {
		final net.thisptr.jackson.jq.BuiltinFunction annotation = fn.getClass().getAnnotation(net.thisptr.jackson.jq.BuiltinFunction.class);
		if (annotation == null)
			return null;
		if (!annotation.version().isEmpty()) {
			final VersionRange range = VersionRange.valueOf(annotation.version());
			if (!range.contains(version))
				return new String[0];
		}
		return annotation.value();
	}

	@SuppressWarnings("deprecation")
	private static String[] extractFunctionNamesFromDeprecatedAnnotationIfVersionMatch(Function fn, final Version version) {
		final net.thisptr.jackson.jq.internal.BuiltinFunction annotation = fn.getClass().getAnnotation(net.thisptr.jackson.jq.internal.BuiltinFunction.class);
		if (annotation == null)
			return null;
		if (!annotation.version().isEmpty()) {
			final VersionRange range = VersionRange.valueOf(annotation.version());
			if (!range.contains(version))
				return new String[0];
		}
		return annotation.value();
	}

	/**
	 * Do not use this method. This method is only for Quarkus extension.
	 */
	public Map loadFunctionsFromServiceLoader(final ClassLoader classLoader, final Version version) {
		final Map functions = new HashMap<>();
		for (final Function fn : ServiceLoader.load(Function.class, classLoader)) {
			String[] names = extractFunctionNamesFromAnnotationIfVersionMatch(fn, version);
			if (names == null) { // i.e. if annotation is missing,
				// Look for deprecated annotation as well for compatibility reasons. TODO: Delete this in 1.0.0 release.
				names = extractFunctionNamesFromDeprecatedAnnotationIfVersionMatch(fn, version);
			}

			if (names == null) // i.e. no annotations found
				continue;

			for (final String name : names)
				functions.put(name, fn);
		}
		return functions;
	}

	/**
	 * Do not use this method. This method is only for Quarkus extension.
	 */
	public Map loadFunctionsFromJsonJq(final ClassLoader classLoader, final Version version, final Scope closureScope) {
		try {
			final Map functions = new HashMap<>();
			final List configs = loadConfig(classLoader, CONFIG_PATH);
			for (final JqJson jqJson : configs) {
				for (final JqJson.JqFuncDef def : jqJson.functions) {
					if (def.version != null && !def.version.contains(version))
						continue;
					functions.put(def.name + "/" + def.args.size(), new JsonQueryFunction(def.name, def.args, new IsolatedScopeQuery(ExpressionParser.compile(def.body, version)), closureScope));
				}
			}
			return functions;
		} catch (final IOException e) {
			throw new RuntimeException("Failed to load macros", e);
		}
	}
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy