001package org.intellimate.izou.sdk.properties; 002 003import org.intellimate.izou.sdk.Context; 004import org.intellimate.izou.sdk.util.AddOnModule; 005import org.intellimate.izou.system.file.ReloadableFile; 006import ro.fortsoft.pf4j.PluginWrapper; 007 008import java.io.*; 009import java.lang.ref.WeakReference; 010import java.util.*; 011import java.util.function.Consumer; 012 013/** 014 * Manages property files, and is also a {@link ReloadableFile} 015 * 016 * <p>Unlike most manager classes in Izou, the PropertiesManager is included in every {@code AddOn} instance</p> 017 */ 018public class PropertiesAssistant extends AddOnModule implements ReloadableFile { 019 private String propertiesPath; 020 private String defaultPropertiesPath; 021 private Properties properties; 022 private final EventPropertiesAssistant assistant; 023 private File propertiesFile; 024 private List<WeakReference<Consumer<PropertiesAssistant>>> listeners = 025 Collections.synchronizedList(new ArrayList<>()); 026 027 public PropertiesAssistant(Context context, String addOnID) { 028 super(context, addOnID + ".PropertiesAssistant"); 029 this.properties = new Properties(); 030 this.propertiesPath = null; 031 this.defaultPropertiesPath = null; 032 this.assistant = new EventPropertiesAssistant(context, addOnID + ".EventPropertiesAssistant"); 033 PluginWrapper plugin = getContext().getAddOn().getPlugin(); 034 if (plugin != null) { 035 this.defaultPropertiesPath = getContext().getFiles().getLibLocation() + 036 getContext().getAddOn().getPlugin().getPluginPath() + File.separator + "classes" + File.separator 037 + "default_properties.txt"; 038 } else { 039 //if we are debugging 040 this.defaultPropertiesPath = getContext().getAddOn().getClass().getClassLoader(). 041 getResource("default_properties.txt").getFile(); 042 } 043 initProperties(); 044 try { 045 getContext().getFiles().registerFileDir(propertiesFile.getParentFile().toPath(), 046 propertiesFile.getName(), this); 047 } catch (IOException e) { 048 error("Error registering reloadablefile with file manager", e); 049 } 050 } 051 052 /** 053 * Gets the EventPropertiesAssistant 054 * 055 * @return the EventPropertiesAssistant 056 */ 057 public EventPropertiesAssistant getEventPropertiesAssistant() { 058 return assistant; 059 } 060 061 /** 062 * Searches for the property with the specified key in this property list. 063 * 064 * If the key is not found in this property list, the default property list, and its defaults, recursively, are 065 * then checked. The method returns null if the property is not found. 066 * 067 * @param key the property key. 068 * @return the value in this property list with the specified key value. 069 */ 070 public String getProperty(String key) { 071 return properties.getProperty(key); 072 } 073 074 /** 075 * Gets the properties object 076 * 077 * @return the properties object 078 */ 079 public Properties getProperties() { 080 return properties; 081 } 082 083 /** 084 * Calls the HashTable method put. 085 * 086 * Provided for parallelism with the getProperty method. Enforces use of strings for 087 * * property keys and values. The value returned is the result of the HashTable call to put. 088 089 * @param key the key to be placed into this property list. 090 * @param value the value corresponding to key. 091 */ 092 public void setProperty(String key, String value) { 093 properties.setProperty(key, value); 094 } 095 096 /** 097 * Gets the path to properties file (the real properties file - as opposed to the {@code defaultProperties.txt} file) 098 * 099 * @return path to properties file 100 */ 101 public String getPropertiesPath() { 102 return propertiesPath; 103 } 104 105 /** 106 * Gets the path to properties file (the real properties file - as opposed to the {@code defaultProperties.txt} file) 107 * 108 * @return path to properties file 109 */ 110 public File getPropertiesFile() { 111 return propertiesFile; 112 } 113 114 /** 115 * the listener will always be called, when the Properties-file changes. 116 * 117 * @param listener the listener 118 */ 119 public void registerUpdateListener(Consumer<PropertiesAssistant> listener) { 120 if (listener != null) 121 listeners.add(new WeakReference<>(listener)); 122 } 123 124 /** 125 * Gets the path to default properties file path (the file which is copied into the real properties on start) 126 * 127 * @return path to default properties file 128 */ 129 public String getDefaultPropertiesPath() { 130 return defaultPropertiesPath; 131 } 132 133 /** 134 * Initializes properties in the addOn. Creates new properties file using default properties. 135 */ 136 public void initProperties() { 137 propertiesPath = getContext().getFiles().getPropertiesLocation() + File.separator 138 + getContext().getAddOn().getID() + ".properties"; 139 140 this.propertiesFile = new File(propertiesPath); 141 if (!this.propertiesFile.exists()) try { 142 this.propertiesFile.createNewFile(); 143 } catch (IOException e) { 144 error("Error while trying to create the new Properties file", e); 145 } 146 147 try { 148 BufferedReader in = new BufferedReader(new InputStreamReader(new FileInputStream(this.propertiesFile), 149 "UTF8")); 150 try { 151 properties.load(in); 152 } catch (IOException e) { 153 error("unable to load the InputStream for the PropertiesFile",e); 154 } 155 } catch (FileNotFoundException | UnsupportedEncodingException e) { 156 error("Error while trying to read Properties-File", e); 157 } 158 159 if (defaultPropertiesPath != null && new File(defaultPropertiesPath).exists()) { 160 @SuppressWarnings("unchecked") 161 Enumeration<String> keys = (Enumeration<String>)properties.propertyNames(); 162 163 if (!keys.hasMoreElements()) { 164 try { 165 createDefaultPropertyFile(defaultPropertiesPath); 166 } catch (IOException e) { 167 error("Error while trying to copy the Default-Properties File", e); 168 } 169 170 if (new File(defaultPropertiesPath).exists() && !writeToPropertiesFile(defaultPropertiesPath)) return; 171 reloadProperties(); 172 } 173 } 174 } 175 176 /** 177 * Writes defaultPropertiesFile.txt to real properties file 178 * This is done so that the final user never has to worry about property file initialization 179 * 180 * @param defaultPropsPath path to defaultPropertyFile.txt (or where it should be created) 181 * @return true if operation has succeeded, else false 182 */ 183 private boolean writeToPropertiesFile(String defaultPropsPath) { 184 return getContext().getFiles().writeToFile(defaultPropsPath, propertiesPath); 185 } 186 187 /** 188 * Creates a defaultPropertyFile.txt in case it does not exist yet. In case it is used by an addOn, 189 * it copies its content into the real properties file every time the addOn is launched. 190 * 191 * It is impossible to get the properties file on default, that way the user should not have to worry about 192 * the property file's initial content. 193 * 194 * @param defaultPropsPath path to defaultPropertyFile.txt (or where it should be created) 195 * @throws java.io.IOException is thrown by bufferedWriter 196 */ 197 private void createDefaultPropertyFile(String defaultPropsPath) throws IOException { 198 getContext().getFiles().createDefaultFile(defaultPropsPath, "# Properties should always be in the " 199 + "form of: \"key = value\""); 200 } 201 202 /** 203 * reloads the propertiesFile into the properties object 204 */ 205 private void reloadProperties() { 206 Properties temp = new Properties(); 207 BufferedReader bufferedReader = null; 208 try { 209 File properties = new File(propertiesPath); 210 bufferedReader = new BufferedReader(new InputStreamReader(new FileInputStream(properties), "UTF8")); 211 temp.load(bufferedReader); 212 this.properties = temp; 213 listeners.removeIf(weakReference -> weakReference.get() == null); 214 listeners.forEach(weakReference -> { 215 Consumer<PropertiesAssistant> consumer = weakReference.get(); 216 if (consumer != null) 217 consumer.accept(this); 218 }); 219 } catch (IOException e) { 220 error("Error while trying to load the Properties-File: " 221 + propertiesPath, e); 222 } finally { 223 if (bufferedReader != null) { 224 try { 225 bufferedReader.close(); 226 } catch (IOException e) { 227 error("Unable to close input stream", e); 228 } 229 } 230 } 231 } 232 233 @Override 234 public void reloadFile(String eventType) { 235 reloadProperties(); 236 } 237}