class GridDistributedTxMapping {
    /** */
    private static final AtomicReferenceFieldUpdater<GridDistributedTxMapping, Set> BACKUPS_FIELD_UPDATER
        = AtomicReferenceFieldUpdater.newUpdater(GridDistributedTxMapping.class, Set.class, "backups");

    /** Mapped node. */
    @GridToStringExclude
    private final ClusterNode primary;

    /** Mapped backup nodes. */
    private volatile Set<UUID> backups;

    /** Entries. */
    @GridToStringInclude
    private final Collection<IgniteTxEntry> entries;

    /** Explicit lock flag. */
    private boolean explicitLock;

    /** Query update flag. */
    private boolean queryUpdate;

    /** DHT version. */
    private GridCacheVersion dhtVer;

    /** {@code True} if this is last mapping for node. */
    private boolean last;

    /** Near cache entries count. */
    private int nearEntries;

    /** {@code True} if this is first mapping for optimistic tx on client node. */
    private boolean clientFirst;

    /**
     * @param primary Primary node.
     */
    public GridDistributedTxMapping(ClusterNode primary) {
        this.primary = primary;

        entries = new LinkedHashSet<>();
    }

    /**
     * @return {@code True} if this is last mapping for node.
     */
    public boolean last() {
        return last;
    }

    /**
     * @param last If {@code True} this is last mapping for node.
     */
    public void last(boolean last) {
        this.last = last;
    }

    /**
     * @return {@code True} if this is first mapping for optimistic tx on client node.
     */
    public boolean clientFirst() {
        return clientFirst;
    }

    /**
     * @param clientFirst {@code True} if this is first mapping for optimistic tx on client node.
     */
    public void clientFirst(boolean clientFirst) {
        this.clientFirst = clientFirst;
    }

    /**
     * @return {@code True} if has colocated cache entries.
     */
    public boolean hasColocatedCacheEntries() {
        return entries.size() > nearEntries;
    }

    /**
     * @return {@code True} if has near cache entries.
     */
    public boolean hasNearCacheEntries() {
        return nearEntries > 0;
    }

    /**
     * @return Node.
     */
    public ClusterNode primary() {
        return primary;
    }

    /**
     * @return Entries.
     */
    public Collection<IgniteTxEntry> entries() {
        return entries;
    }

    /**
     * @return Near cache entries.
     */
    @Nullable public Collection<IgniteTxEntry> nearCacheEntries() {
        assert nearEntries > 0;

        return F.view(entries, CU.FILTER_NEAR_CACHE_ENTRY);
    }

    /**
     * @return {@code True} if mapping was created for a query update.
     */
    public boolean queryUpdate() {
        return queryUpdate;
    }

    /**
     * Sets query update flag to {@code true}.
     */
    public void markQueryUpdate() {
        queryUpdate = true;
    }

    /**
     * @return {@code True} if lock is explicit.
     */
    public boolean explicitLock() {
        return explicitLock;
    }

    /**
     * Sets explicit flag to {@code true}.
     */
    public void markExplicitLock() {
        explicitLock = true;
    }

    /**
     * @return DHT version.
     */
    public GridCacheVersion dhtVersion() {
        return dhtVer;
    }

    /**
     * @param dhtVer DHT version.
     * @param writeVer DHT writeVersion.
     */
    public void dhtVersion(GridCacheVersion dhtVer, GridCacheVersion writeVer) {
        this.dhtVer = dhtVer;

        for (IgniteTxEntry e : entries)
            e.dhtVersion(writeVer);
    }

    /**
     * @return Reads.
     */
    public Collection<IgniteTxEntry> reads() {
        return F.view(entries, CU.READ_FILTER);
    }

    /**
     * @return Writes.
     */
    public Collection<IgniteTxEntry> writes() {
        return F.view(entries, CU.WRITE_FILTER);
    }

    /**
     * @return Near cache reads.
     */
    public Collection<IgniteTxEntry> nearEntriesReads() {
        assert hasNearCacheEntries();

        return F.view(entries, CU.READ_FILTER_NEAR);
    }

    /**
     * @return Near cache writes.
     */
    public Collection<IgniteTxEntry> nearEntriesWrites() {
        assert hasNearCacheEntries();

        return F.view(entries, CU.WRITE_FILTER_NEAR);
    }

    /**
     * @return Colocated cache reads.
     */
    public Collection<IgniteTxEntry> colocatedEntriesReads() {
        assert hasColocatedCacheEntries();

        return F.view(entries, CU.READ_FILTER_COLOCATED);
    }

    /**
     * @return Colocated cache writes.
     */
    public Collection<IgniteTxEntry> colocatedEntriesWrites() {
        assert hasColocatedCacheEntries();

        return F.view(entries, CU.WRITE_FILTER_COLOCATED);
    }

    /**
     * @param entry Adds entry.
     */
    public void add(IgniteTxEntry entry) {
        if (entries.add(entry) && entry.context().isNear())
            nearEntries++;
    }

    /**
     * @param entry Entry to remove.
     * @return {@code True} if entry was removed.
     */
    public boolean removeEntry(IgniteTxEntry entry) {
        return entries.remove(entry);
    }

    /**
     * @param keys Keys to evict readers for.
     */
    public void evictReaders(@Nullable Collection<IgniteTxKey> keys) {
        if (keys == null || keys.isEmpty())
            return;

        evictReaders(keys, entries);
    }

    /**
     * @param keys Keys to evict readers for.
     * @param entries Entries to check.
     */
    private void evictReaders(Collection<IgniteTxKey> keys, @Nullable Collection<IgniteTxEntry> entries) {
        if (entries == null || entries.isEmpty())
            return;

        for (Iterator<IgniteTxEntry> it = entries.iterator(); it.hasNext();) {
            IgniteTxEntry entry = it.next();

            if (keys.contains(entry.txKey()))
                it.remove();
        }
    }

    /**
     * Whether empty or not.
     *
     * @return Empty or not.
     */
    public boolean empty() {
        return entries.isEmpty();
    }

    /**
     * @param newBackups Backups to be added to this mapping.
     */
    public void addBackups(Collection<UUID> newBackups) {<FILL_FUNCTION_BODY>}

    /**
     * @return Mapped backup nodes.
     */
    public Set<UUID> backups() {
        return backups != null ? backups : Collections.emptySet();
    }

    /** {@inheritDoc} */
    @Override public String toString() {
        return S.toString(GridDistributedTxMapping.class, this, "node", primary.id());
    }
}

        if (newBackups == null)
            return;

        if (backups == null)
            BACKUPS_FIELD_UPDATER.compareAndSet(this, null, Collections.newSetFromMap(new ConcurrentHashMap<>()));

        backups.addAll(newBackups);
    
<no_super_class>