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

org.cp.elements.data.conversion.AbstractConverter Maven / Gradle / Ivy

/*
 * Copyright 2011-Present Author or Authors.
 *
 * 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.cp.elements.data.conversion;

import static org.cp.elements.lang.RuntimeExceptionsFactory.newIllegalStateException;

import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.Arrays;
import java.util.Optional;
import java.util.function.Function;

import org.cp.elements.lang.ClassUtils;
import org.cp.elements.lang.Constants;
import org.cp.elements.lang.ObjectUtils;
import org.cp.elements.lang.annotation.NotNull;
import org.cp.elements.lang.annotation.NullSafe;
import org.cp.elements.lang.annotation.Nullable;
import org.cp.elements.util.ArrayUtils;

/**
 * An abstract base class encapsulating functionality and behavior common to all {@link Converter} implementations.
 *
 * @author John J. Blum
 * @param  {@link Class source type} to convert from.
 * @param  {@link Class target type} to convert to.
 * @see java.util.function.Function
 * @see org.cp.elements.data.conversion.ConversionServiceAware
 * @see org.cp.elements.data.conversion.Converter
 * @since 1.0.0
 */
@SuppressWarnings("unused")
public abstract class AbstractConverter implements Converter {

  private ConversionService conversionService;

  private Class sourceType;
  private Class targetType;

  /**
   * Constructs a new instance of {@link AbstractConverter}.
   *
   * @see #init(Class)
   */
  public AbstractConverter() {
    init(getClass());
  }

  /**
   * Constructs a new instance of {@link AbstractConverter} initialized with the given {@link Class type}
   * to determine the {@link Class source} and {@link Class target} types of the conversion.
   *
   * @param type {@link Class} to evaluate for source and target {@link Class types}.
   * @see java.lang.Class
   * @see #init(Class)
   */
  public AbstractConverter(@NotNull Class type) {
    init(type);
  }

  /**
   * Initializes the {@link Class source} and {@link Class target} types of the data conversion
   * performed by this {@link Converter}.
   *
   * @param type {@link Class} to evaluate for source and target {@link Class types}; must not be {@literal null}.
   * @see java.lang.Class
   */
  private void init(@NotNull Class type) {

    ParameterizedType parameterizedType =
      Arrays.stream(ArrayUtils.nullSafeArray(type.getGenericInterfaces(), Type.class))
        .filter(this::isParameterizedFunctionType)
        .findFirst()
        .map(ParameterizedType.class::cast)
        .orElseGet(() -> {

          Type genericSuperclass = type.getGenericSuperclass();

          return isParameterizedFunctionType(genericSuperclass) ? (ParameterizedType) genericSuperclass : null;

        });

    this.sourceType = parameterizedType != null
      ? ClassUtils.toRawType(parameterizedType.getActualTypeArguments()[0])
      : Object.class;

    this.targetType = parameterizedType != null
      ? ClassUtils.toRawType(parameterizedType.getActualTypeArguments()[1])
      : Object.class;
  }

  /**
   * Sets a reference to the {@link ConversionService} used to perform data conversions.
   *
   * @param conversionService reference to the {@link ConversionService} used to perform data conversions.
   * @see org.cp.elements.data.conversion.ConversionService
   */
  public void setConversionService(@Nullable ConversionService conversionService) {
    this.conversionService = conversionService;
  }

  /**
   * Returns an {@link Optional} reference to the {@link ConversionService} used to perform data conversions.
   *
   * @return an {@link Optional} reference to the {@link ConversionService} used to perform data conversions.
   * @see org.cp.elements.data.conversion.ConversionService
   * @see java.util.Optional
   */
  protected Optional getConversionService() {
    return Optional.ofNullable(this.conversionService);
  }

  /**
   * Resolves the reference to the {@link ConversionService} used to perform data conversions.
   *
   * @return the resolved reference to the {@link ConversionService} used to perform data conversions.
   * @throws IllegalStateException if no {@link ConversionService} was configured.
   * @see org.cp.elements.data.conversion.ConversionService
   * @see #getConversionService()
   */
  protected @NotNull ConversionService resolveConversionService() {
    return getConversionService()
      .orElseThrow(() -> newIllegalStateException("No ConversionService was configured"));
  }

  /**
   * Determines whether the {@link Class from type} is {@link ClassUtils#assignableTo(Class, Class) assignable to}
   * any of the {@link Class to types}.
   *
   * @param fromType {@link Class type} to evaluate.
   * @param toTypes {@link Class types} checked for assignment compatibility.
   * @return {@literal true} if {@link Class from type} is assignable to any of the {@link Class to types}.
   * @see org.cp.elements.lang.ClassUtils#assignableTo(Class, Class)
   * @see java.lang.Class
   */
  @NullSafe
  protected boolean isAssignableTo(Class fromType, Class... toTypes) {

    for (Class toType : ArrayUtils.nullSafeArray(toTypes, Class.class)) {
      if (ClassUtils.assignableTo(fromType, toType)) {
        return true;
      }
    }

    return false;
  }

  /**
   * Determines whether the {@link Type} is a generic, parameterized {@link Converter} {@link Class type},
   * such as by implementing the {@link Converter} interface or extending the {@link AbstractConverter} base class.
   *
   * @param type {@link Type} to evaluate.
   * @return a boolean if the {@link Type} represents a generic, parameterized {@link Converter} {@link Class type}.
   * @see java.lang.reflect.Type
   */
  @NullSafe
  protected boolean isParameterizedFunctionType(@Nullable Type type) {

    return Optional.ofNullable(type)
      .filter(ParameterizedType.class::isInstance)
      .map(it -> {

        Class rawType = ObjectUtils.safeGetValue(() -> ClassUtils.toRawType(it), Object.class);

        return isAssignableTo(rawType, Converter.class, Function.class);

      })
      .orElse(false);
  }

  /**
   * Returns an {@link Optional} {@link Class source type} of the data conversion performed by this {@link Converter}.
   *
   * @return an {@link Optional} {@link Class source type} of the data conversion performed by this {@link Converter}.
   * @see java.util.Optional
   * @see java.lang.Class
   */
  protected Optional> getSourceType() {
    return Optional.ofNullable(this.sourceType);
  }

  /**
   * Returns an {@link Optional} {@link Class target type} of the data conversion performed by this {@link Converter}.
   *
   * @return an {@link Optional} {@link Class target type} of the data conversion performed by this {@link Converter}.
   * @see java.util.Optional
   * @see java.lang.Class
   */
  protected Optional> getTargetType() {
    return Optional.ofNullable(this.targetType);
  }

  /**
   * Determines whether this {@link Converter} can convert {@link Object Objects}
   * {@link Class from type} {@link Class to type}.
   *
   * @param fromType {@link Class type} to convert from.
   * @param toType {@link Class type} to convert to.
   * @return a boolean indicating whether this {@link Converter} can convert {@link Object Objects}
   * {@link Class from type} {@link Class to type}.
   * @see org.cp.elements.data.conversion.ConversionService#canConvert(Class, Class)
   * @see #getSourceType()
   * @see #getTargetType()
   */
  @Override
  public boolean canConvert(Class fromType, Class toType) {

    boolean canConvert = getSourceType()
      .filter(this::notObjectTypePredicate)
      .map(sourceType -> ClassUtils.assignableTo(fromType, sourceType))
      .orElse(true);

    // targetType should be assignable to or a subclass of toType
    // toType should be assignable from targetType
    canConvert &= getTargetType()
      .filter(this::notObjectTypePredicate)
      .map(targetType -> ClassUtils.assignableTo(targetType, toType))
      .orElse(true);

    return canConvert;
  }

  private boolean notObjectTypePredicate(@Nullable Class type) {
    return !Object.class.equals(type);
  }

  /**
   * Converts an {@link Object} of {@link Class type S} into an {@link Object} of {@link Class type T}.
   *
   * @param value {@link Object} to convert.
   * @return the converted {@link Object} of {@link Class type T}.
   * @throws ConversionException if the {@link Object} cannot be converted.
   * @see org.cp.elements.data.conversion.ConversionService#convert(Object, Class)
   * @see #convert(Object, Class)
   */
  @Override
  public T convert(S value) {
    throw new UnsupportedOperationException(Constants.OPERATION_NOT_SUPPORTED);
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy