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.openidex.search;
043:
044: import java.beans.PropertyChangeListener;
045: import java.beans.PropertyChangeSupport;
046: import java.util.*;
047:
048: import org.openide.filesystems.FileObject;
049: import org.openide.loaders.DataObject;
050: import org.openide.nodes.Node;
051:
052: /**
053: * Class which groups individual search types. It provides several services
054: * to provide search on them. The services are scanning node system to
055: * provide search object for group of search types -> efficient search.
056: *
057: * @author Peter Zavadsky
058: * @author Marian Petras
059: */
060: public abstract class SearchGroup {
061:
062: /**
063: * Property name which is fired when performing search and searched object
064: * passed criteria.
065: */
066: public static final String PROP_FOUND = "org.openidex.search.found"; // NOI18N
067:
068: /**
069: * Property name which is fired for in for the case original <code>node</code>'s has
070: * changed the way <code>result</code> was changed based on set criteria.
071: * Interested listeners should then get the event with values
072: * <UL>
073: * <LI>property change name = PROP_RESULT
074: * <LI>property source = this search type instance
075: * <LI>old value = detail which was changed or <code>null</code> there wasn't before for the node -> new value has to be non-null
076: * for the latter case.
077: * <LI>new value = detail which was changed or null if the node was removed from the result -> old value has to be non-null
078: * for that case
079: * </UL>
080: * This allows implementation of the dynamic changing of result suggested
081: * by Jesse and Sebastian (at least partially implemented now).
082: */
083: public static final String PROP_RESULT = "org.openidex.search.result"; // NOI18N
084:
085: /** Property change support. */
086: private PropertyChangeSupport propChangeSupport;
087:
088: /** search types added to this search group */
089: protected SearchType[] searchTypes = new SearchType[0];
090:
091: /** Set of nodes on which sub-system to search. */
092: protected final Set<Node> searchRoots = new HashSet<Node>(5);
093:
094: /** Set of objects which passed the search criteria (searchtypes).*/
095: protected final Set<Object> resultObjects = new LinkedHashSet<Object>(
096: 50);
097:
098: /** Flag indicating the search should be stopped. */
099: protected volatile boolean stopped = false;
100:
101: private PropertyChangeListener propListener;
102:
103: /**
104: * Adds a search type to this search group.
105: * If the group already contains the search type, the group is left
106: * unmodified.
107: *
108: * @param searchType search type to be added
109: */
110: protected void add(SearchType searchType) {
111:
112: /* Check whether the search type is already in the list: */
113: for (int i = 0; i < searchTypes.length; i++) {
114: if (searchType.equals(searchTypes[i])) {
115: return;
116: }
117: }
118:
119: /* Add the search type to the list: */
120: SearchType[] temp = new SearchType[searchTypes.length + 1];
121: System.arraycopy(searchTypes, 0, temp, 0, searchTypes.length);
122: temp[searchTypes.length] = searchType;
123: searchTypes = temp;
124: }
125:
126: /**
127: * Returns list of search types.
128: *
129: * @return search types added to this group
130: * @see #add
131: */
132: public SearchType[] getSearchTypes() {
133: return searchTypes;
134: }
135:
136: /**
137: * Sets roots of nodes in which its interested to search.
138: * This method is called at the first search type in the possible created
139: * chain of search types.
140: */
141: public void setSearchRootNodes(Node[] roots) {
142:
143: /*
144: * Gives a chance for individual search types to exclude some
145: * node systems. E.g. CVS search type is not interested
146: * in non CVS node systems.
147: */
148: for (SearchType searchType : searchTypes) {
149: roots = searchType.acceptSearchRootNodes(roots);
150: }
151: searchRoots.clear();
152: searchRoots.addAll(Arrays.asList(roots));
153: }
154:
155: /** Gets search root nodes. */
156: public Node[] getSearchRoots() {
157: return searchRoots.toArray(new Node[searchRoots.size()]);
158: }
159:
160: /** Stops searching. */
161: public final void stopSearch() {
162: stopped = true;
163: }
164:
165: /**
166: * Does search.
167: *
168: * @throw RuntimeException USER level annotated runtime exception
169: * on low memory condition (instead of OutOfMemoryError)
170: */
171: public void search() {
172: resultObjects.clear();
173: prepareSearch();
174: doSearch();
175: }
176:
177: /**
178: * Prepares search.
179: */
180: protected void prepareSearch() {
181: }
182:
183: /**
184: * Provides actual search. The subclasses implementating this method should scan the node system
185: * specified by <code>searchRoots</code>, extract search objects from them, add them
186: * to the search object set, test over all search type items in this group,
187: * in case if satisfied all it should fire <code>PROP_FOUND</code> property change and add
188: * the object to <code>resultObjects</code> set.
189: * The method implemenatation should call {@link #processSearchObject} method for each
190: * search object in the node systems.
191: */
192: protected abstract void doSearch();
193:
194: /**
195: * Provides search on one search object instance. The object is added to
196: * set of searched objects and passed to all search types encapsulated by
197: * this search group. In the case the object passes all search types is added
198: * to the result set and fired an event <code>PROP_FOUND</code> about successful
199: * match to interested property change listeners.
200: *
201: * @param searchObject object to provide actuall test on it. The actual instance
202: * has to be of type returned by all <code>SearchKey.getSearchObjectType</code>
203: * returned by <code>SearchType</code> of this <code>SearchGroup</code>
204: */
205: protected void processSearchObject(Object searchObject) {
206:
207: /*
208: * Give chance to individual search types to exclude some
209: * non interesting search objects from search. E.g. Java data
210: * object search will be not interested in non Java data objects.
211: */
212: for (int i = 0; i < searchTypes.length; i++) {
213: if (!searchTypes[i].acceptSearchObject(searchObject)) {
214: return;
215: }
216: }
217:
218: /*
219: * Give chance to provide additional things.
220: */
221: for (int i = 0; i < searchTypes.length; i++) {
222: searchTypes[i].prepareSearchObject(searchObject);
223: }
224:
225: /* Actually test the search object against all search types. */
226: for (int i = 0; i < searchTypes.length; i++) {
227: if (!searchTypes[i].testObject(searchObject)) {
228: return;
229: }
230: }
231:
232: /*
233: * In case the search object passed the search add it to the result set
234: * and fire an event about successful search to interested listeners.
235: */
236: resultObjects.add(searchObject);
237: firePropertyChange(PROP_FOUND, null, searchObject);
238: }
239:
240: /** Gets node for found object. */
241: public abstract Node getNodeForFoundObject(Object object);
242:
243: /** Getter for result object property. */
244: public Set<Object> getResultObjects() {
245: return new LinkedHashSet<Object>(resultObjects);
246: }
247:
248: /** Adds property change listener. */
249: public synchronized void addPropertyChangeListener(
250: PropertyChangeListener l) {
251: getPropertySupport().addPropertyChangeListener(l);
252: }
253:
254: /** Removes property change listener. */
255: public synchronized void removePropertyChangeListener(
256: PropertyChangeListener l) {
257: getPropertySupport().removePropertyChangeListener(l);
258: }
259:
260: /** Fires property change event. */
261: protected void firePropertyChange(String name, Object oldValue,
262: Object newValue) {
263: getPropertySupport().firePropertyChange(name, oldValue,
264: newValue);
265: }
266:
267: /** Gets lazy initialized property change support. */
268: private synchronized PropertyChangeSupport getPropertySupport() {
269: if (propChangeSupport == null)
270: propChangeSupport = new PropertyChangeSupport(this );
271:
272: return propChangeSupport;
273: }
274:
275: /**
276: * Creates a search group for each type of object searchable by all
277: * the specified search types.
278: * <p>
279: * At first, a set of object types common to all search types
280: * (i.e. <code>Class</code>s representing object types, common
281: * to all search types) is computed. Then a search group is created
282: * for each of the <code>Class</code>s.
283: *
284: * @param search types to create search groups for
285: * @return created search groups
286: * @see SearchType#getSearchTypeClasses()
287: */
288: public static SearchGroup[] createSearchGroups(SearchType[] items) {
289:
290: /*
291: * Build a list of Class's searchable by every search type
292: * from the specified list of search types.
293: * In other words: Build a list of Class'es common to all search types.
294: */
295: Set<Class> classSet = new HashSet<Class>(items.length);
296: for (int i = 0; i < items.length; i++) {
297: List<Class> classes = Arrays.asList(items[i]
298: .getSearchTypeClasses());
299: if (i == 0) {
300: classSet.addAll(classes);
301: } else {
302: classSet.retainAll(classes);
303: }
304: }
305:
306: /* Try to create a search group for each of the Class'es: */
307: if (classSet.isEmpty()) {
308: return new SearchGroup[0];
309: }
310: Set<SearchGroup> groupSet = new HashSet<SearchGroup>(classSet
311: .size());
312: for (Class clazz : classSet) {
313: SearchGroup group = Registry.createSearchGroup(clazz);
314: if (group != null) {
315: for (SearchType item : items) {
316: group.add(item);
317: }
318: groupSet.add(group);
319: }
320: }
321: return groupSet.toArray(new SearchGroup[groupSet.size()]);
322: }
323:
324: /**
325: * Factory which creates <code>SearchGroup</code>. It's used in
326: * <code>Registry</code>
327: * @see SearchGroup.Registry
328: */
329: public interface Factory {
330: /** Creates new <code>SearchGroup</code> object. */
331: public SearchGroup createSearchGroup();
332: } // End of interface Factory.
333:
334: /**
335: * Registry which registers search group factories
336: * ({@link SearchGroup.Factory}) for search object types.
337: * <p>
338: * Initially, factories for search object types {@link DataObject}
339: * and {@link FileObject} are already registered
340: * (<code>DataObjectSearchGroup</code> and
341: * <code>FileObjectSearchGroup</code>).
342: *
343: * @see SearchGroup.Factory
344: * @see DataObjectSearchGroup
345: * @see FileObjectSearchGroup
346: */
347: public static final class Registry extends Object {
348:
349: /** Private constructor so nobody could access it. */
350: private Registry() {
351: }
352:
353: /** Maps search object types to registered factories. */
354: private static final Map<Class, Factory> registry = new HashMap<Class, Factory>(
355: 2);
356:
357: static {
358: registry.put(DataObject.class, new Factory() {
359: public SearchGroup createSearchGroup() {
360: return new DataObjectSearchGroup();
361: }
362: });
363: registry.put(FileObject.class, new Factory() {
364: public SearchGroup createSearchGroup() {
365: return new FileObjectSearchGroup();
366: }
367: });
368: }
369:
370: /**
371: * Registers a search group factory for a search object type
372: * (<code>Class</code>).
373: * If a factory has already been registered for the specified
374: * search object type, the old registration is kept (the registration
375: * fails).
376: *
377: * @param searchObjectClass search object type the factory is
378: * to be registered for
379: * @param factory factory to be registered
380: * @return <code>true</code> if the registration was successful,
381: * <code>false</code> if the registration failed
382: * (i. e. if some factory has already been registered
383: * for the specified search object type)
384: */
385: public static synchronized boolean registerSearchGroupFactory(
386: Class searchObjectClass, Factory factory) {
387: Factory oldFactory = registry.put(searchObjectClass,
388: factory);
389: if (oldFactory != null) {
390:
391: /*
392: * Oops! A factory for the specified search object class
393: * have already been registered. Retain the old registration:
394: */
395: registry.put(searchObjectClass, oldFactory);
396: return false;
397: }
398: return true;
399: }
400:
401: /**
402: * Creates a <code>SearchGroup</code> for the specified search object
403: * type.
404: * The search group is created using
405: * the {@linkplain SearchGroup.Factory factory} registered for
406: * the specified search object type.
407: *
408: * @param searchObjectType search object type to create
409: * a search group for
410: * @return search group created by the registered factory,
411: * or <code>null</code> if no factory has been registed
412: * for the specified search object type
413: * @see #registerSearchGroupFactory registerSearchGroupFactory
414: */
415: public static SearchGroup createSearchGroup(
416: Class searchObjectType) {
417: Factory factory = registry.get(searchObjectType);
418:
419: if (factory == null) {
420: return null;
421: }
422: return factory.createSearchGroup();
423: }
424:
425: /**
426: * Tests whether there is a <code>Factory</code> registered for the
427: * specified search object class type.
428: *
429: * @param searchObjectType search object type
430: * @return <code>true</code> if some factory has been registered
431: * for the specified search object type,
432: * <code>false</code> otherwise
433: */
434: public static boolean hasFactory(Class searchObjectType) {
435: return registry.containsKey(searchObjectType);
436: }
437:
438: } // End of class Registry.
439:
440: }
|