Java tutorial
/** * 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]; } }