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 org.netbeans.editor;
043:
044: import java.util.ArrayList;
045: import java.util.List;
046: import java.util.Random;
047: import javax.swing.event.DocumentEvent;
048: import javax.swing.event.DocumentListener;
049: import javax.swing.text.BadLocationException;
050: import javax.swing.text.Document;
051: import javax.swing.text.Element;
052: import javax.swing.text.PlainDocument;
053: import javax.swing.text.Position;
054: import javax.swing.undo.UndoManager;
055: import junit.framework.TestCase;
056:
057: /**
058: * Test compatibility of the editor's document implementation
059: * with the PlainDocument.
060: *
061: * @author mmetelka
062: */
063: public class PlainDocumentCompatibilityRandomTest extends TestCase
064: implements DocumentListener {
065:
066: private static final boolean debug = false;
067: private static final boolean debugLines = false;
068:
069: private static final int OP_COUNT_1 = 10000;
070: private static final int INSERT_RATIO_1 = 100;
071: private static final int INSERT_MAX_LENGTH_1 = 10;
072: private static final float INSERT_NL_RATIO_1 = 0.5f;
073: private static final int REMOVE_RATIO_1 = 70;
074: private static final int REMOVE_MAX_LENGTH_1 = 10;
075: // [TODO] Reset to zero temporarily
076: // as the current document content has certain specifics for positions undo
077: private static final int CREATE_POSITION_RATIO_1 = 0;
078: private static final int RELEASE_POSITION_RATIO_1 = 20;
079: private static final int UNDO_RATIO_1 = 30;
080: private static final int UNDO_MAX_COUNT_1 = 5;
081: private static final int REDO_RATIO_1 = 30;
082: private static final int REDO_MAX_COUNT_1 = 5;
083:
084: private static final int OP_COUNT_2 = 10000;
085: private static final int INSERT_RATIO_2 = 70;
086: private static final int INSERT_MAX_LENGTH_2 = 10;
087: private static final float INSERT_NL_RATIO_2 = 0.2f;
088: private static final int REMOVE_RATIO_2 = 100;
089: private static final int REMOVE_MAX_LENGTH_2 = 10;
090: // [TODO] Reset to zero temporarily
091: // as the current document content has certain specifics for positions undo
092: private static final int CREATE_POSITION_RATIO_2 = 0;
093: private static final int RELEASE_POSITION_RATIO_2 = 40;
094: private static final int UNDO_RATIO_2 = 30;
095: private static final int UNDO_MAX_COUNT_2 = 5;
096: private static final int REDO_RATIO_2 = 30;
097: private static final int REDO_MAX_COUNT_2 = 5;
098:
099: private PlainDocument masterDoc;
100:
101: private BaseDocument testDoc;
102:
103: private DocumentEvent masterEvent;
104:
105: private List masterPositions = new ArrayList();
106:
107: private List testPositions = new ArrayList();
108:
109: private UndoManager masterUndoManager = new UndoManager();
110:
111: private UndoManager testUndoManager = new UndoManager();
112:
113: public PlainDocumentCompatibilityRandomTest(String testName) {
114: super (testName);
115: }
116:
117: public void test() {
118: testFresh(0);
119: }
120:
121: public void testFresh(long seed) {
122: if (seed == 0) {
123: seed = System.currentTimeMillis();
124: System.err.println("Chosen SEED=" + seed);
125: }
126: Random random = new Random(seed);
127:
128: if (debug) {
129: System.err.println("TESTING with SEED=" + seed);
130: }
131:
132: masterDoc = new PlainDocument();
133: testDoc = new BaseDocument(BaseKit.class, false);
134:
135: // Atach document listener
136: masterDoc.addDocumentListener(this );
137: testDoc.addDocumentListener(this );
138:
139: // Attach undoable listeners
140: masterDoc.addUndoableEditListener(masterUndoManager);
141: testDoc.addUndoableEditListener(testUndoManager);
142:
143: testRound(random, OP_COUNT_1, INSERT_RATIO_1,
144: INSERT_MAX_LENGTH_1, INSERT_NL_RATIO_1, REMOVE_RATIO_1,
145: REMOVE_MAX_LENGTH_1, CREATE_POSITION_RATIO_1,
146: RELEASE_POSITION_RATIO_1, UNDO_RATIO_1,
147: UNDO_MAX_COUNT_1, REDO_RATIO_1, REDO_MAX_COUNT_1);
148:
149: testRound(random, OP_COUNT_2, INSERT_RATIO_2,
150: INSERT_MAX_LENGTH_2, INSERT_NL_RATIO_2, REMOVE_RATIO_2,
151: REMOVE_MAX_LENGTH_2, CREATE_POSITION_RATIO_2,
152: RELEASE_POSITION_RATIO_2, UNDO_RATIO_2,
153: UNDO_MAX_COUNT_2, REDO_RATIO_2, REDO_MAX_COUNT_2);
154:
155: // Detach undo managers
156: masterDoc.removeUndoableEditListener(masterUndoManager);
157: testDoc.removeUndoableEditListener(testUndoManager);
158:
159: // Clear undo managers
160: masterUndoManager.discardAllEdits();
161: testUndoManager.discardAllEdits();
162:
163: // Remove document listeners
164: masterDoc.removeDocumentListener(this );
165: testDoc.removeDocumentListener(this );
166: }
167:
168: private void testRound(Random random, int opCount, int insertRatio,
169: int insertMaxLength, float insertNlRatio, int removeRatio,
170: int removeMaxLength, int createPositionRatio,
171: int releasePositionRatio, int undoRatio, int undoMaxCount,
172: int redoRatio, int redoMaxCount) {
173:
174: int ratioSum = insertRatio + removeRatio + createPositionRatio
175: + releasePositionRatio + undoRatio + redoRatio;
176:
177: for (int op = 0; op < opCount; op++) {
178: double r = random.nextDouble() * ratioSum;
179: int docLength = masterDoc.getLength();
180:
181: if (debugLines) {
182: System.err.println("LINES:\n" + linesToString());
183: }
184:
185: if ((r -= insertRatio) < 0) {
186: int offset = (int) ((docLength + 1) * random
187: .nextDouble());
188: int length = (int) (insertMaxLength * random
189: .nextDouble());
190: StringBuffer sb = new StringBuffer();
191: StringBuffer debugSb = debug ? new StringBuffer()
192: : null;
193:
194: for (int i = length - 1; i >= 0; i--) {
195: char ch;
196: if (random.nextDouble() < insertNlRatio) { // insert '\n'
197: ch = '\n';
198: if (debug) {
199: debugSb.append("\\n");
200: }
201:
202: } else { // insert regular char
203: ch = (char) ('a' + (int) (26 * random
204: .nextDouble()));
205: if (debug) {
206: debugSb.append(ch);
207: }
208: }
209: sb.append(ch);
210: }
211: if (debug) {
212: debugOp(op, "insertString(" + offset + ", \""
213: + debugSb + "\")");
214: }
215:
216: try {
217: masterDoc.insertString(offset, sb.toString(), null);
218: testDoc.insertString(offset, sb.toString(), null);
219: // Reset undoable events merging
220: testDoc.resetUndoMerge();
221: } catch (BadLocationException e) {
222: throw new RuntimeException(e);
223: }
224:
225: } else if ((r -= removeRatio) < 0) {
226: int offset = (int) ((docLength + 1) * random
227: .nextDouble());
228: int length = (int) (removeMaxLength * random
229: .nextDouble());
230: length = Math.min(length, docLength - offset);
231:
232: if (debug) {
233: debugOp(op, "remove(" + offset + ", " + length
234: + ")");
235: }
236:
237: try {
238: masterDoc.remove(offset, length);
239: testDoc.remove(offset, length);
240: // Reset undoable events merging
241: testDoc.resetUndoMerge();
242: } catch (BadLocationException e) {
243: throw new RuntimeException(e);
244: }
245:
246: } else if ((r -= createPositionRatio) < 0) {
247: // Allow position at docLength + 1
248: int offset = (int) ((docLength + 2) * random
249: .nextDouble());
250:
251: if (debug) {
252: debugOp(op, "createPosition(" + offset + ")");
253: }
254: try {
255: masterPositions.add(masterDoc
256: .createPosition(offset));
257: testPositions.add(testDoc.createPosition(offset));
258: } catch (BadLocationException e) {
259: throw new RuntimeException(e);
260: }
261:
262: } else if ((r -= releasePositionRatio) < 0) {
263: int masterPositionsCount = masterPositions.size();
264: if (masterPositionsCount > 0) {
265: int index = (int) (masterPositionsCount * random
266: .nextDouble());
267:
268: if (debug) {
269: debugOp(op, "release position at index="
270: + index);
271: }
272:
273: masterPositions.remove(index);
274: testPositions.remove(index);
275: }
276:
277: } else if ((r -= undoRatio) < 0) {
278: int undoCount = (int) (undoMaxCount * random
279: .nextDouble());
280:
281: if (debug) {
282: debugOp(op, "undo(" + undoCount + ")");
283: }
284:
285: while (undoCount > 0) {
286: undoCount--;
287: if (masterUndoManager.canUndo()) {
288: masterUndoManager.undo();
289: testUndoManager.undo();
290: if (undoCount > 0) {
291: checkConsistency(); // Check consistency after each undo
292: }
293: }
294: }
295:
296: } else if ((r -= redoRatio) < 0) {
297: int redoCount = (int) (redoMaxCount * random
298: .nextDouble());
299:
300: if (debug) {
301: debugOp(op, "redo(" + redoCount + ")");
302: }
303:
304: while (redoCount > 0) {
305: redoCount--;
306: if (masterUndoManager.canRedo()) {
307: masterUndoManager.redo();
308: testUndoManager.redo();
309: if (redoCount > 0) {
310: checkConsistency(); // Check consistency after each redo
311: }
312: }
313: }
314: }
315:
316: checkConsistency();
317: }
318:
319: }
320:
321: private void debugOp(int op, String s) {
322: System.err.println("op: " + op + ", " + s);
323: }
324:
325: private void checkConsistency() {
326: try {
327: int docLength = masterDoc.getLength();
328: assertEquals(docLength, testDoc.getLength());
329:
330: String masterText = masterDoc.getText(0, docLength);
331: String testText = testDoc.getText(0, docLength);
332: assertEquals(masterText, testText);
333:
334: Element lineRoot = masterDoc.getDefaultRootElement();
335: Element testLineRoot = testDoc.getDefaultRootElement();
336: int lineCount = lineRoot.getElementCount();
337: if (lineCount != testLineRoot.getElementCount()) {
338: fail("Line count " + testLineRoot.getElementCount()
339: + " != " + lineCount);
340: }
341: // Compare line boundaries
342: for (int i = 0; i < lineCount; i++) {
343: Element masterLine = lineRoot.getElement(i);
344: Element testLine = testLineRoot.getElement(i);
345: if (masterLine.getStartOffset() != testLine
346: .getStartOffset()) {
347: fail("Start of line " + i + ": Offset "
348: + testLine.getStartOffset() + " != "
349: + masterLine.getStartOffset());
350: }
351: if (masterLine.getEndOffset() != testLine
352: .getEndOffset()) {
353: fail("End of line " + i + ": Offset "
354: + testLine.getEndOffset() + " != "
355: + masterLine.getEndOffset());
356: }
357: }
358:
359: int positionCount = masterPositions.size();
360: for (int i = 0; i < positionCount; i++) {
361: Position masterPos = (Position) masterPositions.get(i);
362: Position testPos = (Position) testPositions.get(i);
363: if (masterPos.getOffset() != testPos.getOffset()) {
364: fail("Tested position " + (i + 1) + " of "
365: + positionCount + ": "
366: + testPos.getOffset() + " != "
367: + masterPos.getOffset());
368: }
369: }
370: } catch (BadLocationException e) {
371: throw new RuntimeException(e);
372: }
373: }
374:
375: private String linesToString() {
376: StringBuffer sb = new StringBuffer();
377: Element masterLineRoot = masterDoc.getDefaultRootElement();
378: Element testLineRoot = testDoc.getDefaultRootElement();
379: int masterLineCount = masterLineRoot.getElementCount();
380: int testLineCount = testLineRoot.getElementCount();
381: int lineCount = Math.max(masterLineCount, testLineCount);
382: sb.append("Line count=" + lineCount + "\n");
383: for (int i = 0; i < lineCount; i++) {
384: if (i < testLineCount) {
385: Element line = testLineRoot.getElement(i);
386: sb.append("[" + i + "]: <" + line.getStartOffset()
387: + ", " + line.getEndOffset() + "> ");
388: } else {
389: sb.append(" <!NONE!> ");
390: }
391:
392: if (i < masterLineCount) {
393: Element line = masterLineRoot.getElement(i);
394: sb.append("[" + i + "]: <" + line.getStartOffset()
395: + ", " + line.getEndOffset() + ">\n");
396: } else {
397: sb.append(" <!NONE!>\n");
398: }
399: }
400: return sb.toString();
401: }
402:
403: private void checkEventsEqual(DocumentEvent testEvent) {
404: if (masterEvent.getOffset() != testEvent.getOffset()) {
405: fail("masterEvent.getOffset()=" + masterEvent.getOffset()
406: + " != testEvent.getOffset()="
407: + testEvent.getOffset());
408: }
409: if (masterEvent.getLength() != testEvent.getLength()) {
410: fail("masterEvent.getLength()=" + masterEvent.getLength()
411: + " != testEvent.getLength()="
412: + testEvent.getLength());
413: }
414: if (masterEvent.getType() != testEvent.getType()) {
415: fail("masterEvent.getType()=" + masterEvent.getType()
416: + " != testEvent.getType()=" + testEvent.getType());
417: }
418: DocumentEvent.ElementChange masterChange = masterEvent
419: .getChange(masterDoc.getDefaultRootElement());
420: DocumentEvent.ElementChange testChange = testEvent
421: .getChange(testDoc.getDefaultRootElement());
422: checkElementChangesEqual(masterChange, testChange);
423: }
424:
425: private void checkElementChangesEqual(
426: DocumentEvent.ElementChange masterChange,
427: DocumentEvent.ElementChange testChange) {
428:
429: if (masterChange == null && testChange == null) { // no line changes
430: return;
431: }
432:
433: if (masterChange == null && testChange != null) {
434: fail("masterChange is null");
435: }
436: if (masterChange != null && testChange == null) {
437: fail("testChange is null");
438: }
439:
440: // Both changes are not null
441: int masterIndex = masterChange.getIndex();
442: int testIndex = testChange.getIndex();
443: if (masterIndex != testIndex) {
444: fail("masterIndex=" + masterIndex + " != testIndex="
445: + testIndex);
446: }
447: Element[] masterAdded = masterChange.getChildrenAdded();
448: Element[] testAdded = testChange.getChildrenAdded();
449: if (masterAdded.length != testAdded.length) {
450: fail("masterAdded.length=" + masterAdded.length
451: + "!= testAdded.length=" + testAdded.length);
452: }
453: Element[] masterRemoved = masterChange.getChildrenRemoved();
454: Element[] testRemoved = testChange.getChildrenRemoved();
455: if (masterRemoved.length != testRemoved.length) {
456: fail("masterRemoved.length=" + masterRemoved.length
457: + "!= testRemoved.length=" + testRemoved.length);
458: }
459: for (int i = 0; i < masterAdded.length; i++) {
460: Element masterElem = masterAdded[i];
461: Element testElem = testAdded[i];
462: checkElementOffsetsEqual(masterElem, testElem);
463: }
464: for (int i = 0; i < masterRemoved.length; i++) {
465: Element masterElem = masterRemoved[i];
466: Element testElem = testRemoved[i];
467: checkElementOffsetsEqual(masterElem, testElem);
468: }
469: }
470:
471: private void checkElementOffsetsEqual(Element masterElem,
472: Element testElem) {
473: if (masterElem.getStartOffset() != testElem.getStartOffset()) {
474: fail("masterElem.getStartOffset()="
475: + masterElem.getStartOffset()
476: + " != testElem.getStartOffset()="
477: + testElem.getStartOffset());
478: }
479: if (masterElem.getEndOffset() != testElem.getEndOffset()) {
480: fail("masterElem.getEndOffset()="
481: + masterElem.getEndOffset()
482: + " != testElem.getEndOffset()="
483: + testElem.getEndOffset());
484: }
485: }
486:
487: private void processEvent(DocumentEvent evt) {
488: // testDoc operations must always be done after the master ones.
489: Document doc = evt.getDocument();
490: if (doc == masterDoc) {
491: masterEvent = evt;
492: } else if (doc == testDoc) {
493: checkEventsEqual(evt);
494: masterEvent = null;
495: } else {
496: fail("Unknown document.");
497: }
498: }
499:
500: public void insertUpdate(DocumentEvent e) {
501: processEvent(e);
502: }
503:
504: public void removeUpdate(DocumentEvent e) {
505: processEvent(e);
506: }
507:
508: public void changedUpdate(DocumentEvent e) {
509: }
510:
511: }
|