Skip to content

Architecture

Matt Akbarian edited this page Oct 4, 2025 · 1 revision

Architecture Overview

This document provides a comprehensive overview of the Java Leaflet architecture, explaining the multi-module design, layer system, and key architectural patterns.

Table of Contents


Multi-Module Architecture

Java Leaflet v2.0.0 introduces a clean multi-module Maven architecture:

java_leaflet/
├── jlmap-parent/          # Parent POM
├── jlmap-api/             # Core API
├── jlmap-fx/              # JavaFX implementation
├── jlmap-vaadin/          # Vaadin implementation
└── jlmap-vaadin-demo/     # Demo application

Module Dependencies

jlmap-fx ──────┐
               ├──> jlmap-api
jlmap-vaadin ──┘

jlmap-vaadin-demo ──> jlmap-vaadin ──> jlmap-api

jlmap-api (Core Module)

Purpose: Provides platform-independent abstractions and models.

Exports:

  • io.github.makbn.jlmap - Main package
  • io.github.makbn.jlmap.layer - Layer management
  • io.github.makbn.jlmap.layer.leaflet - Leaflet layer interfaces
  • io.github.makbn.jlmap.listener - Event listeners
  • io.github.makbn.jlmap.model - Data models
  • io.github.makbn.jlmap.exception - Custom exceptions
  • io.github.makbn.jlmap.geojson - GeoJSON support
  • io.github.makbn.jlmap.engine - Web engine abstractions
  • io.github.makbn.jlmap.element.menu - Context menu system
  • io.github.makbn.jlmap.journey - Journey system

Key Dependencies:

  • Gson & Jackson for JSON processing
  • SLF4J for logging
  • JetBrains annotations
  • SnakeYAML for YAML support

jlmap-fx (JavaFX Implementation)

Purpose: JavaFX-specific implementation using WebView.

Key Classes:

  • io.github.makbn.jlmap.fx.JLMapView - Main JavaFX component
  • io.github.makbn.jlmap.fx.JLEngine - JavaFX WebView wrapper
  • io.github.makbn.jlmap.fx.element.menu.JavaFXContextMenuMediator - Context menu support

Dependencies:

  • jlmap-api
  • JavaFX (controls, web, graphics, base)
  • JDK jsobject module

jlmap-vaadin (Vaadin Implementation)

Purpose: Vaadin component for web applications.

Key Classes:

  • io.github.makbn.jlmap.vaadin.JLMapView - Main Vaadin component
  • io.github.makbn.jlmap.vaadin.JLEngine - Vaadin web component wrapper
  • io.github.makbn.jlmap.vaadin.element.menu.VaadinContextMenuMediator - Context menu support
  • io.github.makbn.jlmap.vaadin.journey.VaadinJourneyPlayer - Journey player

Dependencies:

  • jlmap-api
  • Vaadin Core (24.8.6+)
  • Vaadin Spring Boot Starter (optional)

Layer System

Java Leaflet organizes map functionality into four specialized layers, each with distinct responsibilities:

1. UI Layer (LeafletUILayerInt)

Responsibilities: User interface elements that appear on top of the map.

public interface LeafletUILayerInt {
    // Markers
    JLMarker addMarker(JLLatLng latLng, String text, boolean draggable);
    void removeMarker(String id);

    // Popups
    JLPopup addPopup(JLLatLng latLng, String text);
    JLPopup addPopup(JLLatLng latLng, String text, JLOptions options);
    void removePopup(String id);

    // Image overlays
    JLImageOverlay addImage(JLBounds bounds, String url, JLOptions options);
    void removeImage(String id);
}

Use Cases:

  • Adding markers at points of interest
  • Displaying informational popups
  • Overlaying images on specific geographic areas

2. Vector Layer (LeafletVectorLayerInt)

Responsibilities: Geometric shapes and vector graphics.

public interface LeafletVectorLayerInt {
    // Polylines
    JLPolyline addPolyline(JLLatLng[] vertices, JLOptions options);
    JLMultiPolyline addMultiPolyline(JLLatLng[][] vertices, JLOptions options);

    // Polygons
    JLPolygon addPolygon(JLLatLng[][][] vertices, JLOptions options);

    // Circles
    JLCircle addCircle(JLLatLng center, int radius, JLOptions options);
    JLCircleMarker addCircleMarker(JLLatLng center, int radius, JLOptions options);

    // Removal
    void removePolyline(String id);
    void removePolygon(String id);
    void removeCircle(String id);
}

Key Differences:

  • JLCircle: Radius in meters (geographic), scales with zoom
  • JLCircleMarker: Radius in pixels, maintains size regardless of zoom

3. Control Layer (LeafletControlLayerInt)

Responsibilities: Programmatic control of map navigation and viewport.

public interface LeafletControlLayerInt {
    // Zoom operations
    void setZoom(int zoom);
    void zoomIn(int delta);
    void zoomOut(int delta);
    void setZoomAround(JLLatLng latLng, int zoom);

    // Pan operations
    void panTo(JLLatLng latLng);
    void flyTo(JLLatLng latLng, int zoom);

    // Bounds operations
    void fitBounds(JLBounds bounds);
    void flyToBounds(JLBounds bounds);
    void setMaxBounds(JLBounds bounds);

    // Constraints
    void setMinZoom(int zoom);
    void setMaxZoom(int zoom);
}

Use Cases:

  • Smooth camera transitions
  • Constraining viewable area
  • Programmatic navigation

4. GeoJSON Layer (LeafletGeoJsonLayerInt)

Responsibilities: Loading and styling GeoJSON geographic data.

public interface LeafletGeoJsonLayerInt {
    JLGeoJson addFromFile(File file, JLGeoJsonOptions options);
    JLGeoJson addFromUrl(String url, JLGeoJsonOptions options);
    JLGeoJson addFromContent(String content, JLGeoJsonOptions options);
    void removeGeoJson(String id);
}

Advanced Features:

  • Style Functions: Dynamic styling based on feature properties
  • Filter Functions: Selective feature rendering
  • Point-to-Layer: Custom marker creation for point features

Transport Layer

The transport layer bridges Java code with JavaScript execution in the web engine.

JLServerToClientTransporter

Architecture:

┌─────────────────┐    ┌──────────────────┐    ┌─────────────────┐
│  Java Code      │───>│ Transport Layer  │───>│ JavaScript Code │
│  (JLObject)     │    │ (JLTransporter)  │    │ (Leaflet)       │
└─────────────────┘    └──────────────────┘    └─────────────────┘
                              │
                              │ Convert Result
                              ▼
                       ┌──────────────────┐
                       │ Java Result      │
                       │ (CompletableFuture)│
                       └──────────────────┘

Interface:

public interface JLServerToClientTransporter<T> {
    BiFunction<String, Boolean, T> serverToClientTransport();
    <M> M convertResult(T value, Class<M> target);

    default <M> M execute(JLTransportRequest request) {
        String jsCode = generateJavaScript(request);
        T rawResult = serverToClientTransport().apply(jsCode, request.returnable());
        return convertResult(rawResult, request.resultClass());
    }
}

JLTransportRequest

Purpose: Encapsulates method calls from Java to JavaScript.

public record JLTransportRequest(
    JLObject<?> self,
    String function,
    Class<?> clazz,
    Object... params
) {
    public static JLTransportRequest voidCall(JLObject<?> self, String function, Object... params) {
        return new JLTransportRequest(self, function, Void.class, params);
    }

    public static <M> JLTransportRequest returnableCall(JLObject<?> self, String function,
                                                        Class<M> clazz, Object... params) {
        return new JLTransportRequest(self, function, clazz, params);
    }
}

Example Flow:

// Java: Set circle radius
circle.setRadius(1000);

// Generates Transport Request:
JLTransportRequest.voidCall(circle, "setRadius", 1000);

// Generates JavaScript:
"window.jlmap_objects['circle-uuid-123'].setRadius(1000);"

// Executed in WebView
transport.execute(request);

Async Operations

Operations that return values use CompletableFuture:

// Get circle bounds asynchronously
CompletableFuture<JLBounds> boundsF future = circle.getBounds();

boundsFuture.thenAccept(bounds -> {
    System.out.println("Bounds: " + bounds);
});

Event System Architecture

Event Flow

┌─────────────────┐
│ User Action     │ (click, drag, etc.)
└────────┬────────┘
         │
         ▼
┌─────────────────┐
│ JavaScript      │ (Leaflet event)
│ Event Handler   │
└────────┬────────┘
         │
         ▼
┌─────────────────┐
│ Bridge to Java  │ (JSObject callback)
└────────┬────────┘
         │
         ▼
┌─────────────────┐
│ Event Parser    │ (JLInteractionEventHandler)
└────────┬────────┘
         │
         ▼
┌─────────────────┐
│ Typed Event     │ (ClickEvent, DragEvent, etc.)
└────────┬────────┘
         │
         ▼
┌─────────────────┐
│ Listener        │ (OnJLActionListener)
└─────────────────┘

Event Types Hierarchy

interface Event {
    JLAction action();
}

// Concrete event types
record ClickEvent(JLAction action, JLLatLng center) implements Event
record DragEvent(JLAction action, JLLatLng center, JLBounds bounds, int zoom) implements Event
record MoveEvent(JLAction action, JLLatLng center, JLBounds bounds, int zoom) implements Event
record ZoomEvent(JLAction action, int zoom) implements Event
record ContextMenuEvent(JLAction action, double x, double y, boolean open, boolean close) implements Event

JLAction Enum

Maps Java actions to Leaflet JavaScript events:

public enum JLAction {
    CLICK("click"),
    DOUBLE_CLICK("dblclick"),
    DRAG("drag"),
    DRAG_START("dragstart"),
    DRAG_END("dragend"),
    ZOOM("zoom"),
    MOVE("move"),
    CONTEXT_MENU("contextmenu"),
    // ... more events
}

Listener Pattern

marker.setOnActionListener((source, event) -> {
    switch (event.action()) {
        case CLICK -> {
            ClickEvent clickEvent = (ClickEvent) event;
            System.out.println("Clicked at: " + clickEvent.center());
        }
        case DRAG_END -> {
            DragEvent dragEvent = (DragEvent) event;
            System.out.println("Dragged to: " + dragEvent.center());
        }
    }
});

Builder Pattern

Java Leaflet uses a sophisticated builder pattern that serves dual purposes: building Java objects and generating JavaScript code.

JLObjectBuilder<M, T>

Type Parameters:

  • M: Model type (e.g., JLMarker)
  • T: Builder type (for self-typing)

Dual Build Methods:

public abstract class JLObjectBuilder<M extends JLObject<M>, T extends JLObjectBuilder<M, T>> {
    // Builds JavaScript code
    protected abstract String buildJsElement();

    // Builds Java object
    protected abstract M buildJLObject();

    // Public build method
    public M build() {
        String jsCode = buildJsElement();
        M object = buildJLObject();
        object.setJsCode(jsCode);
        return object;
    }
}

Example: JLMarkerBuilder:

JLMarker marker = JLMarkerBuilder.create()
    .latLng(new JLLatLng(51.5, -0.09))
    .withOptions(JLOptions.builder()
        .draggable(true)
        .build())
    .build();

// Generates JavaScript:
// "L.marker([51.5, -0.09], {draggable: true})"

// And creates Java object:
// JLMarker instance with same properties

Fluent API with Self-Typing

public class JLMarkerBuilder extends JLObjectBuilder<JLMarker, JLMarkerBuilder> {
    private JLLatLng latLng;

    public JLMarkerBuilder latLng(JLLatLng latLng) {
        this.latLng = latLng;
        return this; // Returns concrete type, not base
    }

    @Override
    protected JLMarkerBuilder self() {
        return this;
    }
}

This allows method chaining without losing type information.


Context Menu Architecture

The context menu system uses the Mediator Pattern to abstract platform-specific implementations.

Architecture Diagram

┌──────────────────┐
│ JLHasContextMenu │ (interface)
└────────┬─────────┘
         │
         ├──> JLMarker
         ├──> JLPolygon
         ├──> JLPolyline
         └──> JLMap
              │
              ▼
┌──────────────────┐
│  JLContextMenu   │ (manages menu items)
└────────┬─────────┘
         │
         ▼
┌───────────────────────┐
│ JLContextMenuMediator │ (interface)
└────────┬──────────────┘
         │
         ├──> JavaFXContextMenuMediator
         └──> VaadinContextMenuMediator

Service Locator Pattern

Mediators are discovered at runtime using ServiceLoader:

META-INF/services/io.github.makbn.jlmap.element.menu.JLContextMenuMediator
├── io.github.makbn.jlmap.fx.element.menu.JavaFXContextMenuMediator
└── io.github.makbn.jlmap.vaadin.element.menu.VaadinContextMenuMediator

Discovery Algorithm:

  1. Load all available mediators via ServiceLoader
  2. Filter by platform availability (isAvailable())
  3. Filter by object type support (supportsObjectType())
  4. Sort by priority (getPriority())
  5. Cache the selected mediator

Design Patterns

1. Interface Segregation

Separate interfaces for different responsibilities:

  • LeafletUILayerInt - UI elements
  • LeafletVectorLayerInt - Vector graphics
  • LeafletControlLayerInt - Map control
  • LeafletGeoJsonLayerInt - GeoJSON data

2. Strategy Pattern

Pluggable transport implementations:

  • JLServerToClientTransporter<Object> for JavaFX
  • JLServerToClientTransporter<JsonValue> for Vaadin

3. Observer Pattern

Event listeners for user interactions:

  • OnJLActionListener for object events
  • OnJLContextMenuItemListener for menu selections

4. Builder Pattern

Fluent object construction with self-typing:

  • JLMarkerBuilder, JLPolygonBuilder, JLOptionsBuilder

5. Mediator Pattern

Platform abstraction for context menus:

  • JLContextMenuMediator interface
  • Platform-specific implementations

6. Service Locator

Runtime service discovery:

  • JLContextMenuMediatorLocator
  • Java ServiceLoader integration

7. Async/Await

Non-blocking operations:

  • CompletableFuture for returnable operations
  • toGeoJSON(), getBounds(), getAttribution()

Platform Abstraction

JavaFX Specifics

Web Engine: Uses javafx.scene.web.WebView

JavaScript Execution:

Object result = webEngine.executeScript("window.jlmap_objects['id'].method()");

Event Bridge:

JSObject window = (JSObject) webEngine.executeScript("window");
window.setMember("javaEventHandler", javaEventHandler);

Vaadin Specifics

Web Component: Uses Vaadin's WebComponent API

JavaScript Execution:

JsonValue result = element.executeJs("return window.jlmap_objects[$0].method()", id);

Event Bridge:

element.addEventListener("custom-event", event -> {
    JsonValue detail = event.getEventData().get("detail");
    // Handle event
});

Memory Management

Object Lifecycle

  1. Creation: Builder creates Java object + JS code
  2. Registration: Object registered in window.jlmap_objects
  3. Usage: Transport layer facilitates communication
  4. Disposal: remove() method cleans up both sides

Cleanup Pattern

public void remove() {
    // Remove from JavaScript
    transport.execute(JLTransportRequest.voidCall(this, "remove"));

    // Unregister from global map
    transport.execute(JLTransportRequest.voidCall(this, "delete", "window.jlmap_objects['" + jLId + "']"));

    // Clean up Java references
    this.popup = null;
    this.contextMenu = null;
}

Performance Considerations

1. Lazy Initialization

Context menus are created only when first accessed:

public JLContextMenu<T> getContextMenu() {
    if (contextMenu == null) {
        synchronized (this) {
            if (contextMenu == null) {
                contextMenu = new JLContextMenu<>(self());
                registerContextMenuWithMediator();
            }
        }
    }
    return contextMenu;
}

2. Efficient Caching

Mediators are discovered once and cached:

private final Map<Class<?>, JLContextMenuMediator> mediatorCache = new ConcurrentHashMap<>();

3. Minimal Object Creation

Builders reuse instances where possible.

4. Async Operations

Non-blocking operations return CompletableFuture.


Thread Safety

Concurrent Data Structures

private final Map<String, JLMenuItem> menuItems = new ConcurrentHashMap<>();

Double-Checked Locking

Used for lazy initialization of expensive objects.

Platform Threading

  • JavaFX: Operations must run on JavaFX Application Thread
  • Vaadin: Operations synchronized via Vaadin's session lock

Extensibility Points

1. Custom Mediators

Implement JLContextMenuMediator for new UI frameworks.

2. Custom Events

Implement Event interface for new event types.

3. Custom Map Providers

Create custom tile providers by extending JLMapProvider.

4. Custom Styling

Use JLGeoJsonOptions with style and filter functions.


Previous: ← Getting Started | Next: API Reference →

Clone this wiki locally