com.trinity.engine.protocol.MessageLookupService.java Source code

Java tutorial

Introduction

Here is the source code for com.trinity.engine.protocol.MessageLookupService.java

Source

/**
 * This file is part of Trinity.
 *
 * Copyright (c) 2014 Agustin L. Alvarez <<wolftein1@gmail.com>>
 *
 * 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.trinity.engine.protocol;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;

import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.function.BiConsumer;

/**
 * Define the service for {@link MessageCodec} and {@link Message}
 */
public final class MessageLookupService {
    private final Map<Class<? extends Message>, BiConsumer<Connection, ?>> mHandlers;
    private final Map<Class<? extends Message>, MessageCodec<?>> mClasses;
    private final MessageCodec<?>[] mOpcodes;

    /**
     * Default constructor for {@link MessageLookupService}
     *
     * @param size the max number of codec allowed to register
     */
    public MessageLookupService(int size) {
        this.mClasses = new HashMap<>(size);
        this.mHandlers = new HashMap<>(size);
        this.mOpcodes = new MessageCodec<?>[size];
    }

    /**
     * Bind a message codec to the service table
     *
     * @param clazz the class that represent a codec
     * @param <T>   the class type of the message
     * @param <J>   the class type of the codec
     * @return the codec constructed and attached to the service
     * @throws InstantiationException
     * @throws IllegalAccessException
     * @throws InvocationTargetException
     */
    public <T extends Message, J extends MessageCodec<T>> J bind(Class<J> clazz)
            throws InstantiationException, IllegalAccessException, InvocationTargetException {
        Constructor<J> constructor;
        try {
            constructor = clazz.getConstructor();
        } catch (NoSuchMethodException e) {
            throw (InstantiationException) new InstantiationException().initCause(e);
        }
        J codec = constructor.newInstance();

        if (mOpcodes[codec.getOpcode()] != null) {
            throw new IllegalStateException("Trying to bind a codec in a position where is already taken");
        }
        register(codec);
        return codec;
    }

    /**
     * Bind a message codec to the service table
     *
     * @param codec the instated codec
     * @param <T>   the class type of the message
     * @param <J>   the class type of the codec
     */
    public <T extends Message, J extends MessageCodec<T>> void register(J codec) {
        mOpcodes[codec.getOpcode()] = codec;
        mClasses.put(codec.getType(), codec);
    }

    /**
     * Bind a consumer to the service table
     *
     * @param clazz   the class type of the message
     * @param handler the handler for the given message
     */
    public <T extends Message> void register(Class<T> clazz, BiConsumer<Connection, T> handler) {
        mHandlers.put(clazz, handler);
    }

    /**
     * Retrieves all message codecs registered
     *
     * @return a collection containing all codec
     */
    public Collection<MessageCodec<?>> getCodecs() {
        return Collections.unmodifiableCollection(mClasses.values());
    }

    /**
     * Gets a consumer given the message's class
     *
     * @param clazz the message class
     * @param <T>   the class type of the message
     * @return the handler if the class is valid, null otherwise
     */
    @SuppressWarnings("unchecked")
    public <T extends Message> BiConsumer<?, T> getHandler(Class<T> clazz) {
        return (BiConsumer<?, T>) mHandlers.get(clazz);
    }

    /**
     * Encodes a message into a stream
     *
     * @param message the message to encode to the buffer
     * @return a wrapped buffer that contains the header and the body of the message
     * @throws IOException
     */
    @SuppressWarnings("unchecked")
    public <T extends Message> ByteBuf encode(T message) throws IOException {
        final MessageCodec<Message> codec = (MessageCodec<Message>) getCodec(message.getClass());
        if (codec == null) {
            throw new IOException("Unknown operation class: " + message.getClass());
        }
        final ByteBuf body = codec.encode(message);
        final ByteBuf header = Unpooled.buffer(3).writeByte(codec.getOpcode()).writeShort(body.capacity());
        return Unpooled.wrappedBuffer(header, body);
    }

    /**
     * Gets a  message codec given the message's class
     *
     * @param clazz the message class
     * @param <T>   the class type of the message
     * @return the codec if the class is valid, null otherwise
     */
    @SuppressWarnings("unchecked")
    public <T extends Message> MessageCodec<T> getCodec(Class<T> clazz) {
        return (MessageCodec<T>) mClasses.get(clazz);
    }

    /**
     * Decodes a message from a buffer
     *
     * @param id    the unique identifier of the message
     * @param input the stream that contains the message's bytes
     * @return a new reference to the message decoded
     * @throws IOException
     */
    @SuppressWarnings("unchecked")
    public <T extends Message> T decode(int id, ByteBuf input) throws IOException {
        MessageCodec<? extends Message> codec = getCodec(id);
        if (codec == null) {
            throw new IOException("Unknown operation code: " + id);
        }
        return (T) codec.decode(input);
    }

    /**
     * Gets a message codec given its opcode
     *
     * @param opcode the unique opcode of the message
     * @return the codec if the opcode is valid, null otherwise
     */
    public MessageCodec<?> getCodec(int opcode) {
        if (opcode < 0 || opcode > mOpcodes.length) {
            throw new IllegalArgumentException("Opcode " + opcode + " is out of bounds");
        }
        return mOpcodes[opcode];
    }
}