SynthesizedObject.java

package com.github.dakusui.osynth.core;

import com.github.dakusui.osynth.annotations.BuiltInHandlerFactory;
import com.github.dakusui.osynth.annotations.ReservedByOSynth;
import com.github.dakusui.osynth.core.utils.AssertionUtils;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.*;

import static com.github.dakusui.osynth.core.AbstractObjectSynthesizer.DEFAULT_FALLBACK_OBJECT;
import static com.github.dakusui.osynth.core.MethodHandlerDecorator.chainMethodHandlerDecorators;
import static com.github.dakusui.osynth.core.SynthesizedObject.InternalUtils.builtIndMethodSignatures;
import static com.github.dakusui.osynth.core.SynthesizedObject.InternalUtils.reservedMethodSignatures;
import static com.github.dakusui.osynth.core.utils.MessageUtils.messageForAttemptToCastToUnavailableInterface;
import static com.github.dakusui.pcond.forms.Predicates.*;
import static com.github.dakusui.valid8j.Requires.require;
import static java.lang.String.format;
import static java.util.Collections.unmodifiableList;
import static java.util.stream.Collectors.toList;
import static java.util.stream.Collectors.toSet;

public interface SynthesizedObject {
  Set<Method> RESERVED_METHODS = reservedMethodSignatures();
  Set<Method> BUILT_IN_METHODS = builtIndMethodSignatures();

  @BuiltInHandlerFactory(BuiltInHandlerFactory.ForDescriptor.class)
  @ReservedByOSynth
  Descriptor descriptor();

  @ReservedByOSynth
  default <T> T castTo(Class<T> classInUse) {
    require(classInUse, and(
        isNotNull(),
        or(AssertionUtils.classIsInterface(), isEqualTo(Object.class))));
    return descriptor().interfaces().stream()
        .filter(classInUse::isAssignableFrom)
        .findFirst()
        .map(each -> classInUse.cast(this))
        .orElseThrow(() -> new ClassCastException(messageForAttemptToCastToUnavailableInterface(classInUse, descriptor().interfaces())));
  }

  @BuiltInHandlerFactory(BuiltInHandlerFactory.ForEquals.class)
  @Override
  boolean equals(Object object);

  @BuiltInHandlerFactory(BuiltInHandlerFactory.ForHashCode.class)
  @Override
  int hashCode();

  @BuiltInHandlerFactory(BuiltInHandlerFactory.ForToString.class)
  @Override
  String toString();

  enum InternalUtils {
    ;

    static Set<Method> reservedMethodSignatures() {
      return methodsAnnotatedBy(ReservedByOSynth.class);
    }

    static Set<Method> builtIndMethodSignatures() {
      return methodsAnnotatedBy(BuiltInHandlerFactory.class);
    }

    private static Set<Method> methodsAnnotatedBy(Class<? extends Annotation> annotationClass) {
      return Arrays.stream(SynthesizedObject.class.getMethods())
          .filter(each -> each.isAnnotationPresent(annotationClass))
          .collect(toSet());
    }
  }

  /**
   * A class to describe attributes of a synthesized object.
   */
  final class Descriptor {
    final List<MethodHandlerEntry> methodHandlers;
    final List<Class<?>>           interfaces;
    final MethodHandlerDecorator   methodHandlerDecorator;
    final Object                   fallbackObject;

    public Descriptor(
        List<Class<?>> interfaces,
        List<MethodHandlerEntry> methodHandlers,
        MethodHandlerDecorator methodHandlerDecorator,
        Object fallbackObject) {
      // Not using pcond library to avoid unintentional `toString` call back on failure.
      this.methodHandlers = new LinkedList<>(Objects.requireNonNull(unmodifiableList(methodHandlers)));
      this.interfaces = new LinkedList<>(Objects.requireNonNull(interfaces));
      this.fallbackObject = Objects.requireNonNull(fallbackObject);
      this.methodHandlerDecorator = Objects.requireNonNull(methodHandlerDecorator);
    }

    public static List<MethodHandlerEntry> nonBuiltInMethodHandlerEntriesOf(Descriptor b) {
      return b.methodHandlerEntries().stream().filter(methodHandlerEntry -> !methodHandlerEntry.isBuiltIn()).collect(toList());
    }

    /**
     * Returns a new descriptor merging two descriptors.
     * The merge happens in a manner, where "a overrides b".
     *
     * @param a A descriptor to merge.
     * @param b Another descriptor to merge.
     * @return A merged new descriptor object.
     */
    public static Descriptor merge(Descriptor a, Descriptor b) {
      Builder builder = a.toBuilder();
      if (a.fallbackObject() == DEFAULT_FALLBACK_OBJECT)
        builder.fallbackObject(b.fallbackObject());
      nonBuiltInMethodHandlerEntriesOf(b).forEach(builder::addMethodHandler);
      b.interfaces().forEach(builder::addInterface);
      builder.methodHandlerDecorator(chainMethodHandlerDecorators(a.methodHandlerDecorator(), b.methodHandlerDecorator()));
      return builder.build();
    }

    public List<Class<?>> interfaces() {
      return unmodifiableList(this.interfaces);
    }

    public MethodHandlerDecorator methodHandlerDecorator() {
      return this.methodHandlerDecorator;
    }

    public Object fallbackObject() {
      return this.fallbackObject;
    }

    public List<MethodHandlerEntry> methodHandlerEntries() {
      return this.methodHandlers;
    }

    @Override
    public int hashCode() {
      return this.fallbackObject.hashCode();
    }

    @Override
    public boolean equals(Object anotherObject) {
      if (this == anotherObject)
        return true;
      if (!(anotherObject instanceof Descriptor)) {
        return false;
      }
      Descriptor another = (Descriptor) anotherObject;
      Set<MethodHandlerEntry> methodHandlerSet = new HashSet<>(nonBuiltInMethodHandlerEntriesOf(this));
      Set<MethodHandlerEntry> methodHandlerSetFromAnother = new HashSet<>(nonBuiltInMethodHandlerEntriesOf(another));
      return Objects.equals(fallbackObject, another.fallbackObject) &&
          Objects.equals(methodHandlerDecorator, another.methodHandlerDecorator) &&
          Objects.equals(interfaces, another.interfaces) &&
          Objects.equals(methodHandlerSet, methodHandlerSetFromAnother);
    }

    @Override
    public String toString() {
      return format("{methodHandlers=%s,interfaces=%s,fallback:%s}",
          nonBuiltInMethodHandlerEntriesOf(this),
          this.interfaces(),
          this.fallbackObject());
    }

    public Descriptor.Builder toBuilder() {
      return new Descriptor.Builder() {
        {
          Descriptor.this.interfaces().forEach(this::addInterface);
          this.fallbackObject = Descriptor.this.fallbackObject;
          this.methodHandlerDecorator = Descriptor.this.methodHandlerDecorator();
          this.methodHandlers.addAll(nonBuiltInMethodHandlerEntriesOf(this.build()));
        }
      }.fallbackObject(this.fallbackObject());
    }

    public static class Builder {
      final List<Class<?>>           interfaces;
      final List<MethodHandlerEntry> methodHandlers;
      MethodHandlerDecorator methodHandlerDecorator;
      Object                 fallbackObject;

      public Builder() {
        interfaces = new LinkedList<>();
        methodHandlers = new LinkedList<>();
      }

      public Builder(Descriptor descriptor) {
        this();
        this.interfaces.addAll(descriptor.interfaces());
        this.methodHandlers.addAll(descriptor.methodHandlerEntries());
        this.methodHandlerDecorator = descriptor.methodHandlerDecorator();
        this.fallbackObject = descriptor.fallbackObject();
      }

      public Builder fallbackObject(Object fallbackObject) {
        this.fallbackObject = fallbackObject;
        return this;
      }

      public Builder addInterface(Class<?> interfaceClass) {
        this.interfaces.add(interfaceClass);
        return this;
      }

      public Builder methodHandlerDecorator(MethodHandlerDecorator methodHandlerDecorator) {
        this.methodHandlerDecorator = methodHandlerDecorator;
        return this;
      }

      public void addMethodHandler(MethodHandlerEntry methodHandlerEntry) {
        this.methodHandlers.add(methodHandlerEntry);
      }


      public List<Class<?>> interfaces() {
        return unmodifiableList(this.interfaces);
      }

      public MethodHandlerDecorator methodHandlerDecorator() {
        return this.methodHandlerDecorator;
      }

      public Descriptor build() {
        return new Descriptor(this.interfaces, this.methodHandlers, this.methodHandlerDecorator, this.fallbackObject);
      }
    }
  }
}