Skip to content
Roberto Gentili edited this page Dec 14, 2022 · 2 revisions

Configuration

ClassFactory

ClassHunter

CodeExecutor


How to enable logging of Burningwave Core classes?

You can solve adding to base path of your project classpath a file named burningwave.static.properties and setting the property managed-logger.repository.enabled to true if you want to enable logging otherwise false. E.g.:

managed-logger.repository=autodetect
managed-logger.repository.enabled=true

How to hide banner during startup?

You can solve adding to base path of your project classpath a file named burningwave.static.properties and setting the property banner.hide to true. E.g.:

managed-logger.repository=autodetect
managed-logger.repository.enabled=false
buffer-handler.default-buffer-size=1024
buffer-handler.default-allocation-mode=ByteBuffer::allocateDirect
banner.hide=true

How to configure the StaticComponentContainer?

The configuration of this type of container is done via burningwave.static.properties file or via burningwave.static.default.properties file: the library searches for the first file and if it does not find it, then it searches for the second file and if neither this one is found then the library sets the default configuration programmatically. The default configuration loaded programmatically if no configuration file is found is available at this link and here an example of a custom burningwave.static.properties.


How to configure the ComponentContainer?

The configuration of this type of container can be done via Properties file or programmatically via a Properties object. If you use the singleton instance obtained via ComponentContainer.getInstance() method, you must create a burningwave.properties file and put it on base path of your classpath project. The default configuration automatically loaded if no configuration file is found is available at this link. If in your custom burningwave.properties file one of this four default properties is not found, the relative default value is assumed. If you create a component container instance through method ComponentContainer.create(String relativeConfigFileName), you can specify the file name of your properties file and you can locate it everywhere in your classpath project but remember to use a relative path in this case, i.e.: if you name your file custom-config-file.properties and put it in package org.burningwave you must create the component container as follow: ComponentContainer.create("org/burningwave/custom-config-file.properties") and here an example of a custom burningwave.properties.


Which is the value of 'main-class-paths' variable used in burningwave.properties file?

The value of this property is a list of the runtime classpaths.


What is the 'paths.class-factory.java-memory-compiler.class-repositories' property for?

This property is used by the JavaMemoryCompiler and indirectly by the ClassFactory to indicate, in case the sources to be compiled refer to classes that are not present in the runtime class paths, in which paths the JavaMemoryCompiler must searches them. Here, for example, we are adding the path 'C:/Users/user_name/.m2':

path-scanner-class-loader.parent=Thread.currentThread().getContextClassLoader()
paths.main-class-paths.extension=//${system.properties:java.home}/lib//children:.*\.jar|.*\.jmod;//${system.properties:java.home}/lib/ext//children:.*\.jar|.*\.jmod;//${system.properties:java.home}/jmods//children:.*\.jar|.*\.jmod;
class-factory.default-class-loader=Thread.currentThread().getContextClassLoader()
paths.java-memory-compiler.additional-class-repositories=C:/Users/user_name/.m2;
paths.class-factory.default-class-loader.additional-class-repositories=${paths.class-factory.java-memory-compiler.class-repositories};

What is the 'paths.class-factory.default-class-loader.class-repositories' property for?

This property is used by the ClassFactory to indicate, in case the sources to be compiled refer to classes that are not present in the runtime class paths, in which paths the classloader must searches them. Here, for example, we are telling this property will assume the same values of 'paths.java-memory-compiler.class-repositories':

path-scanner-class-loader.parent=Thread.currentThread().getContextClassLoader()
paths.main-class-paths.extension=//${system.properties:java.home}/lib//children:.*\.jar|.*\.jmod;//${system.properties:java.home}/lib/ext//children:.*\.jar|.*\.jmod;//${system.properties:java.home}/jmods//children:.*\.jar|.*\.jmod;
class-factory.default-class-loader=Thread.currentThread().getContextClassLoader()
paths.java-memory-compiler.class-repositories=${main-class-paths};C:/Users/user_name/.m2;
paths.class-factory.default-class-loader.additional-class-repositories=${paths.class-factory.java-memory-compiler.class-repositories};

How can I integrate the configuration properties into Spring?

To use a single configuration file when using Burningwave Core with Spring, you can proceed by entering the configuration properties of Burningwave Core in the main configuration file of Spring and adding a prefix differentiated by container type to the aforementioned properties:

  • application.yml:
burningwave.core.component-container:
   class-hunter.default-path-scanner-class-loader: '(Supplier<PathScannerClassLoader>)() -> ((ComponentSupplier)parameter[0]).getPathScannerClassLoader()'
burningwave.core.static-component-container:
   managed-logger.repository: 'org.burningwave.core.SLF4JManagedLoggerRepository'
   modules.export-all-to-all: false
  • application.properties:
burningwave.core.component-container.class-hunter.default-path-scanner-class-loader=(Supplier<PathScannerClassLoader>)() -> ((ComponentSupplier)parameter[0]).getPathScannerClassLoader()
burningwave.core.static-component-container.managed-logger.repository=org.burningwave.core.SLF4JManagedLoggerRepository
burningwave.core.static-component-container.modules.export-all-to-all=false

... And then adding the following beans to the Spring setup class:

@Bean("burningwave.core.staticComponentContainer.config")
@ConfigurationProperties("burningwave.core.static-component-container")
public Map<String, String> staticComponentContainerConfig(){
    return new LinkedHashMap<>();
}

@Bean
public Class<org.burningwave.core.assembler.StaticComponentContainer> staticComponentContainer(
    @Qualifier("burningwave.core.staticComponentContainer.config") Map<String, String> configMap
) {
    org.burningwave.core.assembler.StaticComponentContainer.Configuration.Default.add(configMap);
    return org.burningwave.core.assembler.StaticComponentContainer.class;
}

@Bean("burningwave.core.componentContainer.config")
@ConfigurationProperties("burningwave.core.component-container")
public Map<String, String> componentContainerConfig(){
    return new LinkedHashMap<>();
}

@Bean(name="burningwave.core.componentContainer", destroyMethod="")
public org.burningwave.core.assembler.ComponentContainer componentContainer(
    @Qualifier("burningwave.core.componentContainer.config") Map<String, String> configMap
) {
    org.burningwave.core.assembler.ComponentContainer.Configuration.Default.add(configMap);
    return org.burningwave.core.assembler.ComponentContainer.getInstance();
}

How can I use classes located outside the runtime class path in my sources to be generated?

You can do it in two different ways:

  • statically by adding the comma separated paths to the property paths.java-memory-compiler.additional-class-repositories or the property paths.class-factory.default-class-loader.additional-class-repositories of your burningwave.properties file
  • dinamically by calling methods of adding/setting class paths/repositories on your LoadOrBuildAndDefineConfig

If you decide to do it statically you can add the external class paths to the paths.java-memory-compiler.additional-class-repositories or paths.class-factory.default-class-loader.additional-class-repositories properties of burningwave.properties file. In the default configuration these two properties are included in class-factory.default-class-loader.class-repositories property that is automatically loaded by the ClassFactory when executing the method loadOrBuildAndDefine: all paths included in all these properties will be scanned for searching classes useful for compilation and class loading. Here you can find the explanation of the role and examples of how to configure the:

If you decide to do it dinamically it is suggested to call on your LoadOrBuildAndDefineConfig the method addClassRepository/addClassRepositories or the method addClassRepositoriesWhereToSearchNotFoundClasses/addClassRepositoryWhereToSearchNotFoundClasses: these two methods will add the paths received in input to the paths referenced by the paths.java-memory-compiler.class-repositories and paths.class-factory.default-class-loader.additional-class-repositories properties of burningwave.properties file. The difference between the couple addClassRepository/addClassRepositories and addClassRepositoriesWhereToSearchNotFoundClasses/addClassRepositoryWhereToSearchNotFoundClasses is that the first couple of method executes a scan for searching for all class paths of all imported classes by the sources before compiling and then, if compilation has done with errors, will be executed a new scan; instead, the second couple of methods, will executes a scan for searching for all class paths of all imported classes by the sources only if compilation has done with errors. After the compilation has done without errors, the same paths are scanned by the class loader during loading in case of not found classes; Here you can find an example about the generation of classes by using libraries located outside the runtime class paths. If you need to set class paths from scratch, instead of using the previous methods, you must use the method setClassRepository/setClassRepositories or the method setClassRepositoriesWhereToSearchNotFoundClasses/setClassRepositoryWhereToSearchNotFoundClasses.


How to add a method to an interface?

You need to add the abstract modifier to the method:

ClassSourceGenerator.createInterface(
    TypeDeclarationSourceGenerator.create("MyInterface")
).addMethod(
    FunctionSourceGenerator.create("doSomething").setReturnType(void.class)
).addModifier(Modifier.PUBLIC | Modifier.ABSTRACT)

Is it possible to generate a lambda?

Yes, you can do it in several ways:

  • you can use a lambda in your source class:
package org.burningwave.core.examples.classfactory;

import static org.burningwave.core.assembler.StaticComponentContainer.Constructors;

import java.lang.reflect.Modifier;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.Date;
import java.util.function.Function;

import org.burningwave.core.Virtual;
import org.burningwave.core.assembler.ComponentContainer;
import org.burningwave.core.assembler.ComponentSupplier;
import org.burningwave.core.classes.AnnotationSourceGenerator;
import org.burningwave.core.classes.ClassFactory;
import org.burningwave.core.classes.ClassSourceGenerator;
import org.burningwave.core.classes.FunctionSourceGenerator;
import org.burningwave.core.classes.GenericSourceGenerator;
import org.burningwave.core.classes.TypeDeclarationSourceGenerator;
import org.burningwave.core.classes.UnitSourceGenerator;
import org.burningwave.core.classes.VariableSourceGenerator;

public class UseFunctionalInterface {

    @SuppressWarnings("resource")
    public static void execute() throws Throwable {
        UnitSourceGenerator unitSG = UnitSourceGenerator.create("packagename").addClass(
            ClassSourceGenerator.create(
                TypeDeclarationSourceGenerator.create("MyExtendedClass")
            ).addModifier(
                Modifier.PUBLIC
            //generating new method that override MyInterface.convert(LocalDateTime)
            ).addMethod(
                FunctionSourceGenerator.create("convert")
                .setReturnType(TypeDeclarationSourceGenerator.create(Comparable.class).addGeneric(GenericSourceGenerator.create(Date.class)))
                .addParameter(VariableSourceGenerator.create(LocalDateTime.class, "localDateTime"))
                .addModifier(Modifier.PUBLIC)
                .addAnnotation(AnnotationSourceGenerator.create(Override.class))
                .addBodyCodeLine("Function<LocalDateTime, Comparable<Date>> fI = (lDT) -> Date.from(lDT.atZone(ZoneId.systemDefault()).toInstant());")
                .addBodyCodeLine("return fI.apply(localDateTime);")
                .useType(ZoneId.class)
                .useType(Function.class)
            ).addConcretizedType(
                MyInterface.class
            ).expands(ToBeExtended.class)
        );
        System.out.println("\nGenerated code:\n" + unitSG.make());
        //With this we store the generated source to a path
        unitSG.storeToClassPath(System.getProperty("user.home") + "/Desktop/bw-tests");
        ComponentSupplier componentSupplier = ComponentContainer.getInstance();
        ClassFactory classFactory = componentSupplier.getClassFactory();
        //this method compile all compilation units and upload the generated classes to default
        //class loader declared with property "class-factory.default-class-loader" in 
        //burningwave.properties file (see "Overview and configuration").
        //If you need to upload the class to another class loader use
        //loadOrBuildAndDefine(LoadOrBuildAndDefineConfig) method
        Class<?> generatedClass = classFactory.loadOrBuildAndDefine(
            unitSG
        ).get(
            "packagename.MyExtendedClass"
        );
        ToBeExtended generatedClassObject =
            Constructors.newInstanceOf(generatedClass);
        generatedClassObject.printSomeThing();
        System.out.println(
            ((MyInterface)generatedClassObject).convert(LocalDateTime.now()).toString()
        );
        //You can also invoke methods by casting to Virtual (an interface offered by the
        //library for faciliate use of runtime generated classes)
        Virtual virtualObject = (Virtual)generatedClassObject;
        //Invoke by using reflection
        virtualObject.invoke("printSomeThing");
        //Invoke by using MethodHandle
        virtualObject.invokeDirect("printSomeThing");
        System.out.println(
            ((Date)virtualObject.invokeDirect("convert", LocalDateTime.now())).toString()
        );
    }   

    public static class ToBeExtended {

        public void printSomeThing() {
            System.out.println("Called method printSomeThing");
        }

    }

    public static interface MyInterface {

        public Comparable<Date> convert(LocalDateTime localDateTime);

    }

    public static void main(String[] args) throws Throwable {
        execute();
    }
}
  • if you need to create a functional interface and bind it to an object you can use the FunctionalInterfaceFactory as shown in this JUnit test::
@Test
public void getOrBuildFunctionClassTestOne() throws Throwable {
    ComponentSupplier componentSupplier = getComponentSupplier();
    testDoesNotThrow(() -> {
        Method mth = Members.findOne(
            MethodCriteria.forEntireClassHierarchy()
            .name((name) -> name.matches("apply"))
            .and().parameterType((params, idx) -> idx == 0 && params[idx].equals(Object.class))
            .and().parameterType((params, idx) -> idx == 1 && params[idx].equals(String.class))
            .and().parameterType((params, idx) -> idx == 2 && params[idx].equals(String.class)),
            Service.class                
        );
        MultiParamsFunction<String> virtualObj = componentSupplier.getFunctionalInterfaceFactory().create(mth);
        virtualObj.apply(new Service(), "Hello", "World!", "How are you?");
    });
}
  • you can create a MultiParameterFunction with the ClassFactory:
ComponentSupplier componentSupplier = ComponentContainer.getInstance();
ClassFactory classFactory = componentSupplier.getClassFactory();
//In this case we are creating a functional interface with 5 input parameters
Class<MultiParamsFunction<?>> cls = classFactory.loadOrBuildAndDefineFunctionSubType(5);
  • you can generate a functional interface class:
package org.burningwave.core.examples.classfactory;

import java.lang.reflect.Modifier;
import java.util.function.Function;

import org.burningwave.core.assembler.ComponentContainer;
import org.burningwave.core.assembler.ComponentSupplier;
import org.burningwave.core.classes.AnnotationSourceGenerator;
import org.burningwave.core.classes.ClassFactory;
import org.burningwave.core.classes.ClassSourceGenerator;
import org.burningwave.core.classes.FunctionSourceGenerator;
import org.burningwave.core.classes.TypeDeclarationSourceGenerator;
import org.burningwave.core.classes.UnitSourceGenerator;
import org.burningwave.core.classes.VariableSourceGenerator;

public class FunctionalInterfaceBuilder {

    @SuppressWarnings("resource")
    public static void execute() throws Throwable {
        UnitSourceGenerator unitSG = UnitSourceGenerator.create("packagename").addClass(
            ClassSourceGenerator.createInterface(
                TypeDeclarationSourceGenerator.create("MyFunctionalInterface")
            ).addModifier(
                Modifier.PUBLIC
            //generating new method that override MyInterface.convert(LocalDateTime)
            ).addMethod(
                FunctionSourceGenerator.create("convert")
                .setReturnType(TypeDeclarationSourceGenerator.create(Integer.class))
                .addParameter(VariableSourceGenerator.create(String.class, "number"))
                .addModifier(Modifier.ABSTRACT)
            ).addAnnotation(AnnotationSourceGenerator.create(FunctionalInterface.class))
        );
        System.out.println("\nGenerated code:\n" + unitSG.make());
        //With this we store the generated source to a path
        unitSG.storeToClassPath(System.getProperty("user.home") + "/Desktop/bw-tests");
        ComponentSupplier componentSupplier = ComponentContainer.getInstance();
        ClassFactory classFactory = componentSupplier.getClassFactory();
        @SuppressWarnings("unused")
        Class<?> generatedClass = classFactory.loadOrBuildAndDefine(
            unitSG
        ).get(
            "packagename.MyFunctionalInterface"
        );
    }   

    public static void main(String[] args) throws Throwable {
        execute();
    }
}

How to perform a scan based on files signature check instead a scan based on files extension check?

Simply call the method SearchConfigAbst.withScanFileCriteria(FileSystemItem.Criteria) as follow:

import java.util.Collection;
import org.burningwave.core.assembler.ComponentContainer;
import org.burningwave.core.assembler.ComponentSupplier;
import org.burningwave.core.classes.ClassHunter;
import org.burningwave.core.classes.ClassHunter.SearchResult;
import org.burningwave.core.classes.SearchConfig;
import org.burningwave.core.io.FileSystemItem;
import org.burningwave.core.io.PathHelper;

public class Finder {
    public Collection<Class<?>> find() {
        ComponentSupplier componentSupplier = ComponentContainer.getInstance();
        PathHelper pathHelper = componentSupplier.getPathHelper();
        ClassHunter classHunter = componentSupplier.getClassHunter();
        SearchConfig searchConfig = SearchConfig.forPaths(
            pathHelper.getMainClassPaths()
        ).setFileFilter(
            FileSystemItem.Criteria.forClassTypeFiles(    
                FileSystemItem.CheckingOption.FOR_SIGNATURE
            )
        );
        try (ClassHunter.SearchResult searchResult = classHunter.findBy(searchConfig)) {
            return searchResult.getClasses();
        }
    }
}

Is there a CPU optimized way to exclude jar files from class search?

If you need performance and the search criterion is limited to the name of the class or of the package, you should consider the possibility of using the FileSystemItem combined with JavaClass and ClassLoaders. For not compressed folders is better to use the FileSystemItem.findRecursiveInChildren instead for archives (zip, jar, ear, war and jmod) due to their flat nature and for folders within them it's better to use FileSystemItem.findInAllChildren. If you use the ClassHunter with default configuration it loads the classes on the shared PathScannerClassLoader of the ComponentContainer so if you do not call the method useNewIsolatedClassLoader() on the SearchConfig object the classes you have searched are loaded in the parents of PathScannerClassLoader or within itself. But if you want to proceed with manual upload you can do as following:

PathScannerClassLoader pathScannerClassLoader = componentSupplier.getPathScannerClassLoader();
pathScannerClassLoader.scanPathsAndAddAllByteCodesFound(Arrays.asList("absolutePath.of.your.jar"));
pathScannerClassLoader.loadClass("className");

Take also a look here if you want have more informations about the ClassHunter. You can also set the parent of the shared PathScannerClassLoader through the Javacoded property path-scanner-class-loader.parent in the burningwave.properties file. Here an example of an optimized search:

SearchConfig.forPaths(
    pathHelper.getAbsolutePathOfResource("../../src/test/external-resources")
).addPaths(
    pathHelper.getPaths(path -> path.endsWith("jar"))
).addFileSystemItems(
    //folder inside a compressed archive
    pathHelper.getResource("../guava-30.1.1-jre.jar/com/google")
).setFindFunction(
    currentScannedPath -> {
        // Check if the path is a folder outside of a zip archive
        return currentScannedPath.isFolder() && !currentScannedPath.isCompressed() ?
            FileSystemItem.Find.RECURSIVE_IN_CHILDREN:
            FileSystemItem.Find.IN_ALL_CHILDREN;
    }
).setFileFilter(
    currentScannedPath -> {
        if (currentScannedPath.isFolder() && !currentScannedPath.isCompressed()) {
            return FileSystemItem.Criteria.forAllFileThat(
                fileSystemItem ->
                    fileSystemItem.isFolder() ||
                    (fileSystemItem.getExtension() != null &&
                    !fileSystemItem.getExtension().equals("zip") && 
                    !fileSystemItem.getExtension().equals("jar") && 
                    fileSystemItem.toJavaClass() != null)
            );
        }
        return FileSystemItem.Criteria.forClassTypeFiles(FileSystemItem.CheckingOption.FOR_NAME);
    }
)

How does the CodeExecutor work?

In first way the CodeExecutor uses the org.burningwave.core.classes.SourceCodeHandler that, by using the source generators components of the same package (AnnotationSourceGenerator, BodySourceGenerator, ClassSourceGenerator, FunctionSourceGenerator, GenericSourceGenerator, TypeDeclarationSourceGenerator, UnitSourceGenerator, VariableSourceGenerator) it generates the source from the code supplied. Then the generated sources are passed to org.burningwave.core.classes.ClassFactory that uses the org.burningwave.core.classes.JavaMemoryCompiler to compile them: the compiled sources are finally passed to the ClassLoader setted through useClassLoader method of org.burningwave.core.classes.ExecuteConfig received by the CodeExecutor or, if it has not be supplied, by a one shot org.burningwave.core.classes.MemoryClassLoader with the parent class loader set by the property class-factory.default-class-loader of burningwave configuration file (see Architectural overview and configuration). In the default configuration the parent class loader is the class loader obtained through Thread.currentThread().getContextClassLoader() method call.Once the class is loaded the CodeExecutor create an instance of it and calls the method execute inherited from org.burningwave.core.Executor interface.

Examples of use of some components:

BackgroundExecutor
ClassFactory
ClassHunter
ClassPathHunter
CodeExecutor
Constructors
Fields
FileSystemItem
FunctionalInterfaceFactory
IterableObjectHelper
JavaMemoryCompiler
Methods
PathHelper
PropertyAccessor
UnitSourceGenerator

HitCount

Clone this wiki locally