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