| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| |
| |
| package org.openidex.search; |
| |
| |
| import java.beans.PropertyChangeListener; |
| import java.beans.PropertyChangeSupport; |
| import java.util.*; |
| |
| import org.openide.filesystems.FileObject; |
| import org.openide.loaders.DataObject; |
| import org.openide.nodes.Node; |
| |
| |
| /** |
| * Class which groups individual search types. It provides several services |
| * to provide search on them. The services are scanning node system to |
| * provide search object for group of search types -> efficient search. |
| * |
| * @author Peter Zavadsky |
| * @author Marian Petras |
| */ |
| public abstract class SearchGroup { |
| |
| /** |
| * Property name which is fired when performing search and searched object |
| * passed criteria. |
| */ |
| public static final String PROP_FOUND = "org.openidex.search.found"; // NOI18N |
| |
| /** |
| * Property name which is fired for in for the case original <code>node</code>'s has |
| * changed the way <code>result</code> was changed based on set criteria. |
| * Interested listeners should then get the event with values |
| * <UL> |
| * <LI>property change name = PROP_RESULT |
| * <LI>property source = this search type instance |
| * <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 |
| * for the latter case. |
| * <LI>new value = detail which was changed or null if the node was removed from the result -> old value has to be non-null |
| * for that case |
| * </UL> |
| * This allows implementation of the dynamic changing of result suggested |
| * by Jesse and Sebastian (at least partially implemented now). |
| */ |
| public static final String PROP_RESULT = "org.openidex.search.result"; // NOI18N |
| |
| |
| /** Property change support. */ |
| private PropertyChangeSupport propChangeSupport; |
| |
| /** search types added to this search group */ |
| protected SearchType[] searchTypes = new SearchType[0]; |
| |
| /** Set of nodes on which sub-system to search. */ |
| protected final Set<Node> searchRoots = new HashSet<Node>(5); |
| |
| /** Set of objects which passed the search criteria (searchtypes).*/ |
| protected final Set<Object> resultObjects = new LinkedHashSet<Object>(50); |
| |
| /** Flag indicating the search should be stopped. */ |
| protected volatile boolean stopped = false; |
| |
| private PropertyChangeListener propListener; |
| |
| |
| /** |
| * Adds a search type to this search group. |
| * If the group already contains the search type, the group is left |
| * unmodified. |
| * |
| * @param searchType search type to be added |
| */ |
| protected void add(SearchType searchType) { |
| |
| /* Check whether the search type is already in the list: */ |
| for (int i = 0; i < searchTypes.length; i++) { |
| if (searchType.equals(searchTypes[i])) { |
| return; |
| } |
| } |
| |
| /* Add the search type to the list: */ |
| SearchType[] temp = new SearchType[searchTypes.length + 1]; |
| System.arraycopy(searchTypes, 0, temp, 0, searchTypes.length); |
| temp[searchTypes.length] = searchType; |
| searchTypes = temp; |
| } |
| |
| /** |
| * Returns list of search types. |
| * |
| * @return search types added to this group |
| * @see #add |
| */ |
| public SearchType[] getSearchTypes() { |
| return searchTypes; |
| } |
| |
| /** |
| * Sets roots of nodes in which its interested to search. |
| * This method is called at the first search type in the possible created |
| * chain of search types. |
| */ |
| public void setSearchRootNodes(Node[] roots) { |
| |
| /* |
| * Gives a chance for individual search types to exclude some |
| * node systems. E.g. CVS search type is not interested |
| * in non CVS node systems. |
| */ |
| for (SearchType searchType : searchTypes) { |
| roots = searchType.acceptSearchRootNodes(roots); |
| } |
| searchRoots.clear(); |
| searchRoots.addAll(Arrays.asList(roots)); |
| } |
| |
| /** Gets search root nodes. */ |
| public Node[] getSearchRoots() { |
| return searchRoots.toArray(new Node[searchRoots.size()]); |
| } |
| |
| /** This method is invoked when the current search is being stopped. |
| * |
| * You can override it to terminate any internal tasks that have been |
| * started to process individual found items and that take a long time |
| * to finish. |
| * |
| * The default implementation does nothing. |
| * |
| * @since org.openidex.util/3 3.31 |
| */ |
| protected void onStopSearch() { |
| } |
| |
| /** Stops searching. */ |
| public final void stopSearch() { |
| stopped = true; |
| onStopSearch(); |
| } |
| |
| /** |
| * Does search. |
| * |
| * @throw RuntimeException USER level annotated runtime exception |
| * on low memory condition (instead of OutOfMemoryError) |
| */ |
| public void search() { |
| resultObjects.clear(); |
| prepareSearch(); |
| doSearch(); |
| } |
| |
| /** |
| * Prepares search. |
| */ |
| protected void prepareSearch() { |
| } |
| |
| /** |
| * Provides actual search. The subclasses implementating this method should scan the node system |
| * specified by <code>searchRoots</code>, extract search objects from them, add them |
| * to the search object set, test over all search type items in this group, |
| * in case if satisfied all it should fire <code>PROP_FOUND</code> property change and add |
| * the object to <code>resultObjects</code> set. |
| * The method implemenatation should call {@link #processSearchObject} method for each |
| * search object in the node systems. |
| */ |
| protected abstract void doSearch(); |
| |
| /** |
| * Provides search on one search object instance. The object is added to |
| * set of searched objects and passed to all search types encapsulated by |
| * this search group. In the case the object passes all search types is added |
| * to the result set and fired an event <code>PROP_FOUND</code> about successful |
| * match to interested property change listeners. |
| * |
| * @param searchObject object to provide actuall test on it. The actual instance |
| * has to be of type returned by all <code>SearchKey.getSearchObjectType</code> |
| * returned by <code>SearchType</code> of this <code>SearchGroup</code> |
| */ |
| protected void processSearchObject(Object searchObject) { |
| |
| /* |
| * Give chance to individual search types to exclude some |
| * non interesting search objects from search. E.g. Java data |
| * object search will be not interested in non Java data objects. |
| */ |
| for (int i = 0; i < searchTypes.length; i++) { |
| if (!searchTypes[i].acceptSearchObject(searchObject)) { |
| return; |
| } |
| } |
| |
| /* |
| * Give chance to provide additional things. |
| */ |
| for (int i = 0; i < searchTypes.length; i++) { |
| searchTypes[i].prepareSearchObject(searchObject); |
| } |
| |
| /* Actually test the search object against all search types. */ |
| for (int i = 0; i < searchTypes.length; i++) { |
| if (!searchTypes[i].testObject(searchObject)) { |
| return; |
| } |
| } |
| |
| /* |
| * In case the search object passed the search add it to the result set |
| * and fire an event about successful search to interested listeners. |
| */ |
| resultObjects.add(searchObject); |
| firePropertyChange(PROP_FOUND, null, searchObject); |
| } |
| |
| |
| /** Gets node for found object. */ |
| public abstract Node getNodeForFoundObject(Object object); |
| |
| /** Getter for result object property. */ |
| public Set<Object> getResultObjects() { |
| return new LinkedHashSet<Object>(resultObjects); |
| } |
| |
| /** Adds property change listener. */ |
| public synchronized void addPropertyChangeListener(PropertyChangeListener l) { |
| getPropertySupport().addPropertyChangeListener(l); |
| } |
| |
| /** Removes property change listener. */ |
| public synchronized void removePropertyChangeListener(PropertyChangeListener l) { |
| getPropertySupport().removePropertyChangeListener(l); |
| } |
| |
| /** Fires property change event. */ |
| protected void firePropertyChange(String name, Object oldValue, Object newValue) { |
| getPropertySupport().firePropertyChange(name, oldValue, newValue); |
| } |
| |
| /** Gets lazy initialized property change support. */ |
| private synchronized PropertyChangeSupport getPropertySupport() { |
| if(propChangeSupport == null) |
| propChangeSupport = new PropertyChangeSupport(this); |
| |
| return propChangeSupport; |
| } |
| |
| /** |
| * Creates a search group for each type of object searchable by all |
| * the specified search types. |
| * <p> |
| * At first, a set of object types common to all search types |
| * (i.e. <code>Class</code>s representing object types, common |
| * to all search types) is computed. Then a search group is created |
| * for each of the <code>Class</code>s. |
| * |
| * @param search types to create search groups for |
| * @return created search groups |
| * @see SearchType#getSearchTypeClasses() |
| */ |
| public static SearchGroup[] createSearchGroups(SearchType[] items) { |
| |
| /* |
| * Build a list of Class's searchable by every search type |
| * from the specified list of search types. |
| * In other words: Build a list of Class'es common to all search types. |
| */ |
| Set<Class> classSet = new HashSet<Class>(items.length); |
| for (int i = 0; i < items.length; i++) { |
| List<Class> classes = Arrays.asList(items[i].getSearchTypeClasses()); |
| if (i == 0) { |
| classSet.addAll(classes); |
| } else { |
| classSet.retainAll(classes); |
| } |
| } |
| |
| /* Try to create a search group for each of the Class'es: */ |
| if (classSet.isEmpty()) { |
| return new SearchGroup[0]; |
| } |
| Set<SearchGroup> groupSet = new HashSet<SearchGroup>(classSet.size()); |
| for (Class clazz : classSet) { |
| SearchGroup group = Registry.createSearchGroup(clazz); |
| if (group != null) { |
| for (SearchType item : items) { |
| group.add(item); |
| } |
| groupSet.add(group); |
| } |
| } |
| return groupSet.toArray(new SearchGroup[groupSet.size()]); |
| } |
| |
| |
| /** |
| * Factory which creates <code>SearchGroup</code>. It's used in |
| * <code>Registry</code> |
| * @see SearchGroup.Registry |
| */ |
| public interface Factory { |
| /** Creates new <code>SearchGroup</code> object. */ |
| public SearchGroup createSearchGroup(); |
| } // End of interface Factory. |
| |
| |
| /** |
| * Registry which registers search group factories |
| * ({@link SearchGroup.Factory}) for search object types. |
| * <p> |
| * Initially, factories for search object types {@link DataObject} |
| * and {@link FileObject} are already registered |
| * (<code>DataObjectSearchGroup</code> and |
| * <code>FileObjectSearchGroup</code>). |
| * |
| * @see SearchGroup.Factory |
| * @see DataObjectSearchGroup |
| * @see FileObjectSearchGroup |
| */ |
| public static final class Registry extends Object { |
| |
| /** Private constructor so nobody could access it. */ |
| private Registry() {} |
| |
| |
| /** Maps search object types to registered factories. */ |
| private static final Map<Class, Factory> registry |
| = new HashMap<Class, Factory>(2); |
| |
| static { |
| registry.put(DataObject.class, new Factory() { |
| public SearchGroup createSearchGroup() { |
| return new DataObjectSearchGroup(); |
| } |
| }); |
| registry.put(FileObject.class, new Factory() { |
| public SearchGroup createSearchGroup() { |
| return new FileObjectSearchGroup(); |
| } |
| }); |
| } |
| |
| |
| /** |
| * Registers a search group factory for a search object type |
| * (<code>Class</code>). |
| * If a factory has already been registered for the specified |
| * search object type, the old registration is kept (the registration |
| * fails). |
| * |
| * @param searchObjectClass search object type the factory is |
| * to be registered for |
| * @param factory factory to be registered |
| * @return <code>true</code> if the registration was successful, |
| * <code>false</code> if the registration failed |
| * (i. e. if some factory has already been registered |
| * for the specified search object type) |
| */ |
| public static synchronized boolean registerSearchGroupFactory( |
| Class searchObjectClass, |
| Factory factory) { |
| Factory oldFactory = registry.put(searchObjectClass, factory); |
| if (oldFactory != null) { |
| |
| /* |
| * Oops! A factory for the specified search object class |
| * have already been registered. Retain the old registration: |
| */ |
| registry.put(searchObjectClass, oldFactory); |
| return false; |
| } |
| return true; |
| } |
| |
| /** |
| * Creates a <code>SearchGroup</code> for the specified search object |
| * type. |
| * The search group is created using |
| * the {@linkplain SearchGroup.Factory factory} registered for |
| * the specified search object type. |
| * |
| * @param searchObjectType search object type to create |
| * a search group for |
| * @return search group created by the registered factory, |
| * or <code>null</code> if no factory has been registed |
| * for the specified search object type |
| * @see #registerSearchGroupFactory registerSearchGroupFactory |
| */ |
| public static SearchGroup createSearchGroup(Class searchObjectType) { |
| Factory factory = registry.get(searchObjectType); |
| |
| if (factory == null) { |
| return null; |
| } |
| return factory.createSearchGroup(); |
| } |
| |
| /** |
| * Tests whether there is a <code>Factory</code> registered for the |
| * specified search object class type. |
| * |
| * @param searchObjectType search object type |
| * @return <code>true</code> if some factory has been registered |
| * for the specified search object type, |
| * <code>false</code> otherwise |
| */ |
| public static boolean hasFactory(Class searchObjectType) { |
| return registry.containsKey(searchObjectType); |
| } |
| |
| } // End of class Registry. |
| |
| } |