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

org.dellroad.muxable.simple.ChannelIds Maven / Gradle / Ivy

/*
 * Copyright (C) 2021 Archie L. Cobbs. All rights reserved.
 */

package org.dellroad.muxable.simple;

import org.dellroad.stuff.util.LongSet;
import org.slf4j.Logger;

/**
 * Tracks channel ID's for the {@link SimpleMuxableChannel} framing protocol.
 *
 * 

* A shared instance of this class should be provided to both the {@link ProtocolReader} and {@link ProtocolWriter}. * *

* Valid channel ID's are range from one to {@code Long.MAX_VALUE}. Channel ID's are never reused. Each side of the * connection has its own channel ID namespace. In some cases, when both local and remote channel ID's are being passed around, * remote channel ID's are negated to avoid confusion (but this should always be clearly documented). In this class * the side of the connection is implied, and the channel ID's are always positive, except for * {@link #isChannelOpen isChannelOpen()} and {@link #getOpenChannelIds getOpenChannelIds()}. * *

* Instances are thread safe. */ public class ChannelIds extends LoggingSupport { private final LongSet openChannelIds = new LongSet(); // ID's of all unclosed channels (local and remote) private long prevLocalChannelId; // the previous local channel ID allocated private long prevRemoteChannelId; // the previous remote channel ID allocated /** * Deafult constructor. */ public ChannelIds() { } /** * Constructor. * * @param log {@link Logger} to use * @param logPrefix prefix for all log messages, or null for empty string * @throws IllegalArgumentException if {@code log} is null */ public ChannelIds(Logger log, String logPrefix) { super(log, logPrefix); } /** * Get the next available local channel ID. * * @return next local channel ID * @throws IllegalStateException if all 263 local channel ID's have already been allocated */ public synchronized long getNextLocalChannelId() { if (this.prevLocalChannelId == Long.MAX_VALUE) throw new IllegalStateException("channel ID's exhausted"); return this.prevLocalChannelId + 1; } /** * Get the next available remote channel ID. * * @return next remove channel ID * @throws IllegalStateException if all 263 remote channel ID's have already been allocated */ public synchronized long getNextRemoteChannelId() { if (this.prevRemoteChannelId == Long.MAX_VALUE) throw new IllegalStateException("channel ID's exhausted"); return this.prevRemoteChannelId + 1; } /** * Allocate a new local channel ID. * * @return new local channel ID (always positive) * @throws IllegalStateException if all 263 local channel ID's have already been allocated */ public synchronized long allocateLocalChannelId() { if (this.prevLocalChannelId == Long.MAX_VALUE) throw new IllegalStateException("channel ID's exhausted"); final long channelId = ++this.prevLocalChannelId; this.openChannelIds.add(channelId); this.trace("allocated new local channel %d", channelId); return channelId; } /** * Validate a local channel ID. * *

* This verifies that {@code channelId} is valid and represents a channel allocated via {@link #allocateLocalChannelId}. * * @param channelId local channel ID * @return true if {@code channelId} is valid and open, false if valid but released by {@link #freeChannelId freeChannelId()} * @throws IllegalArgumentException if {@code channelId} is non-positive or not yet allocated */ public synchronized boolean validateLocalChannelId(final long channelId) { if (channelId < 1 || channelId > this.prevLocalChannelId) { throw new IllegalArgumentException(String.format("invalid %s channel ID %d: not in the range %d..%d", "local", channelId, 1, this.prevLocalChannelId)); } return this.openChannelIds.contains(channelId); } /** * Allocate a remote channel ID, if {@code channelId} represents the next available remote channel ID to be allocated. * *

* If {@code channelId} represents the next available remote channel ID, then allocate it and return true. * Otherwise, return false. * * @param channelId remote channel ID * @return true if {@code channelId} is the next available remote channel ID which was also just allocated, otherwise false * @throws IllegalArgumentException if {@code channelId} is non-positive */ public synchronized boolean allocateRemoteChannelId(final long channelId) { if (this.prevRemoteChannelId == Long.MAX_VALUE || channelId != this.prevRemoteChannelId + 1) return false; this.prevRemoteChannelId++; this.openChannelIds.add(-channelId); this.trace("allocated new remote channel %d", channelId); return true; } /** * Validate a remote channel ID. * *

* This verifies that {@code channelId} is valid and represents a channel allocated via {@link #allocateRemoteChannelId}. * * @param channelId remote channel ID * @return true if {@code channelId} is valid and open, false if valid but released by {@link #freeChannelId freeChannelId()} * @throws IllegalArgumentException if {@code channelId} is non-positive or not yet allocated */ public synchronized boolean validateRemoteChannelId(final long channelId) { if (channelId < 1 || channelId > this.prevRemoteChannelId) { throw new IllegalArgumentException(String.format("invalid %s channel ID %d: not in the range %d..%d", "remote", channelId, 1, this.prevRemoteChannelId)); } return this.openChannelIds.contains(-channelId); } /** * Deallocate (i.e., mark as closed) a channel ID previously allocated by * {@link #allocateLocalChannelId allocateLocalChannelId()} or {@link #allocateRemoteChannelId allocateRemoteChannelId()}. * * @param channelId local channel ID * @param local true if local channel ID, false if remote channel ID * @return true if channel was actually open, false if channel was already closed * @throws IllegalArgumentException if {@code channelId} is non-positive, never allocated, or already closed */ public synchronized boolean freeChannelId(long channelId, boolean local) { if (channelId < 1) throw new IllegalArgumentException(String.format("invalid %s channel ID %d", local ? "local" : "remote", channelId)); final boolean freed = this.openChannelIds.remove(local ? channelId : -channelId); if (freed) this.trace("freed channel %s%d", local ? "L" : "R", channelId); return freed; } /** * Determine whether the specified channel is still open. * * @param channelId encoded channel ID (positive for local, negative for remote) * @return true if channel is still open, false if channel has been closed * @throws IllegalArgumentException if {@code channelId} is invalid or not yet allocated */ public synchronized boolean isChannelOpen(long channelId) { if (channelId == 0 || channelId == Long.MIN_VALUE) throw new IllegalArgumentException(String.format("invalid channel ID %d", channelId)); return channelId < 0 ? this.validateRemoteChannelId(-channelId) : this.validateLocalChannelId(channelId); } /** * Get all open channel ID's. * *

* The returned set contains both local and remote channels, where remote channels are encoded * as the negatives of their actual values. * *

* The returned set is a copy; modifications do not affect this instance. * * @return all open channel ID's, where remote channel ID's are encoded by negation */ public LongSet getOpenChannelIds() { return this.openChannelIds.clone(); } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy