001: /*
002: The contents of this file are subject to the Mozilla Public License Version 1.1
003: (the "License"); you may not use this file except in compliance with the
004: License. You may obtain a copy of the License at http://www.mozilla.org/MPL/
005:
006: Software distributed under the License is distributed on an "AS IS" basis,
007: WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
008: for the specific language governing rights and
009: limitations under the License.
010:
011: The Original Code is "The Columba Project"
012:
013: The Initial Developers of the Original Code are Frederik Dietz and Timo Stich.
014: Portions created by Frederik Dietz and Timo Stich are Copyright (C) 2003.
015:
016: All Rights Reserved.
017: */
018: package org.columba.core.scripting;
019:
020: import java.io.File;
021: import java.io.FileFilter;
022: import java.util.ArrayList;
023: import java.util.Arrays;
024: import java.util.HashMap;
025: import java.util.Iterator;
026: import java.util.List;
027: import java.util.Map;
028: import java.util.Vector;
029: import java.util.logging.Logger;
030: import java.util.regex.Pattern;
031:
032: import org.columba.core.gui.scripting.ScriptManagerDocument;
033: import org.columba.core.scripting.config.BeanshellConfig;
034: import org.columba.core.scripting.config.OptionsObserver;
035: import org.columba.core.scripting.interpreter.InterpreterManager;
036: import org.columba.core.scripting.model.ColumbaScript;
037:
038: /**
039: The FileObserverThread is a timer that polls the file system to check if any
040: new .bsh scripts were created or existing scripts modified, since the last
041: time it was run.<br>
042: <br>
043: The polling behaviour can be enabled or disabled in the <columba
044: dir>/scripts/config.xml, as well as the polling interval. <br>
045: <br>
046: The default polling interval is of 5 seconds.
047:
048: @author Celso Pinto (cpinto@yimports.com)
049: */
050: public class FileObserverThread extends Thread implements
051: OptionsObserver, ScriptManagerDocument {
052:
053: private static final Logger LOG = Logger
054: .getLogger(FileObserverThread.class.getName());
055:
056: private final BeanshellConfig config;
057: private final ScriptFileFilter fileFilter;
058: private final InterpreterManager interpreterManager;
059: private final List observers;
060: private Map<String, ColumbaScript> scriptList;
061:
062: private long lastExecution;
063: private int pollingInterval = -1;
064: private boolean finish = false;
065:
066: private static FileObserverThread self = null;
067:
068: private FileObserverThread() {
069:
070: config = BeanshellConfig.getInstance();
071: fileFilter = new ScriptFileFilter();
072: interpreterManager = new InterpreterManager();
073:
074: scriptList = new HashMap<String, ColumbaScript>();
075: observers = new Vector();
076: lastExecution = System.currentTimeMillis();
077:
078: pollingInterval = config.getOptions()
079: .getInternalPollingInterval();
080: fileFilter.compileFilter(interpreterManager
081: .getSupportedExtensions());
082:
083: }
084:
085: public void setScriptList(Map scripts) {
086: scriptList = scripts;
087: }
088:
089: public void finish() {
090: finish = true;
091: }
092:
093: private synchronized void executeRefresh(boolean force) {
094: List changedFiles = checkFiles();
095:
096: if (changedFiles.size() > 0)
097: execChangedFiles(changedFiles);
098:
099: lastExecution = System.currentTimeMillis();
100:
101: }
102:
103: public void run() {
104: config.getOptions().addObserver(this );
105:
106: while (!finish) {
107:
108: executeRefresh(false);
109:
110: try {
111: sleep(pollingInterval);
112: } catch (InterruptedException ex) {
113: }
114:
115: }
116:
117: config.getOptions().removeObserver(this );
118: self = null;
119: }
120:
121: private List checkFiles() {
122:
123: List changedFiles = new ArrayList(), removedScripts = new ArrayList(), addedScripts = new ArrayList();
124:
125: // check current file list for changes
126: ColumbaScript script = null;
127: Map.Entry entry = null;
128:
129: synchronized (scriptList) {
130:
131: for (Iterator itCurrent = scriptList.entrySet().iterator(); itCurrent
132: .hasNext();) {
133: entry = (Map.Entry) itCurrent.next();
134: script = (ColumbaScript) entry.getValue();
135:
136: LOG.fine("current script.name=" + script.getName());
137: LOG.fine("current script.path=" + script.getPath());
138:
139: if (!script.exists()) {
140: // it isn't possible to undo whatever the script did
141: removedScripts.add(script);
142: itCurrent.remove();
143: continue;
144: }
145:
146: if (script.getLastModified() > lastExecution)
147: changedFiles.add(script);
148:
149: }
150:
151: /* check for new files in the scripts directory */
152: File[] scripts = getNewScripts();
153: LOG.fine("script file count=" + scripts.length);
154:
155: for (int i = 0; i < scripts.length; i++) {
156: LOG.fine("script file=" + scripts[i].getAbsolutePath());
157:
158: if (!scriptList.containsKey(scripts[i].getPath())) {
159: script = new ColumbaScript(scripts[i]);
160:
161: LOG.fine("added script.name=" + script.getName());
162: LOG.fine("added script.path=" + script.getPath());
163:
164: changedFiles.add(script);
165: scriptList.put(scripts[i].getPath(), script);
166: addedScripts.add(script);
167: }
168: }
169:
170: }
171:
172: for (Iterator it = observers.iterator(); it.hasNext();) {
173:
174: IScriptsObserver obs = (IScriptsObserver) it.next();
175: if (removedScripts.size() > 0)
176: obs.scriptsRemoved(removedScripts);
177:
178: if (addedScripts.size() > 0)
179: obs.scriptsAdded(addedScripts);
180:
181: if (changedFiles.size() > 0)
182: obs.scriptsChanged(changedFiles);
183:
184: }
185:
186: return changedFiles;
187:
188: }
189:
190: private File[] getNewScripts() {
191: /*
192: * I specifically want this here to ensure that the directory exists and
193: * this method never returns null.
194: *
195: * Any files that were in the observation list have already been
196: * previously removed by checkFiles().
197: */
198: File configPath = config.getPath();
199: if (!configPath.exists() || !configPath.isDirectory()) {
200: LOG.warning("Scripts directory doesn't exist:"
201: + configPath.getPath());
202: return new File[] {};
203: }
204:
205: LOG.fine("script search path=" + configPath.getAbsolutePath());
206:
207: return configPath.listFiles(fileFilter);
208: }
209:
210: private void execChangedFiles(List files) {
211: for (Iterator it = files.iterator(); it.hasNext();)
212: interpreterManager.executeScript((ColumbaScript) it.next());
213: }
214:
215: private class ScriptFileFilter implements FileFilter {
216:
217: private Pattern extensionPattern = null;
218:
219: private String join(String[] values, char separator) {
220:
221: StringBuffer bf = new StringBuffer(256);
222: for (int i = 0; i < values.length; i++)
223: bf = bf.append(values[i] + separator);
224:
225: if (bf.length() > 0)
226: bf = bf.deleteCharAt(bf.length() - 1);
227:
228: return bf.toString();
229:
230: }
231:
232: public void compileFilter(String[] validExtensions) {
233: extensionPattern = Pattern.compile(".*\\.(".concat(
234: join(validExtensions, '|')).concat(")$"));
235: LOG.fine("valid extensions pattern="
236: + extensionPattern.toString());
237: }
238:
239: public boolean accept(File aPathname) {
240: boolean accept = extensionPattern.matcher(
241: aPathname.getPath()).matches();
242: LOG.fine("accept=" + aPathname.getAbsolutePath() + " -> "
243: + accept);
244:
245: return accept;
246: }
247:
248: }
249:
250: public void pollingIntervalChanged(int interval) {
251: pollingInterval = interval;
252: }
253:
254: public void pollingStateChanged(boolean enabled) {
255: /*TODO stop polling thread?! */
256: }
257:
258: public static FileObserverThread getInstance() {
259: if (self == null)
260: self = new FileObserverThread();
261:
262: return self;
263: }
264:
265: public void addObserver(IScriptsObserver observer) {
266: if (!observers.contains(observer))
267: observers.add(observer);
268: }
269:
270: public void removeObserver(IScriptsObserver observer) {
271: observers.remove(observer);
272: }
273:
274: public List getScripts() {
275: return new ArrayList<ColumbaScript>(scriptList.values());
276: }
277:
278: public void refreshScriptList() {
279: executeRefresh(true);
280: }
281:
282: public ColumbaScript getScript(String path) {
283: return (ColumbaScript) scriptList.get(path);
284: }
285:
286: public void removeScript(ColumbaScript[] scripts) {
287:
288: synchronized (scriptList) {
289:
290: for (int i = 0; i < scripts.length; i++) {
291:
292: /* really delete file */
293: LOG.fine("Removing script: " + scripts[i].getPath());
294:
295: if (scripts[i].exists())
296: scripts[i].deleteFromDisk();
297:
298: /* remove from script list */
299: scriptList.remove(scripts[i].getPath());
300:
301: }
302:
303: }
304:
305: List removed = Arrays.asList(scripts);
306: for (Iterator it = observers.iterator(); it.hasNext();)
307: ((IScriptsObserver) it.next()).scriptsRemoved(removed);
308:
309: }
310:
311: }
|