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.search;
042:
043: import java.awt.EventQueue;
044: import java.lang.ref.Reference;
045: import java.lang.ref.WeakReference;
046: import java.util.ArrayList;
047: import java.util.Collection;
048: import java.util.Collections;
049: import java.util.Iterator;
050: import java.util.LinkedHashMap;
051: import java.util.List;
052: import java.util.Map;
053: import java.util.logging.Logger;
054: import javax.swing.event.ChangeEvent;
055: import javax.swing.event.ChangeListener;
056: import org.openide.util.Lookup;
057: import static java.util.logging.Level.FINER;
058:
059: /**
060: * Registry of {@code SearchScope}s.
061: * It holds information about registered {@code SearchScope}s
062: * and it informs listeners about changes of their state
063: * - see {@link #addChangeListener addChangeListener(...)},
064: * {@link #removeChangeListener removeChangeListener(...)}.
065: *
066: * @author Marian Petras
067: */
068: public final class SearchScopeRegistry {
069:
070: private static SearchScopeRegistry defaultInstance;
071:
072: private static final Logger LOG = Logger
073: .getLogger("org.netbeans.modules.search.SearchScopeRegistry"); //NOI18N
074:
075: private final int id;
076: private Collection<Reference<SearchScopeRegistry>> extraInstances;
077: private SearchScopeChangeHandler scopeChangeHandler;
078: private List<ChangeListener> changeListeners;
079:
080: private int projectSearchScopesCount;
081: private int applicableSearchScopesCount;
082: private Map<SearchScope, Boolean> searchScopes = new LinkedHashMap<SearchScope, Boolean>(
083: 5);
084:
085: public static synchronized SearchScopeRegistry getDefault() {
086: if (defaultInstance == null) {
087: defaultInstance = new SearchScopeRegistry(0);
088: }
089: return defaultInstance;
090: }
091:
092: static SearchScopeRegistry getInstance(Lookup lookup, int id) {
093: return getDefault().getLookupInstance(lookup, id);
094: }
095:
096: private SearchScopeRegistry(int id) {
097: this .id = id;
098: }
099:
100: private SearchScopeRegistry getLookupInstance(final Lookup lookup,
101: final int id) {
102: assert id > 0;
103: assert EventQueue.isDispatchThread();
104: assert this == defaultInstance;
105: if (LOG.isLoggable(FINER)) {
106: LOG.finer("getLookupInstance(Lookup, " + id + ')');
107: }
108:
109: SearchScopeRegistry instance;
110: Collection<SearchScope> scopes;
111: synchronized (getLock()) {
112: instance = new SearchScopeRegistry(id);
113: if (extraInstances == null) {
114: extraInstances = new ArrayList<Reference<SearchScopeRegistry>>(
115: 4);
116: }
117: extraInstances.add(new WeakReference<SearchScopeRegistry>(
118: instance));
119:
120: scopes = cloneSearchScopes(defaultInstance);
121: }
122: for (SearchScope scope : scopes) {
123: instance.registerSearchScope(scope
124: .getContextSensitiveInstance(lookup));
125: }
126: return instance;
127: }
128:
129: public void registerSearchScope(final SearchScope searchScope) {
130: /* thread: <any> */
131: if (LOG.isLoggable(FINER)) {
132: log("register search scope " + searchScope);
133: }
134:
135: final Collection<ChangeListener> listeners;
136: final Collection<SearchScopeRegistry> lookupInstances;
137: synchronized (getLock()) {
138: if (scopeChangeHandler != null) { //listening
139: searchScope.addChangeListener(scopeChangeHandler);
140: listeners = checkNewState(searchScope, Boolean.TRUE) ? cloneChangeListeners()
141: : null;
142: } else {
143: searchScopes.put(searchScope, null);
144: listeners = null;
145: }
146: if (isProjectSearchScope(searchScope)) {
147: projectSearchScopesCount++;
148: }
149: lookupInstances = cloneLookupInstances();
150: }
151: if (listeners != null) {
152: notifyListeners(listeners);
153: }
154: if (!lookupInstances.isEmpty()) {
155: for (SearchScopeRegistry instance : lookupInstances) {
156: instance.registerSearchScope(searchScope);
157: }
158: }
159: }
160:
161: public void unregisterSearchScope(final SearchScope searchScope) {
162: /* thread: <any> */
163: if (LOG.isLoggable(FINER)) {
164: log("unregister search scope " + searchScope);
165: }
166:
167: final Collection<ChangeListener> listeners;
168: final Collection<SearchScopeRegistry> lookupInstances;
169: synchronized (getLock()) {
170: if (scopeChangeHandler != null) { //listening
171: searchScope.removeChangeListener(scopeChangeHandler);
172: listeners = checkNewState(searchScope, Boolean.FALSE) ? cloneChangeListeners()
173: : null;
174: } else {
175: searchScopes.remove(searchScope);
176: listeners = null;
177: }
178: if (isProjectSearchScope(searchScope)) {
179: projectSearchScopesCount--;
180: }
181: lookupInstances = cloneLookupInstances();
182: }
183: if (!lookupInstances.isEmpty()) {
184: for (SearchScopeRegistry instance : lookupInstances) {
185: instance.unregisterSearchScope(searchScope);
186: }
187: }
188: }
189:
190: void addChangeListener(final ChangeListener l) {
191: /* thread: <any> */
192: assert l != null;
193: if (LOG.isLoggable(FINER)) {
194: log("addChangeListener(" + l + ')');
195: }
196:
197: synchronized (getLock()) {
198: boolean firstListener = (changeListeners == null);
199: if (changeListeners == null) {
200: changeListeners = new ArrayList<ChangeListener>(1);
201: }
202: changeListeners.add(l);
203:
204: if (firstListener) {
205: assert applicableSearchScopesCount == 0;
206: applicableSearchScopesCount = 0;
207: scopeChangeHandler = new SearchScopeChangeHandler();
208: for (Map.Entry<SearchScope, Boolean> entry : searchScopes
209: .entrySet()) {
210: SearchScope scope = entry.getKey();
211: scope.addChangeListener(scopeChangeHandler);
212: boolean applicable = scope.isApplicable();
213: if (applicable) {
214: applicableSearchScopesCount++;
215: }
216: entry.setValue(applicable); //auto-boxing
217: }
218: if (LOG.isLoggable(FINER)) {
219: log(" - initial applicable search scopes count: "
220: + applicableSearchScopesCount);
221: }
222: }
223: }
224: }
225:
226: void removeChangeListener(final ChangeListener l) {
227: /* thread: <any> */
228: assert l != null;
229: if (LOG.isLoggable(FINER)) {
230: log("removeChangeListener(" + l + ')');
231: }
232:
233: synchronized (getLock()) {
234: if (changeListeners == null) {
235: return;
236: }
237: boolean lastListener = changeListeners.remove(l)
238: && changeListeners.isEmpty();
239: if (lastListener) {
240: changeListeners = null;
241: applicableSearchScopesCount = 0;
242: for (SearchScope scope : searchScopes.keySet()) {
243: scope.removeChangeListener(scopeChangeHandler);
244: }
245: scopeChangeHandler = null;
246: }
247: }
248: }
249:
250: private final class SearchScopeChangeHandler implements
251: ChangeListener {
252: public void stateChanged(ChangeEvent e) {
253: assert e.getSource() instanceof SearchScope;
254: searchScopeStateChanged((SearchScope) e.getSource());
255: }
256: }
257:
258: private void searchScopeStateChanged(final SearchScope searchScope) {
259: /* thread: <any> */
260: if (LOG.isLoggable(FINER)) {
261: log("searchScopeStateChanged(" + searchScope + ')');
262: }
263:
264: final Collection<ChangeListener> listeners;
265: synchronized (getLock()) {
266: listeners = checkNewState(searchScope, null) ? cloneChangeListeners()
267: : null;
268: }
269: if (listeners != null) {
270: notifyListeners(listeners);
271: }
272: }
273:
274: private boolean checkNewState(final SearchScope searchScope,
275: final Boolean addition) {
276: /* thread: <any> */
277: assert Thread.holdsLock(getLock());
278: if (LOG.isLoggable(FINER)) {
279: log("checkNewState(" + searchScope + ')');
280: }
281:
282: boolean newValue;
283: if (addition == null) { //SearchScope state changed
284: newValue = searchScope.isApplicable();
285: Boolean oldValue = searchScopes.put(searchScope, newValue);
286: if (oldValue == null) {
287: searchScopes.remove(searchScope);
288: return false;
289: }
290: if (newValue == oldValue.booleanValue()) {
291: return false;
292: }
293: } else if (addition.booleanValue()) { //SearchScope registered
294: newValue = searchScope.isApplicable();
295: searchScopes.put(searchScope, newValue);
296: } else { //SearchScope unregistered
297: newValue = false;
298: searchScopes.remove(searchScope);
299: }
300:
301: boolean stateChanged;
302: if (newValue) {
303: applicableSearchScopesCount++;
304: if (LOG.isLoggable(FINER)) {
305: log(" - search scope count increased to "
306: + applicableSearchScopesCount);
307: }
308: stateChanged = (applicableSearchScopesCount == 1);
309: } else {
310: applicableSearchScopesCount--;
311: if (LOG.isLoggable(FINER)) {
312: log(" - search scope count decreased to "
313: + applicableSearchScopesCount);
314: }
315: stateChanged = (applicableSearchScopesCount == 0);
316: }
317: return stateChanged;
318: }
319:
320: private void notifyListeners(Collection<ChangeListener> listeners) {
321: assert listeners != null;
322:
323: final ChangeEvent e = new ChangeEvent(this );
324: for (ChangeListener listener : listeners) {
325: listener.stateChanged(e);
326: }
327: }
328:
329: SearchScope getNodeSelectionSearchScope() {
330: /*
331: * There are several conditions that must be met for implementation
332: * of this method to work correctly:
333: * - the Map of search scopes (field "searchScopes") preserves order
334: * - node selection search scope is the first registered search scope
335: * - lookup-sensitive search scopes are registered in the same order
336: * as the default search scopes
337: * - lookup-sensitive variant of SearchScopeNodeSelection
338: * is a (direct or indirect) subclass of SearchScopeNodeSelection
339: */
340: SearchScope nodeSelectionScope;
341: synchronized (getLock()) {
342: if (searchScopes.isEmpty()) {
343: nodeSelectionScope = null;
344: } else {
345: nodeSelectionScope = searchScopes.entrySet().iterator()
346: .next().getKey();
347: assert nodeSelectionScope
348: .getClass()
349: .getName()
350: .startsWith(
351: "org.netbeans.modules.search.SearchScopeNodeSelection");//NOI18N
352: }
353: }
354: return nodeSelectionScope;
355: }
356:
357: public boolean hasApplicableSearchScope() {
358: /* thread: <any> */
359: if (LOG.isLoggable(FINER)) {
360: log("hasApplicableSearchScope");
361: }
362:
363: synchronized (getLock()) {
364: if (changeListeners != null) {
365: if (LOG.isLoggable(FINER)) {
366: log(" - listening, search scopes count = "
367: + applicableSearchScopesCount);
368: }
369: return (applicableSearchScopesCount != 0);
370: } else {
371: if (LOG.isLoggable(FINER)) {
372: log(" - not listening, going to check...");
373: }
374: return checkIsApplicable();
375: }
376: }
377: }
378:
379: Map<SearchScope, Boolean> getSearchScopes() {
380: assert EventQueue.isDispatchThread();
381:
382: final Map<SearchScope, Boolean> result;
383: final Collection<SearchScope> scopes;
384: synchronized (getLock()) {
385: if (changeListeners != null) {
386: return new LinkedHashMap<SearchScope, Boolean>(
387: searchScopes);
388: }
389:
390: scopes = cloneSearchScopes();
391: }
392:
393: result = new LinkedHashMap<SearchScope, Boolean>(
394: scopes.size() * 2);
395: for (SearchScope scope : scopes) {
396: result.put(scope, scope.isApplicable());
397: }
398: return result;
399: }
400:
401: boolean hasProjectSearchScopes() {
402: return projectSearchScopesCount > 0;
403: }
404:
405: /**
406: * Checks whether the given collection of {@code SearchScope}s contains
407: * a project-type search scope.
408: *
409: * @param searchScopes collection of search scopes to be checked
410: * @return {@code true} if the given collection contains at least
411: * one project-type search scope, {@code false} otherwise
412: * @see #isProjectSearchScope(SearchScope)
413: */
414: static boolean hasProjectSearchScopes(
415: Collection<SearchScope> searchScopes) {
416: if (searchScopes.isEmpty()) {
417: return false;
418: }
419:
420: for (SearchScope searchScope : searchScopes) {
421: if (isProjectSearchScope(searchScope)) {
422: return true;
423: }
424: }
425: return false;
426: }
427:
428: private static boolean isProjectSearchScope(SearchScope searchScope) {
429: return searchScope.getClass().getName().startsWith(
430: "org.netbeans.modules.search.project"); //NOI18N
431: }
432:
433: private boolean checkIsApplicable() {
434: /* thread: <any> */
435: if (LOG.isLoggable(FINER)) {
436: log("checkIsApplicable()");
437: }
438:
439: final Collection<SearchScope> scopes;
440: synchronized (getLock()) {
441: scopes = cloneSearchScopes();
442: }
443: for (SearchScope searchScope : scopes) {
444: if (searchScope.isApplicable()) {
445: if (LOG.isLoggable(FINER)) {
446: log(" - returning true");
447: }
448: return true;
449: }
450: }
451: if (LOG.isLoggable(FINER)) {
452: log(" - returning false");
453: }
454: return false;
455: }
456:
457: private Object getLock() {
458: return this ;
459: }
460:
461: /**
462: * Gets a copy of lookup-sensitive {@code SearchScopeRegistry}
463: * instances that are accessible via references held in
464: * {@link #extraInstances}.
465: *
466: * @return collection of lookup-sensitive {@code SearchScopeRegistry}
467: * instances, or an empty list of there are no lookup-sensitive
468: * instances accessible
469: */
470: private Collection<SearchScopeRegistry> cloneLookupInstances() {
471: assert Thread.holdsLock(getLock());
472:
473: Collection<SearchScopeRegistry> extras;
474: if ((extraInstances != null) && !extraInstances.isEmpty()) {
475: extras = new ArrayList<SearchScopeRegistry>(extraInstances
476: .size());
477: Iterator<Reference<SearchScopeRegistry>> it = extraInstances
478: .iterator();
479: while (it.hasNext()) {
480: Reference<SearchScopeRegistry> extraInstanceRef = it
481: .next();
482: SearchScopeRegistry inst = extraInstanceRef.get();
483: if (inst == null) {
484: it.remove();
485: continue;
486: }
487: extras.add(inst);
488: }
489: assert extras.size() == extraInstances.size();
490: } else {
491: extras = null;
492: }
493: if ((extras == null) || extras.isEmpty()) {
494: extras = null;
495: extraInstances = null;
496: }
497: return (extras != null) ? extras : Collections
498: .<SearchScopeRegistry> emptyList();
499: }
500:
501: /**
502: */
503: private Collection<SearchScope> cloneSearchScopes() {
504: return cloneSearchScopes(this );
505: }
506:
507: /**
508: */
509: private Collection<SearchScope> cloneSearchScopes(
510: SearchScopeRegistry fromInstance) {
511: assert Thread.holdsLock(getLock());
512:
513: return new ArrayList<SearchScope>(fromInstance.searchScopes
514: .keySet());
515: }
516:
517: /**
518: */
519: private Collection<ChangeListener> cloneChangeListeners() {
520: assert Thread.holdsLock(getLock());
521:
522: return (changeListeners != null) ? new ArrayList<ChangeListener>(
523: changeListeners)
524: : null;
525: }
526:
527: private void log(String msg) {
528: LOG.finer("registry #" + id + ": " + msg);
529: }
530:
531: }
|