/grails-core/src/main/groovy/org/codehaus/groovy/grails/compiler/DirectoryWatcher.java

https://github.com/aclement/grails-core · Java · 220 lines · 128 code · 25 blank · 67 comment · 24 complexity · 2c799304f25d0d37db6aaaa33c93cae8 MD5 · raw file

  1. /*
  2. * Copyright 2011 SpringSource
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. package org.codehaus.groovy.grails.compiler;
  17. import java.io.File;
  18. import java.util.ArrayList;
  19. import java.util.Collection;
  20. import java.util.List;
  21. import java.util.Map;
  22. import java.util.Set;
  23. import java.util.concurrent.ConcurrentHashMap;
  24. import java.util.concurrent.ConcurrentLinkedQueue;
  25. import org.springframework.util.StringUtils;
  26. /**
  27. * Utility class to watch directories for changes.
  28. *
  29. * @author Graeme Rocher
  30. * @since 2.0
  31. */
  32. public class DirectoryWatcher extends Thread {
  33. protected Collection<String> extensions = new ConcurrentLinkedQueue<String>();
  34. private List<FileChangeListener> listeners = new ArrayList<FileChangeListener>();
  35. private Map<File, Long> lastModifiedMap = new ConcurrentHashMap<File, Long>();
  36. private Map<File, Long> directoryWatch = new ConcurrentHashMap<File, Long>();
  37. private boolean active = true;
  38. private long sleepTime = 3000;
  39. private boolean windows;
  40. public DirectoryWatcher() {
  41. setDaemon(true);
  42. windows = System.getProperty("os.name").toLowerCase().indexOf("windows") != -1;
  43. }
  44. /**
  45. * Sets whether to stop the directory watcher
  46. *
  47. * @param active False if you want to stop watching
  48. */
  49. public void setActive(boolean active) {
  50. this.active = active;
  51. }
  52. /**
  53. * Sets the amount of time to sleep between checks
  54. *
  55. * @param sleepTime The sleep time
  56. */
  57. public void setSleepTime(long sleepTime) {
  58. this.sleepTime = sleepTime;
  59. }
  60. /**
  61. * Adds a file listener that can react to change events
  62. *
  63. * @param listener The file listener
  64. */
  65. public void addListener(FileChangeListener listener) {
  66. this.listeners.add(listener);
  67. }
  68. /**
  69. * Adds a file to the watch list
  70. *
  71. * @param fileToWatch The file to watch
  72. */
  73. public void addWatchFile(File fileToWatch) {
  74. lastModifiedMap.put(fileToWatch, fileToWatch.lastModified());
  75. }
  76. /**
  77. * Adds a directory to watch for the given file and extensions.
  78. *
  79. * @param dir The directory
  80. * @param fileExtensions The extensions
  81. */
  82. public void addWatchDirectory(File dir, List<String> fileExtensions) {
  83. cacheFilesForDirectory(dir, fileExtensions, false);
  84. }
  85. /**
  86. * Adds a directory to watch for the given file and extensions.
  87. *
  88. * @param dir The directory
  89. * @param extension The extension
  90. */
  91. public void addWatchDirectory(File dir, String extension) {
  92. extension = removeStartingDotIfPresent(extension);
  93. List<String> fileExtensions = new ArrayList<String>();
  94. if (!StringUtils.hasText(extension)) {
  95. fileExtensions.add("*");
  96. }
  97. else {
  98. fileExtensions.add(extension);
  99. }
  100. cacheFilesForDirectory(dir, fileExtensions, false);
  101. }
  102. /**
  103. * Interface for FileChangeListeners
  104. */
  105. public static interface FileChangeListener {
  106. /**
  107. * Fired when a file changes
  108. *
  109. * @param file The file that changed
  110. */
  111. void onChange(File file);
  112. /**
  113. * Fired when a new file is created
  114. *
  115. * @param file The file that was created
  116. */
  117. void onNew(File file);
  118. }
  119. @Override
  120. public void run() {
  121. int count = 0;
  122. while (active) {
  123. Set<File> files = lastModifiedMap.keySet();
  124. for (File file : files) {
  125. long currentLastModified = file.lastModified();
  126. Long cachedTime = lastModifiedMap.get(file);
  127. if (currentLastModified > cachedTime) {
  128. lastModifiedMap.put(file, currentLastModified);
  129. fireOnChange(file);
  130. }
  131. }
  132. try {
  133. count++;
  134. if (count > 2) {
  135. count = 0;
  136. checkForNewFiles();
  137. }
  138. sleep(sleepTime);
  139. } catch (InterruptedException e) {
  140. // ignore
  141. }
  142. }
  143. }
  144. private void fireOnChange(File file) {
  145. for (FileChangeListener listener : listeners) {
  146. listener.onChange(file);
  147. }
  148. }
  149. private void checkForNewFiles() {
  150. for (File directory : directoryWatch.keySet()) {
  151. final Long currentTimestamp = directoryWatch.get(directory);
  152. if (windows || currentTimestamp < directory.lastModified()) {
  153. cacheFilesForDirectory(directory, extensions, true);
  154. }
  155. }
  156. }
  157. private void cacheFilesForDirectory(File directory, Collection<String> fileExtensions, boolean fireEvent) {
  158. addExtensions(fileExtensions);
  159. directoryWatch.put(directory, directory.lastModified());
  160. File[] files = directory.listFiles();
  161. if (files == null) {
  162. return;
  163. }
  164. for (File file : files) {
  165. if (file.isDirectory()) {
  166. cacheFilesForDirectory(file, fileExtensions, fireEvent);
  167. }
  168. else if (isValidFileToMonitor(file.getName(), fileExtensions)) {
  169. if (!lastModifiedMap.containsKey(file) && fireEvent) {
  170. for (FileChangeListener listener : listeners) {
  171. listener.onNew(file);
  172. }
  173. }
  174. lastModifiedMap.put(file, file.lastModified());
  175. }
  176. }
  177. }
  178. private void addExtensions(Collection<String> toAdd) {
  179. for (String extension : toAdd) {
  180. extension = removeStartingDotIfPresent(extension);
  181. if (!extensions.contains(extension)) {
  182. extensions.add(extension);
  183. }
  184. }
  185. }
  186. private String removeStartingDotIfPresent(String extension) {
  187. if (extension.startsWith(".")) {
  188. extension = extension.substring(1);
  189. }
  190. return extension;
  191. }
  192. private boolean isValidFileToMonitor(String name, Collection<String> fileExtensions) {
  193. return fileExtensions.contains("*") || fileExtensions.contains(StringUtils.getFilenameExtension(name));
  194. }
  195. }