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

nullablej.nullabledata.NullableData Maven / Gradle / Ivy

The newest version!
//  MIT License
//  
//  Copyright (c) 2017-2023 Nawa Manusitthipol
//  
//  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 nullablej.nullabledata;


import static nullablej.utils.reflection.UProxy.invokeDefaultMethod;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Supplier;

import lombok.val;
import nullablej.NullableJ;
import nullablej.nullable.IAsNullable;
import nullablej.nullable.Nullable;
import nullablej.nullvalue.NullValues;

/**
 * NullableData can create an instance of any interface that act as a null object.
 * 
 * The result object (called nullable data) implements both the data interface and {@link IAsNullable} interface.
 * This object holds the actual value of that interface type.
 * But if that value is null, this nullable data instance will act as null object.
 * The method {@code asNullable} from IAsNullable will return an instance of {@link Nullable} of the object.
 * If the methods has default implementation, the implementation will be called.
 * All other methods will do nothing and return null value of that method type
 *   (by calling {@code NullValues.nullValueOf(returnType)}).
 *   
 * The nullable data implements both the data interface and {@link IAsNullable} instance.
 * But the implement of {@link IAsNullable} is hidden meaning that
 *   the instance has to be casted to IAsNullable before it can be used as such.
 * You can also make the data interface to implement the {@link IAsNullable} interface.
 *   
 * 
 * @author NawaMan -- [email protected]
 */
public class NullableData {
    
    @SuppressWarnings("rawtypes")
    private static final Map nullableObjects = new ConcurrentHashMap<>();
    
    @SuppressWarnings("rawtypes")
    private static final Supplier nullSupplier = ()->null;
    
    private NullableData() {
        
    }
    
    /**
     * Create a nullable data object.
     * 
     * This method is similar to {@link NullableData#from(Supplier, Class, Class)}
     *   but the value given as object which this method will convert to supplier.
     * 
     * @param dataValue              the value.
     * @param dataObjectClass        the data object class.
     * @param asNullableObjectClass  the combine data and iAsNullable class.
     * @return  the nullable data object.
     * 
     * @param         the data type.
     * @param   the IAsNullable data type.
     */
    @SuppressWarnings("unchecked")
    public static > ASNULLABLE of(
            DATA              dataValue, 
            Class       dataObjectClass, 
            Class asNullableObjectClass) {
        if (dataValue == null) {
            return (ASNULLABLE)nullableObjects.computeIfAbsent(asNullableObjectClass, clzz->{
                return from((Supplier)nullSupplier, dataObjectClass, asNullableObjectClass, Nullable.empty());
            });
        }
        if ((dataValue instanceof IAsNullable)
                && dataObjectClass.isInstance(dataValue)
                && asNullableObjectClass.isInstance(dataValue))
            return asNullableObjectClass.cast(dataValue);
        
        return from(()->dataValue, dataObjectClass, asNullableObjectClass);
    }
    
    /**
     * Create a Nullable Data object without the combined class.
     * 
     * @param dataValue  the data value.
     * @param dataClass  the data class.
     * @return the nullable data object.
     * 
     * @param   the data type.
     */
    @SuppressWarnings("unchecked")
    public static  DATA of(DATA dataValue, Class dataClass) {
        if (dataValue == null) {
            return (DATA)nullableObjects.computeIfAbsent(dataClass, clzz->{
                return from((Supplier)nullSupplier, dataClass, Nullable.empty());
            });
        }
        if ((dataValue instanceof IAsNullable)
                && dataClass.isInstance(dataValue))
            return dataClass.cast(dataValue);
        
        
        return from(()->dataValue, dataClass);
    }
    
    /**
     * Create a nullable data object.
     * 
     * A Nullable data object is an object that is the mix of the data and Nullable.
     * It contains both the method from the data interface as well as Nullable.
     * 
     * Exapmle: the data interface might look like this.
     * 
     * public interface Data {
     *     public Value getValue();
     *     public void setValue(Value value);
     * }
     * 
* Then, the nullable object might look like this: *
     * public interface NullableData extends Data, Nullable {
     * }
     * 
* And this method can be used to create an instance of NullableData like this. *
     * NullableData nullableData = Nullable.createNullableObject(()->myValue, Data.class, NullableData.class);
     * System.out.println(nullableData.getValue());
     * System.out.println(nullableData.map(Data::getValue()).orElse(Value.NO_VALUE));
     * 
* * @param valueSupplier the supplier for the value. * @param dataObjectClass the data object class. * @param asNullableObjectClass the combine data and nullable class. * @return the nullable data object. * * @param the data type. * @param the Nullable data type. */ public static > ASNULLABLE from( Supplier valueSupplier, Class dataObjectClass, Class asNullableObjectClass) { return from(valueSupplier, dataObjectClass, asNullableObjectClass, null); } @SuppressWarnings("unchecked") private static > ASNULLABLE from( Supplier valueSupplier, Class dataObjectClass, Class asNullableObjectClass, Nullable nullable) { if (!dataObjectClass.isInterface()) throw new IllegalArgumentException("The data class must be an interface: " + dataObjectClass); val interfaces = new Class[] { dataObjectClass, asNullableObjectClass }; val classLoader = dataObjectClass.getClassLoader(); val handler = createNullableInvocationHandler(valueSupplier, (Class)asNullableObjectClass, nullable); val rawProxy = Proxy.newProxyInstance(classLoader, interfaces, handler); val proxy = asNullableObjectClass.cast(rawProxy); return proxy; } /** * Create a Nullable Data object without the combined class. * * @param valueSupplier the value supplier. * @param dataObjectClass the data object class. * @return the nullable data object. * * @param the data type. */ public static DATA from(Supplier valueSupplier, Class dataObjectClass) { return from(valueSupplier, dataObjectClass, null); } @SuppressWarnings("unchecked") private static DATA from( Supplier valueSupplier, Class dataObjectClass, Nullable nullable) { if (!dataObjectClass.isInterface()) throw new IllegalArgumentException("The data class must be an interface: " + dataObjectClass); val interfaces = new Class[] { dataObjectClass, IAsNullable.class }; val classLoader = dataObjectClass.getClassLoader(); val handler = createNullableInvocationHandler(valueSupplier, dataObjectClass, nullable); val rawProxy = Proxy.newProxyInstance(classLoader, interfaces, handler); val proxy = (DATA)rawProxy; return proxy; } private static Object invokeNullableProxy(Object proxy, Method method, Object[] args) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException { @SuppressWarnings("rawtypes") val value = ((IAsNullable)proxy).asNullable().get(); if (value == null) { Class returnType = method.getReturnType(); Object rawNullValue = NullValues.nullValueOf(returnType); if (returnType.isPrimitive()) return rawNullValue; return returnType.cast(rawNullValue); } return method.invoke(value, args); } private static InvocationHandler createNullableInvocationHandler( Supplier valueSupplier, Class dataClass, Nullable nullable) { val theNullable = NullableJ._orGet(nullable, ()->Nullable.from(valueSupplier)); val handler = (InvocationHandler)(proxy, method, methodArgs) -> { if ("get".equals(method.getName())) return valueSupplier.get(); if ("toString".equals(method.getName()) && (method.getParameterCount() == 0)) return dataClass.getSimpleName() + "=null"; if ("hashCode".equals(method.getName()) && (method.getParameterCount() == 0)) return dataClass.hashCode(); if ("equals".equals(method.getName()) && (method.getParameterCount() == 1)) { if (methodArgs[0] == null) return true; if (methodArgs[0] == proxy) return true; if (dataClass != methodArgs[0].getClass()) return false; if (!Nullable.class.isAssignableFrom(methodArgs[0].getClass())) return false; @SuppressWarnings("rawtypes") boolean isPresent = ((Nullable)proxy).isPresent(); return !isPresent; } if ("asNullable".equals(method.getName()) && (method.getParameterCount() == 0)) return theNullable; if (method.isDefault()) { return invokeDefaultMethod(proxy, method, methodArgs); } boolean isICanBeNullableMethod = IAsNullable.class == method.getDeclaringClass(); if (!isICanBeNullableMethod) return invokeNullableProxy(proxy, method, methodArgs); return null; }; return handler; } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy