public boolean hasNotJoinedGroup() {
            return equals(UNJOINED) || equals(PREPARING_REBALANCE);
        }
AbstractCoordinator implements group management for a single group member by interacting with
a designated Kafka broker (the coordinator). Group semantics are provided by extending this class.
See {@link ConsumerCoordinator} for example usage.

From a high level, Kafka's group management protocol consists of the following sequence of actions:

<ol>
    <li>Group Registration: Group members register with the coordinator providing their own metadata
        (such as the set of topics they are interested in).</li>
    <li>Group/Leader Selection: The coordinator select the members of the group and chooses one member
        as the leader.</li>
    <li>State Assignment: The leader collects the metadata from all the members of the group and
        assigns state.</li>
    <li>Group Stabilization: Each member receives the state assigned by the leader and begins
        processing.</li>
</ol>

To leverage this protocol, an implementation must define the format of metadata provided by each
member for group registration in {@link #metadata()} and the format of the state assignment provided
by the leader in {@link #onLeaderElected(String, String, List, boolean)} and becomes available to members in
{@link #onJoinComplete(int, String, String, ByteBuffer)}.

Note on locking: this class shares state between the caller and a background thread which is
used for sending heartbeats after the client has joined the group. All mutable state as well as
state transitions are protected with the class's monitor. Generally this means acquiring the lock
before reading or writing the state of the group (e.g. generation, memberId) and holding the lock
when sending a request that affects the state of the group (e.g. JoinGroup, LeaveGroup).
clients/src/main/java/org/apache/kafka/clients/consumer/internals/AbstractCoordinator.java