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

com.blackbuild.klum.ast.util.FactoryHelper Maven / Gradle / Ivy

There is a newer version: 2.0.0-rc.39
Show newest version
/*
 * The MIT License (MIT)
 *
 * Copyright (c) 2015-2024 Stephan Pauxberger
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */
package com.blackbuild.klum.ast.util;

import com.blackbuild.annodocimal.annotations.InlineJavadocs;
import com.blackbuild.groovy.configdsl.transform.PostApply;
import com.blackbuild.groovy.configdsl.transform.PostCreate;
import com.blackbuild.klum.ast.process.BreadcrumbCollector;
import com.blackbuild.klum.ast.process.PhaseDriver;
import groovy.lang.Closure;
import groovy.lang.GroovyClassLoader;
import groovy.lang.GroovyShell;
import groovy.lang.Script;
import groovy.util.DelegatingScript;
import org.codehaus.groovy.control.CompilerConfiguration;
import org.codehaus.groovy.runtime.InvokerHelper;
import org.codehaus.groovy.runtime.ResourceGroovyMethods;
import org.jetbrains.annotations.NotNull;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.nio.file.Paths;
import java.util.Map;
import java.util.Properties;
import java.util.function.Consumer;
import java.util.function.Supplier;

/**
 * Helper methods fro use in convenience factories. This will eventually take a lot of code from
 * the AST generated methods.
 */
@InlineJavadocs
public class FactoryHelper {

    public static final String MODEL_CLASS_KEY = "model-class";

    private FactoryHelper() {
        // static only
    }

    /**
     * Creates an instance of the given class by reading the model script class from the classpath.
     * 

* The model script is determined by reading the properties file META-INF/klum-model/<type>.properties, * which must contain the key 'model-class' with the fully qualified class name of the model script. *

* @param type The type to create * @return The created instance * @param The type to create */ public static T createFromClasspath(Class type) { return createFromClasspath(type, Thread.currentThread().getContextClassLoader()); } /** * Creates an instance of the given class by reading the model script class from the classpath. *

* The model script is determined by reading the properties file META-INF/klum-model/<type>.properties, * which must contain the key 'model-class' with the fully qualified class name of the model script. * The properties and the script class is loaded using the given class loader. *

* @param type The type to create * @param loader The class loader to load the script name and class from * @return The created instance * @param The type to create */ public static T createFromClasspath(Class type, ClassLoader loader) { String path = "META-INF/klum-model/" + type.getName() + ".properties"; try (InputStream stream = loader.getResourceAsStream(path)) { assertResourceExists(path, stream); String configModelClassName = readModelClass(path, stream); return createModelFrom(type, loader, path, configModelClassName); } catch (IOException e) { throw new IllegalStateException("Error while reading marker properties.", e); } } /** * Creates a new instance of the given type using the provided values, key and config closure. *

* First the class is instantiated using the key as constructor argument if keyed. Then the values are applied * by using the keys as method names of the RW instance and the values as the single argument of that method. * Finally the config closure is applied to the RW instance. *

* @param type The type to create * @param values The value map to apply * @param key The key to use for instantiation, ignored if the type is not keyed * @param body The config closure to apply * @return The created instance * @param The type to create */ public static T create(Class type, Map values, String key, Closure body) { return doCreate(type, key, () -> createInstance(type, key), proxy -> proxy.apply(values, body)); } private static T doCreate(Class type, String key, Supplier createInstance, Consumer apply) { try { BreadcrumbCollector.getInstance().enter(type.getSimpleName(), key); T result = createInstance.get(); PhaseDriver.enter(result); KlumInstanceProxy proxy = KlumInstanceProxy.getProxyFor(result); proxy.copyFromTemplate(); LifecycleHelper.executeLifecycleMethods(proxy, PostCreate.class); apply.accept(proxy); PhaseDriver.executeIfReady(); return result; } finally { BreadcrumbCollector.getInstance().leave(); PhaseDriver.leave(); PhaseDriver.cleanup(); } } static T createInstance(Class type, String key) { if (key == null && DslHelper.isKeyed(type)) return createInstanceWithNullArg(type); //noinspection unchecked return (T) InvokerHelper.invokeConstructorOf(type, key); } static T createInstanceWithArgs(Class type, Object... args) { //noinspection unchecked return (T) InvokerHelper.invokeConstructorOf(type, args); } /** * Creates an instance of the given type by running the given script. *

* The script can either be a regular script or a delegating script. A regular script will be instantiated * and executed, the result will be returned. If the script returns the wrong type, an error will be thrown. * For a delegating script, a new instance of the given type will be created and the script run as a closure * on the RW instance. *

*

* Basically, a regular script must start with {@code Type.Create...}, while a delegating script should only * contain the body of the configuration closure. *

*

For a keyed type with a delegating script, the simple name of the script is used as the key, for a * regular script, the script itself is responsible to provide the key.

* @param type The type to create * @param scriptType The script to run * @return The created instance * @param The type to create */ public static T createFrom(Class type, Class scriptType) { if (DelegatingScript.class.isAssignableFrom(scriptType)) return createFromDelegatingScript(type, (DelegatingScript) InvokerHelper.invokeConstructorOf(scriptType, null)); Object result = InvokerHelper.runScript(scriptType, null); if (!type.isInstance(result)) throw new IllegalStateException("Script " + scriptType.getName() + " did not return an instance of " + type.getName()); //noinspection unchecked return (T) result; } static T createFromDelegatingScript(Class type, DelegatingScript script) { Consumer apply = proxy -> { script.setDelegate(proxy.getRwInstance()); script.run(); LifecycleHelper.executeLifecycleMethods(proxy, PostApply.class); }; if (DslHelper.isKeyed(type)) return doCreate(type, script.getClass().getSimpleName(), () -> createInstance(type, script.getClass().getSimpleName()), apply); else return doCreate(type, null, () -> createInstance(type, null), apply); } /** * Creates an instance of the given type by compiling the given text into a delegating script * and applying it to a newly created instance. * * @param type The type to create * @param name The name of the script, only relevant for keyed types * @return The created instance * @param The type to create */ public static T createFrom(Class type, String name, String text, ClassLoader loader) { GroovyShell shell = createGroovyShell(loader); Script parse = name != null ? shell.parse(text, name) : shell.parse(text); return createFromDelegatingScript(type, (DelegatingScript) parse); } /** * Creates an instance of the given type by reading the given URL, compiling it into a delegating script * and applying it to a newly created instance. * * @param type The type to create * @param src The URL to read * @return The created instance * @param The type to create */ public static T createFrom(Class type, URL src, ClassLoader loader) { try { String path = Paths.get(src.getPath()).getFileName().toString(); return createFrom(type, path, ResourceGroovyMethods.getText(src), loader); } catch (IOException e) { throw new KlumException(e); } } /** * Creates an instance of the given type by reading the given file, compiling it into a delegating script * and applying it to a newly created instance. * * @param type The type to create * @param file The file to read * @return The created instance * @param The type to create */ public static T createFrom(Class type, File file, ClassLoader loader) { try { return createFrom(type, file.getName(), ResourceGroovyMethods.getText(file), loader); } catch (IOException e) { throw new KlumException(e); } } /** * Creates a template of the given type by reading the given resource, compiling it into a delegating script * and applying it to a newly created instance. *

* Template instance don't run lifecycle phases/methods. *

* * @param type The type to create * @param scriptFile The resource to read * @return The created instance * @param The type to create */ public static T createAsTemplate(Class type, File scriptFile, ClassLoader loader) { try { return createAsTemplate(type, ResourceGroovyMethods.getText(scriptFile), loader); } catch (IOException e) { throw new KlumException(e); } } /** * Creates a template of the given type by reading the given resource, compiling it into a delegating script * and applying it to a newly created instance. *

* Template instance don't run lifecycle phases/methods. *

* * @param type The type to create * @param script The resource to read * @return The created instance * @param The type to create */ public static T createAsTemplate(Class type, URL script, ClassLoader loader) { try { return createAsTemplate(type, ResourceGroovyMethods.getText(script), loader); } catch (IOException e) { throw new KlumException(e); } } /** * Creates a template of the given type by reading the given resource, compiling it into a delegating script * and applying it to a newly created instance. *

* Template instance don't run lifecycle phases/methods. *

* * @param type The type to create * @param text The script text * @return The created instance * @param The type to create */ public static T createAsTemplate(Class type, String text, ClassLoader loader) { T result = createAsTemplate(type); KlumInstanceProxy proxy = KlumInstanceProxy.getProxyFor(result); proxy.copyFromTemplate(); DelegatingScript script = (DelegatingScript) createGroovyShell(loader).parse(text); script.setDelegate(proxy.getRwInstance()); script.run(); return result; } @NotNull private static GroovyShell createGroovyShell(ClassLoader loader) { GroovyClassLoader gLoader = new GroovyClassLoader(loader != null ? loader : Thread.currentThread().getContextClassLoader()); CompilerConfiguration compilerConfiguration = new CompilerConfiguration(); compilerConfiguration.setScriptBaseClass(DelegatingScript.class.getName()); return new GroovyShell(gLoader, compilerConfiguration); } /** * Creates a new template instance of the given type using the provided values and config closure. *

* The values are applied by using the keys as method names of the RW instance and the values as the single argument * of that method. Finally the config closure is applied to the RW instance. * Template instance don't run lifecycle phases/methods. *

* @param type The type to create * @param values The value map to apply * @param closure The config closure to apply * @return The created instance * @param The type to create */ public static T createAsTemplate(Class type, Map values, Closure closure) { T result = createAsTemplate(type); KlumInstanceProxy proxy = KlumInstanceProxy.getProxyFor(result); proxy.copyFromTemplate(); proxy.applyOnly(values, closure); return result; } private static T createAsTemplate(Class type) { if (!DslHelper.isInstantiable(type)) return createAsSyntheticTemplate(type); else if (DslHelper.isKeyed(type)) return createInstanceWithNullArg(type); else return createInstanceWithArgs(type); } public static T createAsStub(Class type, String key) { return createInstance(type, key); } private static T createInstanceWithNullArg(Class type) { //noinspection unchecked return (T) InvokerHelper.invokeConstructorOf(type, new Object[] {null}); } private static T createAsSyntheticTemplate(Class type) { try { //noinspection unchecked return (T) type.getClassLoader().loadClass(type.getName() + "$Template").getDeclaredConstructor().newInstance(); } catch (Exception e) { throw new IllegalArgumentException(String.format("Could new instantiate synthetic template class, is %s a KlumDSL Object?", type), e); } } @SuppressWarnings("unchecked") private static T createModelFrom(Class type, ClassLoader loader, String path, String configModelClassName) { try { Class modelClass = (Class) loader.loadClass(configModelClassName); return createFrom(type, modelClass); } catch (ClassNotFoundException e) { throw new IllegalStateException("Class '" + configModelClassName + "' defined in " + path + " does not exist", e); } catch (Exception e) { throw new IllegalStateException("Could not read model from " + configModelClassName, e); } } private static void assertResourceExists(String path, InputStream stream) { if (stream == null) throw new IllegalStateException("File " + path + " not found in classpath."); } private static String readModelClass(String path, InputStream stream) throws IOException { Properties marker = new Properties(); marker.load(stream); String configModelClassName = marker.getProperty(MODEL_CLASS_KEY); if (configModelClassName == null) throw new IllegalStateException("No entry 'model-class' found in " + path); return configModelClassName; } private static void breadcrumb(String path) { BreadcrumbCollector.getInstance().enter(path); } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy