001: /*
002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
003: *
004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
005: *
006: * The contents of this file are subject to the terms of either the GNU
007: * General Public License Version 2 only ("GPL") or the Common
008: * Development and Distribution License("CDDL") (collectively, the
009: * "License"). You may not use this file except in compliance with the
010: * License. You can obtain a copy of the License at
011: * http://www.netbeans.org/cddl-gplv2.html
012: * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
013: * specific language governing permissions and limitations under the
014: * License. When distributing the software, include this License Header
015: * Notice in each file and include the License file at
016: * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
017: * particular file as subject to the "Classpath" exception as provided
018: * by Sun in the GPL Version 2 section of the License file that
019: * accompanied this code. If applicable, add the following below the
020: * License Header, with the fields enclosed by brackets [] replaced by
021: * your own identifying information:
022: * "Portions Copyrighted [year] [name of copyright owner]"
023: *
024: * Contributor(s):
025: *
026: * The Original Software is NetBeans. The Initial Developer of the Original
027: * Software is Sun Microsystems, Inc. Portions Copyright 1997-2007 Sun
028: * Microsystems, Inc. All Rights Reserved.
029: *
030: * If you wish your version of this file to be governed by only the CDDL
031: * or only the GPL Version 2, indicate your decision by adding
032: * "[Contributor] elects to include this software in this distribution
033: * under the [CDDL or GPL Version 2] license." If you do not indicate a
034: * single choice of license, a recipient has the option to distribute
035: * your version of this file under either the CDDL, the GPL Version 2 or
036: * to extend the choice of license to its licensees as provided above.
037: * However, if you add GPL Version 2 code and therefore, elected the GPL
038: * Version 2 license, then the option applies only if the new code is
039: * made subject to such option by the copyright holder.
040: */
041:
042: package org.netbeans.modules.visualweb.insync.models;
043:
044: import java.beans.PropertyChangeEvent;
045: import java.beans.PropertyChangeListener;
046: import java.util.ArrayList;
047: import java.util.Arrays;
048: import java.util.Enumeration;
049: import java.util.HashMap;
050: import java.util.List;
051: import java.util.Map;
052:
053: import javax.swing.event.ChangeEvent;
054: import javax.swing.event.ChangeListener;
055:
056: import org.netbeans.api.project.FileOwnerQuery;
057: import org.netbeans.api.project.Project;
058: import org.netbeans.api.project.SourceGroup;
059: import org.netbeans.api.project.Sources;
060: import org.netbeans.api.project.ui.OpenProjects;
061: import org.netbeans.api.queries.FileBuiltQuery;
062: import org.openide.ErrorManager;
063: import org.openide.filesystems.FileAttributeEvent;
064: import org.openide.filesystems.FileChangeListener;
065: import org.openide.filesystems.FileEvent;
066: import org.openide.filesystems.FileObject;
067: import org.openide.filesystems.FileRenameEvent;
068: import org.openide.filesystems.FileStateInvalidException;
069: import org.openide.filesystems.FileSystem;
070: import org.openide.util.NbBundle;
071: import org.openide.util.WeakListeners;
072:
073: /**
074: * A query to check and monitor wheather a project can be considered to be built
075: * (up to date). This is analogous to <code>org.netbeans.api.queries.FileBuiltQuery</code>.
076: *
077: * The basic idea behind this is to consider a project built if all it's file are built. This
078: * implementation monitors the status of files under <code>org.netbeans.api.project.SourceGroup</code>
079: * returned by <code>org.netbeans.api.project.Sources</code> using the <code>FileBuiltQuery</code>.
080: * It returns a <code>ProjectBuiltQuery.Status</code> object for a specified project. Using this
081: * the client code can query if the project is built. The dynamic changes to the project's built
082: * state can be monitored using a <code>javax.swing.event.ChangeListener</code>.
083: *
084: * NOTE: Currently only <b>java</b> <code>org.netbeans.api.project.SourceGroup<code>s are monitored.
085: *
086: * @see FileBuiltQuery
087: * @author Sandip Chitale
088: */
089: public class ProjectBuiltQuery {
090:
091: /**
092: * This returns a <code>ProjectBuiltQuery.Status</code> object which can be
093: * used to query and monitor the built status of the project.
094: *
095: * This will throw a <code>NullPointerException</code> if a <code>null</code>
096: * <code>project</code> is passed in.
097: * This will throw a <code>IllegalArgumentException</code> if the
098: * <code>project</code> does not have any <code>org.netbeans.api.project.SourceGroup<code>s.
099: *
100: * @param project - the project for whose built status is to be monitor.
101: * @return Staus - an object to monitor
102: */
103: public static Status getStatus(Project project) {
104: return new StatusImpl(project);
105: }
106:
107: /**
108: * An interface to monitor and query the built state of a project.
109: */
110: public static interface Status {
111:
112: /**
113: * Returns the project being monitored.
114: *
115: * @return project being monitored.
116: */
117: Project getProject();
118:
119: /**
120: * Check whether the project is currently built.
121: *
122: * This will throw a <code>IllegalStateException</code> if the
123: * <code>project</code> is not open.
124: *
125: * @return true if the project is in built state, false if it may need to be built
126: */
127: boolean isBuilt();
128:
129: /**
130: * Add a listener to monitor changes in the built status.
131: *
132: * @param l a listener to add
133: */
134: void addChangeListener(ChangeListener l);
135:
136: /**
137: * Stop listening to changes.
138: * @param l a listener to remove
139: */
140: void removeChangeListener(ChangeListener l);
141: }
142:
143: private static class StatusImpl implements Status,
144: FileChangeListener, PropertyChangeListener {
145: private Project project;
146:
147: private boolean built = false;
148:
149: private Map<String, Boolean> fileObjectBuiltStatusMap;
150: private Map<String, FileObjectStatusChangeListener> fileObjectStatusChangeListenerMap;
151:
152: private List<ChangeListener> listeners = new ArrayList<ChangeListener>();
153:
154: // A simple class to monitor change in built status of a file path
155: private class FileObjectStatusChangeListener implements
156: ChangeListener {
157: private final String fileObjectPath;
158: private final FileBuiltQuery.Status status;
159:
160: FileObjectStatusChangeListener(
161: FileBuiltQuery.Status status, String fileObjectPath) {
162: this .fileObjectPath = fileObjectPath;
163: this .status = status;
164: this .status.addChangeListener(this );
165: }
166:
167: public void stateChanged(ChangeEvent e) {
168: FileBuiltQuery.Status status = (FileBuiltQuery.Status) e
169: .getSource();
170: if (status != null) {
171: synchronized (StatusImpl.this ) {
172: if (fileObjectBuiltStatusMap == null) {
173: dispose();
174: } else {
175: fileObjectBuiltStatusMap.put(
176: fileObjectPath, status.isBuilt());
177: update();
178: }
179: }
180: }
181: }
182:
183: void dispose() {
184: status.removeChangeListener(this );
185: }
186: }
187:
188: /**
189: * Returns the project being monitored.
190: *
191: * @return project being monitored.
192: */
193: StatusImpl(Project project) {
194: this .project = project;
195:
196: init();
197: }
198:
199: private void init() {
200: if (project == null) {
201: throw new NullPointerException();
202: }
203:
204: Sources sources = project.getLookup().lookup(Sources.class);
205: if (sources == null) {
206: throw new IllegalArgumentException(NbBundle.getMessage(
207: ProjectBuiltQuery.class,
208: "ERROR_ProjectHasNoSources"));
209: }
210: try {
211: fileObjectBuiltStatusMap = new HashMap<String, Boolean>();
212: fileObjectStatusChangeListenerMap = new HashMap<String, FileObjectStatusChangeListener>();
213: SourceGroup[] sourceGroups = sources
214: .getSourceGroups("java"); // TODO Support other source groups
215: for (SourceGroup group : sourceGroups) {
216: FileObject rootFileObject = group.getRootFolder();
217: if (rootFileObject != null
218: && rootFileObject.isFolder()) {
219: Enumeration<? extends FileObject> fileObjects = rootFileObject
220: .getChildren(true);
221: while (fileObjects.hasMoreElements()) {
222: FileObject fileObject = fileObjects
223: .nextElement();
224: FileBuiltQuery.Status status = FileBuiltQuery
225: .getStatus(fileObject);
226: if (status != null) {
227: String fileObjectPath = fileObject
228: .getPath();
229: synchronized (this ) {
230: fileObjectBuiltStatusMap.put(
231: fileObjectPath, status
232: .isBuilt());
233: fileObjectStatusChangeListenerMap
234: .put(
235: fileObjectPath,
236: new FileObjectStatusChangeListener(
237: status,
238: fileObjectPath));
239: }
240: }
241: }
242: }
243: }
244: update();
245: FileSystem fileSystem = project.getProjectDirectory()
246: .getFileSystem();
247: // monitor file addition, deletion, rename
248: fileSystem.addFileChangeListener(WeakListeners.create(
249: FileChangeListener.class, this , fileSystem));
250: // Monitor project close.
251: OpenProjects.getDefault().addPropertyChangeListener(
252: WeakListeners.propertyChange(this , OpenProjects
253: .getDefault()));
254: } catch (FileStateInvalidException e) {
255: ErrorManager.getDefault().notify(ErrorManager.ERROR, e);
256: }
257: }
258:
259: public void propertyChange(PropertyChangeEvent evt) {
260: if (OpenProjects.PROPERTY_OPEN_PROJECTS.equals(evt
261: .getPropertyName())) {
262: checkProjectOpen();
263: }
264: }
265:
266: public Project getProject() {
267: return project;
268: }
269:
270: public boolean isBuilt() {
271: if (!checkProjectOpen()) {
272: throw new IllegalStateException(NbBundle.getMessage(
273: ProjectBuiltQuery.class,
274: "ERROR_ProjectIsNotOpen"));
275: }
276: return built;
277: }
278:
279: public void addChangeListener(ChangeListener l) {
280: synchronized (listeners) {
281: listeners.add(l);
282: }
283: }
284:
285: public void removeChangeListener(ChangeListener l) {
286: synchronized (listeners) {
287: listeners.remove(l);
288: }
289: }
290:
291: private void fireChange() {
292: ChangeListener[] _listeners;
293: synchronized (listeners) {
294: if (listeners.isEmpty()) {
295: return;
296: }
297: _listeners = listeners
298: .toArray(new ChangeListener[listeners.size()]);
299: }
300: ChangeEvent ev = new ChangeEvent(this );
301: for (ChangeListener l : _listeners) {
302: l.stateChanged(ev);
303: }
304: }
305:
306: public void fileAttributeChanged(FileAttributeEvent fe) {
307: // Ignore
308: }
309:
310: public void fileChanged(FileEvent fe) {
311: // Ignore
312: }
313:
314: public void fileDataCreated(FileEvent fe) {
315: FileObject fileObject = fe.getFile();
316: Project owner = FileOwnerQuery.getOwner(fileObject);
317: if (owner == project) {
318: FileBuiltQuery.Status status = FileBuiltQuery
319: .getStatus(fileObject);
320: if (status != null) {
321: String fileObjectPath = fileObject.getPath();
322: synchronized (this ) {
323: fileObjectBuiltStatusMap.put(fileObjectPath,
324: status.isBuilt());
325: fileObjectStatusChangeListenerMap.put(
326: fileObjectPath,
327: new FileObjectStatusChangeListener(
328: status, fileObjectPath));
329: }
330: update();
331: }
332: }
333: }
334:
335: public void fileDeleted(FileEvent fe) {
336: FileObject fileObject = fe.getFile();
337: Project owner = FileOwnerQuery.getOwner(fileObject);
338: if (owner == project) {
339: synchronized (this ) {
340: if (fileObjectBuiltStatusMap == null) {
341: return;
342: }
343: String fileObjectPath = fileObject.getPath();
344: fileObjectBuiltStatusMap.remove(fileObjectPath);
345: FileObjectStatusChangeListener fileObjectStatusChangeListener = fileObjectStatusChangeListenerMap
346: .remove(fileObjectPath);
347: if (fileObjectStatusChangeListener != null) {
348: fileObjectStatusChangeListener.dispose();
349: }
350: }
351: update();
352: }
353: }
354:
355: public void fileFolderCreated(FileEvent fe) {
356: // Ignore
357: }
358:
359: public void fileRenamed(FileRenameEvent fe) {
360: FileObject fileObject = fe.getFile();
361: Project owner = FileOwnerQuery.getOwner(fileObject);
362: if (owner == project) {
363: String ext = fe.getExt();
364: String fileObjectPathBeforeRename = fileObject
365: .getParent().getPath()
366: + "/"
367: + fe.getName()
368: + (ext.length() == 0 ? ext : "." + ext);
369: FileBuiltQuery.Status status = FileBuiltQuery
370: .getStatus(fileObject);
371: String fileObjectPath = fileObject.getPath();
372: synchronized (this ) {
373: fileObjectBuiltStatusMap
374: .remove(fileObjectPathBeforeRename);
375: FileObjectStatusChangeListener fileObjectStatusChangeListener = fileObjectStatusChangeListenerMap
376: .remove(fileObjectPathBeforeRename);
377: if (fileObjectStatusChangeListener != null) {
378: fileObjectStatusChangeListener.dispose();
379: }
380: if (status != null) {
381: fileObjectBuiltStatusMap.put(fileObjectPath,
382: status.isBuilt());
383: fileObjectStatusChangeListenerMap.put(
384: fileObjectPath,
385: new FileObjectStatusChangeListener(
386: status, fileObjectPath));
387: }
388: }
389: update();
390: }
391: }
392:
393: private void update() {
394: synchronized (this ) {
395: boolean newBuiltStatus = true;
396: for (String fileObjectPath : fileObjectBuiltStatusMap
397: .keySet()) {
398: boolean fileObjectBuilt = fileObjectBuiltStatusMap
399: .get(fileObjectPath);
400: if (!fileObjectBuilt) {
401: newBuiltStatus = false;
402: break;
403: }
404: }
405:
406: if (built != newBuiltStatus) {
407: built = newBuiltStatus;
408: fireChange();
409: }
410: }
411: }
412:
413: private boolean isProjectOpen() {
414: if (project == null) {
415: return false;
416: }
417: return Arrays.asList(
418: OpenProjects.getDefault().getOpenProjects())
419: .contains(project);
420: }
421:
422: private boolean checkProjectOpen() {
423: boolean open = isProjectOpen();
424: if (!open) {
425: dispose();
426: }
427: return open;
428: }
429:
430: private void dispose() {
431: if (project == null) {
432: return;
433: }
434: project = null;
435: fileObjectBuiltStatusMap = null;
436: synchronized (this ) {
437: for (FileObjectStatusChangeListener fileObjectStatusChangeListener : fileObjectStatusChangeListenerMap
438: .values()) {
439: fileObjectStatusChangeListener.dispose();
440: }
441: }
442: fileObjectStatusChangeListenerMap = null;
443: listeners = null;
444: }
445: }
446: }
|