pyrih / mini-git

MIT License
0 stars 0 forks source link

refactor: Pick command implementation dinamically #3

Open pyrih opened 2 months ago

pyrih commented 2 months ago
package io.github.pyrih.minigit.dispatcher;

import io.github.pyrih.minigit.component.Repository;
import io.github.pyrih.minigit.dispatcher.command.Command;
import io.github.pyrih.minigit.dispatcher.command.annotation.CommandDefinition;
import io.github.pyrih.minigit.dispatcher.command.impl.HelpCommand;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;
import java.util.Map;

public class CommandDispatcher {
    private static final Map<String, Command> COMMANDS = new HashMap<>();

    public CommandDispatcher(Repository repository, String packageName) {
        lookUpCommands(repository, packageName);
    }

    public void dispatch(String commandDefinition, String... arguments) {
        Command command = COMMANDS.getOrDefault(commandDefinition, new HelpCommand());

        if (command != null) {
            command.execute(arguments);
        } else {
            throw new IllegalArgumentException(STR."Unkown command: \{commandDefinition}");
        }
    }

    private void lookUpCommands(Repository repository, String packageName) {
        try {
            Class<?>[] classes = Class.forName(packageName).getDeclaredClasses();

            for (Class<?> cls : classes) {
                if (isCommandClass(cls)) {
                    @SuppressWarnings("unchecked")
                    Class<? extends Command> commandCls = (Class<? extends Command>) cls;
                    register(commandCls, repository);
                }
            }
        } catch (ClassNotFoundException e) {
            throw new RuntimeException(STR."Package not found: \{packageName}", e);
        }
    }

    private boolean isCommandClass(Class<?> cls) {
        return Command.class.isAssignableFrom(cls) && cls.isAnnotationPresent(CommandDefinition.class);
    }

    private void register(Class<? extends Command> cls, Repository repository) {
        if (cls.isAnnotationPresent(CommandDefinition.class)) {
            CommandDefinition definition = cls.getAnnotation(CommandDefinition.class);

            try {
                Constructor<? extends Command> constructor = cls.getConstructor(Repository.class);
                Command command = constructor.newInstance(repository);
                COMMANDS.put(definition.name(), command);
            } catch (NoSuchMethodException | InvocationTargetException | InstantiationException |
                     IllegalAccessException e) {
                throw new RuntimeException(STR."Failed to register command: \{cls.getName()}", e);
            }
        }
    }
}

and main:

package io.github.pyrih.minigit;

import io.github.pyrih.minigit.component.Repository;
import io.github.pyrih.minigit.dispatcher.CommandDispatcher;
import io.github.pyrih.minigit.util.ArgumentUtils;

public class Main {

    public static final String COMMAND_IMPL_PACKAGE_MANE = "io.github.pyrih.minigit.dispatcher.command.impl";

    public static void main(String[] args) {
        ArgumentUtils.validateArguments(args);

        Repository repository = new Repository();
        CommandDispatcher dispatcher = new CommandDispatcher(repository, COMMAND_IMPL_PACKAGE_MANE);

        String commandDefinition = args[0].toLowerCase();

        if (args.length == 1) {
            dispatcher.dispatch(commandDefinition);
        } else {
            String[] arguments = ArgumentUtils.extractRemainingArguments(args);
            dispatcher.dispatch(commandDefinition, arguments);
        }
    }
}
pyrih commented 2 months ago
public class AccessingAllClassesInPackage {

    public Set<Class> findAllClassesUsingClassLoader(String packageName) {
        InputStream stream = ClassLoader.getSystemClassLoader()
          .getResourceAsStream(packageName.replaceAll("[.]", "/"));
        BufferedReader reader = new BufferedReader(new InputStreamReader(stream));
        return reader.lines()
          .filter(line -> line.endsWith(".class"))
          .map(line -> getClass(line, packageName))
          .collect(Collectors.toSet());
    }

    private Class getClass(String className, String packageName) {
        try {
            return Class.forName(packageName + "."
              + className.substring(0, className.lastIndexOf('.')));
        } catch (ClassNotFoundException e) {
            // handle the exception
        }
        return null;
    }
}