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

org.jboss.logmanager.configuration.ObjectBuilder Maven / Gradle / Ivy

The newest version!
/*
 * JBoss, Home of Professional Open Source.
 *
 * Copyright 2022 Red Hat, Inc., and individual contributors
 * as indicated by the @author tags.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.jboss.logmanager.configuration;

import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.nio.charset.Charset;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
import java.util.TimeZone;
import java.util.function.Supplier;
import java.util.logging.Level;
import java.util.regex.Pattern;

import org.jboss.modules.Module;
import org.jboss.modules.ModuleLoader;

/**
 * Helper to lazily build an object.
 *
 * @author James R. Perkins
 */
@SuppressWarnings({ "UnusedReturnValue" })
class ObjectBuilder {
    private final ContextConfiguration contextConfiguration;
    private final Class baseClass;
    private final String className;
    private final Map constructorProperties;
    private final Map properties;
    private final Set definedProperties;
    private final Set postConstructMethods;
    private String moduleName;

    private ObjectBuilder(final ContextConfiguration contextConfiguration,
            final Class baseClass, final String className) {
        this.contextConfiguration = contextConfiguration;
        this.baseClass = baseClass;
        this.className = className;
        constructorProperties = new LinkedHashMap<>();
        properties = new LinkedHashMap<>();
        definedProperties = new LinkedHashSet<>();
        postConstructMethods = new LinkedHashSet<>();
    }

    /**
     * Create a new {@link ObjectBuilder}.
     *
     * @param baseClass the base type
     * @param className the name of the class to create
     * @param        the type being created
     *
     * @return a new {@link ObjectBuilder}
     */
    static  ObjectBuilder of(final ContextConfiguration contextConfiguration,
            final Class baseClass, final String className) {
        return new ObjectBuilder<>(contextConfiguration, baseClass, className);
    }

    /**
     * Adds a property used for constructing the object.
     * 

* The {@code name} must be the base name for a getter or setter so the type can be determined. *

* * @param name the name of the property * @param value a string representing the value * * @return this builder */ ObjectBuilder addConstructorProperty(final String name, final String value) { constructorProperties.put(name, value); return this; } /** * Adds a method name to be executed after the object is created and the properties are set. The method must not * have any parameters. * * @param methodNames the name of the method to execute * * @return this builder */ ObjectBuilder addPostConstructMethods(final String... methodNames) { if (methodNames != null) { Collections.addAll(postConstructMethods, methodNames); } return this; } /** * Adds a property to be set on the object after it has been created. * * @param name the name of the property * @param value a string representing the value * * @return this builder */ ObjectBuilder addProperty(final String name, final String value) { properties.put(name, value); return this; } /** * Adds a defined property to be set after the object is created. * * @param name the name of the property * @param type the type of the property * @param value a supplier for the property value * * @return this builder */ ObjectBuilder addDefinedProperty(final String name, final Class type, final Supplier value) { definedProperties.add(new PropertyValue(name, type, value)); return this; } /** * Sets the name of the module used to load the object being created. * * @param moduleName the module name or {@code null} to use this class loader * * @return this builder */ ObjectBuilder setModuleName(final String moduleName) { this.moduleName = moduleName; return this; } /** * Creates a the object when the {@linkplain Supplier#get() supplier} value is accessed. * * @return a supplier which can create the object */ Supplier build() { if (className == null) { throw new IllegalArgumentException("className is null"); } final Map constructorProperties = new LinkedHashMap<>(this.constructorProperties); final Map properties = new LinkedHashMap<>(this.properties); final Set postConstructMethods = new LinkedHashSet<>(this.postConstructMethods); final String moduleName = this.moduleName; return () -> { final ClassLoader classLoader; if (moduleName != null) { try { classLoader = ModuleFinder.getClassLoader(moduleName); } catch (Throwable e) { throw new IllegalArgumentException(String.format("Failed to load module \"%s\"", moduleName), e); } } else { classLoader = getClass().getClassLoader(); } final Class actualClass; try { actualClass = Class.forName(className, true, classLoader).asSubclass(baseClass); } catch (Exception e) { throw new IllegalArgumentException(String.format("Failed to load class \"%s\"", className), e); } final int length = constructorProperties.size(); final Class[] paramTypes = new Class[length]; final Object[] params = new Object[length]; int i = 0; for (Map.Entry entry : constructorProperties.entrySet()) { final String property = entry.getKey(); final Class type = getConstructorPropertyType(actualClass, property); if (type == null) { throw new IllegalArgumentException( String.format("No property named \"%s\" in \"%s\"", property, className)); } paramTypes[i] = type; params[i] = getValue(actualClass, property, type, entry.getValue()); i++; } final Constructor constructor; try { constructor = actualClass.getConstructor(paramTypes); } catch (Exception e) { throw new IllegalArgumentException(String.format("Failed to locate constructor in class \"%s\"", className), e); } // Get all the setters final Map setters = new LinkedHashMap<>(); for (Map.Entry entry : properties.entrySet()) { final Method method = getPropertySetter(actualClass, entry.getKey()); if (method == null) { throw new IllegalArgumentException(String .format("Failed to locate setter for property \"%s\" on type \"%s\"", entry.getKey(), className)); } // Get the value type for the setter Class type = getPropertyType(method); if (type == null) { throw new IllegalArgumentException(String .format("Failed to determine type for setter \"%s\" on type \"%s\"", method.getName(), className)); } setters.put(method, getValue(actualClass, entry.getKey(), type, entry.getValue())); } // Define known type parameters for (PropertyValue value : definedProperties) { final String methodName = getPropertySetterName(value.name); try { final Method method = actualClass.getMethod(methodName, value.type); setters.put(method, value.value.get()); } catch (NoSuchMethodException e) { throw new IllegalArgumentException(String.format( "Failed to find setter method for property \"%s\" on type \"%s\"", value.name, className), e); } } // Get all the post construct methods final Set postConstruct = new LinkedHashSet<>(); for (String methodName : postConstructMethods) { try { postConstruct.add(actualClass.getMethod(methodName)); } catch (NoSuchMethodException e) { throw new IllegalArgumentException( String.format("Failed to find post construct method \"%s\" on type \"%s\"", methodName, className), e); } } try { T instance = constructor.newInstance(params); // Execute setters for (Map.Entry entry : setters.entrySet()) { entry.getKey().invoke(instance, entry.getValue()); } // Execute post construct methods for (Method method : postConstruct) { method.invoke(instance); } return instance; } catch (Exception e) { throw new IllegalArgumentException(String.format("Failed to instantiate class \"%s\"", className), e); } }; } private Object getValue(final Class objClass, final String propertyName, final Class paramType, final String value) { if (value == null) { if (paramType.isPrimitive()) { throw new IllegalArgumentException( String.format("Cannot assign null value to primitive property \"%s\" of %s", propertyName, objClass)); } return null; } final var trimmedValue = value.trim(); if (paramType == String.class) { // Don't use the trimmed value for strings return value; } else if (paramType == java.util.logging.Level.class) { return contextConfiguration.getContext().getLevelForName(trimmedValue); } else if (paramType == java.util.logging.Logger.class) { return contextConfiguration.getContext().getLogger(trimmedValue); } else if (paramType == boolean.class || paramType == Boolean.class) { return Boolean.valueOf(trimmedValue); } else if (paramType == byte.class || paramType == Byte.class) { return Byte.valueOf(trimmedValue); } else if (paramType == short.class || paramType == Short.class) { return Short.valueOf(trimmedValue); } else if (paramType == int.class || paramType == Integer.class) { return Integer.valueOf(trimmedValue); } else if (paramType == long.class || paramType == Long.class) { return Long.valueOf(trimmedValue); } else if (paramType == float.class || paramType == Float.class) { return Float.valueOf(trimmedValue); } else if (paramType == double.class || paramType == Double.class) { return Double.valueOf(trimmedValue); } else if (paramType == char.class || paramType == Character.class) { return trimmedValue.length() > 0 ? trimmedValue.charAt(0) : 0; } else if (paramType == TimeZone.class) { return TimeZone.getTimeZone(trimmedValue); } else if (paramType == Charset.class) { return Charset.forName(trimmedValue); } else if (paramType.isAssignableFrom(Level.class)) { return Level.parse(trimmedValue); } else if (paramType.isEnum()) { return Enum.valueOf(paramType.asSubclass(Enum.class), trimmedValue); } else if (contextConfiguration.hasObject(trimmedValue)) { return contextConfiguration.getObject(trimmedValue); } else if (definedPropertiesContains(propertyName)) { final PropertyValue propertyValue = findDefinedProperty(propertyName); if (propertyValue == null) { throw new IllegalArgumentException("Unknown parameter type for property " + propertyName + " on " + objClass); } return propertyValue.value.get(); } else { throw new IllegalArgumentException("Unknown parameter type for property " + propertyName + " on " + objClass); } } private boolean definedPropertiesContains(final String name) { return findDefinedProperty(name) != null; } private PropertyValue findDefinedProperty(final String name) { for (PropertyValue value : definedProperties) { if (name.equals(value.name)) { return value; } } return null; } private static Class getPropertyType(Class clazz, String propertyName) { return getPropertyType(getPropertySetter(clazz, propertyName)); } private static Class getPropertyType(final Method setter) { return setter != null ? setter.getParameterTypes()[0] : null; } private static Class getConstructorPropertyType(Class clazz, String propertyName) { final Method getter = getPropertyGetter(clazz, propertyName); return getter != null ? getter.getReturnType() : getPropertyType(clazz, propertyName); } private static Method getPropertySetter(Class clazz, String propertyName) { final String set = getPropertySetterName(propertyName); for (Method method : clazz.getMethods()) { if ((method.getName().equals(set) && Modifier.isPublic(method.getModifiers())) && method.getParameterTypes().length == 1) { return method; } } return null; } private static Method getPropertyGetter(Class clazz, String propertyName) { final String upperPropertyName = Character.toUpperCase(propertyName.charAt(0)) + propertyName.substring(1); final Pattern pattern = Pattern.compile("(get|has|is)(" + Pattern.quote(upperPropertyName) + ")"); for (Method method : clazz.getMethods()) { if ((pattern.matcher(method.getName()).matches() && Modifier.isPublic(method.getModifiers())) && method.getParameterTypes().length == 0) { return method; } } return null; } private static String getPropertySetterName(final String propertyName) { return "set" + Character.toUpperCase(propertyName.charAt(0)) + propertyName.substring(1); } static class ModuleFinder { private ModuleFinder() { } static ClassLoader getClassLoader(final String moduleName) throws Exception { return Holder.getClassLoader(moduleName); } private static class Holder { static ClassLoader getClassLoader(final String moduleName) throws Exception { ModuleLoader moduleLoader = ModuleLoader.forClass(ModuleFinder.class); if (moduleLoader == null) { moduleLoader = Module.getBootModuleLoader(); } return moduleLoader.loadModule(moduleName).getClassLoader(); } } } private static class PropertyValue implements Comparable { final String name; final Class type; final Supplier value; private PropertyValue(final String name, final Class type, final Supplier value) { this.name = name; this.type = type; this.value = value; } @Override public int compareTo(final PropertyValue o) { return name.compareTo(o.name); } } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy