com.spotify.netty4.handler.codec.zmtp.ZMTPMessage.java Source code

Java tutorial

Introduction

Here is the source code for com.spotify.netty4.handler.codec.zmtp.ZMTPMessage.java

Source

/*
 * Copyright (c) 2012-2013 Spotify AB
 *
 * Licensed 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 com.spotify.netty4.handler.codec.zmtp;

import java.nio.CharBuffer;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufAllocator;
import io.netty.util.AbstractReferenceCounted;
import io.netty.util.internal.RecyclableArrayList;

import static com.spotify.netty4.handler.codec.zmtp.ZMTPUtils.checkNotNull;
import static io.netty.buffer.ByteBufUtil.encodeString;
import static io.netty.util.CharsetUtil.UTF_8;
import static java.util.Arrays.asList;

public class ZMTPMessage extends AbstractReferenceCounted implements Iterable<ByteBuf> {

    private final ByteBuf[] frames;

    private ZMTPMessage(final ByteBuf[] frames) {
        this.frames = checkNotNull(frames, "frames");
    }

    @Override
    public ZMTPMessage retain() {
        super.retain();
        return this;
    }

    @Override
    public ZMTPMessage retain(final int increment) {
        super.retain(increment);
        return this;
    }

    /**
     * Create a new message from a string frames, using UTF-8 encoding.
     */
    public static ZMTPMessage fromUTF8(final CharSequence... strings) {
        return from(ByteBufAllocator.DEFAULT, UTF_8, strings);
    }

    /**
     * Create a new message from a string frames, using UTF-8 encoding.
     */
    public static ZMTPMessage fromUTF8(final ByteBufAllocator alloc, final CharSequence... strings) {
        return from(alloc, UTF_8, strings);
    }

    /**
     * Create a new message from a list of string frames, using UTF-8 encoding.
     */
    public static ZMTPMessage fromUTF8(final Iterable<? extends CharSequence> strings) {
        return from(UTF_8, strings);
    }

    /**
     * Create a new message from a list of string frames, using UTF-8 encoding.
     */
    public static ZMTPMessage fromUTF8(final ByteBufAllocator alloc,
            final Iterable<? extends CharSequence> strings) {
        return from(alloc, UTF_8, strings);
    }

    /**
     * Create a new message from a list of string frames, using a specified encoding.
     */
    public static ZMTPMessage from(final Charset charset, final CharSequence... strings) {
        return from(charset, asList(strings));
    }

    /**
     * Create a new message from a list of string frames, using a specified encoding.
     */
    public static ZMTPMessage from(final ByteBufAllocator alloc, final Charset charset,
            final CharSequence... strings) {
        return from(alloc, charset, asList(strings));
    }

    /**
     * Create a new message from a list of string frames, using a specified encoding.
     */
    public static ZMTPMessage from(final Charset charset, final Iterable<? extends CharSequence> strings) {
        return from(ByteBufAllocator.DEFAULT, charset, strings);
    }

    /**
     * Create a new message from a list of string frames, using a specified encoding.
     */
    public static ZMTPMessage from(final ByteBufAllocator alloc, final Charset charset,
            final Iterable<? extends CharSequence> strings) {
        final List<ByteBuf> frames = new ArrayList<ByteBuf>();
        for (final CharSequence string : strings) {
            frames.add(encodeString(alloc, CharBuffer.wrap(string), charset));
        }
        return from(frames);
    }

    /**
     * Create a new message from a list of frames.
     */
    public static ZMTPMessage from(final Collection<ByteBuf> frames) {
        checkNotNull(frames, "frames");
        return new ZMTPMessage(frames.toArray(new ByteBuf[frames.size()]));
    }

    /**
     * Create a new message from a list of frames.
     */
    public static ZMTPMessage from(final ByteBuf[] frames) {
        return new ZMTPMessage(frames.clone());
    }

    public int size() {
        return frames.length;
    }

    @Override
    public Iterator<ByteBuf> iterator() {
        return new FrameIterator();
    }

    /**
     * Get a specific frame.
     */
    public ByteBuf frame(final int i) {
        return frames[i];
    }

    @Override
    protected void deallocate() {
        for (final ByteBuf frame : frames) {
            frame.release();
        }
    }

    @Override
    public boolean equals(final Object o) {
        if (this == o) {
            return true;
        }
        if (o == null || getClass() != o.getClass()) {
            return false;
        }

        final ZMTPMessage byteBufs = (ZMTPMessage) o;

        return Arrays.equals(frames, byteBufs.frames);

    }

    @Override
    public int hashCode() {
        return frames != null ? Arrays.hashCode(frames) : 0;
    }

    @Override
    public String toString() {
        return "ZMTPMessage{" + toString(frames) + '}';
    }

    /**
     * Create a human readable string representation of binary data, keeping printable ascii and hex
     * encoding everything else.
     *
     * @param data The data
     * @return A human readable string representation of the data.
     */
    private static String toString(final ByteBuf data) {
        if (data == null) {
            return null;
        }
        final StringBuilder sb = new StringBuilder();
        for (int i = data.readerIndex(); i < data.writerIndex(); i++) {
            final byte b = data.getByte(i);
            if (b > 31 && b < 127) {
                if (b == '%') {
                    sb.append('%');
                }
                sb.append((char) b);
            } else {
                sb.append('%');
                sb.append(String.format("%02X", b));
            }
        }
        return sb.toString();
    }

    /**
     * Create a human readable string representation of a list of ZMTP frames, keeping printable ascii
     * and hex encoding everything else.
     *
     * @param frames The ZMTP frames.
     * @return A human readable string representation of the frames.
     */
    private static String toString(final ByteBuf[] frames) {
        final StringBuilder builder = new StringBuilder("[");
        for (int i = 0; i < frames.length; i++) {
            final ByteBuf frame = frames[i];
            builder.append('"');
            builder.append(toString(frame));
            builder.append('"');
            if (i < frames.length - 1) {
                builder.append(',');
            }
        }
        builder.append(']');
        return builder.toString();
    }

    /**
     * Convenience method for reading a {@link ZMTPMessage} from a {@link ByteBuf}.
     */
    public static ZMTPMessage read(final ByteBuf in, final ZMTPVersion version) throws ZMTPParsingException {
        final int mark = in.readerIndex();
        final ZMTPWireFormat wireFormat = ZMTPWireFormats.wireFormat(version);
        final ZMTPWireFormat.Header header = wireFormat.header();
        final RecyclableArrayList frames = RecyclableArrayList.newInstance();
        while (true) {
            final boolean read = header.read(in);
            if (!read) {
                frames.recycle();
                in.readerIndex(mark);
                return null;
            }
            if (in.readableBytes() < header.length()) {
                frames.recycle();
                in.readerIndex(mark);
                return null;
            }
            if (header.length() > Integer.MAX_VALUE) {
                throw new ZMTPParsingException("frame is too large: " + header.length());
            }
            final ByteBuf frame = in.readSlice((int) header.length());
            frame.retain();
            frames.add(frame);
            if (!header.more()) {
                @SuppressWarnings("unchecked")
                final ZMTPMessage message = ZMTPMessage.from((List<ByteBuf>) (Object) frames);
                frames.recycle();
                return message;
            }
        }
    }

    /**
     * Convenience method for writing a {@link ZMTPMessage} to a {@link ByteBuf}.
     */
    public ByteBuf write(final ZMTPVersion version) {
        return write(ByteBufAllocator.DEFAULT, version);
    }

    /**
     * Convenience method for writing a {@link ZMTPMessage} to a {@link ByteBuf}.
     */
    public ByteBuf write(final ByteBufAllocator alloc, final ZMTPVersion version) {
        final ZMTPMessageEncoder encoder = new ZMTPMessageEncoder();
        final ZMTPEstimator estimator = ZMTPEstimator.create(version);
        encoder.estimate(this, estimator);
        final ByteBuf out = alloc.buffer(estimator.size());
        final ZMTPWriter writer = ZMTPWriter.create(version);
        writer.reset(out);
        encoder.encode(this, writer);
        return out;
    }

    /**
     * Convenience method for writing a {@link ZMTPMessage} to a {@link ByteBuf}.
     */
    public void write(final ByteBuf out, final ZMTPVersion version) {
        final ZMTPWriter writer = ZMTPWriter.create(version);
        final ZMTPMessageEncoder encoder = new ZMTPMessageEncoder();
        writer.reset(out);
        encoder.encode(this, writer);
    }

    /**
     * Create a new {@link ZMTPMessage} with a frame added at the front.
     */
    public ZMTPMessage push(final ByteBuf frame) {
        for (final ByteBuf f : frames) {
            f.retain();
        }
        final ByteBuf[] frames = new ByteBuf[this.frames.length + 1];
        frames[0] = frame;
        System.arraycopy(this.frames, 0, frames, 1, this.frames.length);
        return new ZMTPMessage(frames);
    }

    /**
     * Create a new {@link ZMTPMessage} with the front frame removed.
     */
    public ZMTPMessage pop() {
        if (this.frames.length == 0) {
            throw new IllegalStateException("empty message");
        }
        final ByteBuf[] frames = new ByteBuf[this.frames.length - 1];
        System.arraycopy(this.frames, 1, frames, 0, frames.length);
        for (final ByteBuf f : frames) {
            f.retain();
        }
        return new ZMTPMessage(frames);
    }

    /**
     * Iterates over the frames of the {@link ZMTPMessage}.
     */
    private class FrameIterator implements Iterator<ByteBuf> {

        int i;

        @Override
        public boolean hasNext() {
            return i < frames.length;
        }

        @Override
        public ByteBuf next() {
            return frames[i++];
        }

        @Override
        public void remove() {
            throw new UnsupportedOperationException("remove");
        }
    }
}