/*
* Copyright 2012 The Netty Project
*
* The Netty Project 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.jboss.netty.handler.codec.embedder;
import org.jboss.netty.buffer.ChannelBufferFactory;
import org.jboss.netty.channel.Channel;
import org.jboss.netty.channel.ChannelEvent;
import org.jboss.netty.channel.ChannelFuture;
import org.jboss.netty.channel.ChannelHandler;
import org.jboss.netty.channel.ChannelHandlerContext;
import org.jboss.netty.channel.ChannelPipeline;
import org.jboss.netty.channel.ChannelPipelineException;
import org.jboss.netty.channel.ChannelSink;
import org.jboss.netty.channel.ChannelUpstreamHandler;
import org.jboss.netty.channel.DefaultChannelPipeline;
import org.jboss.netty.channel.ExceptionEvent;
import org.jboss.netty.channel.MessageEvent;
import java.lang.reflect.Array;
import java.util.ConcurrentModificationException;
import java.util.LinkedList;
import java.util.Queue;
import static org.jboss.netty.channel.Channels.*;
/**
* A skeletal {@link CodecEmbedder} implementation.
*/
abstract class AbstractCodecEmbedder<E> implements CodecEmbedder<E> {
private final Channel channel;
private final ChannelPipeline pipeline;
private final EmbeddedChannelSink sink = new EmbeddedChannelSink();
final Queue<Object> productQueue = new LinkedList<Object>();
/**
* Creates a new embedder whose pipeline is composed of the specified
* handlers.
*/
protected AbstractCodecEmbedder(ChannelHandler... handlers) {
pipeline = new EmbeddedChannelPipeline();
configurePipeline(handlers);
channel = new EmbeddedChannel(pipeline, sink);
fireInitialEvents();
}
/**
* Creates a new embedder whose pipeline is composed of the specified
* handlers.
*
* @param bufferFactory the {@link ChannelBufferFactory} to be used when
* creating a new buffer.
*/
protected AbstractCodecEmbedder(ChannelBufferFactory bufferFactory, ChannelHandler... handlers) {
this(handlers);
getChannel().getConfig().setBufferFactory(bufferFactory);
}
private void fireInitialEvents() {
// Fire the typical initial events.
fireChannelOpen(channel);
fireChannelBound(channel, channel.getLocalAddress());
fireChannelConnected(channel, channel.getRemoteAddress());
}
private void configurePipeline(ChannelHandler... handlers) {
if (handlers == null) {
throw new NullPointerException("handlers");
}
if (handlers.length == 0) {
throw new IllegalArgumentException(
"handlers should contain at least one " +
ChannelHandler.class.getSimpleName() + '.');
}
for (int i = 0; i < handlers.length; i ++) {
ChannelHandler h = handlers[i];
if (h == null) {
throw new NullPointerException("handlers[" + i + ']');
}
pipeline.addLast(String.valueOf(i), handlers[i]);
}
pipeline.addLast("SINK", sink);
}
public boolean finish() {
close(channel);
fireChannelDisconnected(channel);
fireChannelUnbound(channel);
fireChannelClosed(channel);
return !productQueue.isEmpty();
}
/**
* Returns the virtual {@link Channel} which will be used as a mock
* during encoding and decoding.
*/
protected final Channel getChannel() {
return channel;
}
/**
* Returns {@code true} if and only if the produce queue is empty and
* therefore {@link #poll()} will return {@code null}.
*/
protected final boolean isEmpty() {
return productQueue.isEmpty();
}
@SuppressWarnings("unchecked")
public final E poll() {
return (E) productQueue.poll();
}
@SuppressWarnings("unchecked")
public final E peek() {
return (E) productQueue.peek();
}
public final Object[] pollAll() {
final int size = size();
Object[] a = new Object[size];
for (int i = 0; i < size; i ++) {
E product = poll();
if (product == null) {
throw new ConcurrentModificationException();
}
a[i] = product;
}
return a;
}
@SuppressWarnings("unchecked")
public final <T> T[] pollAll(T[] a) {
if (a == null) {
throw new NullPointerException("a");
}
final int size = size();
// Create a new array if the specified one is too small.
if (a.length < size) {
a = (T[]) Array.newInstance(a.getClass().getComponentType(), size);
}
for (int i = 0;; i ++) {
T product = (T) poll();
if (product == null) {
break;
}
a[i] = product;
}
// Put the terminator if necessary.
if (a.length > size) {
a[size] = null;
}
return a;
}
public final int size() {
return productQueue.size();
}
public ChannelPipeline getPipeline() {
return pipeline;
}
private final class EmbeddedChannelSink implements ChannelSink, ChannelUpstreamHandler {
EmbeddedChannelSink() {
}
public void handleUpstream(ChannelHandlerContext ctx, ChannelEvent e) {
handleEvent(e);
}
public void eventSunk(ChannelPipeline pipeline, ChannelEvent e) {
handleEvent(e);
}
private void handleEvent(ChannelEvent e) {
if (e instanceof MessageEvent) {
boolean offered = productQueue.offer(((MessageEvent) e).getMessage());
assert offered;
} else if (e instanceof ExceptionEvent) {
throw new CodecEmbedderException(((ExceptionEvent) e).getCause());
}
// Swallow otherwise.
}
public void exceptionCaught(
ChannelPipeline pipeline, ChannelEvent e,
ChannelPipelineException cause) throws Exception {
Throwable actualCause = cause.getCause();
if (actualCause == null) {
actualCause = cause;
}
throw new CodecEmbedderException(actualCause);
}
public ChannelFuture execute(ChannelPipeline pipeline, Runnable task) {
try {
task.run();
return succeededFuture(pipeline.getChannel());
} catch (Throwable t) {
return failedFuture(pipeline.getChannel(), t);
}
}
}
private static final class EmbeddedChannelPipeline extends DefaultChannelPipeline {
EmbeddedChannelPipeline() {
}
@Override
protected void notifyHandlerException(ChannelEvent e, Throwable t) {
while (t instanceof ChannelPipelineException && t.getCause() != null) {
t = t.getCause();
}
if (t instanceof CodecEmbedderException) {
throw (CodecEmbedderException) t;
} else {
throw new CodecEmbedderException(t);
}
}
}
}