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

com.bakdata.deduplication.fusion.ConflictResolutions Maven / Gradle / Ivy

/*
 * The MIT License
 *
 * Copyright (c) 2018 bakdata GmbH
 *
 * 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 com.bakdata.deduplication.fusion;

import com.bakdata.util.FunctionalClass;
import com.bakdata.util.ObjectUtils;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Optional;
import java.util.function.BiConsumer;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import lombok.Value;
import lombok.experimental.UtilityClass;
import lombok.experimental.Wither;

@UtilityClass
public class ConflictResolutions {

    public static  Merge.MergeBuilder merge(final Supplier ctor) {
        return Merge.builder(ctor);
    }

    public static  Merge.MergeBuilder merge(final Class clazz) {
        return Merge.builder(clazz);
    }

    @Value
    public static class Merge implements ConflictResolution {
        private final Supplier ctor;
        private final List> fieldMerges;

        @SuppressWarnings("unchecked")
        static  MergeBuilder builder(final Supplier ctor) {
            return new MergeBuilder<>(ctor, FunctionalClass.from((Class) ctor.get().getClass()));
        }

        static  MergeBuilder builder(final Class clazz) {
            final FunctionalClass f = FunctionalClass.from(clazz);
            return new MergeBuilder<>(f.getConstructor(), f);
        }

        @Override
        public List> resolvePartially(final List> annotatedValues, final FusionContext context) {
            final R r = this.ctor.get();
            for (final FieldMerge fieldMerge : this.fieldMerges) {
                fieldMerge.mergeInto(r, annotatedValues, context);
            }
            return List.of(AnnotatedValue.calculated(r));
        }

        @Value
        private static class FieldMerge {
            Function getter;
            BiConsumer setter;
            @Wither
            ConflictResolution resolution;

            void mergeInto(final R r, final Collection> annotatedValues, final FusionContext context) {
                final List> fieldValues = annotatedValues.stream()
                        .map(ar -> ar.withValue(this.getter.apply(ar.getValue())))
                        .filter(ar -> ObjectUtils.isNonEmpty(ar.getValue()))
                        .collect(Collectors.toList());
                context.safeExecute(() -> {
                    final Optional resolvedValue = this.resolution.resolve(fieldValues, context);
                    resolvedValue.ifPresent(v -> this.setter.accept(r, v));
                });
            }
        }

        @Value
        public static class MergeBuilder {
            Supplier ctor;
            FunctionalClass clazz;
            List> fieldMerges = new ArrayList<>();

            public  FieldMergeBuilder field(final Function getter, final BiConsumer setter) {
                return new FieldMergeBuilder<>(this, getter, setter);
            }

            public  FieldMergeBuilder field(final FunctionalClass.Field field) {
                final Function getter = field.getGetter();
                final BiConsumer setter = field.getSetter();
                return this.field(getter, setter);
            }

            public  FieldMergeBuilder field(final String name) {
                final FunctionalClass.Field field = this.clazz.field(name);
                return this.field(field);
            }

            void replaceLast(final FieldMerge fieldMerge) {
                this.fieldMerges.set(this.fieldMerges.size() - 1, fieldMerge);
            }

            @SuppressWarnings("squid:S1452")
            FieldMerge getLast() {
                if (this.fieldMerges.isEmpty()) {
                    throw new IllegalStateException();
                }
                return this.fieldMerges.get(this.fieldMerges.size() - 1);
            }

            public ConflictResolution build() {
                return new Merge<>(this.ctor, this.fieldMerges);
            }

            private void add(final FieldMerge fieldMerge) {
                this.fieldMerges.add(fieldMerge);
            }
        }

        @Value
        public static class FieldMergeBuilder {
            MergeBuilder mergeBuilder;
            Function getter;
            BiConsumer setter;

            public AdditionalFieldMergeBuilder with(final ConflictResolution resolution) {
                return new AdditionalFieldMergeBuilder<>(this.convertingWith(resolution));
            }

            @SafeVarargs
            public final AdditionalFieldMergeBuilder with(final ConflictResolution resolution, final ConflictResolution... resolutions) {
                return this.with(Arrays.stream(resolutions).reduce(resolution, ConflictResolution::andThen));
            }

            public  IllTypedFieldMergeBuilder convertingWith(final ConflictResolution resolution) {
                return new IllTypedFieldMergeBuilder<>(this, resolution);
            }

            public AdditionalFieldMergeBuilder corresponding(final ResolutionTag tag) {
                return this.with(CommonConflictResolutions.corresponding(tag));
            }

            @SuppressWarnings("unchecked")
            public AdditionalFieldMergeBuilder correspondingToPrevious() {
                final var last = this.mergeBuilder.getLast();
                final ResolutionTag tag;
                // auto tag previous merge if it is not tagged already
                if (last.getResolution() instanceof CommonConflictResolutions.TaggedResolution) {
                    tag = ((CommonConflictResolutions.TaggedResolution) last.getResolution()).getResolutionTag();
                } else {
                    final var fieldMerges = this.mergeBuilder.getFieldMerges();
                    tag = new ResolutionTag<>("tag-" + System.identityHashCode(fieldMerges) + "-" + fieldMerges.size());
                    this.mergeBuilder.replaceLast(last.withResolution(
                            CommonConflictResolutions.saveAs(last.getResolution(), tag)));
                }
                return this.corresponding(tag);
            }

            void finish(final ConflictResolution resolution) {
                this.mergeBuilder.add(new FieldMerge<>(this.getter, this.setter, resolution));
            }
        }

        @Value
        public static class IllTypedFieldMergeBuilder {
            FieldMergeBuilder fieldMergeBuilder;
            ConflictResolution resolution;

            public  IllTypedFieldMergeBuilder then(final ConflictResolution resolution) {
                return new IllTypedFieldMergeBuilder<>(this.fieldMergeBuilder, this.resolution.andThen(resolution));
            }

            public AdditionalFieldMergeBuilder convertingBack(final ConflictResolution resolution) {
                return new AdditionalFieldMergeBuilder<>(this.then(resolution));
            }

            MergeBuilder getMergeBuilder() {
                return this.getFieldMergeBuilder().getMergeBuilder();
            }
        }

        @Value
        public static class AdditionalFieldMergeBuilder {
            IllTypedFieldMergeBuilder inner;

            public  FieldMergeBuilder field(final Function getter, final BiConsumer setter) {
                this.inner.getFieldMergeBuilder().finish(this.inner.getResolution());
                return new FieldMergeBuilder<>(this.inner.getMergeBuilder(), getter, setter);
            }

            public  FieldMergeBuilder field(final FunctionalClass.Field field) {
                final Function getter = field.getGetter();
                final BiConsumer setter = field.getSetter();
                return this.field(getter, setter);
            }

            public  FieldMergeBuilder field(final String name) {
                final FunctionalClass clazz = this.inner.getMergeBuilder().getClazz();
                final FunctionalClass.Field field = clazz.field(name);
                return this.field(field);
            }

            public  IllTypedFieldMergeBuilder convertingWith(final ConflictResolution resolution) {
                return this.inner.then(resolution);
            }

            public AdditionalFieldMergeBuilder then(final ConflictResolution resolution) {
                return new AdditionalFieldMergeBuilder<>(this.inner.then(resolution));
            }

            public ConflictResolution build() {
                this.inner.getFieldMergeBuilder().finish(this.inner.getResolution());
                return this.inner.getMergeBuilder().build();
            }
        }
    }
}