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

com.netflix.hollow.tools.diff.HollowDiffMatcher Maven / Gradle / Ivy

There is a newer version: 7.13.0
Show newest version
/*
 *  Copyright 2016-2019 Netflix, Inc.
 *
 *     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 com.netflix.hollow.tools.diff;

import com.netflix.hollow.core.HollowConstants;
import com.netflix.hollow.core.index.HollowPrimaryKeyIndex;
import com.netflix.hollow.core.read.engine.PopulatedOrdinalListener;
import com.netflix.hollow.core.read.engine.object.HollowObjectTypeReadState;
import com.netflix.hollow.core.util.IntList;
import com.netflix.hollow.core.util.LongList;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.BitSet;
import java.util.List;

/**
 * Intended for use in the context of a HollowDiff.
 *
 * This class will match records of a specific type from two data states based on a user defined primary key.
 */
public class HollowDiffMatcher {

    private final List matchPaths;

    private final HollowObjectTypeReadState fromTypeState;
    private final HollowObjectTypeReadState toTypeState;

    private final LongList matchedOrdinals;
    private final IntList extraInFrom;
    private final IntList extraInTo;

    private HollowPrimaryKeyIndex fromIdx;
    private HollowPrimaryKeyIndex toIdx;

    public HollowDiffMatcher(HollowObjectTypeReadState fromTypeState, HollowObjectTypeReadState toTypeState) {
        this.matchPaths = new ArrayList<>();
        this.fromTypeState = fromTypeState;
        this.toTypeState = toTypeState;
        this.matchedOrdinals = new LongList();
        this.extraInFrom = new IntList();
        this.extraInTo = new IntList();
    }

    public void addMatchPath(String path) {
        matchPaths.add(path);
    }

    public List getMatchPaths() {
        return matchPaths;
    }

    public void calculateMatches() {
        if (fromTypeState==null) {
            toTypeState.getPopulatedOrdinals().stream().forEach(i -> extraInTo.add(i));
            return;
        }

        if (toTypeState==null) {
            fromTypeState.getPopulatedOrdinals().stream().forEach(i -> extraInFrom.add(i));
            return;
        }

        // No Primary Key so no matching will be done
        if (matchPaths==null || matchPaths.isEmpty()) {
            toTypeState.getPopulatedOrdinals().stream().forEach(i -> extraInTo.add(i));
            fromTypeState.getPopulatedOrdinals().stream().forEach(i -> extraInFrom.add(i));
            return;
        }

        fromIdx = new HollowPrimaryKeyIndex(fromTypeState.getStateEngine(), fromTypeState.getSchema().getName(), matchPaths.toArray(new String[matchPaths.size()]));
        toIdx = new HollowPrimaryKeyIndex(toTypeState.getStateEngine(), toTypeState.getSchema().getName(), matchPaths.toArray(new String[matchPaths.size()]));

        BitSet fromPopulatedOrdinals = fromTypeState.getListener(PopulatedOrdinalListener.class).getPopulatedOrdinals();
        BitSet fromUnmatchedOrdinals = new BitSet(fromPopulatedOrdinals.length());
        fromUnmatchedOrdinals.or(fromPopulatedOrdinals);

        BitSet toPopulatedOrdinals = toTypeState.getListener(PopulatedOrdinalListener.class).getPopulatedOrdinals();

        int candidateToMatchOrdinal = toPopulatedOrdinals.nextSetBit(0);
        while(candidateToMatchOrdinal != -1) {
            Object key[] = toIdx.getRecordKey(candidateToMatchOrdinal);
            int matchedOrdinal = HollowConstants.ORDINAL_NONE;
            try {
                matchedOrdinal = fromIdx.getMatchingOrdinal(key);
            } catch(NullPointerException ex) {
                throw new RuntimeException("Error fetching matching ordinal for null type " + toTypeState.getSchema().getName()
                        + " with key field values " + Arrays.asList(key) + " at ordinal : " + candidateToMatchOrdinal
                        + "with stack trace ", ex);
            }

            if(matchedOrdinal != -1) {
                matchedOrdinals.add(((long)matchedOrdinal << 32) | candidateToMatchOrdinal);
                fromUnmatchedOrdinals.clear(matchedOrdinal);
            } else {
                extraInTo.add(candidateToMatchOrdinal);
            }

            candidateToMatchOrdinal = toPopulatedOrdinals.nextSetBit(candidateToMatchOrdinal + 1);
        }

        int unmatchedFromOrdinal = fromUnmatchedOrdinals.nextSetBit(0);
        while(unmatchedFromOrdinal != -1) {
            extraInFrom.add(unmatchedFromOrdinal);
            unmatchedFromOrdinal = fromUnmatchedOrdinals.nextSetBit(unmatchedFromOrdinal + 1);
        }
    }

    public LongList getMatchedOrdinals() {
        return matchedOrdinals;
    }

    public IntList getExtraInFrom() {
        return extraInFrom;
    }

    public IntList getExtraInTo() {
        return extraInTo;
    }

    public String getKeyDisplayString(HollowObjectTypeReadState state, int ordinal) {
        Object[] key = null;

        if(state == fromTypeState && fromIdx!=null) {
            key = fromIdx.getRecordKey(ordinal);
        } else if(state == toTypeState && toIdx!=null) {
            key = toIdx.getRecordKey(ordinal);
        }

        // Show Display similar to Hollow Explorer when there is no primary key
        if(key == null)
            return "ORDINAL:" + ordinal;

        return keyDisplayString(key);
    }

    private String keyDisplayString(Object[] key) {
        StringBuilder sb = new StringBuilder(key[0].toString());

        for(int i=1;i




© 2015 - 2024 Weber Informatics LLC | Privacy Policy