
org.fulib.fx.controller.Router Maven / Gradle / Ivy
package org.fulib.fx.controller;
import dagger.Lazy;
import javafx.scene.Node;
import javafx.scene.Parent;
import javafx.util.Pair;
import org.fulib.fx.FulibFxApp;
import org.fulib.fx.annotation.Route;
import org.fulib.fx.annotation.controller.Component;
import org.fulib.fx.annotation.controller.Controller;
import org.fulib.fx.controller.exception.ControllerDuplicatedRouteException;
import org.fulib.fx.controller.exception.ControllerInvalidRouteException;
import org.fulib.fx.data.*;
import org.fulib.fx.util.ControllerUtil;
import org.fulib.fx.util.FrameworkUtil;
import org.fulib.fx.util.ReflectionUtil;
import org.fulib.fx.util.reflection.Reflection;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.inject.Inject;
import javax.inject.Provider;
import javax.inject.Singleton;
import java.lang.reflect.Field;
import java.util.Map;
import java.util.Objects;
import static org.fulib.fx.util.FrameworkUtil.error;
@Singleton
public class Router {
private final TraversableTree routes;
private final SizeableTraversableQueue, Object>, Map>> history;
@Inject
Lazy manager;
private Object routerObject;
@Inject
public Router() {
this.routes = new TraversableNodeTree<>();
this.history = new EvictingQueue<>(5);
}
/**
* Registers all routes in the given class.
*
* @param routes The class to register the routes from
*/
public void registerRoutes(@NotNull Object routes) {
if (this.routerObject != null)
throw new IllegalStateException(error(3000).formatted(this.routerObject.getClass().getName()));
this.routerObject = routes;
Reflection.getFieldsWithAnnotation(routes.getClass(), Route.class).forEach(this::registerRoute);
}
/**
* Registers a field as a route.
*
* The field has to be marked with {@link Route}.
*
* @param field The controller to register
*/
private void registerRoute(@NotNull Field field) {
if (!field.isAnnotationPresent(Route.class))
throw new RuntimeException(error(3001).formatted(field.getName()));
// Check if the field is of type Provider where T is annotated with @Controller
ControllerUtil.requireControllerProvider(field);
Route annotation = field.getAnnotation(Route.class);
String route = annotation.value().equals("$name") ? "/" + field.getName() : annotation.value();
// Make sure the route starts with a slash to prevent issues with the traversal
route = route.startsWith("/") ? route : "/" + route;
if (this.routes.containsPath(route))
throw new ControllerDuplicatedRouteException(route, field.getType(), this.routes.get(route).getType());
this.routes.insert(route, field);
}
/**
* Initializes and renders the controller/component with the given route.
* This only works for controllers and components having a parent as their root node.
*
* @param route The route of the controller
* @param parameters The parameters to pass to the controller
* @return A pair containing the controller instance and the rendered parent (will be the same if the controller is a component)
* @throws ControllerInvalidRouteException If the route couldn't be found
*/
public @NotNull Pair