PacketExecutor.java

/*
 * BSD 2-Clause License
 * 
 * Copyright (c) 2022, [Aleksandra Serba, Marcin Czerniak, Bartosz Wawrzyniak, Adrian Antkowiak]
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 * 
 * 1. Redistributions of source code must retain the above copyright notice, this
 * list of conditions and the following disclaimer.
 * 
 * 2. Redistributions in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer in the documentation
 * and/or other materials provided with the distribution.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

package dev.vernite.vernite.ws;

import java.lang.reflect.ParameterizedType;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Logger;

import com.google.protobuf.Any;
import com.google.protobuf.InvalidProtocolBufferException;
import com.google.protobuf.Descriptors.Descriptor;
import com.google.protobuf.Message;

import dev.vernite.protobuf.VerniteProtobuf;

public class PacketExecutor {
    private static final Logger L = Logger.getLogger("PacketExecutor");

    private static final Map<String, IHandler<? extends Message>> HANDLERS;
    private static final Map<String, Class<? extends Message>> PACKET_CLASSES;

    public static void call(SocketSession session, Any payload) throws InvalidProtocolBufferException {
        String type = getTypeNameFromTypeUrl(payload.getTypeUrl());
        @SuppressWarnings("unchecked")
        IHandler<Message> handler = (IHandler<Message>) HANDLERS.get(type);
        if (handler == null) {
            L.warning(session + ": No handler for " + type);
            return;
        }
        Message m = payload.unpack(PACKET_CLASSES.get(type));
        handler.handle(session, m);
    }

    private static String getTypeNameFromTypeUrl(String typeUrl) {
        int pos = typeUrl.lastIndexOf('/');
        if (pos == -1) {
            return "";
        }
        return typeUrl.substring(pos + 1);
    }

    private static void fill(HashMap<String, Class<? extends Message>> packetClasses,
            HashMap<String, IHandler<? extends Message>> handlers, Descriptor descriptor) {
        if (handlers.containsKey(descriptor.getFullName())) {
            throw new RuntimeException("Duplicate handler: " + descriptor.getFullName());
        }
        String pkg = SocketHandler.class.getPackageName();
        String name = descriptor.getName();
        String clName = String.format("%s.packets.%sHandler", pkg, name);
        try {
            Class<?> cl = SocketHandler.class.getClassLoader().loadClass(clName);
            for (var i : cl.getGenericInterfaces()) {
                if (!(i instanceof ParameterizedType)) {
                    continue;
                }
                ParameterizedType pt = (ParameterizedType) i;
                if (pt.getRawType() != IHandler.class) {
                    continue;
                }
                @SuppressWarnings("unchecked")
                Class<? extends Message> clazz = (Class<? extends Message>) pt.getActualTypeArguments()[0];
                packetClasses.put(descriptor.getFullName(), clazz);
            }
            IHandler<?> handler = (IHandler<?>) cl.getDeclaredConstructor().newInstance();
            handlers.put(descriptor.getFullName(), handler);

        } catch (ClassNotFoundException e) {
            // no handler
        } catch (ReflectiveOperationException e) {
            throw new RuntimeException(e);
        }
        for (Descriptor i : descriptor.getNestedTypes()) {
            fill(packetClasses, handlers, i);
        }
    }

    static {
        HashMap<String, Class<? extends Message>> packetClasses = new HashMap<>();
        HashMap<String, IHandler<? extends Message>> map = new HashMap<>();
        for (Descriptor m : VerniteProtobuf.getDescriptor().getMessageTypes()) {
            fill(packetClasses, map, m);
        }
        HANDLERS = Collections.unmodifiableMap(map);
        PACKET_CLASSES = Collections.unmodifiableMap(packetClasses);
    }
}