This document explains the architecture so you can extend OrthodoxIcons safely.
com.orthodoxicons
├── OrthodoxIconsPlugin Composition root / lifecycle (manual DI)
├── OrthodoxIconsApi Public developer API (stable surface)
├── model Immutable domain types (Builder pattern)
│ ├── License, Icon, IconMetadata, PlacedIcon
├── config ConfigManager + typed PluginConfig snapshot
├── locale LocaleManager (message files, placeholders)
├── util Scheduler, Hashing, Text helpers
├── storage Database abstraction + repositories
│ ├── Database, SqliteDatabase
│ ├── IconRepository, PlacedIconRepository, FavoritesRepository
├── http HttpClientFactory, HttpService, RobotsTxtChecker
├── provider IconProvider + ProviderRegistry + providers
│ ├── CuratedProvider, WikimediaCommonsProvider
├── cache CacheManager (on-disk layout + cleanup)
├── image ImageProcessor (validate/resize/fit/thumbnail)
├── fetch IconFetchService + FetchResult
├── item IconItemKeys, IconMapRenderer, IconItemFactory
├── craft RecipeManager, CraftListener
├── placement PlacementService + listeners
├── gui GuiManager, BrowserHolder, GuiSort, listeners
└── command IconsCommand, IconsTabCompleter
- Dependency Injection:
OrthodoxIconsPlugin#onEnableis the single composition root; every service receives its collaborators via constructor. No service reaches back into the plugin singleton except for lifecycle hooks. - Repository pattern: all persistence goes through
*Repositoryclasses that own an in-memory cache for main-thread reads and serialize writes on a single-threaded executor. - Provider pattern (Open/Closed): implement
IconProviderand register it withProviderRegistry(orOrthodoxIconsApi#registerProvider). No existing class needs to change. - Service layer:
IconFetchService,PlacementService,HttpService, etc. encapsulate business logic away from Bukkit event plumbing. - Factory pattern:
IconItemFactorybuilds decorated item stacks and memoizes map views per icon. - Builder pattern:
Icon,IconMetadatause fluent builders.
- Main thread: all Bukkit API calls (inventory, entities, maps, items).
- HTTP pool: the JDK
HttpClientruns on a dedicated daemon executor; aSemaphorelimits concurrent downloads tomax-concurrent-downloads. - DB executor: a single-threaded daemon executor serializes every SQL statement, so repositories are thread-safe by construction.
Schedulerbridges Bukkit tasks toCompletableFutures; usescheduler.async(...)for off-thread work andscheduler.runSync(...)to return to the main thread before touching the Bukkit API.
Rule: never call the Bukkit API from an async task, and never perform HTTP or SQL on the main thread.
public final class MyProvider implements IconProvider {
public static final String ID = "myprovider";
@Override public String id() { return ID; }
@Override public String displayName() { return "My Source"; }
@Override public boolean isEnabled() { return true; }
@Override
public List<IconMetadata> discover() throws Exception {
return List.of(IconMetadata.builder()
.providerId(ID)
.name("Christ Pantocrator")
.saint("Jesus Christ")
.feast("")
.category("Christ")
.description("Sixth-century encaustic icon.")
.imageUrl("https://example.org/pantocrator.jpg")
.sourceUrl("https://example.org/pantocrator")
.attribution("Public domain")
.license(License.PUBLIC_DOMAIN)
.tags(List.of("christ", "sinai"))
.build());
}
}Register it during your own plugin's enable:
OrthodoxIconsPlugin oi = (OrthodoxIconsPlugin) Bukkit.getPluginManager()
.getPlugin("OrthodoxIcons");
oi.api().registerProvider(new MyProvider());
oi.api().triggerUpdate();The fetch service handles conditional downloading, hashing, dedup, image processing, and persistence for you. Only free-licensed icons are stored.
Database is a small interface (initialize, connection, kind, close).
SqliteDatabase is the shipped implementation. To add MySQL, implement
Database with a pooled DataSource, adjust the schema DDL for MySQL types,
and select it in OrthodoxIconsPlugin#setupDatabase based on
storage.type. Repositories are unchanged because they depend only on the
interface and standard JDBC.
IconItemFactory#mapIdFor creates a persistent MapView, strips default
renderers, and attaches an IconMapRenderer that draws the cached processed
PNG onto the map canvas once per viewer. PlacementService spawns an
ItemFrame holding that map. WorldListener rebinds renderers on chunk load
so artwork survives restarts. This approach is fully server-side and therefore
compatible with every client version routed through Via*.