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.netbeans.modules.search;
043:
044: import java.util.ArrayList;
045: import java.util.HashSet;
046: import java.util.List;
047: import java.util.Set;
048: import org.openide.ErrorManager;
049: import org.openide.nodes.Node;
050: import org.openidex.search.SearchType;
051:
052: /**
053: * Holds search result data.
054: *
055: * @author Petr Kuzel
056: * @author Marian Petras
057: */
058: public final class ResultModel {
059:
060: /** maximum number of found objects */
061: private static final int COUNT_LIMIT = 500;
062: /** maximum total number of detail entries for found objects */
063: private static final int DETAILS_COUNT_LIMIT = 5000;
064:
065: /** */
066: private final long creationTime;
067:
068: /** */
069: private int size = 0;
070: /** */
071: private int totalDetailsCount = 0;
072: /**
073: */
074: private ResultTreeModel treeModel;
075: /** */
076: private ResultView resultView;
077:
078: /**
079: * flag - did number of found objects reach the limit?
080: *
081: * @see #COUNT_LIMIT
082: */
083: private boolean limitReached = false;
084:
085: /** Search group this result shows search results for. */
086: private SpecialSearchGroup searchGroup;
087:
088: /**
089: * are all search types defined in the {@code SearchGroup} those
090: * defined in the Utilities module?
091: */
092: final boolean isBasicCriteriaOnly;
093: /** */
094: final BasicSearchCriteria basicCriteria;
095: /** */
096: private final boolean isFullText;
097: /** */
098: final String replaceString;
099: /** */
100: final boolean searchAndReplace;
101: /** list of matching objects (usually {@code DataObject}s) */
102: final List<MatchingObject> matchingObjects = new ArrayList<MatchingObject>();
103: /** set of matching objects - only used as a prevention from duplicates */
104: private final Set<MatchingObject> matchingObjectsSet = new HashSet<MatchingObject>(
105: 40);
106:
107: /** Contains optional finnish message often reason why finished. */
108: private String finishMessage;
109:
110: /** Creates new <code>ResultModel</code>. */
111: ResultModel(SpecialSearchGroup searchGroup, String replaceString) {
112: this .searchGroup = searchGroup;
113: this .replaceString = replaceString;
114: this .searchAndReplace = (replaceString != null);
115:
116: basicCriteria = searchGroup.basicCriteria;
117: isFullText = (basicCriteria != null)
118: && basicCriteria.isFullText();
119: isBasicCriteriaOnly = (searchGroup.getSearchTypes().length == 0);
120: creationTime = System.currentTimeMillis();
121: }
122:
123: /**
124: */
125: long getCreationTime() {
126: return creationTime;
127: }
128:
129: /**
130: * Sets an observer which will be notified whenever an object is found.
131: *
132: * @param observer observer or <code>null</code>
133: */
134: void setObserver(ResultTreeModel observer) {
135: this .treeModel = observer;
136: }
137:
138: /**
139: * Sets an observer which will be notified whenever an object is found.
140: *
141: * @param observer observer or <code>null</code>
142: */
143: void setObserver(ResultView observer) {
144: this .resultView = observer;
145: }
146:
147: /**
148: * Clean the allocated resources. Do not rely on GC there as we are often
149: * referenced from various objects. So keep leak as small as possible.
150: * */
151: void close() {
152: if ((matchingObjects != null) && !matchingObjects.isEmpty()) {
153: for (MatchingObject matchingObj : matchingObjects) {
154: matchingObj.cleanup();
155: }
156: }
157:
158: // eliminate search group content
159: // no other way then leaving it on GC, it should work because
160: // search group is always recreated by a it's factory and
161: // nobody keeps reference to it. 7th May 2004
162:
163: searchGroup = null;
164: }
165:
166: /**
167: * Notifies ths result model of a newly found matching object.
168: *
169: * @param object matching object
170: * @return {@code true} if this result model can accept more objects,
171: * {@code false} if number of found objects reached the limit
172: */
173: synchronized boolean objectFound(Object object) {
174: MatchingObject matchingObject = new MatchingObject(this , object);
175: if (matchingObjectsSet.add(matchingObject) == false) {
176: return true;
177: }
178:
179: matchingObjects.add(matchingObject);
180:
181: assert limitReached == false;
182: assert treeModel != null;
183: assert resultView != null;
184:
185: totalDetailsCount += getDetailsCount(matchingObject);
186:
187: treeModel.objectFound(matchingObject, size++);
188: resultView.objectFound(matchingObject, totalDetailsCount);
189:
190: return size < COUNT_LIMIT
191: && totalDetailsCount < DETAILS_COUNT_LIMIT;
192: }
193:
194: /**
195: */
196: void objectBecameInvalid(MatchingObject matchingObj) {
197:
198: /* may be called from non-EDT thread */
199:
200: int index = matchingObjects.indexOf(matchingObj);
201: assert index != -1;
202:
203: treeModel.objectBecameInvalid(matchingObj);
204: }
205:
206: /**
207: */
208: synchronized int getTotalDetailsCount() {
209: return totalDetailsCount;
210: }
211:
212: /**
213: */
214: synchronized MatchingObject[] getMatchingObjects() {
215: return matchingObjects
216: .toArray(new MatchingObject[matchingObjects.size()]);
217: }
218:
219: /**
220: */
221: synchronized Object[] getFoundObjects() {
222: Object[] foundObjects = new Object[matchingObjects.size()];
223: int index = 0;
224: for (MatchingObject matchingObj : matchingObjects) {
225: foundObjects[index++] = matchingObj.object;
226: }
227: return foundObjects;
228: }
229:
230: public void run() {
231:
232: }
233:
234: /**
235: */
236: boolean hasDetails() {
237: return totalDetailsCount != 0; //PENDING - synchronization?
238: }
239:
240: /**
241: * Performs a quick check whether
242: * {@linkplain MatchingObject matching objects} contained in this model
243: * can have details.
244: *
245: * @return {@code Boolean.TRUE} if all matching objects have details,
246: * {@code Boolean.FALSE} if no matching object has details,
247: * {@code null} if matching objects may have details
248: * (if more time consuming check would be necessary)
249: */
250: Boolean canHaveDetails() {
251: Boolean ret;
252: if (isFullText) {
253: ret = Boolean.TRUE;
254: } else if (isBasicCriteriaOnly) {
255: ret = Boolean.FALSE;
256: } else {
257: ret = null;
258: }
259: return ret;
260: }
261:
262: /*
263: * A cache exists for information about a single MatchingObject
264: * to prevent from repetitive calls of time-consuming queries on
265: * number of details and list of details. These calls are initiated
266: * by the node renderer (class NodeRenderer).
267: */
268:
269: private MatchingObject infoCacheMatchingObject;
270: private Boolean infoCacheHasDetails;
271: private int infoCacheDetailsCount;
272: private Node[] infoCacheDetailNodes;
273: private final Node[] EMPTY_NODES_ARRAY = new Node[0];
274:
275: /**
276: */
277: private void prepareCacheFor(MatchingObject matchingObject) {
278: if (matchingObject != infoCacheMatchingObject) {
279: infoCacheHasDetails = null;
280: infoCacheDetailsCount = -1;
281: infoCacheDetailNodes = null;
282: infoCacheMatchingObject = matchingObject;
283: }
284: }
285:
286: /**
287: */
288: boolean hasDetails(MatchingObject matchingObject) {
289: prepareCacheFor(matchingObject);
290: if (infoCacheHasDetails != null) {
291: return infoCacheHasDetails.booleanValue();
292: }
293:
294: boolean hasDetails = hasDetailsReal(matchingObject);
295: infoCacheHasDetails = Boolean.valueOf(hasDetails);
296:
297: assert (infoCacheHasDetails == Boolean.TRUE)
298: || (infoCacheHasDetails == Boolean.FALSE);
299: return hasDetails;
300: }
301:
302: /**
303: */
304: private boolean hasDetailsReal(MatchingObject matchingObject) {
305: boolean ret;
306: if (isFullText) {
307: ret = true;
308: } else if (isBasicCriteriaOnly) {
309: ret = false;
310: } else {
311: ret = false;
312: final Object foundObject = matchingObject.object;
313: for (SearchType searchType : searchGroup.getSearchTypes()) {
314: Node[] detailNodes = searchType.getDetails(foundObject);
315: if ((detailNodes != null) && (detailNodes.length != 0)) {
316: ret = true;
317: break;
318: }
319: }
320: }
321: return ret;
322: }
323:
324: /**
325: */
326: int getDetailsCount(MatchingObject matchingObject) {
327: prepareCacheFor(matchingObject);
328: if (infoCacheDetailsCount == -1) {
329: infoCacheDetailsCount = getDetailsCountReal(matchingObject);
330: if (infoCacheDetailsCount == 0) {
331: infoCacheDetailNodes = EMPTY_NODES_ARRAY;
332: }
333: }
334:
335: assert infoCacheDetailsCount >= 0;
336: return infoCacheDetailsCount;
337: }
338:
339: /**
340: * Returns number of detail nodes available to the given found object.
341: *
342: * @param foundObject object matching the search criteria
343: * @return number of detail items (represented by individual nodes)
344: * available for the given object (usually {@code DataObject})
345: */
346: private int getDetailsCountReal(MatchingObject matchingObject) {
347: int count = isFullText ? basicCriteria
348: .getDetailsCount(matchingObject.object) : 0;
349: if (isBasicCriteriaOnly) {
350: return count;
351: }
352:
353: final Object foundObject = matchingObject.object;
354: for (SearchType searchType : searchGroup.getSearchTypes()) {
355: Node[] detailNodes = searchType.getDetails(foundObject);
356: count += (detailNodes != null) ? detailNodes.length : 0;
357: }
358: return count;
359: }
360:
361: /**
362: *
363: * @return non-empty array of detail nodes
364: * or {@code null} if there are no detail nodes
365: */
366: Node[] getDetails(MatchingObject matchingObject) {
367: prepareCacheFor(matchingObject);
368: Node[] detailNodes;
369: if (infoCacheDetailNodes == null) {
370: detailNodes = getDetailsReal(matchingObject);
371: infoCacheDetailNodes = (detailNodes != null) ? detailNodes
372: : EMPTY_NODES_ARRAY;
373: infoCacheDetailsCount = infoCacheDetailNodes.length;
374: } else {
375: detailNodes = (infoCacheDetailNodes != EMPTY_NODES_ARRAY) ? infoCacheDetailNodes
376: : null;
377: }
378:
379: assert (infoCacheDetailNodes != null)
380: && ((infoCacheDetailNodes == EMPTY_NODES_ARRAY) || (infoCacheDetailNodes.length > 0));
381: assert (detailNodes == null) || (detailNodes.length > 0);
382: return detailNodes;
383: }
384:
385: /**
386: *
387: * @return non-empty array of detail nodes
388: * or {@code null} if there are no detail nodes
389: */
390: private Node[] getDetailsReal(MatchingObject matchingObject) {
391: Node[] nodesTotal = null;
392: if (basicCriteria != null) {
393: nodesTotal = basicCriteria.isFullText() ? basicCriteria
394: .getDetails(matchingObject.object) : null;
395: }
396: if (isBasicCriteriaOnly) {
397: return nodesTotal;
398: }
399:
400: final Object foundObject = matchingObject.object;
401: for (SearchType searchType : searchGroup.getSearchTypes()) {
402: Node[] detailNodes = searchType.getDetails(foundObject);
403: if ((detailNodes == null) || (detailNodes.length == 0)) {
404: continue;
405: }
406: if (nodesTotal == null) {
407: nodesTotal = detailNodes;
408: } else {
409: Node[] oldNodesTotal = nodesTotal;
410: nodesTotal = new Node[nodesTotal.length
411: + detailNodes.length];
412: System.arraycopy(oldNodesTotal, 0, nodesTotal, 0,
413: oldNodesTotal.length);
414: System.arraycopy(detailNodes, 0, nodesTotal,
415: oldNodesTotal.length, detailNodes.length);
416: }
417: }
418: return nodesTotal;
419: }
420:
421: /**
422: */
423: synchronized int size() {
424: return size;
425: }
426:
427: /**
428: */
429: boolean isEmpty() {
430: return size == 0;
431: }
432:
433: /** Getter for search group property. */
434: SpecialSearchGroup getSearchGroup() {
435: return searchGroup;
436: }
437:
438: /**
439: * Returns search types that were used during the search.
440: *
441: * @return array of <code>SearchType</code>s that each tested object was
442: * tested for compliance
443: */
444: SearchType[] getQueriedSearchTypes() {
445: return searchGroup.getSearchTypes();
446: }
447:
448: /**
449: */
450: boolean wasLimitReached() {
451: return limitReached;
452: }
453:
454: /** This exception stoped search */
455: void searchException(RuntimeException ex) {
456: ErrorManager.Annotation[] annotations = ErrorManager
457: .getDefault().findAnnotations(ex);
458: for (int i = 0; i < annotations.length; i++) {
459: ErrorManager.Annotation annotation = annotations[i];
460: if (annotation.getSeverity() == ErrorManager.USER) {
461: finishMessage = annotation.getLocalizedMessage();
462: if (finishMessage != null)
463: return;
464: }
465: }
466: finishMessage = ex.getLocalizedMessage();
467: }
468:
469: /**
470: */
471: String getExceptionMsg() {
472: return finishMessage;
473: }
474:
475: }
|