com.ghrum.common.protocol.MessageLookupService.java Source code

Java tutorial

Introduction

Here is the source code for com.ghrum.common.protocol.MessageLookupService.java

Source

/**
 * This file is part of Argentum Online.
 *
 * Copyright (c) 2014 Argentum Online <https://github.com/orgs/Argentum-Online/members>
 *
 * 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.ghrum.common.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, ?>> handlerTable;
    private final Map<Class<? extends Message>, MessageCodec<?>> classTable;
    private final MessageCodec<?>[] opcodeTable;

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

    /**
     * Bind a {@link MessageCodec} 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 {@link MessageCodec} constructed and attached to the service
     * @throws InstantiationException
     * @throws IllegalAccessException
     * @throws InvocationTargetException
     */
    protected <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 (opcodeTable[codec.getOpcode()] != null) {
            throw new IllegalStateException("Trying to bind a codec in a position where is already taken");
        }
        register(codec);
        return codec;
    }

    /**
     * Bind a {@link MessageCodec} 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
     */
    protected <T extends Message, J extends MessageCodec<T>> void register(J codec) {
        opcodeTable[codec.getOpcode()] = codec;
        classTable.put(codec.getType(), codec);
    }

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

    /**
     * Gets all {@link MessageCodec} of the service
     *
     * @return a collection containing all codec
     */
    public Collection<MessageCodec<?>> getCodecs() {
        return Collections.unmodifiableCollection(classTable.values());
    }

    /**
     * Gets a {@link BiConsumer} from the service given the {@link 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<Connection, T> getHandler(Class<T> clazz) {
        return (BiConsumer<Connection, T>) handlerTable.get(clazz);
    }

    /**
     * Encodes a {@link 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 java.io.IOException
     */
    @SuppressWarnings("unchecked")
    protected <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 {@link MessageCodec} from the service given the {@link 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>) classTable.get(clazz);
    }

    /**
     * Decodes a {@link Message} given its byte representation
     *
     * @param id    the unique identifier of the message
     * @param input the stream that stores the message's bytes
     * @return the object instated
     * @throws IOException
     */
    @SuppressWarnings("unchecked")
    protected <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 {@link MessageCodec} from the service 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 > opcodeTable.length) {
            throw new IllegalArgumentException("Opcode " + opcode + " is out of bounds");
        }
        return opcodeTable[opcode];
    }
}