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: package org.netbeans.modules.versioning;
042:
043: import org.netbeans.modules.versioning.spi.VersioningSystem;
044: import org.netbeans.modules.versioning.spi.VCSContext;
045: import org.netbeans.modules.versioning.spi.VersioningSupport;
046: import org.netbeans.modules.versioning.diff.DiffSidebarManager;
047: import org.netbeans.modules.masterfs.providers.InterceptionListener;
048: import org.openide.util.Lookup;
049: import org.openide.util.LookupListener;
050: import org.openide.util.LookupEvent;
051:
052: import java.io.File;
053: import java.util.*;
054: import java.util.prefs.PreferenceChangeListener;
055: import java.util.prefs.PreferenceChangeEvent;
056: import java.beans.PropertyChangeListener;
057: import java.beans.PropertyChangeEvent;
058:
059: /**
060: * Top level versioning manager that mediates communitation between IDE and registered versioning systems.
061: *
062: * @author Maros Sandor
063: */
064: public class VersioningManager implements PropertyChangeListener,
065: LookupListener, PreferenceChangeListener {
066:
067: /**
068: * Indicates to the Versioning manager that the layout of versioned files may have changed. Previously unversioned
069: * files became versioned, versioned files became unversioned or the versioning system for some files changed.
070: * The manager will flush any caches that may be holding such information.
071: * A versioning system usually needs to fire this after an Import action.
072: */
073: public static final String EVENT_VERSIONED_ROOTS = "null VCS.VersionedFilesChanged";
074:
075: /**
076: * The NEW value is a Set of Files whose versioning status changed. This event is used to re-annotate files, re-fetch
077: * original content of files and generally refresh all components that are connected to these files.
078: */
079: public static final String EVENT_STATUS_CHANGED = "Set<File> VCS.StatusChanged";
080:
081: /**
082: * Used to signal the Versioning manager that some annotations changed. Note that this event is NOT required in case
083: * the status of the file changes in which case annotations are updated automatically. Use this event to force annotations
084: * refresh in special cases, for example when the format of annotations changes.
085: * Use null as new value to force refresh of all annotations.
086: */
087: public static final String EVENT_ANNOTATIONS_CHANGED = "Set<File> VCS.AnnotationsChanged";
088:
089: private static VersioningManager instance;
090:
091: public static synchronized VersioningManager getInstance() {
092: if (instance == null) {
093: instance = new VersioningManager();
094: instance.init();
095: }
096: return instance;
097: }
098:
099: // ======================================================================================================
100:
101: private final FilesystemInterceptor filesystemInterceptor;
102:
103: /**
104: * Result of Lookup.getDefault().lookup(new Lookup.Template<VersioningSystem>(VersioningSystem.class));
105: */
106: private final Lookup.Result<VersioningSystem> systemsLookupResult;
107:
108: /**
109: * Holds all registered versioning systems.
110: */
111: private final Collection<VersioningSystem> versioningSystems = new ArrayList<VersioningSystem>(
112: 2);
113:
114: /**
115: * What folder is versioned by what versioning system.
116: * TODO: use SoftHashMap if there is one available in APIs
117: */
118: private final Map<File, VersioningSystem> folderOwners = new HashMap<File, VersioningSystem>(
119: 200);
120:
121: /**
122: * Holds registered local history system.
123: */
124: private VersioningSystem localHistory;
125:
126: /**
127: * What folders are managed by local history.
128: * TODO: use SoftHashMap if there is one available in APIs
129: */
130: private Map<File, Boolean> localHistoryFolders = new HashMap<File, Boolean>(
131: 200);
132:
133: private final VersioningSystem NULL_OWNER = new VersioningSystem() {
134: };
135:
136: private VersioningManager() {
137: systemsLookupResult = Lookup.getDefault().lookup(
138: new Lookup.Template<VersioningSystem>(
139: VersioningSystem.class));
140: filesystemInterceptor = new FilesystemInterceptor();
141: }
142:
143: private void init() {
144: systemsLookupResult.addLookupListener(this );
145: refreshVersioningSystems();
146: filesystemInterceptor.init(this );
147: VersioningSupport.getPreferences().addPreferenceChangeListener(
148: this );
149: }
150:
151: /**
152: * List of versioning systems changed.
153: */
154: private synchronized void refreshVersioningSystems() {
155: unloadVersioningSystems();
156: Collection<? extends VersioningSystem> systems = systemsLookupResult
157: .allInstances();
158: loadVersioningSystems(systems);
159: flushFileOwnerCache();
160: refreshDiffSidebars(null);
161: VersioningAnnotationProvider.refreshAllAnnotations();
162: }
163:
164: private void loadVersioningSystems(
165: Collection<? extends VersioningSystem> systems) {
166: assert versioningSystems.size() == 0;
167: assert localHistory == null;
168: versioningSystems.addAll(systems);
169: for (VersioningSystem system : versioningSystems) {
170: if (localHistory == null && Utils.isLocalHistory(system)) {
171: localHistory = system;
172: }
173: system.addPropertyChangeListener(this );
174: }
175: }
176:
177: private void unloadVersioningSystems() {
178: for (VersioningSystem system : versioningSystems) {
179: system.removePropertyChangeListener(this );
180: }
181: versioningSystems.clear();
182: localHistory = null;
183: }
184:
185: InterceptionListener getInterceptionListener() {
186: return filesystemInterceptor;
187: }
188:
189: private void refreshDiffSidebars(Set<File> files) {
190: // pushing the change ... DiffSidebarManager may as well listen for changes
191: DiffSidebarManager.getInstance().refreshSidebars(files);
192: }
193:
194: private synchronized void flushFileOwnerCache() {
195: folderOwners.clear();
196: localHistoryFolders.clear();
197: }
198:
199: synchronized VersioningSystem[] getVersioningSystems() {
200: return versioningSystems
201: .toArray(new VersioningSystem[versioningSystems.size()]);
202: }
203:
204: /**
205: * Determines versioning systems that manage files in given context.
206: *
207: * @param ctx VCSContext to examine
208: * @return VersioningSystem systems that manage this context or an empty array if the context is not versioned
209: */
210: VersioningSystem[] getOwners(VCSContext ctx) {
211: Set<File> files = ctx.getRootFiles();
212: Set<VersioningSystem> owners = new HashSet<VersioningSystem>();
213: for (File file : files) {
214: VersioningSystem vs = getOwner(file);
215: if (vs != null) {
216: owners.add(vs);
217: }
218: }
219: return (VersioningSystem[]) owners
220: .toArray(new VersioningSystem[owners.size()]);
221: }
222:
223: /**
224: * Determines the versioning system that manages given file.
225: * Owner of a file:
226: * - annotates its label in explorers, editor tab, etc.
227: * - provides menu actions for it
228: * - supplies "original" content of the file
229: *
230: * Owner of a file may change over time (one common example is the Import command). In such case, the appropriate
231: * Versioning System is expected to fire the PROP_VERSIONED_ROOTS property change.
232: *
233: * @param file a file
234: * @return VersioningSystem owner of the file or null if the file is not under version control
235: */
236: public synchronized VersioningSystem getOwner(File file) {
237: File folder = file;
238: if (file.isFile()) {
239: folder = file.getParentFile();
240: if (folder == null)
241: return null;
242: }
243:
244: VersioningSystem owner = folderOwners.get(folder);
245: if (owner == NULL_OWNER)
246: return null;
247: if (owner != null)
248: return owner;
249:
250: File closestParent = null;
251: for (VersioningSystem system : versioningSystems) {
252: if (system != localHistory) { // currently, local history is never an owner of a file
253: File topmost = system.getTopmostManagedAncestor(folder);
254: if (topmost != null
255: && (closestParent == null || Utils
256: .isAncestorOrEqual(closestParent,
257: topmost))) {
258: owner = system;
259: closestParent = topmost;
260: }
261: }
262: }
263:
264: if (owner != null) {
265: folderOwners.put(folder, owner);
266: } else {
267: folderOwners.put(folder, NULL_OWNER);
268: }
269: return owner;
270: }
271:
272: /**
273: * Returns local history module that handles the given file.
274: *
275: * @param file the file to examine
276: * @return VersioningSystem local history versioning system or null if there is no local history for the file
277: */
278: synchronized VersioningSystem getLocalHistory(File file) {
279: if (localHistory == null)
280: return null;
281: File folder = file;
282: if (file.isFile()) {
283: folder = file.getParentFile();
284: if (folder == null)
285: return null;
286: }
287:
288: Boolean isManagedByLocalHistory = localHistoryFolders
289: .get(folder);
290: if (isManagedByLocalHistory != null) {
291: return isManagedByLocalHistory ? localHistory : null;
292: }
293:
294: boolean isManaged = localHistory
295: .getTopmostManagedAncestor(folder) != null;
296: if (isManaged) {
297: localHistoryFolders.put(folder, Boolean.TRUE);
298: return localHistory;
299: } else {
300: localHistoryFolders.put(folder, Boolean.FALSE);
301: return null;
302: }
303: }
304:
305: public void resultChanged(LookupEvent ev) {
306: refreshVersioningSystems();
307: }
308:
309: /**
310: * Versioning status or other parameter changed.
311: */
312: public void propertyChange(PropertyChangeEvent evt) {
313: if (EVENT_STATUS_CHANGED.equals(evt.getPropertyName())) {
314: Set<File> files = (Set<File>) evt.getNewValue();
315: VersioningAnnotationProvider.instance
316: .refreshAnnotations(files);
317: refreshDiffSidebars(files);
318: } else if (EVENT_ANNOTATIONS_CHANGED.equals(evt
319: .getPropertyName())) {
320: Set<File> files = (Set<File>) evt.getNewValue();
321: VersioningAnnotationProvider.instance
322: .refreshAnnotations(files);
323: } else if (EVENT_VERSIONED_ROOTS.equals(evt.getPropertyName())) {
324: flushFileOwnerCache();
325: refreshDiffSidebars(null);
326: }
327: }
328:
329: public void preferenceChange(PreferenceChangeEvent evt) {
330: VersioningAnnotationProvider.instance.refreshAnnotations(null);
331: }
332: }
|