@@ -0,0 +1,3621 @@
+package com.nitorcreations.willow.utils;%0A%0Aimport static java.nio.file.StandardWatchEventKinds.*;%0A%0Aimport java.io.File;%0Aimport java.io.FileInputStream;%0Aimport java.io.IOException;%0Aimport java.nio.file.FileSystems;%0Aimport java.nio.file.Path;%0Aimport java.nio.file.WatchEvent;%0Aimport java.nio.file.WatchKey;%0Aimport java.nio.file.WatchService;%0Aimport java.util.Map.Entry;%0A%0Apublic class FilePollingProperties %7B%0A  public interface PropertyChangeListerner %7B%0A    public void propertyValueChanged(String key, String newValue, String oldValue);%0A    public void propertyAdded(String key, String value);%0A    public void propertyRemoved(String key, String value);%0A  %7D%0A  private final MergeableProperties properties;%0A  %0A  public FilePollingProperties(final File source, final PropertyChangeListerner listener) throws IOException %7B%0A    properties = new MergeableProperties();%0A    properties.load(new FileInputStream(source));%0A    final Path sourcePath = source.getParentFile().toPath();%0A    try %7B%0A      final WatchService watcher = FileSystems.getDefault().newWatchService();%0A      sourcePath.register(watcher, ENTRY_MODIFY, ENTRY_CREATE, ENTRY_DELETE);%0A      new Thread(new Runnable() %7B%0A        @Override%0A        public void run() %7B%0A          for (;;) %7B%0A            WatchKey key = null;%0A            try %7B%0A              key = watcher.take();%0A            %7D catch (InterruptedException x) %7B%0A              return;%0A            %7D%0A            for (WatchEvent%3C?%3E event: key.pollEvents()) %7B%0A              WatchEvent.Kind%3C?%3E kind = event.kind();%0A              if (kind == OVERFLOW) %7B%0A                  continue;%0A              %7D%0A              @SuppressWarnings(%22unchecked%22)%0A              WatchEvent%3CPath%3E ev = (WatchEvent%3CPath%3E)event;%0A              if (!ev.context().equals(sourcePath)) %7B%0A                continue;%0A              %7D%0A              if (kind == ENTRY_DELETE) %7B%0A                for (Entry%3CString, String%3E next : properties.backingEntrySet()) %7B%0A                  listener.propertyRemoved(next.getKey(), next.getValue());%0A                %7D%0A                properties.clear();%0A              %7D else %7B%0A                MergeableProperties newProps = new MergeableProperties();%0A                try %7B%0A                  newProps.load(new FileInputStream(source));%0A                  MergeableProperties oldProps ;%0A                  synchronized (properties) %7B%0A                    oldProps = (MergeableProperties) properties.clone();%0A                    properties.clear();%0A                    properties.putAll(newProps);%0A                  %7D%0A                  for (Entry%3CString, String%3E next : newProps.backingEntrySet()) %7B%0A                      if (oldProps.containsKey(next.getKey())) %7B%0A                        if (!oldProps.getProperty(next.getKey()).equals(next.getValue())) %7B%0A                          listener.propertyValueChanged(next.getKey(), next.getValue(), oldProps.getProperty(next.getKey()));%0A                        %7D%0A                        oldProps.remove(next.getKey());%0A                      %7D else %7B%0A                        listener.propertyAdded(next.getKey(), next.getValue());%0A                      %7D%0A                    %7D%0A                    for (Entry%3CString, String%3E next : oldProps.backingEntrySet()) %7B%0A                      listener.propertyRemoved(next.getKey(), next.getValue());%0A                    %7D%0A                %7D catch (IOException e) %7B%0A                %7D%0A              %7D%0A            %7D%0A          %7D%0A        %7D%0A      %7D).start();%0A    %7D catch (Exception e) %7B%0A    %7D%0A  %7D%0A  public MergeableProperties getProperties() %7B%0A    synchronized (properties) %7B%0A      return (MergeableProperties) properties.clone();%0A    %7D%0A  %7D%0A%7D%0A

Add file backed properties to serve as backend for simple user data