AbstractObjectSynthesizer.java

package com.github.dakusui.osynth.core;

import com.github.dakusui.osynth.core.utils.AssertionUtils;
import com.github.dakusui.osynth.exceptions.ValidationException;
import com.github.dakusui.osynth.invocationcontrollers.StandardInvocationController;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintStream;
import java.lang.reflect.Proxy;
import java.util.*;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.BiFunction;
import java.util.function.Consumer;
import java.util.function.Predicate;
import java.util.stream.Collectors;

import static com.github.dakusui.osynth.annotations.BuiltInHandlerFactory.MethodHandlerFactory.createMethodHandlersForBuiltInMethods;
import static com.github.dakusui.osynth.core.AbstractObjectSynthesizer.InternalUtils.validateValue;
import static com.github.dakusui.osynth.core.MethodHandlerDecorator.filterOutPredefinedMethods;
import static com.github.dakusui.osynth.core.SynthesizedObject.RESERVED_METHODS;
import static com.github.dakusui.osynth.core.utils.AssertionUtils.*;
import static com.github.dakusui.osynth.core.utils.MessageUtils.messageForReservedMethodOverridingValidationFailure;
import static com.github.dakusui.pcond.forms.Predicates.*;
import static com.github.dakusui.pcond.forms.Printables.predicate;
import static com.github.dakusui.pcond.internals.InternalUtils.formatObject;
import static com.github.dakusui.valid8j.Ensures.ensure;
import static com.github.dakusui.valid8j.Requires.*;
import static com.github.dakusui.valid8j.Validates.validate;

public abstract class AbstractObjectSynthesizer<O extends AbstractObjectSynthesizer<O>> {
  protected static final Object                                        DEFAULT_FALLBACK_OBJECT = new Object() {
    @Override
    public String toString() {
      return "autoCreated:<" + super.toString() + ">";
    }
  };
  protected final        SynthesizedObject.Descriptor.Builder          descriptorBuilder;
  private final          AtomicReference<SynthesizedObject.Descriptor> finalizedDescriptor     = new AtomicReference<>(null);
  private                Validator                                     validator;
  private                Preprocessor                                  preprocessor;
  private                ClassLoader                                   classLoader;
  private                InvocationControllerFactory                   invocationControllerFactory;

  public AbstractObjectSynthesizer(SynthesizedObject.Descriptor.Builder builder) {
    this.descriptorBuilder = builder;
    this.classLoader(this.getClass().getClassLoader())
        .handleMethodsWithSignatureMatching()
        .validateWith(Validator.DEFAULT)
        .preprocessWith(Preprocessor.DEFAULT)
        .disableMethodHandlerDecorator();
  }

  @SuppressWarnings("unchecked")
  public O addInterface(Class<?> interfaceClass) {
    descriptorBuilder.addInterface(interfaceClass);
    return (O) this;
  }

  @SuppressWarnings("unchecked")
  public O classLoader(ClassLoader classLoader) {
    this.classLoader = classLoader;
    return (O) this;
  }

  @SuppressWarnings("unchecked")
  public O fallbackTo(Object fallbackObject) {
    this.descriptorBuilder.fallbackObject(fallbackObject);
    return (O) this;
  }

  @SuppressWarnings("unchecked")
  public O handle(MethodHandlerEntry handlerEntry) {
    requireNonNull(handlerEntry);
    this.descriptorBuilder.addMethodHandler(handlerEntry);
    return (O) this;
  }

  @SuppressWarnings("unchecked")
  public O validateWith(Validator validator) {
    this.validator = requireNonNull(validator);
    return (O) this;
  }

  public O enableDuplicatedInterfaceCheck() {
    return this.validateWith(Validator.sequence(this.validator(), Validator.ENFORCE_NO_DUPLICATION));

  }

  public O disableValidation() {
    return this.validateWith(Validator.PASS_THROUGH);
  }

  @SuppressWarnings("unchecked")
  public O preprocessWith(Preprocessor preprocessor) {
    this.preprocessor = requireNonNull(preprocessor);
    return (O) this;
  }

  public O includeInterfacesFromFallbackObject() {
    return this.preprocessWith(Preprocessor.sequence(this.preprocessor(), Preprocessor.INCLUDE_INTERFACES_FROM_FALLBACK));
  }

  public O disablePreprocessing() {
    return this.preprocessWith(Preprocessor.PASS_THROUGH);
  }

  @SuppressWarnings("unchecked")
  public O createInvocationControllerWith(InvocationControllerFactory factory) {
    this.invocationControllerFactory = requireNonNull(factory);
    return (O) this;
  }

  @SuppressWarnings("unchecked")
  public O methodHandlerDecorator(MethodHandlerDecorator methodHandlerDecorator) {
    this.descriptorBuilder.methodHandlerDecorator(requireNonNull(methodHandlerDecorator));
    return (O) this;
  }

  public O disableMethodHandlerDecorator() {
    return this.methodHandlerDecorator(MethodHandlerDecorator.IDENTITY);
  }

  public O enableAutoLogging() {
    return enableAutoLoggingWritingTo(System.err::println);
  }

  /**
   * Note that this method is using `defaultLogEntryPrinter(Consumer)`,
   * which is not meant for production usages.
   * This method should also not be used in the production.
   *
   * @param out A consumer to which log records are sent.
   * @return This object.
   */
  public O enableAutoLoggingWritingTo(Consumer<String> out) {
    return enableAutoLoggingWith(AbstractObjectSynthesizer.defaultLogEntryPrinter(out));
  }

  public O enableAutoLoggingWith(AutoLogger autoLogger) {
    return this.methodHandlerDecorator(AutoLogger.create(autoLogger));
  }

  public O handleMethodsWithSignatureMatching() {
    return this.createInvocationControllerWith(objectSynthesizer -> new StandardInvocationController(objectSynthesizer.finalizedDescriptor()));
  }

  public SynthesizedObject synthesize(Object fallbackObject) {
    return this.fallbackTo(fallbackObject).synthesize();
  }

  public SynthesizedObject synthesize() {
    finalizeDescriptor(
        preprocessDescriptor(
            validateDescriptor(
                this.descriptorBuilder.methodHandlerDecorator(
                        filterOutPredefinedMethods(this.descriptorBuilder.methodHandlerDecorator()))
                    .build())));
    return (SynthesizedObject) InternalUtils.createProxy(this);
  }

  public Preprocessor preprocessor() {
    return this.preprocessor;
  }

  public Validator validator() {
    return this.validator;
  }

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

  public SynthesizedObject.Descriptor finalizedDescriptor() {
    return Optional.ofNullable(finalizedDescriptor.get())
        .orElseThrow(IllegalAccessError::new);
  }

  public boolean isDescriptorFinalized() {
    return finalizedDescriptor.get() != null;
  }

  private void finalizeDescriptor(SynthesizedObject.Descriptor descriptor) {
    requireState(this.isDescriptorFinalized(), isFalse());
    this.finalizedDescriptor.set(descriptor);
  }

  private SynthesizedObject.Descriptor validateDescriptor(SynthesizedObject.Descriptor descriptor) {
    requireState(this.validator, isNotNull());
    SynthesizedObject.Descriptor ret = this.validator.apply(this, descriptor);
    ensure(ret, predicate("Validation must not change the content of the descriptor.", allOf(
        transform(descriptorInterfaces()).check(isEqualTo(descriptor.interfaces())),
        transform(descriptorMethodHandlerEntries()).check(isEqualTo(descriptor.methodHandlerEntries())),
        transform(descriptorFallbackObject()).check(isEqualTo(descriptor.fallbackObject())))));
    return ret;
  }

  private SynthesizedObject.Descriptor preprocessDescriptor(SynthesizedObject.Descriptor descriptor) {
    requireState(this.preprocessor, isNotNull());
    return ensure(this.preprocessor.apply(this, descriptor), isNotNull());
  }

  /**
   * Note that the {@link AutoLogger} instance returned by this method is meant
   * only for demonstrating how the feature works, not for real-production usage.
   *
   * @param out A consumer log records sent to.
   * @return A default log entry printer instance.
   */
  static AutoLogger defaultLogEntryPrinter(Consumer<String> out) {
    return entry -> {
      out.accept(InternalUtils.formatLogEntry(entry));
      if (entry.type() == AutoLogger.Entry.Type.EXCEPTION) {
        require(entry.value(), isInstanceOf(Throwable.class));
        try (PrintStream ps = InternalUtils.toPrintStream(out)) {
          ((Throwable) entry.value()).printStackTrace(ps);
        }
      }
    };
  }

  enum InternalUtils {
    ;

    static Object createProxy(AbstractObjectSynthesizer<?> objectSynthesizer) {
      SynthesizedObject.Descriptor descriptor = objectSynthesizer.finalizedDescriptor();
      return Proxy.newProxyInstance(
          objectSynthesizer.classLoader,
          descriptor.interfaces().toArray(new Class[0]),
          objectSynthesizer.invocationControllerFactory.apply(objectSynthesizer));
    }

    public static List<Object> reservedMethodMisOverridings(Collection<MethodHandlerEntry> methodHandlerEntries) {
      return methodHandlerEntries
          .stream()
          .map((MethodHandlerEntry methodHandlerEntry) -> new ReservedMethodViolation(
              methodHandlerEntry,
              RESERVED_METHODS
                  .stream()
                  .filter(method -> methodHandlerEntry.matcher().test(method))
                  .map(MethodSignature::create)
                  .collect(Collectors.toList())))
          .filter(reservedMethodViolation -> !reservedMethodViolation.violatedReservedMethods.isEmpty())
          .collect(Collectors.toList());
    }

    static <V> void validateValue(V value, Predicate<V> predicate) {
      validate(
          value,
          predicate,
          s -> {
            throw new ValidationException(s);
          }
      );
    }

    private static String formatLogEntry(AutoLogger.Entry logEntry) {
      String valueType = logEntry.type().outputValueLabel();

      return String.format(
          "%-10s class:<%s> method:<%s> object:<%10s>  %s:<%s>",
          logEntry.type() + ":",
          logEntry.method().getDeclaringClass().getSimpleName(),
          MethodSignature.create(logEntry.method()),
          formatObject(logEntry.object(), 20),
          valueType,
          formatObject(logEntry.value(), 80));
    }

    private static PrintStream toPrintStream(Consumer<String> out) {
      return new PrintStream(new OutputStream() {
        final ByteArrayOutputStream bos = new ByteArrayOutputStream();
        final char LINE_SEPARATOR_CHAR = String.format("%n").charAt(0);

        @Override
        public void write(int b) {
          if (b == LINE_SEPARATOR_CHAR) {
            lineBreak();
          } else {
            bos.write(b);
          }
        }

        private void lineBreak() {
          out.accept(bos.toString());
          bos.reset();
        }

        @Override
        public void close() throws IOException {
          super.close();
          lineBreak();
        }
      });
    }

    static class ReservedMethodViolation {
      final List<MethodSignature> violatedReservedMethods;
      final MethodHandlerEntry    violatingEntry;

      ReservedMethodViolation(MethodHandlerEntry violatingEntry, List<MethodSignature> violatedReservedMethods) {
        this.violatedReservedMethods = violatedReservedMethods;
        this.violatingEntry = violatingEntry;
      }

      @Override
      public String toString() {
        return String.format("violation:entry:%s -> %s", violatingEntry, violatedReservedMethods);
      }
    }
  }

  interface Stage extends BiFunction<AbstractObjectSynthesizer<?>, SynthesizedObject.Descriptor, SynthesizedObject.Descriptor> {
  }

  public interface Validator extends Stage {
    Validator DEFAULT = toNamed("defaultValidator", (objectSynthesizer, descriptor) -> {
      require(objectSynthesizer, isNotNull());
      require(descriptor, isNotNull());
      validateValue(
          descriptor,
          predicate(
              () -> messageForReservedMethodOverridingValidationFailure(InternalUtils.reservedMethodMisOverridings(descriptor.methodHandlerEntries())),
              transform(descriptorMethodHandlerEntries()
                  .andThen(AbstractObjectSynthesizer.InternalUtils::reservedMethodMisOverridings))
                  .check(isEmpty())));
      return descriptor;
    });

    Validator ENFORCE_NO_DUPLICATION = toNamed("noDuplicationEnforcingValidator", (objectSynthesizer, descriptor) -> {
      require(objectSynthesizer, isNotNull());
      require(descriptor, isNotNull());
      validateValue(descriptor, transform(descriptorInterfaces().andThen(AssertionUtils.collectionDuplicatedElements())).check(isEmpty()));
      return descriptor;
    });

    Validator PASS_THROUGH = toNamed("passThroughValidator", (objectSynthesizer, descriptor) -> descriptor);

    static Validator sequence(Validator... validators) {
      return toNamed("validatorSequence:" + Arrays.toString(validators), (objectSynthesizer, descriptor) -> {
        SynthesizedObject.Descriptor ret = descriptor;
        for (Validator each : validators) {
          ret = requireNonNull(each).apply(objectSynthesizer, descriptor);
          ensure(ret, predicate("Validation must not change the content of the descriptor.", allOf(
              transform(descriptorInterfaces()).check(isEqualTo(descriptor.interfaces())),
              transform(descriptorMethodHandlerEntries()).check(isEqualTo(descriptor.methodHandlerEntries())),
              transform(descriptorFallbackObject()).check(isEqualTo(descriptor.fallbackObject())))));
        }
        return ret;
      });
    }

    static Validator toNamed(String name, Validator validator) {
      require(name, isNotNull());
      require(validator, isNotNull());
      return new Validator() {
        @Override
        public SynthesizedObject.Descriptor apply(AbstractObjectSynthesizer<?> objectSynthesizer, SynthesizedObject.Descriptor descriptor) {
          return validator.apply(objectSynthesizer, descriptor);
        }

        @Override
        public String toString() {
          return name;
        }
      };
    }

    SynthesizedObject.Descriptor apply(
        AbstractObjectSynthesizer<?> objectSynthesizer,
        SynthesizedObject.Descriptor descriptor);
  }

  public interface Preprocessor {
    Preprocessor INCLUDE_BUILTIN_METHOD_HANDLERS = toNamed("builtInMethodHandlers", ((objectSynthesizer, descriptor) -> {
      SynthesizedObject.Descriptor.Builder builder = new SynthesizedObject.Descriptor.Builder(descriptor);
      createMethodHandlersForBuiltInMethods(objectSynthesizer::finalizedDescriptor)
          .forEach(builder::addMethodHandler);
      return builder.build();
    }));
    Preprocessor INCLUDE_BUILTIN_INTERFACES      = toNamed("builtInInterfaces", ((objectSynthesizer, descriptor) -> {
      SynthesizedObject.Descriptor.Builder builder = new SynthesizedObject.Descriptor.Builder(descriptor);
      if (!builder.interfaces().contains(SynthesizedObject.class))
        builder.addInterface(SynthesizedObject.class);
      return builder.build();
    }));

    Preprocessor DEFAULT                          = toNamed("defaultPreprocessor", sequence(
        INCLUDE_BUILTIN_METHOD_HANDLERS,
        INCLUDE_BUILTIN_INTERFACES
    ));
    Preprocessor INCLUDE_INTERFACES_FROM_FALLBACK = toNamed("interfacesFromFallback", (objectSynthesizer, descriptor) -> {
      SynthesizedObject.Descriptor.Builder builder = new SynthesizedObject.Descriptor.Builder(descriptor);
      Set<Class<?>> interfacesInOriginalDescriptor = new HashSet<>(descriptor.interfaces());
      Arrays.stream(descriptor.fallbackObject().getClass().getInterfaces())
          .filter(eachInterfaceInFallback -> !interfacesInOriginalDescriptor.contains(eachInterfaceInFallback))
          .forEach(builder::addInterface);
      return builder.build();
    });

    Preprocessor PASS_THROUGH = toNamed("passThrough", (objectSynthesizer, descriptor) -> descriptor);

    static Preprocessor importDescriptorFromAnotherSynthesizedObject(SynthesizedObject.Descriptor descriptor) {
      return toNamed(
          "importDescriptorFromAnotherSynthesizedObject",
          (objectSynthesizer, descriptorFromThisSynthesizer) -> SynthesizedObject.Descriptor.merge(descriptorFromThisSynthesizer, descriptor));
    }


    SynthesizedObject.Descriptor apply(
        AbstractObjectSynthesizer<?> objectSynthesizer,
        SynthesizedObject.Descriptor descriptor);

    static Preprocessor sequence(Preprocessor... preprocessors) {
      return toNamed("preprocessorSequence:" + Arrays.toString(preprocessors), (objectSynthesizer, descriptor) -> {
        SynthesizedObject.Descriptor ret = descriptor;
        for (Preprocessor each : preprocessors) {
          ret = ensure(requireNonNull(each).apply(objectSynthesizer, ret), isNotNull());
        }
        return ret;
      });
    }

    static Preprocessor toNamed(String name, Preprocessor preprocessor) {
      require(name, isNotNull());
      require(preprocessor, isNotNull());
      return new Preprocessor() {
        @Override
        public SynthesizedObject.Descriptor apply(AbstractObjectSynthesizer<?> objectSynthesizer, SynthesizedObject.Descriptor descriptor) {
          return preprocessor.apply(objectSynthesizer, descriptor);
        }

        @Override
        public String toString() {
          return name;
        }

      };
    }
  }
}