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-2006 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 code_folding;
043:
044: import java.awt.event.KeyEvent;
045: import javax.swing.text.AbstractDocument;
046: import javax.swing.text.BadLocationException;
047: import javax.swing.text.JTextComponent;
048: import org.netbeans.jellytools.Bundle;
049: import org.netbeans.jellytools.EditorOperator;
050: import org.netbeans.jellytools.EditorWindowOperator;
051: import lib.EditorTestCase;
052: import org.netbeans.api.editor.fold.Fold;
053: import org.netbeans.api.editor.fold.FoldHierarchy;
054: import org.netbeans.api.editor.fold.FoldUtilities;
055: import org.netbeans.jemmy.operators.JEditorPaneOperator;
056: import org.netbeans.jemmy.operators.JTextComponentOperator;
057:
058: /**
059: * Basic Code Folding Test class.
060: * It contains basic folding functionality methods.
061: *
062: *
063: * @author Martin Roskanin
064: */
065: public class CodeFoldingTest extends EditorTestCase {
066:
067: private final int COLLAPSE_EXPAND_DELAY = 200;
068:
069: /** Creates a new instance of Main */
070: public CodeFoldingTest(String testMethodName) {
071: super (testMethodName);
072: }
073:
074: protected static void appendSpaces(StringBuffer sb, int spaces) {
075: while (--spaces >= 0) {
076: sb.append(' ');
077: }
078: }
079:
080: protected static String foldToStringChildren(Fold fold, int indent) {
081: indent += 4;
082: StringBuffer sb = new StringBuffer();
083: String foldTxt = fold.toString();
084: foldTxt = foldTxt.replace("E1", "E0");
085: foldTxt = foldTxt.replace("C1", "C0");
086: sb.append(foldTxt);
087: sb.append('\n');
088: int foldCount = fold.getFoldCount();
089: for (int i = 0; i < foldCount; i++) {
090: appendSpaces(sb, indent);
091: sb.append('[');
092: sb.append(i);
093: sb.append("]: "); // NOI18N
094: sb.append(foldToStringChildren(fold.getFold(i), indent));
095: }
096:
097: return sb.toString();
098: }
099:
100: public static String foldHierarchyToString(JTextComponent target) {
101: String ret = "";
102: AbstractDocument adoc = (AbstractDocument) target.getDocument();
103:
104: // Dump fold hierarchy
105: FoldHierarchy hierarchy = FoldHierarchy.get(target);
106: adoc.readLock();
107: try {
108: hierarchy.lock();
109: try {
110: Fold root = hierarchy.getRootFold();
111: ret = (root == null) ? "root is null"
112: : foldToStringChildren(root, 0); //NOI18N
113: } finally {
114: hierarchy.unlock();
115: }
116: } finally {
117: adoc.readUnlock();
118: }
119: return ret;
120: }
121:
122: protected void waitForFolding(JTextComponent target,
123: int maxMiliSeconds) {
124: //wait for parser and folding hierarchy creation
125: int time = (int) maxMiliSeconds / 100;
126:
127: AbstractDocument adoc = (AbstractDocument) target.getDocument();
128:
129: // Dump fold hierarchy
130: FoldHierarchy hierarchy = FoldHierarchy.get(target);
131: int foldCount = 0;
132: while (foldCount == 0 && time > 0) {
133:
134: adoc.readLock();
135: try {
136: hierarchy.lock();
137: try {
138: foldCount = hierarchy.getRootFold().getFoldCount();
139: } finally {
140: hierarchy.unlock();
141: }
142: } finally {
143: adoc.readUnlock();
144: }
145:
146: try {
147: Thread.currentThread().sleep(100);
148: } catch (InterruptedException ex) {
149: time = 0;
150: }
151: time--;
152:
153: }
154:
155: }
156:
157: private ValueResolver getResolver(final JTextComponent target) {
158:
159: ValueResolver foldValueResolver = new ValueResolver() {
160: public Object getValue() {
161: FoldHierarchy hierarchy = FoldHierarchy.get(target);
162: int dot = target.getCaret().getDot();
163: hierarchy.lock();
164: try {
165: try {
166: int rowStart = javax.swing.text.Utilities
167: .getRowStart(target, dot);
168: int rowEnd = javax.swing.text.Utilities
169: .getRowEnd(target, dot);
170: Fold fold = getLineFold(hierarchy, dot,
171: rowStart, rowEnd);
172: if (fold != null) {
173: return Boolean.valueOf(fold.isCollapsed());
174: } else {
175: return null;
176: }
177: } catch (BadLocationException ble) {
178: ble.printStackTrace();
179: }
180: } finally {
181: hierarchy.unlock();
182: }
183: return null;
184: }
185: };
186:
187: return foldValueResolver;
188: }
189:
190: protected void collapseFoldAtCaretPosition(EditorOperator editor,
191: int line, int column) {
192: // 1. move to adequate place
193: editor.setCaretPosition(line, column);
194:
195: // 2. hit CTRL -
196: JEditorPaneOperator txtOper = editor.txtEditorPane();
197: txtOper.pushKey(KeyEvent.VK_SUBTRACT, KeyEvent.CTRL_DOWN_MASK);
198:
199: JTextComponentOperator text = new JTextComponentOperator(editor);
200: JTextComponent target = (JTextComponent) text.getSource();
201:
202: // give max 500 milis to fold to collapse
203: waitMaxMilisForValue(500, getResolver(target), Boolean.TRUE);
204:
205: }
206:
207: protected void expandFoldAtCaretPosition(EditorOperator editor,
208: int line, int column) {
209: // 1. move to adequate place
210: editor.setCaretPosition(line, column);
211:
212: // 2. hit CTRL +
213: JEditorPaneOperator txtOper = editor.txtEditorPane();
214: txtOper.pushKey(KeyEvent.VK_ADD, KeyEvent.CTRL_DOWN_MASK);
215:
216: JTextComponentOperator text = new JTextComponentOperator(editor);
217: JTextComponent target = (JTextComponent) text.getSource();
218:
219: // give max 500 milis to fold to expand
220: waitMaxMilisForValue(500, getResolver(target), Boolean.FALSE);
221: }
222:
223: protected void collapseAllFolds(EditorOperator editor) {
224: // hit CTRL Shift -
225: JEditorPaneOperator txtOper = editor.txtEditorPane();
226: txtOper.pushKey(KeyEvent.VK_SUBTRACT, KeyEvent.CTRL_DOWN_MASK
227: | KeyEvent.SHIFT_DOWN_MASK);
228:
229: // wait a while
230: try {
231: Thread.currentThread().sleep(COLLAPSE_EXPAND_DELAY);
232: } catch (InterruptedException ex) {
233: }
234: }
235:
236: protected void expandAllFolds(EditorOperator editor) {
237: // hit CTRL Shift +
238: JEditorPaneOperator txtOper = editor.txtEditorPane();
239: txtOper.pushKey(KeyEvent.VK_ADD, KeyEvent.CTRL_DOWN_MASK
240: | KeyEvent.SHIFT_DOWN_MASK);
241:
242: // wait a while
243: try {
244: Thread.currentThread().sleep(COLLAPSE_EXPAND_DELAY);
245: } catch (InterruptedException ex) {
246: }
247: }
248:
249: /** Returns the fold that should be collapsed/expanded in the caret row
250: * @param hierarchy hierarchy under which all folds should be collapsed/expanded.
251: * @param dot caret position offset
252: * @param lineStart offset of the start of line
253: * @param lineEnd offset of the end of line
254: * @return the fold that meet common criteria in accordance with the caret position
255: */
256: private static Fold getLineFold(FoldHierarchy hierarchy, int dot,
257: int lineStart, int lineEnd) {
258: Fold caretOffsetFold = FoldUtilities.findOffsetFold(hierarchy,
259: dot);
260:
261: // beginning searching from the lineStart
262: Fold fold = FoldUtilities.findNearestFold(hierarchy, lineStart);
263:
264: while (fold != null && (fold.getEndOffset() <= dot || // find next available fold if the 'fold' is one-line
265: // or it has children and the caret is in the fold body
266: // i.e. class A{ |public void method foo(){}}
267: (!fold.isCollapsed() && fold.getFoldCount() > 0 && fold
268: .getStartOffset() + 1 < dot))) {
269:
270: // look for next fold in forward direction
271: Fold nextFold = FoldUtilities
272: .findNearestFold(hierarchy,
273: (fold.getFoldCount() > 0) ? fold
274: .getStartOffset() + 1 : fold
275: .getEndOffset());
276: if (nextFold != null && nextFold.getStartOffset() < lineEnd) {
277: if (nextFold == fold)
278: return fold;
279: fold = nextFold;
280: } else {
281: break;
282: }
283: }
284:
285: // a fold on the next line was found, returning fold at offset (in most cases inner class)
286: if (fold == null || fold.getStartOffset() > lineEnd) {
287:
288: // in the case:
289: // class A{
290: // } |
291: // try to find an offset fold on the offset of the line beginning
292: if (caretOffsetFold == null) {
293: caretOffsetFold = FoldUtilities.findOffsetFold(
294: hierarchy, lineStart);
295: }
296:
297: return caretOffsetFold;
298: }
299:
300: // no fold at offset found, in this case return the fold
301: if (caretOffsetFold == null)
302: return fold;
303:
304: // skip possible inner class members validating if the innerclass fold is collapsed
305: if (caretOffsetFold.isCollapsed())
306: return caretOffsetFold;
307:
308: // in the case:
309: // class A{
310: // public vo|id foo(){} }
311: // 'fold' (in this case fold of the method foo) will be returned
312: if (caretOffsetFold.getEndOffset() > fold.getEndOffset()
313: && fold.getEndOffset() > dot) {
314: return fold;
315: }
316:
317: // class A{
318: // |} public void method foo(){}
319: // inner class fold will be returned
320: if (fold.getStartOffset() > caretOffsetFold.getEndOffset())
321: return caretOffsetFold;
322:
323: // class A{
324: // public void foo(){} |}
325: // returning innerclass fold
326: if (fold.getEndOffset() < dot)
327: return caretOffsetFold;
328:
329: return fold;
330: }
331:
332: }
|