KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > netbeans > modules > search > MatchingObject


1 /*
2  * The contents of this file are subject to the terms of the Common Development
3  * and Distribution License (the License). You may not use this file except in
4  * compliance with the License.
5  *
6  * You can obtain a copy of the License at http://www.netbeans.org/cddl.html
7  * or http://www.netbeans.org/cddl.txt.
8  *
9  * When distributing Covered Code, include this CDDL Header Notice in each file
10  * and include the License file at http://www.netbeans.org/cddl.txt.
11  * If applicable, add the following below the CDDL Header, with the fields
12  * enclosed by brackets [] replaced by your own identifying information:
13  * "Portions Copyrighted [year] [name of copyright owner]"
14  *
15  * The Original Software is NetBeans. The Initial Developer of the Original
16  * Software is Sun Microsystems, Inc. Portions Copyright 1997-2007 Sun
17  * Microsystems, Inc. All Rights Reserved.
18  */

19
20 package org.netbeans.modules.search;
21
22 import java.awt.EventQueue JavaDoc;
23 import java.beans.PropertyChangeEvent JavaDoc;
24 import java.beans.PropertyChangeListener JavaDoc;
25 import java.io.File JavaDoc;
26 import java.io.FileInputStream JavaDoc;
27 import java.io.FileOutputStream JavaDoc;
28 import java.io.IOException JavaDoc;
29 import java.nio.ByteBuffer JavaDoc;
30 import java.nio.CharBuffer JavaDoc;
31 import java.nio.channels.ClosedByInterruptException JavaDoc;
32 import java.nio.channels.FileChannel JavaDoc;
33 import java.nio.charset.Charset JavaDoc;
34 import java.util.Arrays JavaDoc;
35 import java.util.List JavaDoc;
36 import java.util.regex.Matcher JavaDoc;
37 import java.util.regex.Pattern JavaDoc;
38 import org.netbeans.modules.search.types.FullTextType;
39 import org.netbeans.modules.search.types.TextDetail;
40 import org.openide.filesystems.FileObject;
41 import org.openide.filesystems.FileUtil;
42 import org.openide.loaders.DataObject;
43 import org.openide.util.NbBundle;
44
45 /**
46  * Data structure holding a reference to the found object and information
47  * whether occurences in the found object should be replaced or not.
48  *
49  * @author Marian Petras
50  * @author Tim Boudreau
51  */

52 final class MatchingObject implements PropertyChangeListener JavaDoc {
53     
54     /** */
55     private final ResultModel resultModel;
56     /** */
57     private final File JavaDoc file;
58     /** */
59     private final long timestamp;
60     
61     /**
62      * matching object as returned by the {@code SearchGroup}
63      * (usually a {@code DataObject})
64      */

65     final Object JavaDoc object;
66     
67     /**
68      * holds information on whether the {@code object} is selected
69      * to be replaced or not.
70      * Unless {@link #selectedMatches} is non-{@code null}, this field's
71      * value also applies to the object's subnodes (if any).
72      *
73      * @see #selectedMatches
74      */

75     private boolean selected = true;
76     /**
77      * holds information on whether the node representing this object
78      * is expanded or collapsed
79      *
80      * @see #markExpanded(boolean)
81      */

82     private boolean expanded = false;
83     /**
84      * holds information about which matches should be replaced and which
85      * should not be replaced.
86      * Value {@code null} means that either all or none matches are selected,
87      * depending on the value of field {@link #selected}.
88      *
89      * @see #selected
90      */

91     private boolean[] selectedMatches;
92     /**
93      * flag that indicates that the tree was not notified of this
94      * {@code MatchingObject}'s children's selection change and that
95      * it must be notified before the children nodes are made visible
96      *
97      * @see #markChildrenSelectionDirty()
98      */

99     private boolean childrenSelectionDirty;
100     /** */
101     private boolean valid = true;
102     /** */
103     private StringBuilder JavaDoc text;
104     
105     /**
106      * {@code true} if the file's line terminator is other than {@code "\\n"}
107      */

108     boolean wasCrLf = false;
109     
110     /**
111      * Creates a new {@code MatchingObject} with a reference to the found
112      * object (returned by {@code SearchGroup}).
113      *
114      * @param object found object returned by the {@code SearchGroup}
115      * (usually a {@code DataObject}) - must not be {@code null}
116      * @exception java.lang.IllegalArgumentException
117      * if the passed {@code object} is {@code null}
118      */

119     MatchingObject(ResultModel resultModel, Object JavaDoc object) {
120         if (resultModel == null) {
121             throw new IllegalArgumentException JavaDoc("resultModel = null"); //NOI18N
122
}
123         if (object == null) {
124             throw new IllegalArgumentException JavaDoc("object = null"); //NOI18N
125
}
126         
127         this.resultModel = resultModel;
128         this.object = object;
129         
130         FileObject fileObject = getFileObject();
131         file = FileUtil.toFile(fileObject);
132         timestamp = (file != null) ? file.lastModified() : 0L;
133         valid = (timestamp != 0L);
134         
135         setUpDataObjValidityChecking();
136     }
137     
138     /**
139      */

140     private void setUpDataObjValidityChecking() {
141         final DataObject dataObj = (DataObject) object;
142         if (dataObj.isValid()) {
143             dataObj.addPropertyChangeListener(this);
144         }
145     }
146     
147     /**
148      */

149     void cleanup() {
150         final DataObject dataObj = (DataObject) object;
151         dataObj.removePropertyChangeListener(this);
152     }
153     
154     public void propertyChange(PropertyChangeEvent JavaDoc e) {
155         if (DataObject.PROP_VALID.equals(e.getPropertyName())
156                 && Boolean.FALSE.equals(e.getNewValue())) {
157             assert e.getSource() == (DataObject) object;
158             
159             final DataObject dataObj = (DataObject) object;
160             dataObj.removePropertyChangeListener(this);
161             
162             resultModel.objectBecameInvalid(this);
163         }
164     }
165     
166     /**
167      * Is the {@code DataObject} encapsulated by this {@code MatchingObject}
168      * valid?
169      *
170      * @return {@code true} if the {@code DataObject} is valid, false otherwise
171      * @see DataObject#isValid
172      */

173     boolean isObjectValid() {
174         return ((DataObject) object).isValid();
175     }
176     
177     private FileObject getFileObject() {
178         return ((DataObject) object).getPrimaryFile();
179     }
180     
181     /**
182      */

183     void setSelected(boolean selected) {
184         if (selected == this.selected) {
185             return;
186         }
187         
188         this.selected = selected;
189         selectedMatches = null;
190     }
191     
192     /**
193      */

194     boolean isSelected() {
195         return selected;
196     }
197     
198     /**
199      */

200     boolean isUniformSelection() {
201         return selectedMatches == null;
202     }
203     
204     /**
205      * Checks selection of this object's subnodes.
206      *
207      * @return {@code Boolean.TRUE} if all subnodes are selected,
208      * {@code Boolean.FALSE} if all subnodes are unselected,
209      * {@code null} if some subnodes are selected and some are
210      * unselected
211      */

212     Boolean JavaDoc checkSubnodesSelection() {
213         if (selectedMatches == null) {
214             return Boolean.valueOf(selected);
215         }
216         
217         final boolean firstMatchSelection = selectedMatches[0];
218         for (int i = 1; i < selectedMatches.length; i++) {
219             if (selectedMatches[i] != firstMatchSelection) {
220                 return null;
221             }
222         }
223         return Boolean.valueOf(firstMatchSelection);
224     }
225     
226     /**
227      */

228     void toggleSubnodeSelection(ResultModel resultModel, int index) {
229         if (selectedMatches == null) {
230             selectedMatches = new boolean[resultModel.getDetailsCount(this)];
231             Arrays.fill(selectedMatches, this.selected);
232         }
233         selectedMatches[index] = !selectedMatches[index];
234     }
235     
236     /**
237      */

238     void setSubnodeSelected(int index,
239                             boolean selected,
240                             ResultModel resultModel) {
241         if (selectedMatches == null) {
242             if (selected == this.selected) {
243                 return;
244             }
245             selectedMatches = new boolean[resultModel.getDetailsCount(this)];
246             Arrays.fill(selectedMatches, this.selected);
247         }
248         
249         assert (index >= 0) && (index < selectedMatches.length);
250         selectedMatches[index] = selected;
251     }
252     
253     /**
254      */

255     boolean isSubnodeSelected(int index) {
256         assert (selectedMatches == null)
257                || ((index >= 0) && (index < selectedMatches.length));
258         return (selectedMatches == null) ? selected
259                                          : selectedMatches[index];
260     }
261     
262     @Override JavaDoc
263     public boolean equals(Object JavaDoc anotherObject) {
264         return (anotherObject != null)
265                && (anotherObject.getClass() == MatchingObject.class)
266                && (((MatchingObject) anotherObject).object == this.object);
267     }
268     
269     @Override JavaDoc
270     public int hashCode() {
271         return object.hashCode() + 1;
272     }
273
274     /**
275      * Sets the {@link #childrenSelectionDirty} flag.
276      *
277      * @see #markChildrenSelectionClean
278      */

279     void markChildrenSelectionDirty() {
280         childrenSelectionDirty = true;
281     }
282     
283     /**
284      * Clears the {@link #childrenSelectionDirty} flag.
285      *
286      * @see #markChildrenSelectionDirty()
287      */

288     void markChildrenSelectionClean() {
289         childrenSelectionDirty = false;
290     }
291     
292     /**
293      * Is the {@link #childrenSelectionDirty} flag set?
294      *
295      * @return {@code true} if the flag is set, {@code false} otherwise
296      * @see #markChildrenSelectionDirty()
297      */

298     boolean isChildrenSelectionDirty() {
299         return childrenSelectionDirty;
300     }
301     
302     /**
303      * Stores information whether the node representing this object is expanded
304      * or collapsed.
305      *
306      * @param expanded {@code true} if the node is expanded,
307      * {@code false} if the node is collapsed
308      * @see #isExpanded()
309      */

310     void markExpanded(boolean expanded) {
311         this.expanded = expanded;
312     }
313     
314     /**
315      * Provides information whether the node representing this object
316      * is expanded or collapsed.
317      *
318      * @return {@code true} if the node is expanded,
319      * {@code false} if the node is collapsed
320      * @see #markExpanded
321      */

322     boolean isExpanded() {
323         return expanded;
324     }
325     
326     /**
327      */

328     File JavaDoc getFile() {
329         return file;
330     }
331     
332     /** Get the name (not the path) of the file */
333     String JavaDoc getName() {
334         return getFile().getName();
335     }
336
337     /**
338      */

339     long getTimestamp() {
340         return timestamp;
341     }
342     
343     /**
344      */

345     String JavaDoc getDescription() {
346         return getFile().getParent();
347     }
348
349     /**
350      */

351     String JavaDoc getText() throws IOException JavaDoc {
352         StringBuilder JavaDoc txt = text();
353         if (txt != null) {
354             return txt.toString();
355         } else {
356             return null;
357         }
358     }
359     
360     /**
361      * Reads the file if it has not been read already.
362      *
363      * @author TimBoudreau
364      * @author Marian Petras
365      */

366     private StringBuilder JavaDoc text() throws IOException JavaDoc {
367         return text(false);
368     }
369     
370     private StringBuilder JavaDoc text(boolean refreshCache) throws IOException JavaDoc {
371         assert !EventQueue.isDispatchThread();
372         
373         if (refreshCache || (text == null)) {
374             text = readText();
375         }
376         return text == null ? new StringBuilder JavaDoc() : text;
377     }
378     
379     /**
380      * Reads the text from the file.
381      *
382      * @return {@code StringBuilder} containing the text file's content,
383      * or {@code null} if reading was interrupted
384      * @exception java.io.IOException if some error occured while reading
385      * the file
386      */

387     private StringBuilder JavaDoc readText() throws IOException JavaDoc {
388         StringBuilder JavaDoc ret = null;
389         
390         ByteBuffer JavaDoc buf = getByteBuffer();
391         if (buf != null) {
392             Charset JavaDoc charset = FullTextType.getCharset(getFileObject());
393             CharBuffer JavaDoc cbuf = charset.decode(buf);
394             String JavaDoc terminator
395                     = System.getProperty("line.separator"); //NOI18N
396

397             if (!terminator.equals("\n")) { //NOI18N
398
Matcher JavaDoc matcher = Pattern.compile(terminator).matcher(cbuf);
399                 if (matcher.find()) {
400                     wasCrLf = true;
401                     matcher.reset();
402                     ret = new StringBuilder JavaDoc(
403                                         matcher.replaceAll("\n")); //NOI18N
404
}
405             }
406             if (ret == null) {
407                 ret = new StringBuilder JavaDoc(cbuf);
408             }
409         }
410         return ret;
411     }
412
413     /**
414      *
415      * @author Tim Boudreau
416      */

417     private ByteBuffer JavaDoc getByteBuffer() throws IOException JavaDoc {
418         assert !EventQueue.isDispatchThread();
419         
420         File JavaDoc file = getFile();
421         //XXX optimize with a single shared bytebuffer if performance
422
//problems noted
423
FileInputStream JavaDoc str = new FileInputStream JavaDoc(file);
424
425         ByteBuffer JavaDoc buffer = ByteBuffer.allocate((int) file.length());
426         FileChannel JavaDoc channel = str.getChannel();
427         try {
428             channel.read(buffer, 0);
429         } catch (ClosedByInterruptException JavaDoc cbie) {
430             return null; //this is actually okay
431
} finally {
432             channel.close();
433         }
434         buffer.rewind();
435         return buffer;
436     }
437     
438     /**
439      * Describes invalidity status of this item.
440      */

441     enum InvalidityStatus {
442         
443         DELETED(true, "Inv_status_Err_deleted"), //NOI18N
444
BECAME_DIR(true, "Inv_status_Err_became_dir"), //NOI18N
445
CHANGED(false, "Inv_status_Err_changed"), //NOI18N
446
TOO_BIG(false, "Inv_status_Err_too_big"), //NOI18N
447
CANT_READ(false, "Inv_status_Err_cannot_read"), //NOI18N
448
TOO_SHORT(false, "Inv_status_Err_too_short"); //NOI18N
449

450         /**
451          * Is this invalidity the fatal one?
452          *
453          * @see #isFatal()
454          */

455         private final boolean fatal;
456         /**
457          * resource bundle key for the description
458          *
459          * @see #getDescription(String)
460          */

461         private final String JavaDoc descrBundleKey;
462         
463         /**
464          * Creates an invalidity status.
465          *
466          * @param fatal whether this status means that the invalidity is fatal
467          * @see #isFatal()
468          */

469         private InvalidityStatus(boolean fatal, String JavaDoc descrBundleKey) {
470             this.fatal = fatal;
471             this.descrBundleKey = descrBundleKey;
472         }
473         
474         /**
475          * Is this invalidity fatal such that the item should be removed
476          * from the search results?
477          */

478         boolean isFatal() {
479             return fatal;
480         }
481         
482         /**
483          * Provides human-readable description of this invalidity status.
484          *
485          * @param path path or name of file that has this invalidity status
486          * @return description of the invalidity status with the given path
487          * or name embedded
488          */

489         String JavaDoc getDescription(String JavaDoc path) {
490             return NbBundle.getMessage(getClass(), descrBundleKey, path);
491         }
492         
493     }
494     
495     /**
496      */

497     InvalidityStatus checkValidity() {
498         InvalidityStatus status = getInvalidityStatus();
499         if (status != null) {
500             valid = false;
501         }
502         return status;
503     }
504     
505     /**
506      */

507     String JavaDoc getInvalidityDescription() {
508         String JavaDoc descr;
509         
510         InvalidityStatus status = getInvalidityStatus();
511         if (status != null) {
512             descr = status.getDescription(getFile().getPath());
513         } else {
514             descr = null;
515         }
516         return descr;
517     }
518     
519     /**
520      * Check validity status of this item.
521      *
522      * @return an invalidity status of this item if it is invalid,
523      * or {@code null} if this item is valid
524      * @author Tim Boudreau
525      * @author Marian Petras
526      */

527     private InvalidityStatus getInvalidityStatus() {
528         File JavaDoc f = getFile();
529         if (!f.exists()) {
530             return InvalidityStatus.DELETED;
531         }
532         
533         if (f.isDirectory()) {
534             return InvalidityStatus.BECAME_DIR;
535         }
536         
537         long stamp = f.lastModified();
538         if (stamp > resultModel.getCreationTime()) {
539             return InvalidityStatus.CHANGED;
540         }
541         
542         if (f.length() > Integer.MAX_VALUE) {
543             return InvalidityStatus.TOO_BIG;
544         }
545         
546         if (!f.canRead()) {
547             return InvalidityStatus.CANT_READ;
548         }
549         
550         FullTextType fullTextType = resultModel.fullTextSearchType;
551         if ((fullTextType.getRe().length() == 0)
552                 && f.length() < fullTextType.getMatchString().length()) {
553             return InvalidityStatus.TOO_SHORT;
554         }
555         
556         return null;
557     }
558     
559     /**
560      */

561     boolean isValid() {
562         return valid;
563     }
564
565     /**
566      */

567     public InvalidityStatus replace() throws IOException JavaDoc {
568         assert !EventQueue.isDispatchThread();
569         assert isSelected();
570         
571         Boolean JavaDoc uniformSelection = checkSubnodesSelection();
572         final boolean shouldReplaceAll = (uniformSelection == Boolean.TRUE);
573         final boolean shouldReplaceNone = (uniformSelection == Boolean.FALSE);
574         
575         if (shouldReplaceNone) {
576             return null;
577         }
578         
579         StringBuilder JavaDoc content = text(true); //refresh the cache, reads the file
580

581         List JavaDoc<TextDetail> textMatches = resultModel.fullTextSearchType
582                                                     .getTextDetails(object);
583         int matchIndex = 0;
584
585         int currLineOffset = 0;
586         int currLine = 1;
587
588         int inlineMatchNumber = 0; //order of a match in a line
589
int inlineOffsetShift = 0; //shift of offsets caused by replacements
590

591         mainloop:
592         for (TextDetail textDetail : textMatches) {
593             int matchLine = textDetail.getLine();
594
595             while (currLine < matchLine) {
596                 int lfOffset = content.indexOf("\n",currLineOffset);//NOI18N
597
if (lfOffset == -1) {
598                     assert false; //PENDING - should notify user
599
break mainloop;
600                 }
601
602                 currLineOffset = lfOffset + 1; //skips "\n"
603
currLine++;
604                 inlineMatchNumber = 0;
605                 inlineOffsetShift = 0;
606             }
607
608             if (!isSubnodeSelected(matchIndex++)) {
609                 continue;
610             }
611
612             if (++inlineMatchNumber == 1) { //first selected match on a line
613
boolean check = false;
614                 assert check = true; //side-effect - turns the check on
615
if (check) {
616                     int lineEndOffset = content.indexOf("\n", //NOI18N
617
currLineOffset);
618                     String JavaDoc fileLine = (lineEndOffset != -1)
619                                       ? content.substring(currLineOffset,
620                                                           lineEndOffset)
621                                       : content.substring(currLineOffset);
622                     if (!fileLine.equals(textDetail.getLineText())) {
623                         return InvalidityStatus.CHANGED;
624                     }
625                 }
626             }
627
628             int matchLength = textDetail.getMarkLength();
629             int matchOffset = currLineOffset + inlineOffsetShift
630                               + (textDetail.getColumn() - 1);
631             int matchEndOffset = matchOffset + matchLength;
632             if (!content.substring(matchOffset, matchEndOffset)
633                 .equals(textDetail.getLineText().substring(
634                                 textDetail.getColumn() - 1,
635                                 textDetail.getColumn() - 1 + matchLength))) {
636                 return InvalidityStatus.CHANGED;
637             }
638             
639             content.replace(matchOffset, matchEndOffset,
640                             resultModel.replaceString);
641             inlineOffsetShift += resultModel.replaceString.length()
642                                  - matchLength;
643         }
644         return null;
645     }
646     
647     /** debug flag */
648     private static final boolean REALLY_WRITE = true;
649
650     /**
651      */

652     void write() throws IOException JavaDoc {
653         if (text == null) {
654             throw new IllegalStateException JavaDoc("Buffer is gone"); //NOI18N
655
}
656         
657         if (REALLY_WRITE) {
658             if (wasCrLf) {
659                 String JavaDoc terminator
660                         = System.getProperty("line.separator"); //NOI18N
661
//XXX use constant - i.e. on mac, only \r, etc.
662
text = new StringBuilder JavaDoc(
663                         text.toString().replace("\n", terminator)); //NOI18N
664
}
665             Charset JavaDoc charset = FullTextType.getCharset(getFileObject());
666             ByteBuffer JavaDoc buffer = charset.encode(text.toString());
667
668             FileOutputStream JavaDoc fos = new FileOutputStream JavaDoc(getFile());
669             FileChannel JavaDoc channel = fos.getChannel();
670             channel.write(buffer);
671             //if (Search.UNIT_TESTING) {
672
// channel.force(true);
673
//}
674
channel.close();
675         } else {
676             System.err.println("Would write to " + getFile().getPath());//NOI18N
677
System.err.println(text);
678         }
679     }
680
681     /** Returns name of this node.
682      * @return name of this node.
683      */

684     public String JavaDoc toString() {
685         return super.toString() + "[" + getName()+ "]"; // NOI18N
686
}
687 }
688
Popular Tags