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.util.ArrayList;
045: import java.util.HashMap;
046: import java.util.Iterator;
047: import java.util.List;
048: import java.util.Map;
049: import org.openide.ErrorManager;
050:
051: import org.openide.loaders.DataObject;
052: import org.openide.util.NbBundle;
053: import org.openide.nodes.Node;
054:
055: //import org.netbeans.api.project.FileOwnerQuery;
056:
057: /**
058: * Search group which perform search on data objects. It is a
059: * convenience and the default implementation of <code>SearchGroup</code>
060: * abstract class.
061: *
062: * @author Peter Zavadsky
063: * @author Marian Petras
064: * @see org.openidex.search.SearchGroup
065: */
066: public class DataObjectSearchGroup extends SearchGroup {
067:
068: /**
069: * {@inheritDoc} If the specified search type does not support searching
070: * in <code>DataObject</code>s, the group is left unmodified, too.
071: *
072: * @see SearchType#getSearchTypeClasses()
073: */
074: @Override
075: protected void add(SearchType searchType) {
076: boolean ok = false;
077: for (Class clazz : searchType.getSearchTypeClasses()) {
078: if (clazz == DataObject.class) {
079: ok = true;
080: break;
081: }
082: }
083: if (ok) {
084: super .add(searchType);
085: }
086: }
087:
088: /**
089: * Actual search implementation. Fires PROP_FOUND notifications.
090: * Implements superclass abstract method.
091: *
092: * @throws RuntimeException annotated at USER level by reason (on low memory condition)
093: */
094: public void doSearch() {
095: Node[] nodes = normalizeNodes(searchRoots
096: .toArray(new Node[searchRoots.size()]));
097:
098: lowMemoryWarning = false;
099: lowMemoryWarningCount = 0;
100: assureMemory(REQUIRED_PER_ITERATION, true);
101:
102: for (Node node : nodes) {
103: SearchInfo info = Utils.getSearchInfo(node);
104: if (info != null) {
105: for (Iterator<DataObject> j = info.objectsToSearch(); j
106: .hasNext();) {
107: if (stopped)
108: return;
109: assureMemory(REQUIRED_PER_ITERATION, false);
110: processSearchObject(j.next());
111: }
112: }
113: }
114: }
115:
116: private static boolean lowMemoryWarning = false;
117: private static int lowMemoryWarningCount = 0;
118: private static int MB = 1024 * 1024;
119: private static int REQUIRED_PER_ITERATION = 2 * MB;
120: private static int REQUIRED_PER_FULL_GC = 7 * MB;
121:
122: /**
123: * throws RuntimeException if low memory condition happens
124: * @param estimate etimated memory requirements before next check
125: * @param tryGC on true use potentionally very slow test that is more accurate (cooperates with GC)
126: */
127: private static void assureMemory(int estimate, boolean tryGC) {
128: Runtime rt = Runtime.getRuntime();
129: long total = rt.totalMemory();
130: long max = rt.maxMemory(); // XXX on some 1.4.1 returns heap&native instead of -Xmx
131: long required = Math.max(total / 13, estimate
132: + REQUIRED_PER_FULL_GC);
133: if (total == max && rt.freeMemory() < required) {
134: // System.err.println("MEM " + max + " " + total + " " + rt.freeMemory());
135: if (tryGC) {
136: try {
137: byte[] gcProvocation = new byte[(int) required];
138: gcProvocation[0] = 75;
139: gcProvocation = null;
140: return;
141: } catch (OutOfMemoryError e) {
142: throwNoMemory();
143: }
144: } else {
145: lowMemoryWarning = true;
146: }
147: } else if (lowMemoryWarning) {
148: lowMemoryWarning = false;
149: lowMemoryWarningCount++;
150: }
151: // gc is getting into corner
152: if (lowMemoryWarningCount > 7
153: || (total == max && rt.freeMemory() < REQUIRED_PER_FULL_GC)) {
154: throwNoMemory();
155: }
156:
157: }
158:
159: private static void throwNoMemory() {
160: RuntimeException ex = new RuntimeException(
161: "Low memory condition"); // NOI18N
162: String msg = NbBundle.getMessage(DataObjectSearchGroup.class,
163: "EX_memory");
164: ErrorManager.getDefault().annotate(ex, ErrorManager.USER, null,
165: msg, null, null);
166: throw ex;
167: }
168:
169: // /**
170: // * Gets data folder roots on which to search.
171: // *
172: // * @return array of data folder roots
173: // */
174: // private DataObject.Container[] getContainers() {
175: // List children = null;
176: // Node[] nodes = normalizeNodes(
177: // (Node[]) searchRoots.toArray(new Node[searchRoots.size()]));
178: //
179: // for (int i = 0; i < nodes.length; i++) {
180: // Node node = nodes[i];
181: // if (node.getParentNode() == null) {
182: //
183: // /* it should be the root of some project */
184: // }
185: // }
186: //
187: // /* test whether to scan whole repository: */
188: // if (nodes.length == 1) {
189: // InstanceCookie ic = (InstanceCookie) nodes[0].getCookie(
190: // InstanceCookie.class);
191: // try {
192: // if (ic != null && Repository.class
193: // .isAssignableFrom(ic.instanceClass())) {
194: //
195: // /* yes - scanning whole repository: */
196: // children = new ArrayList(10);
197: // Enumeration fss = Repository.getDefault().getFileSystems();
198: // while (fss.hasMoreElements()) {
199: // FileSystem fs = (FileSystem) fss.nextElement();
200: // if (fs.isValid() && !fs.isHidden()) {
201: // children.add(DataObject.find(fs.getRoot()));
202: // }
203: // }
204: // }
205: // } catch (IOException ioe) {
206: // ErrorManager.getDefault().notify(ErrorManager.EXCEPTION, ioe);
207: // children = null;
208: // } catch (ClassNotFoundException cnfe) {
209: // ErrorManager.getDefault().notify(ErrorManager.EXCEPTION, cnfe);
210: // children = null;
211: // }
212: // }
213: // if (children == null) {
214: // children = new ArrayList(nodes.length);
215: // for (int i = 0; i < nodes.length; i++) {
216: // DataObject.Container container = (DataObject.Container)
217: // nodes[i].getCookie(DataObject.Container.class);
218: // if (container != null) {
219: // children.add(container);
220: // }
221: // }
222: // }
223: // return (DataObject.Container[])
224: // children.toArray(new DataObject.Container[children.size()]);
225: // }
226: //
227: // /**
228: // * Scans data folder recursively.
229: // *
230: // * @return <code>true</code> if scanned entire folder successfully
231: // * or <code>false</code> if scanning was stopped. */
232: // private boolean scanContainer(DataObject.Container container) {
233: // DataObject[] children = container.getChildren();
234: //
235: // for (int i = 0; i < children.length; i++) {
236: //
237: // /* Test if the search was stopped. */
238: // if (stopped) {
239: // stopped = true;
240: // return false;
241: // }
242: //
243: // DataObject.Container c = (DataObject.Container)
244: // children[i].getCookie(DataObject.Container.class);
245: // if (c != null) {
246: // if (!scanContainer(c)) {
247: // return false;
248: // }
249: // } else {
250: // processSearchObject(children[i]);
251: // }
252: // }
253: //
254: // return true;
255: // }
256:
257: /** Gets node for found object. Implements superclass method.
258: * @return node delegate for found data object or <code>null</code>
259: * if the object is not of <code>DataObjectType</code> */
260: public Node getNodeForFoundObject(Object object) {
261: if (!(object instanceof DataObject)) {
262: return null;
263: }
264: return ((DataObject) object).getNodeDelegate();
265: }
266:
267: /**
268: * Computes a subset of nodes (search origins) covering all specified nodes.
269: * <p>
270: * Search is performed on trees whose roots are the specified nodes.
271: * If node A is a member of the tree determined by node B, then the A's tree
272: * is a subtree of the B's tree. It means that it is redundant to extra
273: * search the A's tree. This method computes a minimum set of nodes whose
274: * trees cover all nodes' subtrees but does not contain any node not covered
275: * by the original set of nodes.
276: *
277: * @param nodes roots of search trees
278: * @return subset of the original set of nodes
279: * (may be the same object as the parameter)
280: */
281: private static Node[] normalizeNodes(Node[] nodes) {
282:
283: /* No need to normalize: */
284: if (nodes.length < 2) {
285: return nodes;
286: }
287:
288: /*
289: * In the algorithm, we use two sets of nodes: "good nodes" and "bad
290: * nodes". "Good nodes" are nodes not known to be covered by any
291: * search root. "Bad nodes" are nodes known to be covered by at least
292: * one of the search roots.
293: *
294: * Since all the search roots are covered by themselves, they are all
295: * put to "bad nodes" initially. To recognize whether a search root
296: * is covered only by itself or whether it is covered by any other
297: * search root, the former group of nodes has mapped value FALSE
298: * and the later group of nodes has mapped value TRUE.
299: *
300: * Initially, all search roots have mapped value FALSE (not known to be
301: * covered by any other search root) and as the procedure runs, some of
302: * them may be remapped to value TRUE (known to be covered by at least
303: * one other search root).
304: *
305: * The algorithm checks all search roots one by one. The ckeck starts
306: * at a search root to be tested and continues up to its parents until
307: * one of the following:
308: * a) the root of the whole tree of nodes is reached
309: * - i.e. the node being checked is not covered by any other
310: * search root
311: * - mark all the nodes in the path from the node being checked
312: * to the root as "good nodes", except the search root being
313: * checked
314: * - put the search root being checked into the resulting set
315: * of nodes
316: * b) a "good node" is reached
317: * - i.e. neither the good node nor any of the nodes on the path
318: * are covered by any other search root
319: * - mark all the nodes in the path from the node being checked
320: * to the root as "good nodes", except the search root being
321: * checked
322: * - put the search root being checked into the resulting set
323: * of nodes
324: * c) a "bad node" is reached (it may be either another search root
325: * or another "bad node")
326: * - i.e. we know that the reached node is covered by another search
327: * root or the reached node is another search root - in both cases
328: * the search root being checked is covered by another search root
329: * - mark all the nodes in the path from the node being checked
330: * to the root as "bad nodes"; the search root being checked
331: * will be remapped to value TRUE
332: */
333:
334: Map<Node, Boolean> badNodes = new HashMap<Node, Boolean>(
335: 2 * nodes.length, 0.75f);
336: Map<Node, Boolean> goodNodes = new HashMap<Node, Boolean>(
337: 2 * nodes.length, 0.75f);
338: List<Node> path = new ArrayList<Node>(10);
339: List<Node> result = new ArrayList<Node>(nodes.length);
340:
341: /* Put all search roots into "bad nodes": */
342: for (Node node : nodes) {
343: badNodes.put(node, Boolean.FALSE);
344: }
345:
346: main_cycle: for (Node node : nodes) {
347: path.clear();
348: boolean isBad = false;
349: for (Node n = node.getParentNode(); n != null; n = n
350: .getParentNode()) {
351: if (badNodes.containsKey(n)) {
352: isBad = true;
353: break;
354: }
355: if (goodNodes.containsKey(n)) {
356: break;
357: }
358: path.add(n);
359: }
360: if (isBad) {
361: badNodes.put(node, Boolean.TRUE);
362: for (Node pathNode : path) {
363: badNodes.put(pathNode, Boolean.TRUE);
364: }
365: } else {
366: for (Node pathNode : path) {
367: goodNodes.put(pathNode, Boolean.TRUE);
368: }
369: result.add(node);
370: }
371: }
372: return result.toArray(new Node[result.size()]);
373: }
374:
375: }
|