""" Java java.util.prefs standard library APIs implemented in Python. java.util.prefs package - Preferences, PreferencesFactory, etc. """ from __future__ import annotations from .._core import Stub from ..java.lang import RuntimeException, Exception as _JavaException import typing as _typing import json as _json import os as _os import threading as _threading __all__ = [ "Preferences", "PreferencesFactory", "AbstractPreferences", "NodeChangeEvent", "NodeChangeListener", "PreferenceChangeEvent", "PreferenceChangeListener", "BackingStoreException", "InvalidPreferencesFormatException", ] # ============================================================ # Exceptions # ============================================================ class BackingStoreException(_JavaException): """java.util.prefs.BackingStoreException""" def __init__(self, message): super().__init__(message) class InvalidPreferencesFormatException(_JavaException): """java.util.prefs.InvalidPreferencesFormatException""" def __init__(self, message, cause=None): super().__init__(message) self._cause = cause def getCause(self): return self._cause # ============================================================ # Event Classes # ============================================================ class NodeChangeEvent(Stub): """java.util.prefs.NodeChangeEvent""" def __init__(self, parent, child): super().__init__() self._parent = parent self._child = child def getParent(self): return self._parent def getChild(self): return self._child class PreferenceChangeEvent(Stub): """java.util.prefs.PreferenceChangeEvent""" def __init__(self, node, key, new_value): super().__init__() self._node = node self._key = key self._new_value = new_value def getNode(self): return self._node def getKey(self): return self._key def getNewValue(self): return self._new_value class NodeChangeListener(Stub): """java.util.prefs.NodeChangeListener""" def childAdded(self, evt): pass def childRemoved(self, evt): pass class PreferenceChangeListener(Stub): """java.util.prefs.PreferenceChangeListener""" def preferenceChange(self, evt): pass # ============================================================ # Preferences # ============================================================ class Preferences(Stub): """java.util.prefs.Preferences""" MAX_KEY_LENGTH = 80 MAX_VALUE_LENGTH = 8192 MAX_NAME_LENGTH = 80 def __init__(self, name="", parent=None): super().__init__() self._name = name self._parent = parent self._children = {} self._prefs = {} self._node_listeners = [] self._pref_listeners = [] self._absolute_path = self._build_path() def _build_path(self): if self._parent is None: return "/" + self._name if self._name else "/" parent_path = self._parent._absolute_path if parent_path == "/": return "/" + self._name return parent_path + "/" + self._name def put(self, key, value): if len(key) > Preferences.MAX_KEY_LENGTH: raise IllegalArgumentException(f"Key too long: {len(key)}") if len(value) > Preferences.MAX_VALUE_LENGTH: raise IllegalArgumentException(f"Value too long: {len(value)}") self._prefs[key] = value self._fire_preference_change(key, value) def get(self, key, default_value): return self._prefs.get(key, default_value) def remove(self, key): if key in self._prefs: old = self._prefs.pop(key) self._fire_preference_change(key, None) def clear(self): self._prefs.clear() def putInt(self, key, value): self.put(key, str(value)) def getInt(self, key, default_value): val = self.get(key, None) if val is None: return default_value try: return int(val) except ValueError: return default_value def putLong(self, key, value): self.put(key, str(value)) def getLong(self, key, default_value): val = self.get(key, None) if val is None: return default_value try: return int(val) except ValueError: return default_value def putFloat(self, key, value): self.put(key, str(value)) def getFloat(self, key, default_value): val = self.get(key, None) if val is None: return default_value try: return float(val) except ValueError: return default_value def putDouble(self, key, value): self.put(key, str(value)) def getDouble(self, key, default_value): val = self.get(key, None) if val is None: return default_value try: return float(val) except ValueError: return default_value def putBoolean(self, key, value): self.put(key, "true" if value else "false") def getBoolean(self, key, default_value): val = self.get(key, None) if val is None: return default_value return val.lower() == "true" def putByteArray(self, key, value): import base64 self.put(key, base64.b64encode(value).decode()) def getByteArray(self, key, default_value): import base64 val = self.get(key, None) if val is None: return default_value try: return base64.b64decode(val) except Exception: return default_value def keys(self): return list(self._prefs.keys()) def childrenNames(self): return list(self._children.keys()) def parent(self): return self._parent def node(self, path_name): if path_name.startswith("/"): return self._root()._node_path(path_name[1:]) return self._node_path(path_name) def nodeExists(self, path_name): try: self.node(path_name) return True except BackingStoreException: return False def removeNode(self): if self._parent is not None: self._parent._remove_child(self._name) def name(self): return self._name def absolutePath(self): return self._absolute_path def isUserNode(self): return False def toString(self): return self._absolute_path def flush(self): pass def sync(self): pass def addNodeChangeListener(self, listener): self._node_listeners.append(listener) def removeNodeChangeListener(self, listener): if listener in self._node_listeners: self._node_listeners.remove(listener) def addPreferenceChangeListener(self, listener): self._pref_listeners.append(listener) def removePreferenceChangeListener(self, listener): if listener in self._pref_listeners: self._pref_listeners.remove(listener) def exportNode(self, output_stream): data = self._to_dict() output_stream.write(_json.dumps(data, indent=2).encode()) def exportSubtree(self, output_stream): data = self._to_dict(recursive=True) output_stream.write(_json.dumps(data, indent=2).encode()) def _to_dict(self, recursive=False): result = {"prefs": dict(self._prefs)} if recursive: result["children"] = { name: child._to_dict(recursive=True) for name, child in self._children.items() } return result def _root(self): if self._parent is None: return self return self._parent._root() def _node_path(self, path): if not path: return self parts = path.split("/") current = self for part in parts: if not part: continue if part == "..": if current._parent is not None: current = current._parent continue if part not in current._children: current._children[part] = Preferences(part, current) current = current._children[part] return current def _remove_child(self, name): if name in self._children: child = self._children.pop(name) evt = NodeChangeEvent(self, child) for listener in self._node_listeners: listener.childRemoved(evt) def _fire_preference_change(self, key, value): evt = PreferenceChangeEvent(self, key, value) for listener in self._pref_listeners: listener.preferenceChange(evt) @staticmethod def userRoot(): if Preferences._user_root is None: Preferences._user_root = Preferences("") return Preferences._user_root @staticmethod def systemRoot(): if Preferences._system_root is None: Preferences._system_root = Preferences("") return Preferences._system_root @staticmethod def importPreferences(input_stream): data = _json.loads(input_stream.read()) root = Preferences("") root._prefs = data.get("prefs", {}) return root _user_root = None _system_root = None # ============================================================ # AbstractPreferences # ============================================================ class AbstractPreferences(Preferences): """java.util.prefs.AbstractPreferences""" def __init__(self, parent, name): super().__init__(name, parent) self._is_removed = False self._lock = _threading.RLock() def put(self, key, value): with self._lock: if self._is_removed: raise BackingStoreException("Node has been removed") super().put(key, value) def get(self, key, default_value): with self._lock: if self._is_removed: raise BackingStoreException("Node has been removed") return super().get(key, default_value) def remove(self, key): with self._lock: super().remove(key) def keys(self): with self._lock: return super().keys() def childrenNames(self): with self._lock: return super().childrenNames() def node(self, path_name): with self._lock: return super().node(path_name) def removeNode(self): with self._lock: super().removeNode() self._is_removed = True def flush(self): pass def sync(self): pass def isUserNode(self): return False # ============================================================ # PreferencesFactory # ============================================================ class PreferencesFactory(Stub): """java.util.prefs.PreferencesFactory""" def systemRoot(self): return Preferences.systemRoot() def userRoot(self): return Preferences.userRoot() # ============================================================ # IllegalArgumentException # ============================================================ class IllegalArgumentException(RuntimeException): """java.lang.IllegalArgumentException""" pass # ============================================================ # Preferences utility helpers # ============================================================ class _PreferencesService(Stub): """Helper to manage Preferences with file-backed storage""" def __init__(self, root_path=None): super().__init__() if root_path is None: import os root_path = os.path.join(os.path.expanduser("~"), ".java", "prefs") self._root_path = root_path self._cache = {} def loadPreferences(self, node_path): import os import json file_path = os.path.join(self._root_path, node_path.replace("/", "_") + ".json") prefs = Preferences(node_path) if os.path.exists(file_path): try: with open(file_path, "r") as f: data = json.load(f) for key, val in data.items(): prefs.put(key, val) except Exception: pass self._cache[node_path] = prefs return prefs def savePreferences(self, prefs): import os import json file_path = os.path.join(self._root_path, prefs.absolutePath().replace("/", "_") + ".json") os.makedirs(os.path.dirname(file_path), exist_ok=True) data = {} for key in prefs.keys(): data[key] = prefs.get(key, None) with open(file_path, "w") as f: json.dump(data, f, indent=2) def clearCache(self): self._cache.clear() class _EventDispatchThread(Stub): """Helper for dispatching preference change events""" def __init__(self): super().__init__() self._listeners = [] import threading self._lock = threading.Lock() def addListener(self, listener): with self._lock: self._listeners.append(listener) def removeListener(self, listener): with self._lock: if listener in self._listeners: self._listeners.remove(listener) def dispatchEvent(self, event): with self._lock: for listener in self._listeners: try: if hasattr(listener, 'preferenceChange'): listener.preferenceChange(event) elif callable(listener): listener(event) except Exception: pass def getListeners(self): with self._lock: return list(self._listeners) # ============================================================ # FileSystemPreferences - persistent Preferences backed by file # ============================================================ class FileSystemPreferences(AbstractPreferences): """java.util.prefs.FileSystemPreferences - persistent file-backed preferences""" def __init__(self, parent, name, storage_path=None): super().__init__(parent, name) import os if storage_path is None: storage_path = os.path.join(os.path.expanduser("~"), ".java", "user_prefs") self._storage_path = os.path.join( storage_path, self.absolutePath().replace("/", "_") + ".xml" ) self._loaded = False self._service = _PreferencesService(storage_path) def _ensureLoaded(self): if not self._loaded: try: import os if os.path.exists(self._storage_path): node = self._service.loadPreferences(self.absolutePath()) for key in node.keys(): self._prefs[key] = node.get(key, "") self._loaded = True except Exception: self._loaded = True def put(self, key, value): self._ensureLoaded() super().put(key, value) def get(self, key, default_value): self._ensureLoaded() return super().get(key, default_value) def keys(self): self._ensureLoaded() return super().keys() def flush(self): self._service.savePreferences(self) super().flush() def sync(self): self._ensureLoaded() self._service.savePreferences(self)