/* * This file is part of LanternServer, licensed under the MIT License (MIT). * * Copyright (c) LanternPowered <https://www.lanternpowered.org> * Copyright (c) SpongePowered <https://www.spongepowered.org> * Copyright (c) contributors * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the Software), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ package org.lanternpowered.server.network.message; import static com.google.common.base.Preconditions.checkNotNull; import it.unimi.dsi.fastutil.ints.Int2ObjectMap; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; import org.lanternpowered.server.network.message.codec.Codec; import org.lanternpowered.server.network.message.handler.Handler; import org.lanternpowered.server.network.message.processor.Processor; import java.lang.reflect.Constructor; import java.util.HashMap; import java.util.Map; import java.util.Optional; public final class MessageRegistry { private final Map<Class<? extends Message>, MessageRegistration<?>> registrationByMessageType = new HashMap<>(); private final Int2ObjectMap<CodecRegistration<?, ?>> registrationByOpcode = new Int2ObjectOpenHashMap<>(); <M extends Message> MessageRegistration<M> checkCodecBinding(Class<M> messageType) { final MessageRegistration messageRegistration = this.registrationByMessageType.computeIfAbsent(messageType, messageType0 -> new MessageRegistration<>(messageType)); if (messageRegistration.codecRegistration.isPresent()) { throw new IllegalArgumentException("The message type " + messageType.getName() + " is already bound to " + ((CodecRegistration) messageRegistration.codecRegistration.get()).getCodec().getClass().getName()); } return messageRegistration; } /** * Registers a new {@link Codec} for the specified opcode. * * @param opcode the opcode * @param codec the codec type * @param <M> the type of the processed message * @param <C> the type of the codec * @return the codec registration */ public <M extends Message, C extends Codec<M>> CodecRegistration<M, C> bind(int opcode, Class<C> codec) { try { Constructor<C> constructor = codec.getDeclaredConstructor(); constructor.setAccessible(true); return this.bind(opcode, constructor.newInstance()); } catch (Exception e) { throw new IllegalArgumentException("Unable to instantiate the codec class.", e); } } /** * Registers a new {@link Codec} for the specified opcode. * * @param opcode the opcode * @param codec the codec * @param messageType the message type * @param <M> the type of the processed message * @param <C> the type of the codec * @return the codec registration */ public <N extends Message, M extends N, C extends Codec<N>> MessageRegistration<M> bind(int opcode, Class<C> codec, Class<M> messageType) { MessageRegistration<M> registration = this.checkCodecBinding(messageType); CodecRegistration<N, C> codecRegistration = this.bind(opcode, codec); codecRegistration.bind(messageType, registration); return registration; } /** * Registers a new {@link Codec} for the specified opcode. * * @param opcode the opcode * @param codec the codec * @param <M> the type of the processed message * @param <C> the type of the codec * @return the codec registration */ public <M extends Message, C extends Codec<M>> CodecRegistration<M, C> bind(int opcode, C codec) { checkNotNull(codec, "codec"); if (this.registrationByOpcode.containsKey(opcode)) { throw new IllegalArgumentException("Opcode " + opcode + " is already in use by " + this.registrationByOpcode.get(opcode).getCodec().getClass().getName()); } final CodecRegistration<M, C> registration = new CodecRegistration<>(this, opcode, codec); this.registrationByOpcode.put(opcode, registration); return registration; } /** * Registers a new {@link Codec} for the specified opcode. * * @param opcode the opcode * @param codec the codec * @param messageType the message type * @param <M> the type of the processed message * @param <C> the type of the codec * @return the codec registration */ public <N extends Message, M extends N, C extends Codec<N>> MessageRegistration<M> bind(int opcode, C codec, Class<M> messageType) { MessageRegistration<M> registration = this.checkCodecBinding(messageType); CodecRegistration<N, C> codecRegistration = this.bind(opcode, codec); codecRegistration.bind(messageType, registration); return registration; } /** * Binds a {@link Message} type to this registry and * attaches the {@link Handler} to it. * * @param messageType the message type * @param handler the handler * @param <M> the type of the message * @param <H> the type of the handler * @return the registration */ public <M extends Message, H extends Handler<? super M>> MessageRegistration<M> bindHandler(Class<M> messageType, H handler) { MessageRegistration<M> registration = this.bind(messageType); registration.bindHandler(handler); return registration; } /** * Binds a {@link Message} type to this registry and * attaches the {@link Processor} to it. * * @param messageType the message type * @param processor the processor * @param <M> the type of the message * @param <P> the type of the processor * @return the registration */ public <M extends Message, P extends Processor<? super M>> MessageRegistration<M> bindProcessor(Class<M> messageType, P processor) { MessageRegistration<M> registration = this.bind(messageType); registration.bindProcessor(processor); return registration; } /** * Searches a {@link CodecRegistration} for the specified {@link Codec}. * * @param codec the codec * @param <M> the type of the processed message * @param <C> the type of the codec * @return the codec registration */ public <M extends Message, C extends Codec<M>> Optional<CodecRegistration<M, C>> find(C codec) { for (CodecRegistration<?, ?> registration : this.registrationByOpcode.values()) { if (codec.equals(registration.getCodec())) { return Optional.of((CodecRegistration) registration); } } return Optional.empty(); } /** * Searches a {@link CodecRegistration} for the specified {@link Codec} type. * * @param codec the codec type * @param <M> the type of the processed message * @param <C> the type of the codec * @return the codec registration */ public <M extends Message, C extends Codec<M>> Optional<CodecRegistration<M, C>> find(Class<C> codec) { for (CodecRegistration<?, ?> registration : this.registrationByOpcode.values()) { if (codec.isInstance(registration.getCodec())) { return Optional.of((CodecRegistration) registration); } } return Optional.empty(); } /** * Searches a {@link CodecRegistration} for the specified opcode. * * @param opcode the opcode * @param <M> the type of the processed message * @param <C> the type of the codec * @return the codec registration */ public <M extends Message, C extends Codec<M>> Optional<CodecRegistration<M, C>> find(int opcode) { return Optional.ofNullable((CodecRegistration) this.registrationByOpcode.get(opcode)); } /** * Searches a {@link MessageRegistration} for the specified message type. * * @param messageType the message type * @param <M> the type of the message * @return the message registration */ public <M extends Message> Optional<MessageRegistration<M>> findByMessageType(Class<M> messageType) { return Optional.ofNullable((MessageRegistration) this.registrationByMessageType.get(messageType)); } /** * Binds a {@link Message} type to this registry. * * @param messageType the message type * @param <M> the type of the message * @return the registration */ public <M extends Message> MessageRegistration<M> bind(Class<M> messageType) { return (MessageRegistration) this.registrationByMessageType.computeIfAbsent(messageType, messageType0 -> new MessageRegistration<>(messageType)); } }