Skip to content

Configuration and Persistence

Samera2022 edited this page Jan 30, 2026 · 1 revision

Configuration and Persistence

Relevant source files

Purpose and Scope

The Configuration and Persistence system manages two distinct categories of application data: durable user preferences and ephemeral runtime state. This dual-persistence architecture separates configuration settings that survive across installations from session-specific UI state that changes during normal operation.

This page covers the overall architecture and integration of both persistence subsystems. For detailed information about specific components, see:

For information about how configuration integrates with the UI, see Settings Dialog.

Sources:


Architecture Overview

The persistence system employs a dual-manager architecture that separates durable configuration from volatile runtime state:

Persistence Architecture Diagram

flowchart TD

ConfigStatic["ConfigManager static block<br>lines 25-29"]
CacheStatic["CacheManager static field<br>line 17"]
ConfigManager["ConfigManager<br>Static Utility Class"]
CacheManager["CacheManager<br>Static Utility Class"]
ConfigObj["Config<br>User Preferences<br>lines 31-44"]
CacheObj["Cache<br>Runtime State<br>CacheManager:23-28"]
ConfigFile["config.cfg<br>CONFIG_DIR"]
CacheFile["cache.json<br>CACHE_PATH"]
FileUtil["FileUtil<br>getLocalStoragePath()"]
Gson["Gson<br>JSON Serialization"]
SettingsDialog["SettingsDialog<br>reads/writes config"]
MacroSettingsDialog["MacroSettingsDialog<br>reads/writes config"]
ExitDialog["ExitDialog<br>reads/writes cache"]
MainFrame["MainFrame<br>reads config and cache"]
MacroManager["MacroManager<br>reads config"]

ConfigStatic --> ConfigManager
CacheStatic --> CacheManager
ConfigManager --> ConfigObj
CacheManager --> CacheObj
ConfigManager --> Gson
ConfigManager --> FileUtil
CacheManager --> Gson
CacheManager --> FileUtil
ConfigObj --> ConfigFile
CacheObj --> CacheFile
FileUtil --> ConfigFile
FileUtil --> CacheFile
SettingsDialog --> ConfigManager
MacroSettingsDialog --> ConfigManager
ExitDialog --> CacheManager
MainFrame --> ConfigManager
MainFrame --> CacheManager
MacroManager --> ConfigManager

subgraph Consumers ["Consumers"]
    SettingsDialog
    MacroSettingsDialog
    ExitDialog
    MainFrame
    MacroManager
end

subgraph subGraph4 ["File I/O"]
    FileUtil
    Gson
end

subgraph subGraph3 ["Persistence Layer"]
    ConfigFile
    CacheFile
end

subgraph subGraph2 ["Data Objects"]
    ConfigObj
    CacheObj
end

subgraph subGraph1 ["Manager Layer"]
    ConfigManager
    CacheManager
end

subgraph subGraph0 ["Static Initialization"]
    ConfigStatic
    CacheStatic
end
Loading

Sources:


Persistence Components

The system consists of two primary manager classes that work in parallel to manage different types of application state:

ConfigManager

ConfigManager is a static utility class that manages durable user preferences. It exposes a single public static field:

Field Type Purpose
config Config Application-level settings (language, theme, hotkeys, macro settings)

The field is initialized in a static block ConfigManager.java L25-L29

ensuring configuration is loaded before any other application code executes.

Sources: src/io/github/samera2022/mouse_macros/manager/ConfigManager.java L14-L29

CacheManager

CacheManager is a static utility class that manages ephemeral runtime state. It exposes a single public static field:

Field Type Purpose
cache Cache Runtime UI state (last used directories, window sizes, exit preferences)

The field is initialized during class loading CacheManager.java L17

loading cached state from the previous session.

Sources: src/io/github/samera2022/mouse_macros/manager/CacheManager.java L13-L17

Data Structure Class Diagram

classDiagram
    class Config {
        +boolean followSystemSettings
        +String lang
        +boolean enableDarkMode
        +boolean enableDefaultStorage
        +String defaultMmcStoragePath
        +boolean enableQuickMode
        +boolean allowLongStr
        +String readjustFrameMode
        +Map<String,String> keyMap
        +boolean enableCustomMacroSettings
        +int repeatTime
        +double repeatDelay
    }
    class Cache {
        +String lastLoadDirectory
        +String lastSaveDirectory
        +Map<String,String> windowSizeMap
        +String defaultCloseOperation
    }
    class ConfigManager {
        +static String CONFIG_DIR
        +static Config config
        +static Config loadConfig()
        +static void saveConfig(Config)
        +static void reloadConfig()
        +static String[] getAvailableLangs()
    }
    class CacheManager {
        +static Cache cache
        +static void reloadCache()
        +static void saveCache()
    }
    ConfigManager --> Config
    CacheManager --> Cache
Loading

Sources:

Config Fields Reference

Field Name Type Default Value Purpose
followSystemSettings boolean true When enabled, synchronizes lang and enableDarkMode with OS settings
lang String "zh_cn" Current language code (e.g., "en_us", "zh_cn", "ja_jp")
enableDarkMode boolean false Dark theme enabled state
enableDefaultStorage boolean false When true, uses defaultMmcStoragePath for file operations
defaultMmcStoragePath String "" (empty) Default directory for saving/loading .mmc macro files
enableQuickMode boolean false Enables quick playback mode (ignores recorded delays)
allowLongStr boolean false Allows long strings in UI components
readjustFrameMode String "STANDARDIZED" Frame resize mode: MIXED, STANDARDIZED, or MEMORIZED
keyMap Map<String,String> new HashMap<>() Custom hotkey assignments (e.g., "record" → "F2")
enableCustomMacroSettings boolean false Enables per-macro custom settings
repeatTime int 1 Number of times to repeat macro playback
repeatDelay double 0 Delay in seconds between macro repetitions

Sources: src/io/github/samera2022/mouse_macros/manager/ConfigManager.java L31-L44

Cache Fields Reference

Field Name Type Default Value Purpose
lastLoadDirectory String "" (empty) Last directory used for loading macro files
lastSaveDirectory String "" (empty) Last directory used for saving macro files
windowSizeMap Map<String,String> new HashMap<>() Stores window sizes by window name
defaultCloseOperation String "" (empty) Default close behavior: exit_on_close, minimize_to_tray, or empty

Sources: src/io/github/samera2022/mouse_macros/manager/CacheManager.java L23-L28


Persistence Strategy

The system persists data to two separate JSON files in platform-specific application data directories:

File Locations

The storage directory is resolved via FileUtil.getLocalStoragePath() ConfigManager.java L26

which returns platform-specific paths:

  • Windows: %LOCALAPPDATA%/MouseMacros/
  • macOS: ~/Library/Application Support/MouseMacros/
  • Linux: ~/.local/share/MouseMacros/

The two persistence files are:

File Manager Constant Content
config.cfg ConfigManager CONFIG_PATH User preferences (Config object serialized as JSON)
cache.json CacheManager CACHE_PATH Runtime state (Cache object serialized as JSON)

Sources:

Configuration Serialization Flow

sequenceDiagram
  participant Application Code
  participant ConfigManager
  participant Gson
  participant FileUtil
  participant File System

  note over Application Code,File System: Save Configuration
  Application Code->>ConfigManager: saveConfig(config)
  ConfigManager->>ConfigManager: Check CONFIG_DIR exists
  ConfigManager->>Gson: Create if needed (line 69-70)
  Gson-->>ConfigManager: toJson(config)
  ConfigManager->>FileUtil: JSON string
  FileUtil->>File System: writeFile(CONFIG_PATH, json)
  note over Application Code,File System: Load Configuration
  Application Code->>ConfigManager: Write UTF-8 encoded JSON
  ConfigManager->>FileUtil: loadConfig()
  loop [File exists and valid]
    FileUtil-->>ConfigManager: readFile(CONFIG_PATH)
    ConfigManager->>Gson: JSON string
    Gson-->>ConfigManager: fromJson(json, Config.class)
    ConfigManager->>ConfigManager: Config object
    ConfigManager-->>Application Code: Create new Config()
  end
  ConfigManager-->>Application Code: Save default config (line 53-54)
Loading

Sources: src/io/github/samera2022/mouse_macros/manager/ConfigManager.java L49-L76

Cache Serialization Flow

sequenceDiagram
  participant Application Code
  participant CacheManager
  participant Gson
  participant java.nio.file.Files
  participant File System

  note over Application Code,File System: Save Cache
  Application Code->>CacheManager: saveCache()
  CacheManager->>CacheManager: Get CACHE_PATH
  CacheManager->>CacheManager: Ensure parent directory exists
  CacheManager->>Gson: (line 42)
  Gson-->>CacheManager: toJson(cache)
  CacheManager->>java.nio.file.Files: JSON string
  java.nio.file.Files->>File System: write(cachePath, json.getBytes())
  note over Application Code,File System: Load Cache
  CacheManager->>CacheManager: Write UTF-8 encoded JSON
  CacheManager->>CacheManager: loadCache() during static init
  loop [File exists]
    CacheManager->>java.nio.file.Files: Get CACHE_PATH
    java.nio.file.Files-->>CacheManager: readAllBytes(cachePath)
    CacheManager->>Gson: byte array
    Gson-->>CacheManager: fromJson(json, Cache.class)
    CacheManager->>CacheManager: Cache object
    CacheManager-->>CacheManager: Create new Cache()
  end
Loading

Sources: src/io/github/samera2022/mouse_macros/manager/CacheManager.java L34-L61

Directory Creation

Both managers ensure the parent directory exists before writing:

ConfigManager ConfigManager.java L69-L70

:

java.io.File dir = new java.io.File(CONFIG_DIR);
if (!dir.exists()) dir.mkdirs();

CacheManager CacheManager.java L42

:

if (cachePath.getParent() != null) Files.createDirectories(cachePath.getParent());

This guarantees that persistence files can be saved on first run, even when the application data directory doesn't exist.

Sources:


Static Initialization

Configuration loading occurs during class initialization, before any application code executes. This is achieved through a static initialization block:

Static Initialization Sequence

sequenceDiagram
  participant JVM Class Loader
  participant ConfigManager
  participant CacheManager
  participant FileUtil
  participant Gson

  note over JVM Class Loader,Gson: ConfigManager Static Initialization
  JVM Class Loader->>ConfigManager: Load ConfigManager class
  ConfigManager->>ConfigManager: Initialize Gson (line 18)
  ConfigManager->>ConfigManager: Execute static block (lines 25-29)
  ConfigManager->>FileUtil: getLocalStoragePath()
  FileUtil-->>ConfigManager: Platform-specific path
  ConfigManager->>ConfigManager: Set CONFIG_DIR and CONFIG_PATH
  ConfigManager->>ConfigManager: loadConfig()
  loop [config.cfg exists]
    ConfigManager->>FileUtil: readFile(CONFIG_PATH)
    FileUtil-->>ConfigManager: JSON content
    ConfigManager->>Gson: fromJson(json, Config.class)
    Gson-->>ConfigManager: Config object
    ConfigManager->>ConfigManager: new Config() with defaults
    ConfigManager->>ConfigManager: saveConfig(defaultConfig)
    ConfigManager->>ConfigManager: Assign to static config field
    note over JVM Class Loader,Gson: CacheManager Static Initialization
    JVM Class Loader->>CacheManager: Load CacheManager class
    CacheManager->>CacheManager: Initialize Gson (line 16)
    CacheManager->>CacheManager: Initialize CACHE_PATH (line 15)
    CacheManager->>FileUtil: getLocalStoragePath()
    FileUtil-->>CacheManager: Platform-specific path
    CacheManager->>CacheManager: loadCache() (line 17)
    CacheManager->>CacheManager: Read file via Files.readAllBytes
    CacheManager->>Gson: fromJson(json, Cache.class)
    Gson-->>CacheManager: Cache object
    CacheManager->>CacheManager: new Cache() with defaults
  end
  CacheManager->>CacheManager: Assign to static cache field
  note over JVM Class Loader,Gson: Both managers ready for use
Loading

Sources:

Access Patterns

Once initialized, both managers are accessed statically throughout the application:

Configuration Access:

// Direct field access
String language = ConfigManager.config.lang;
boolean darkMode = ConfigManager.config.enableDarkMode;
int repeatCount = ConfigManager.config.repeatTime;

Cache Access:

// Direct field access
String lastDir = CacheManager.cache.lastLoadDirectory;
String defaultOp = CacheManager.cache.defaultCloseOperation;

// Persistence
CacheManager.cache.lastSaveDirectory = "/path/to/dir";
CacheManager.saveCache();

Configuration access pattern appears in SettingsDialog.java L11

and MainFrame

while cache access pattern appears in ExitDialog.java L83-L88

Sources:


System Integration

The configuration system integrates with the operating system through the followSystemSettings flag, enabling automatic synchronization with OS preferences.

System Settings Synchronization

When config.followSystemSettings is true, the application queries the OS for:

  1. System Language: Via SystemUtil.getSystemLang(availableLangs)
  2. Dark Mode Preference: Via SystemUtil.isSystemDarkMode()

This synchronization occurs in the SettingsDialog SettingsDialog.java L154-L168

:

flowchart TD

FollowSys["config.followSystemSettings"]
SysUtil["SystemUtil"]
GetLang["getSystemLang()"]
GetDark["isSystemDarkMode()"]
OSLang["System Language<br>Locale"]
OSDark["Windows Registry<br>AppsUseLightTheme"]
LangCombo["langCombo<br>Disabled when following"]
DarkBox["darkModeBox<br>Disabled when following"]

FollowSys --> SysUtil
GetLang --> OSLang
GetDark --> OSDark
OSLang --> LangCombo
OSDark --> DarkBox

subgraph subGraph3 ["UI State"]
    LangCombo
    DarkBox
end

subgraph OS ["OS"]
    OSLang
    OSDark
end

subgraph subGraph1 ["System Queries"]
    SysUtil
    GetLang
    GetDark
    SysUtil --> GetLang
    SysUtil --> GetDark
end

subgraph Configuration ["Configuration"]
    FollowSys
end
Loading

Sources: src/io/github/samera2022/mouse_macros/ui/frame/SettingsDialog.java L154-L168

ItemListener Implementation

The synchronization logic is implemented through an ItemListener attached to the "Follow System Settings" checkbox:

Key behaviors:

  • When checked: Disables manual controls and queries system settings
  • When unchecked: Re-enables manual controls, preserving last user selections
  • Initial load: Executes once during dialog construction SettingsDialog.java L168

Sources: src/io/github/samera2022/mouse_macros/ui/frame/SettingsDialog.java L154-L168


Language Discovery

The ConfigManager.getAvailableLangs() method ConfigManager.java L69-L114

detects available language files for the language selector in SettingsDialog. It handles two runtime environments:

Environment Detection Flow

flowchart TD

Start["getAvailableLangs()"]
CheckDevMode["Localizer.isDevMode()"]
DevPath["Read lang/ directory<br>from filesystem<br>(lines 72-81)"]
DevList["FileUtil.listFileNames('lang')"]
DevFilter["Filter *.json files<br>Remove .json extension"]
CheckProtocol["Check resource protocol"]
JarPath["JAR protocol<br>(lines 85-96)"]
FilePath["file protocol<br>(lines 97-109)"]
JarEnum["Enumerate JAR entries<br>Filter lang/*.json"]
FileList["List directory<br>Filter *.json"]
Return["Return String[] of lang codes"]

Start --> CheckDevMode
CheckDevMode --> DevPath
CheckDevMode --> CheckProtocol
DevPath --> DevList
DevList --> DevFilter
DevFilter --> Return
CheckProtocol --> JarPath
CheckProtocol --> FilePath
JarPath --> JarEnum
FilePath --> FileList
JarEnum --> Return
FileList --> Return
Loading

Sources: src/io/github/samera2022/mouse_macros/manager/ConfigManager.java L69-L114

Discovery Scenarios

Scenario Detection Method Implementation
Development mode File system access to lang/ directory FileUtil.listFileNames("lang") [line 73](https://github.com/Samera2022/MouseMacros/blob/1eb6620b/line 73)
JAR deployment Enumerate JAR entries matching lang/*.json JarFile.entries() iteration [lines 86-96](https://github.com/Samera2022/MouseMacros/blob/1eb6620b/lines 86-96)
file:// protocol URI-based file listing File.listFiles() with filter [lines 99-109](https://github.com/Samera2022/MouseMacros/blob/1eb6620b/lines 99-109)

All methods strip the .json extension to return language codes (e.g., "en_us", "zh_cn").

Sources: src/io/github/samera2022/mouse_macros/manager/ConfigManager.java L69-L114


Usage Patterns by Component

Different UI components interact with the persistence system based on their responsibilities:

SettingsDialog Usage

SettingsDialog reads and writes configuration settings SettingsDialog.java L1-L180

:

Read Pattern:

import static io.github.samera2022.mouse_macros.manager.ConfigManager.config;

// Initialize UI from config
langCombo.setSelectedItem(config.lang);
darkModeBox.setSelected(config.enableDarkMode);
followSystemBox.setSelected(config.followSystemSettings);

Write Pattern:

// Modify in-memory config
config.lang = (String) langCombo.getSelectedItem();
config.enableDarkMode = darkModeBox.isSelected();
config.followSystemSettings = followSystemBox.isSelected();

// Persist changes
ConfigManager.saveConfig(config);

Sources: src/io/github/samera2022/mouse_macros/ui/frame/SettingsDialog.java L1-L180

ExitDialog Usage

ExitDialog uses CacheManager to persist exit preferences ExitDialog.java L81-L100

:

Cache Write Pattern:

String op = "";
if (exitOnCloseRadio.isSelected()) op = CacheManager.EXIT_ON_CLOSE;
if (minimizeToTrayRadio.isSelected()) op = CacheManager.MINIMIZE_TO_TRAY;

if (rememberOptionBox.isSelected()) {
    CacheManager.cache.defaultCloseOperation = op;
    CacheManager.saveCache();
}

switch (op) {
    case CacheManager.EXIT_ON_CLOSE:
        System.exit(0);
        break;
    case CacheManager.MINIMIZE_TO_TRAY:
        mf.minimizeToTray();
        break;
}

The cache constants are defined in CacheManager.java L19-L21

:

Constant Value Purpose
EXIT_ON_CLOSE "exit_on_close" Application exits when closed
MINIMIZE_TO_TRAY "minimize_to_tray" Application minimizes to system tray when closed
UNKNOWN "" (empty) No default close behavior set

Sources:

MacroManager Usage

MacroManager reads configuration settings for playback behavior:

Read Pattern:

// Check execution settings
boolean quickMode = ConfigManager.config.enableQuickMode;
int repeatCount = ConfigManager.config.repeatTime;
double repeatDelay = ConfigManager.config.repeatDelay;

// Check storage settings
boolean useDefaultPath = ConfigManager.config.enableDefaultStorage;
String defaultPath = ConfigManager.config.defaultMmcStoragePath;

Sources: src/io/github/samera2022/mouse_macros/manager/MacroManager.java


Configuration Constants

ConfigManager defines constants for the readjustFrameMode setting ConfigManager.java L21-L23

:

Constant Value Purpose
RFM_MIXED "MIXED" Use memorized sizes when available, otherwise use standardized sizes
RFM_STANDARDIZED "STANDARDIZED" Always calculate standardized window sizes based on content
RFM_MEMORIZED "MEMORIZED" Always use memorized window sizes from CacheManager

CacheManager defines constants for exit behavior CacheManager.java L19-L21

:

Constant Value Purpose
EXIT_ON_CLOSE "exit_on_close" Application exits when main window is closed
MINIMIZE_TO_TRAY "minimize_to_tray" Application minimizes to system tray when closed
UNKNOWN "" (empty) No default close behavior configured

These constants are used throughout the application to maintain consistency in configuration values.

Sources:


Summary

The Configuration System provides:

  1. Static Initialization: Configuration loaded before application code executes
  2. Two-Tier Persistence: Separate files for application settings (config.cfg) and UI state (cache.json)
  3. System Integration: Optional synchronization with OS language and dark mode settings
  4. Environment Awareness: Supports both development and JAR-packaged execution
  5. Simple Access: Static fields enable straightforward configuration access throughout the codebase
  6. Automatic Defaults: Missing configuration files trigger creation of defaults with sensible values

For implementation details, see the child pages:

Sources:

Clone this wiki locally