/**
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.apache.avro.ipc;

import java.io.Closeable;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.List;
import java.util.concurrent.locks.ReentrantLock;

import org.apache.avro.Protocol;

/** Base transport class used by {@link Requestor}. */
public abstract class Transceiver implements Closeable {
  private final ReentrantLock channelLock = new ReentrantLock();

  public abstract String getRemoteName() throws IOException;

  /**
   * Acquires an exclusive lock on the transceiver's channel.
   */
  public void lockChannel() {
    channelLock.lock();
  }

  /**
   * Releases the lock on the transceiver's channel if held by the calling thread.
   */
  public void unlockChannel() {
    if (channelLock.isHeldByCurrentThread()) {
      channelLock.unlock();
    }
  }

  /** Called by {@link Requestor#request(String,Object)} for two-way messages.
   * By default calls {@link #writeBuffers(List)} followed by
   * {@link #readBuffers()}. */
  public List<ByteBuffer> transceive(List<ByteBuffer> request)
    throws IOException {
    lockChannel();
    try {
      writeBuffers(request);
      return readBuffers();
    } finally {
      unlockChannel();
    }
  }

  /**
   * Called by {@link Requestor#request(String,Object,Callback)} for two-way messages using callbacks.
   */
  public void transceive(List<ByteBuffer> request, Callback<List<ByteBuffer>> callback)
    throws IOException {
    // The default implementation works synchronously
    try {
      List<ByteBuffer> response = transceive(request);
      callback.handleResult(response);
    } catch (IOException e) {
      callback.handleError(e);
    }
  }

  /** Called by the default definition of {@link #transceive(List)}.*/
  public abstract List<ByteBuffer> readBuffers() throws IOException;

  /** Called by {@link Requestor#request(String,Object)} for one-way messages.*/
  public abstract void writeBuffers(List<ByteBuffer> buffers)
    throws IOException;

  /** True if a handshake has been completed for this connection.  Used to
   * determine whether a handshake need be completed prior to a one-way
   * message.  Requests and responses are always prefixed by handshakes, but
   * one-way messages.  If the first request sent over a connection is one-way,
   * then a handshake-only response is returned.  Subsequent one-way messages
   * over the connection will have no response data sent.  Returns false by
   * default. */
  public boolean isConnected() { return false; }

  /** Called with the remote protocol when a handshake has been completed.
   * After this has been called and while a connection is maintained, {@link
   * #isConnected()} should return true and #getRemote() should return this
   * protocol.  Does nothing by default. */
  public void setRemote(Protocol protocol) {}

  /** Returns the protocol passed to {@link #setRemote(Protocol)}.  Throws
   * IllegalStateException by default. */
  public Protocol getRemote() {
    throw new IllegalStateException("Not connected.");
  }

  public void close() throws IOException {}
}

