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

com.tangosol.net.partition.PartitionSet Maven / Gradle / Ivy

There is a newer version: 24.09
Show newest version
/*
 * Copyright (c) 2000, 2023, Oracle and/or its affiliates.
 *
 * Licensed under the Universal Permissive License v 1.0 as shown at
 * https://oss.oracle.com/licenses/upl.
 */

package com.tangosol.net.partition;


import com.tangosol.io.ExternalizableLite;

import com.tangosol.io.pof.PofReader;
import com.tangosol.io.pof.PofWriter;
import com.tangosol.io.pof.PortableObject;

import com.tangosol.util.Base;
import com.tangosol.util.ExternalizableHelper;

import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;

import java.util.Arrays;
import java.util.Collection;
import java.util.Iterator;

import jakarta.json.bind.annotation.JsonbProperty;


/**
* PartitionSet is a light-weight data structure that represents a set of
* partitions that are used in parallel processing. This set quite often
* accompanies a result of partial parallel execution and is used to determine
* whether or not the entire set of partitions was successfully processed.
* 

* Note that all PartitionSet operations that take another set as an argument * assume that both sets have the same partition count. *

* This implementation is not thread-safe. * * @author gg 2005.12.20 * @since Coherence 3.1 */ /* * Internal note: while this functionality is a small part of what the * java.util.BitSet offers, prior to JDK 1.4.2 there was no way to extract the * internal array that represents the BitSet state for a purpose of a * lite serialization (bug_id=5037068). By the time it was fixed, the existing * implementation proved to be sufficient. We may reevaluate this decision * again in the future. */ public class PartitionSet extends Base implements ExternalizableLite, PortableObject, Iterable { // ----- constructors --------------------------------------------------- /** * Default constructor (necessary for the ExternalizableLite interface). */ public PartitionSet() { } /** * Construct an empty partition set with a given count. * * @param cPartitions the partition count */ public PartitionSet(int cPartitions) { azzert(cPartitions > 0); m_cPartitions = cPartitions; m_alBits = new long[(cPartitions + 63) >>> 6]; m_lTailMask = -1L >>> (64 - (cPartitions & 63)); m_cMarked = 0; } /** * Construct a partition set with a given partition count and the specified * partitions set. * * @param cPartitions the partition count * @param colPartitions the partitions to set */ public PartitionSet(int cPartitions, Collection colPartitions) { this(cPartitions); colPartitions.forEach(this::add); } /** * Construct a partition set with a given partition count and the specified * partitions set. * * @param cPartitions the partition count * @param aiPartitions the partitions to set */ public PartitionSet(int cPartitions, int... aiPartitions) { this(cPartitions); Arrays.stream(aiPartitions).forEach(this::add); } /** * Copy constructor: construct a new PartitionSet object equivalent to the * specified one. * * @param partitions the partition set to copy */ public PartitionSet(PartitionSet partitions) { m_cPartitions = partitions.m_cPartitions; m_alBits = partitions.m_alBits.clone(); m_lTailMask = partitions.m_lTailMask; m_cMarked = partitions.m_cMarked; } // ----- pseudo Set operations ------------------------------------------ /** * Add the specified partition to the set. * * @param nPartition the partition to add * * @return true if the specified partition was actually added as a result * of this call; false otherwise */ public boolean add(int nPartition) { if (nPartition < 0 || nPartition >= m_cPartitions) { throw new IndexOutOfBoundsException( nPartition + " not in [0, " + m_cPartitions + ')'); } long[] alBits = m_alBits; int iLong = nPartition >>> 6; long lBits = alBits[iLong]; long lMask = 1L << (nPartition & 63); if ((lBits & lMask) == 0L) { alBits[iLong] = lBits | lMask; int cMarked = m_cMarked; if (cMarked >= 0) { m_cMarked = cMarked + 1; } return true; } else { return false; } } /** * Add the specified PartitionSet to this set. * * @param partitions the PartitionSet to add * * @return true if all of the partitions were actually added as a result * of this call; false otherwise */ public boolean add(PartitionSet partitions) { int cPartitions = m_cPartitions; azzert(cPartitions == partitions.m_cPartitions); long[] alBitsThis = this .m_alBits; long[] alBitsThat = partitions.m_alBits; boolean fResult = true; for (int i = 0, c = alBitsThis.length; i < c; ++i) { long lBitsThis = alBitsThis[i]; long lBitsThat = alBitsThat[i]; fResult &= (lBitsThis & lBitsThat) == 0L; alBitsThis[i] = lBitsThis | lBitsThat; } m_cMarked = -1; return fResult; } /** * Remove the specified partition from the set. * * @param nPartition the partition to remove * * @return true if the specified partition was actually removed as a * result of this call; false otherwise */ public boolean remove(int nPartition) { if (nPartition < 0 || nPartition >= m_cPartitions) { throw new IndexOutOfBoundsException( nPartition + " not in [0, " + m_cPartitions + ')'); } long[] alBits = m_alBits; int iLong = nPartition >>> 6; long lBits = alBits[iLong]; long lMask = 1L << (nPartition & 63); if ((lBits & lMask) != 0L) { alBits[iLong] = lBits & ~lMask; int cMarked = m_cMarked; if (cMarked >= 0) { m_cMarked = cMarked - 1; } return true; } else { return false; } } /** * Remove the first marked partition starting at the specified partition. * If there are no marked partitions greater or equal to the specified * partition, the first marked partition greater or equal to 0 but less * than the specified partition is removed. If this PartitionSet is empty, * -1 is returned. * * @param nPartition the partition to start checking from (inclusive) * * @return the marked partition that was removed or -1 if this * PartitionSet is empty * * @throws IndexOutOfBoundsException if the specified partition is invalid */ public int removeNext(int nPartition) { int nNext = next(nPartition); if (nNext == -1 && nPartition > 0) { nNext = next(0); } if (nNext >= 0) { remove(nNext); } return nNext; } /** * Remove the specified PartitionSet from this set. * * @param partitions the PartitionSet to remove * * @return true if all of the specified partitions were actually removed; * false otherwise */ public boolean remove(PartitionSet partitions) { int cPartitions = m_cPartitions; azzert(cPartitions == partitions.m_cPartitions); long[] alBitsThis = this .m_alBits; long[] alBitsThat = partitions.m_alBits; boolean fResult = true; for (int i = 0, c = alBitsThis.length; i < c; ++i) { long lBitsThis = alBitsThis[i]; long lBitsThat = alBitsThat[i]; fResult &= (lBitsThis & lBitsThat) == lBitsThat; alBitsThis[i] = lBitsThis & ~lBitsThat; } m_cMarked = -1; return fResult; } /** * Retain only partitions in this set that are contained in the specified * PartitionSet. * * @param partitions the PartitionSet to retain * * @return true if this PartitionSet changes as a result of this call; * false otherwise */ public boolean retain(PartitionSet partitions) { int cPartitions = m_cPartitions; azzert(cPartitions == partitions.m_cPartitions); long[] alBitsThis = this .m_alBits; long[] alBitsThat = partitions.m_alBits; boolean fResult = false; for (int i = 0, c = alBitsThis.length; i < c; ++i) { long lBitsThis = alBitsThis[i]; long lBitsThat = alBitsThat[i]; long lIntrsctn = lBitsThis & lBitsThat; if (lIntrsctn != lBitsThis) { alBitsThis[i] = lIntrsctn; fResult = true; } } if (fResult) { m_cMarked = -1; } return fResult; } /** * Check whether or not the specified partition belongs to the set. * * @param nPartition the partition to check * * @return true if the specified partition is in the set; * false otherwise */ public boolean contains(int nPartition) { if (nPartition < 0 || nPartition >= m_cPartitions) { throw new IndexOutOfBoundsException( nPartition + " not in [0, " + m_cPartitions + ')'); } int iLong = nPartition >>> 6; long lBits = m_alBits[iLong]; long lMask = 1L << (nPartition & 63); return (lBits & lMask) != 0L; } /** * Check whether or not the specified partition set belongs to this set. * * @param partitions the partition set to check * * @return true if all the partitions from the specified set are in this * set; false otherwise */ public boolean contains(PartitionSet partitions) { int cPartitions = m_cPartitions; azzert(cPartitions == partitions.m_cPartitions); long[] alBitsThis = this .m_alBits; long[] alBitsThat = partitions.m_alBits; for (int i = 0, c = alBitsThis.length; i < c; ++i) { long lBitsThis = alBitsThis[i]; long lBitsThat = alBitsThat[i]; long lIntrsctn = lBitsThis & lBitsThat; if (lIntrsctn != lBitsThat) { return false; } } return true; } /** * Check whether or not the specified partition set intersects with this * set. * * @param partitions the partition set to check * * @return true if the specified set contains at least one partition that * is also present in this partition set; false otherwise */ public boolean intersects(PartitionSet partitions) { int cPartitions = m_cPartitions; azzert(cPartitions == partitions.m_cPartitions); long[] alBitsThis = this .m_alBits; long[] alBitsThat = partitions.m_alBits; for (int i = 0, c = alBitsThis.length; i < c; ++i) { long lBitsThis = alBitsThis[i]; long lBitsThat = alBitsThat[i]; long lIntrsctn = lBitsThis & lBitsThat; if (lIntrsctn != 0L) { return true; } } return false; } /** * Check whether or not the partition set is empty. * * @return true if none of the partitions are marked; false otherwise */ public boolean isEmpty() { int cMarked = m_cMarked; if (cMarked >= 0) { return cMarked == 0; } long[] alBits = m_alBits; for (int i = 0, c = alBits.length; i < c; ++i) { if (alBits[i] != 0L) { return false; } } m_cMarked = 0; return true; } /** * Check whether or not the partition set is full. * * @return true if all the partitions are marked; false otherwise */ public boolean isFull() { return cardinality() == getPartitionCount(); } /** * Clear the set. * * @return this PartitionSet */ public PartitionSet clear() { long[] alBits = m_alBits; for (int i = 0, c = alBits.length; i < c; ++i) { alBits[i] = 0L; } m_cMarked = 0; return this; } /** * Fill the set to contain all the partitions. * * @return this PartitionSet */ public PartitionSet fill() { long[] alBits = m_alBits; int iLast = alBits.length - 1; for (int i = 0; i < iLast; ++i) { alBits[i] = -1L; } alBits[iLast] = m_lTailMask; m_cMarked = m_cPartitions; return this; } /** * Invert all the partitions. As a result of this operation, all marked * partitions will be cleared and all cleared partitions will become * marked. * * @return this PartitionSet */ public PartitionSet invert() { long[] alBits = m_alBits; int iLast = alBits.length - 1; for (int i = 0; i <= iLast; ++i) { alBits[i] = ~alBits[i]; } alBits[iLast] &= m_lTailMask; int cMarked = m_cMarked; if (cMarked >= 0) { m_cMarked = m_cPartitions - cMarked; } return this; } /** * Return an index of the first marked partition. If no marked partitions * exists then -1 is returned. * * @return the first marked partition, or -1 if no marked partitions * exists in the set */ public int first() { return next(0); } /** * Return an index of the first marked partition that is greater than or * equal to the specified partition. If no such partition exists then -1 is * returned. *

* This method could be used to iterate over all marked partitions: *

{@code
    * for (int i = ps.next(0); i >= 0; i = ps.next(i+1))
    *     {
    *     // process partition
    *     }
    * }
* * @param nPartition the partition to start checking from (inclusive) * * @return the next marked partition, or -1 if no next marked partition * exists in the set * * @throws IndexOutOfBoundsException if the specified partition is * invalid */ public int next(int nPartition) { int cPartitions = m_cPartitions; if (nPartition < 0 || nPartition > cPartitions) { throw new IndexOutOfBoundsException( nPartition + " not in [0, " + cPartitions + ')'); } if (nPartition == cPartitions || m_cMarked == 0) { return -1; } long[] alBits = m_alBits; int iLong = nPartition >>> 6; int ofBit = nPartition & 63; long lBits = alBits[iLong] >>> ofBit; if (lBits == 0L) { ofBit = 0; // skip empty parts for (int iLast = alBits.length - 1; lBits == 0L && iLong < iLast; ) { lBits = alBits[++iLong]; } if (lBits == 0L) { return -1; } } return (iLong << 6) + ofBit + Long.numberOfTrailingZeros(lBits); } /** * Returns the number of marked partitions. * * @return the number of marked partitions */ public int cardinality() { int cMarked = m_cMarked; if (cMarked < 0) { cMarked = 0; long[] alBits = m_alBits; for (int i = 0, c = alBits.length; i < c; ++i) { cMarked += Long.bitCount(alBits[i]); } m_cMarked = cMarked; } return cMarked; } /** * Convert the partition set to an array of partition identifiers. * * @return an array of integer partition identifiers */ public int[] toArray() { int cPids = cardinality(); int[] anPid = new int[cPids]; for (int i = next(0), c = 0; i >= 0; i = next(i+1)) { anPid[c++] = i; } return anPid; } /** * Obtain a random partition from the partition set. * * @return a randomly selected marked partition, or -1 if no partitions * are marked */ public int rnd() { int cPids = cardinality(); if (cPids == 0) { return -1; } int nPid = next(0); int cSkip = getRandom().nextInt(cPids); while (cSkip-- > 0) { nPid = next(nPid + 1); } return nPid; } /** * Split this partition set into two partition sets that are mutually * exclusive. * * @return a new PartitionSet containing approximately half of the marked * partitions from this set, or null if this PartitionSet cannot * be split */ public PartitionSet split() { int cPids = cardinality() / 2; if (cPids == 0) { return null; } PartitionSet parts = new PartitionSet(m_cPartitions); for (int i = next(0), c = 0; c < cPids; i = next(i+1), c++) { parts.add(i); remove(i); } return parts; } /** * Return a union of the two provided PartitionSets. *

* This method will return a reference to one of the provided sets (or null), * and may mutate the returned set. * * @param partsA set A * @param partsB set B * * @return union of {@code A & B} */ public static PartitionSet union(PartitionSet partsA, PartitionSet partsB) { if (partsA == null) { return partsB; } if (partsB == null) { return partsA; } partsA.add(partsB); return partsA; } // ----- Iterable interface --------------------------------------------- @Override public Iterator iterator() { return new Iterator() { @Override public boolean hasNext() { return PartitionSet.this.next(m_current + 1) != -1; } @Override public Integer next() { return m_current = m_current == -1 ? first() : PartitionSet.this.next(m_current + 1); } private volatile int m_current = -1; }; } // ----- ExternalizableLite interface ----------------------------------- /** * {@inheritDoc} */ public void readExternal(DataInput in) throws IOException { int cPartitions = in.readUnsignedShort(); int cLongs = (cPartitions + 63) >>> 6; long[] alBits = new long[cLongs]; m_cPartitions = cPartitions; m_alBits = alBits; m_lTailMask = -1L >>> (64 - (cPartitions & 63)); m_cMarked = 0; int nFormat = in.readUnsignedByte(); switch (nFormat) { case MARKED_NONE: break; case MARKED_FEW: { for (int iLast = 0, cSkip; (cSkip = ExternalizableHelper.readInt(in)) >= 0; ) { iLast += cSkip; add(iLast); } } break; case MARKED_MANY: { int cMarked = 0; for (int i = 0; i < cLongs; ++i) { long lBits = in.readLong(); alBits[i] = lBits; cMarked += Long.bitCount(lBits); } m_cMarked = cMarked; } break; case MARKED_ALL: fill(); break; default: throw new IOException("stream corrupted; format=" + nFormat); } } /** * {@inheritDoc} */ public void writeExternal(DataOutput out) throws IOException { int cPartitions = m_cPartitions; out.writeShort(cPartitions); int cMarked = cardinality(); if (cMarked == 0) { out.writeByte(MARKED_NONE); } else if (cMarked == cPartitions) { out.writeByte(MARKED_ALL); } else if (cMarked < (cPartitions >>> 5)) { // likely to be optimal with the "few" format out.writeByte(MARKED_FEW); for (int iLast = 0, iCurr = next(0); iCurr >= 0; iCurr = next(iCurr +1)) { ExternalizableHelper.writeInt(out, iCurr-iLast); iLast = iCurr; } ExternalizableHelper.writeInt(out, -1); } else { out.writeByte(MARKED_MANY); long[] alBits = m_alBits; for (int i = 0, c = alBits.length; i < c; ++i) { out.writeLong(alBits[i]); } } } // ----- PortableObject interface --------------------------------------- /** * {@inheritDoc} */ public void readExternal(PofReader in) throws IOException { // 0: partition-count // 1: format-indicator // 2: int array of gaps (for MARKED_FEW format) // 3: long array of bit masks (for MARKED_MANY format) // 4: reserved int cPartitions = in.readInt(0); int nFormat = in.readInt(1); int cLongs = (cPartitions + 63) >>> 6; long[] alBits = nFormat == MARKED_MANY ? in.readLongArray(3) : new long[cLongs]; m_cPartitions = cPartitions; m_alBits = alBits; m_lTailMask = -1L >>> (64 - (cPartitions & 63)); m_cMarked = -1; switch (nFormat) { case MARKED_NONE: m_cMarked = 0; break; case MARKED_FEW: { int[] acSkip = in.readIntArray(2); int cSkips = acSkip.length; for (int i = 0, iLast = 0; i < cSkips; ++i) { iLast += acSkip[i]; add(iLast); } m_cMarked = cSkips; } break; case MARKED_MANY: // handled above break; case MARKED_ALL: fill(); break; default: throw new IOException("stream corrupted; format=" + nFormat); } } /** * {@inheritDoc} */ public void writeExternal(PofWriter out) throws IOException { int cPartitions = m_cPartitions; out.writeInt(0, cPartitions); int cMarked = cardinality(); if (cMarked == 0) { out.writeInt(1, MARKED_NONE); } else if (cMarked == cPartitions) { out.writeInt(1, MARKED_ALL); } else if (cMarked < (cPartitions >>> 5)) { out.writeInt(1, MARKED_FEW); int[] an = toArray(); for (int i = an.length - 1; i > 0; --i) { an[i] -= an[i-1]; } out.writeIntArray(2, an); } else { out.writeInt(1, MARKED_MANY); out.writeLongArray(3, m_alBits); } } // ----- Object methods ------------------------------------------------- /** * Indicates whether some other object is "equal to" this one. * * @param o the object to test for equality * * @return true if this object is the same as the given one; * false otherwise. */ public boolean equals(Object o) { if (o instanceof PartitionSet) { if (o == this) { return true; } PartitionSet that = (PartitionSet) o; if (this.m_cPartitions == that.m_cPartitions) { // short-cut: compare the number marked int cMarkedThis = this.m_cMarked; int cMarkedThat = that.m_cMarked; if (cMarkedThis != cMarkedThat && cMarkedThis >= 0 && cMarkedThat >= 0) { return false; } // full compare: compare all bits long[] alBitsThis = this.m_alBits; long[] alBitsThat = that.m_alBits; for (int i = 0, c = alBitsThis.length; i < c; ++i) { if (alBitsThis[i] != alBitsThat[i]) { return false; } } return true; } } return false; } /** * Returns a hash code value for this PartitionSet. * * @return the hash code value for this PartitionSet */ public int hashCode() { return 7 + Arrays.hashCode(m_alBits) + m_cPartitions + m_cMarked; } /** * Returns a string representation of this PartitionSet. * * @return a string representation of this PartitionSet */ public String toString() { return toString(true); } /** * Returns a string representation of this PartitionSet. * * * @param fVerbose true for full information, false for terse * * @return a string representation of this PartitionSet */ public String toString(boolean fVerbose) { StringBuilder sb = new StringBuilder(); boolean fAppend = false; int cRange = 0; int iPrev = -1; if (fVerbose) { sb.append("PartitionSet{"); } for (int iPid = next(0); iPid >= 0; iPid = next(iPid + 1)) { if (iPid == (iPrev + 1) && iPrev >= 0) { // range continuation cRange++; } else { if (cRange > 0) { // range completion sb.append(cRange > 1 ? ".." : ", ").append(iPrev); cRange = 0; } if (fAppend) { sb.append(", "); } else { fAppend = true; } sb.append(iPid); } iPrev = iPid; } if (cRange > 0) { sb.append(cRange > 1 ? ".." : ", ").append(iPrev); } if (fVerbose) { sb.append('}'); } return sb.toString(); } // ----- accessors ------------------------------------------------------ /** * Return the number of partitions represented by this PartitionSet. * * @return the total partition count */ public int getPartitionCount() { return m_cPartitions; } // ----- constants ------------------------------------------------------ /** * Serialization format indicator: Indicates that no partitions are * marked; MARKED_NONE requires no additional data. */ protected static final int MARKED_NONE = 0; /** * Serialization format indicator: Indicates that a small number of * partitions are marked; followed by stream of packed integers indicating * gaps between each marked partition, terminated with a -1. */ protected static final int MARKED_FEW = 1; /** * Serialization format indicator: Indicates that a large number of * partitions are marked; followed by a sequence of 64-bit values * sufficient to represent the cardinality of the PartitionSet. */ protected static final int MARKED_MANY = 2; /** * Serialization format indicator: Indicates that all partitions are * marked; MARKED_ALL requires no additional data. */ protected static final int MARKED_ALL = 3; // ----- data members --------------------------------------------------- /** * Total partition count. */ @JsonbProperty("partitionCount") private int m_cPartitions; /** * A bit array representing the partitions, stored as an array of longs. */ @JsonbProperty("bits") private long[] m_alBits; /** * A mask for the last long that indicates what bits get used. */ @JsonbProperty("tailMask") private long m_lTailMask; /** * A cached count of marked partitions; -1 indicates that the value must * be recalculated. */ @JsonbProperty("markedCount") private int m_cMarked; }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy