code: public void put(E e) throws InterruptedException {
          if (e == null) throw new NullPointerException();
          // Note: convention in all put/take/etc is to preset local var
          // holding count negative to indicate failure unless set.
          int c = -1;
          Node<E> node = new Node<E>(e);
          final ReentrantLock putLock = this.putLock;
          final AtomicInteger count = this.count;
          putLock.lockInterruptibly();
          try {
              /*
               * Note that count is used in wait guard even though it is
               * not protected by lock. This works because count can
               * only decrease at this point (all other puts are shut
               * out by lock), and we (or some other waiting put) are
               * signalled if it ever changes from capacity. Similarly
               * for all other uses of count in other wait guards.
               */
              while (count.get() == capacity) {
                  notFull.await();
              }
              enqueue(node);
              c = count.getAndIncrement();
              if (c + 1 < capacity)
                  notFull.signal();
          } finally {
              putLock.unlock();
          }
          if (c == 0)
              signalNotEmpty();
      }
docs: Inserts the specified element at the tail of this queue, waiting if
  necessary for space to become available.
  
  @throws InterruptedException {@inheritDoc}
  @throws NullPointerException {@inheritDoc}