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

io.fluxcapacitor.common.api.tracking.Position Maven / Gradle / Ivy

There is a newer version: 0.1148.0
Show newest version
/*
 * Copyright (c) Flux Capacitor IP B.V. or its affiliates. All Rights Reserved.
 *
 * 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 io.fluxcapacitor.common.api.tracking;

import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonToken;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import io.fluxcapacitor.common.api.SerializedMessage;
import lombok.AllArgsConstructor;
import lombok.Value;

import java.io.IOException;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.SortedMap;
import java.util.TreeMap;
import java.util.stream.IntStream;

@Value
@AllArgsConstructor
@JsonSerialize(using = Position.PositionSerializer.class)
@JsonDeserialize(using = Position.PositionDeserializer.class)
public class Position {

    private static final Position newPosition = new Position(new TreeMap<>());

    public static int MAX_SEGMENT = 128;
    public static int[] FULL_SEGMENT = new int[]{0, MAX_SEGMENT};

    public static Position newPosition() {
        return newPosition;
    }

    SortedMap indexBySegment;

    public Position(long index) {
        this(new int[]{0, MAX_SEGMENT}, index);
    }

    public Position(int[] segment, long index) {
        SortedMap indexBySegment = new TreeMap<>();
        IntStream.range(segment[0], segment[1]).forEach(i -> indexBySegment.put(i, index));
        this.indexBySegment = indexBySegment;
    }

    public Optional getIndex(int segment) {
        return Optional.ofNullable(indexBySegment.get(segment));
    }

    public boolean isNew(int[] segment) {
        return indexBySegment.entrySet().stream().filter(entry -> entry.getValue() != null)
                .noneMatch(entry -> entry.getKey() >= segment[0] && entry.getKey() < segment[1]);
    }

    public Optional lowestIndexForSegment(int[] segment) {
        int start = segment[0], end = segment[1];
        return indexBySegment.entrySet().stream()
                .filter(entry -> entry.getValue() != null)
                .filter(entry -> entry.getKey() >= start && entry.getKey() < end)
                .map(Map.Entry::getValue)
                .sorted().findFirst();
    }

    public Position merge(Position newPosition) {
        SortedMap indexBySegment = new TreeMap<>(this.indexBySegment);
        newPosition.indexBySegment.forEach((s, i) -> indexBySegment.merge(s, i, (v, v2) -> v2.compareTo(v) > 0 ? v2 : v));
        return new Position(indexBySegment);
    }

    public Map splitInSegments() {
        Map result = new LinkedHashMap<>();
        indexBySegment.entrySet().stream()
                .map(e -> Map.entry(new int[]{e.getKey(), e.getKey() + 1}, e.getValue())).reduce((a, b) -> {
            if (Objects.equals(a.getValue(), b.getValue()) && a.getKey()[1] == b.getKey()[0]) {
                return Map.entry(new int[]{a.getKey()[0], b.getKey()[1]}, b.getValue());
            }
            result.put(a.getKey(), a.getValue());
            return b;
        }).ifPresent(e -> result.put(e.getKey(), e.getValue()));
        return result;
    }

    public boolean isNewMessage(SerializedMessage message) {
        return isNewIndex(message.getSegment(), message.getIndex());
    }

    public boolean isNewIndex(int segment, Long messageIndex) {
        Long lastIndex = indexBySegment.get(segment);
        return lastIndex == null || messageIndex == null || lastIndex < messageIndex;
    }

    static class PositionSerializer extends JsonSerializer {
        @Override
        public void serialize(Position value, JsonGenerator gen,
                              SerializerProvider provider) throws IOException {
            gen.writeStartArray();
            for (Map.Entry entry : value.splitInSegments().entrySet()) {
                gen.writeStartArray();
                gen.writeNumber(entry.getKey()[0]);
                gen.writeNumber(entry.getKey()[1]);
                gen.writeEndArray();
                gen.writeNumber(entry.getValue());
            }
            gen.writeEndArray();
        }
    }

    static class PositionDeserializer extends JsonDeserializer {
        private final TypeReference> mapTypeReference = new TypeReference<>() {};

        @Override
        public Position deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
            return switch (p.getCurrentToken()) {
                case START_OBJECT -> {
                    p.nextToken();
                    p.nextToken();
                    yield new Position(p.readValueAs(mapTypeReference));
                }
                case START_ARRAY -> {
                    Position position = Position.newPosition();
                    while (p.nextToken() == JsonToken.START_ARRAY) {
                        int[] range = new int[2];
                        p.nextToken();
                        range[0] = p.getIntValue();
                        p.nextToken();
                        range[1] = p.getIntValue();
                        p.nextToken();
                        p.nextToken();
                        long index = p.getLongValue();
                        position = position.merge(new Position(range, index));
                    }
                    yield position;
                }
                default -> throw new UnsupportedOperationException();
            };
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy