001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: */
017: /**
018: * @author Alexey A. Ivanov
019: * @version $Revision$
020: */package javax.swing.text;
021:
022: import java.awt.Rectangle;
023: import java.util.ArrayList;
024: import java.util.List;
025: import javax.swing.BasicSwingTestCase;
026: import javax.swing.event.DocumentEvent.EventType;
027: import javax.swing.text.AbstractDocument.BranchElement;
028: import javax.swing.text.AbstractDocument.DefaultDocumentEvent;
029: import javax.swing.text.AbstractDocument.ElementEdit;
030: import javax.swing.text.FlowView.FlowStrategy;
031: import javax.swing.text.FlowViewTest.FlowViewImplWithFactory;
032: import javax.swing.text.GlyphView.GlyphPainter;
033: import javax.swing.text.GlyphViewTest.EmptyPainter;
034: import junit.framework.TestCase;
035:
036: public class FlowView_FlowStrategyTest extends TestCase {
037: private DefaultStyledDocument doc;
038:
039: private Element root;
040:
041: private Element p1;
042:
043: private Element p1L;
044:
045: private DefaultDocumentEvent event;
046:
047: private FlowView view;
048:
049: private View row;
050:
051: private FlowStrategy strategy;
052:
053: static final List<Object> viewsCreated = new ArrayList<Object>();
054:
055: private static final Rectangle alloc = new Rectangle(3, 7, 37, 141);
056:
057: private static final int X_AXIS = View.X_AXIS;
058:
059: private static final int Y_AXIS = View.Y_AXIS;
060:
061: private static boolean excellentBreak;
062:
063: protected static class TestStrategy extends FlowStrategy {
064: @Override
065: protected View createView(FlowView fv, int startOffset,
066: int spanLeft, int rowIndex) {
067: View result = super .createView(fv, startOffset, spanLeft,
068: rowIndex);
069: viewsCreated.add(result);
070: return result;
071: }
072:
073: @Override
074: public String toString() {
075: return "logStrategy";
076: }
077: }
078:
079: protected static class FixedPainter extends EmptyPainter {
080: protected static final GlyphPainter PAINTER = new FixedPainter();
081:
082: private FixedPainter() {
083: }
084:
085: @Override
086: public int getBoundedPosition(GlyphView v, int startOffset,
087: float x, float len) {
088: int result = (int) len / PartView.CHAR_WIDTH + startOffset;
089: return result <= v.getEndOffset() ? result : v
090: .getEndOffset();
091: }
092:
093: @Override
094: public float getSpan(GlyphView v, int startOffset,
095: int endOffset, TabExpander e, float x) {
096: return PartView.CHAR_WIDTH * (endOffset - startOffset);
097: }
098: }
099:
100: protected static class PartView extends GlyphView {
101: private static int count = 0;
102:
103: private int id = count++;
104:
105: static final int CHAR_WIDTH = 4;
106:
107: static final int CHAR_HEIGHT = 9;
108:
109: public PartView(Element element) {
110: super (element);
111: setGlyphPainter(FixedPainter.PAINTER);
112: }
113:
114: @Override
115: public float getPreferredSpan(int axis) {
116: int result = axis == X_AXIS ? (int) super
117: .getPreferredSpan(axis) : CHAR_HEIGHT;
118: return result;
119: }
120:
121: @Override
122: public String toString() {
123: final Element e = getElement();
124: return "glyph(" + id + ", " + e.getName() + "["
125: + e.getStartOffset() + ", " + e.getEndOffset()
126: + "]" + ") [" + getStartOffset() + ", "
127: + getEndOffset() + "]";
128: }
129: }
130:
131: protected static class PartFactory implements ViewFactory {
132: public View create(Element element) {
133: return new PartView(element);
134: }
135: }
136:
137: @Override
138: protected void setUp() throws Exception {
139: super .setUp();
140: doc = new DefaultStyledDocument();
141: doc
142: .insertString(
143: 0,
144: "this is the test text with some long words in p2\n"
145: // | |
146: // 0123456789012345678901234567890123456789012345678
147: // 0 1 2 3 4
148: + "Long words: internationalization, localization",
149: null);
150: root = doc.getDefaultRootElement();
151: p1 = root.getElement(0);
152: // Break the first paragraph into several
153: doc.writeLock();
154: Element[] leaves = new Element[] {
155: doc.createLeafElement(p1, null, 0, 17),
156: doc.createLeafElement(p1, null, 17, 32),
157: doc.createLeafElement(p1, null, 32, 49) };
158: ((BranchElement) p1).replace(0, p1.getElementCount(), leaves);
159: doc.writeUnlock();
160: p1L = p1.getElement(0);
161: // Initialize the view
162: view = new FlowViewImplWithFactory(p1, Y_AXIS,
163: new PartFactory());
164: strategy = view.strategy = new TestStrategy();
165: view.loadChildren(null);
166: row = view.createRow();
167: view.append(row);
168: }
169:
170: @Override
171: protected void tearDown() throws Exception {
172: super .tearDown();
173: viewsCreated.clear();
174: }
175:
176: public void testInsertUpdate() {
177: event = doc.new DefaultDocumentEvent(4, 5, EventType.INSERT);
178: event.addEdit(new ElementEdit(p1, 0, new Element[] { p1L },
179: new Element[] { p1L }));
180: event.end();
181: view.layout(alloc.width, alloc.height);
182: assertTrue(view.isAllocationValid());
183: view.strategy.insertUpdate(view, event, alloc);
184: assertTrue(view.isAllocationValid());
185: }
186:
187: public void testInsertUpdateNull() {
188: view.layout(alloc.width, alloc.height);
189: assertTrue(view.isAllocationValid());
190: view.strategy.insertUpdate(view, null, null);
191: assertFalse(view.isAllocationValid());
192: assertFalse(view.isLayoutValid(X_AXIS));
193: assertFalse(view.isLayoutValid(Y_AXIS));
194: }
195:
196: public void testRemoveUpdate() {
197: event = doc.new DefaultDocumentEvent(4, 5, EventType.REMOVE);
198: event.addEdit(new ElementEdit(p1, 0, new Element[] { p1L },
199: new Element[] { p1L }));
200: event.end();
201: view.layout(alloc.width, alloc.height);
202: assertTrue(view.isAllocationValid());
203: view.strategy.removeUpdate(view, event, alloc);
204: assertTrue(view.isAllocationValid());
205: }
206:
207: public void testRemoveUpdateNull() {
208: view.layout(alloc.width, alloc.height);
209: assertTrue(view.isAllocationValid());
210: view.strategy.removeUpdate(view, null, null);
211: assertFalse(view.isAllocationValid());
212: assertFalse(view.isLayoutValid(X_AXIS));
213: assertFalse(view.isLayoutValid(Y_AXIS));
214: }
215:
216: public void testChangedUpdate() {
217: event = doc.new DefaultDocumentEvent(4, 5, EventType.CHANGE);
218: event.addEdit(new ElementEdit(p1, 0, new Element[] { p1L },
219: new Element[] { p1L }));
220: event.end();
221: view.layout(alloc.width, alloc.height);
222: assertTrue(view.isAllocationValid());
223: view.strategy.changedUpdate(view, event, alloc);
224: assertTrue(view.isAllocationValid());
225: }
226:
227: public void testChangedUpdateNull() {
228: view.layout(alloc.width, alloc.height);
229: assertTrue(view.isAllocationValid());
230: view.strategy.changedUpdate(view, null, null);
231: assertFalse(view.isAllocationValid());
232: assertFalse(view.isLayoutValid(X_AXIS));
233: assertFalse(view.isLayoutValid(Y_AXIS));
234: }
235:
236: /**
237: * Tests <code>adjustRow</code> in ordinary "environment".
238: */
239: public void testAdjustRowOrdinary() {
240: view.append(row);
241: View[] views = new View[view.layoutPool.getViewCount() - 1];
242: for (int i = 0; i < views.length; i++) {
243: views[i] = view.layoutPool.getView(i);
244: }
245: row.replace(0, 0, views);
246: view.layoutSpan = (int) (view.layoutPool.getView(0)
247: .getPreferredSpan(X_AXIS) + view.layoutPool.getView(1)
248: .getPreferredSpan(X_AXIS) / 2);
249: assertEquals(2, row.getViewCount());
250: strategy.adjustRow(view, 0, view.layoutSpan, 0);
251: assertEquals(2, row.getViewCount());
252: // The first physical view will be the same as the logical one (copied)
253: assertSame(view.layoutPool.getView(0), row.getView(0));
254: assertSame(row, view.layoutPool.getView(0).getParent());
255: assertSame(row, row.getView(0).getParent());
256: // The second is broken
257: final View second = row.getView(1);
258: assertEquals(view.layoutPool.getView(1).getStartOffset(),
259: second.getStartOffset());
260: assertEquals(22, row.getView(1).getEndOffset());
261: assertSame(view.layoutPool, view.layoutPool.getView(1)
262: .getParent());
263: assertSame(row, second.getParent());
264: assertEquals(0, row.getStartOffset());
265: assertEquals(22, row.getEndOffset());
266: }
267:
268: /**
269: * Tests <code>adjustRow</code> in a situation where the first and the third
270: * views return <code>GoodBreakWeight</code> but the second returns
271: * <code>ExcellentBreakWeight</code>. Here the second view will be broken
272: * although it fits (the third doesn't fit the layoutSpan only).
273: */
274: public void testAdjustRowGoodExcellentGood() {
275: View[] views = new View[view.layoutPool.getViewCount()];
276: view.layoutSpan = 0;
277: for (int i = 0; i < views.length; i++) {
278: excellentBreak = i == 1;
279: // Special view to get the required break weights easily
280: views[i] = new PartView(view.layoutPool.getView(i)
281: .getElement()) {
282: private final boolean excellent = excellentBreak;
283:
284: @Override
285: public int getBreakWeight(int axis, float x, float len) {
286: return excellent ? ExcellentBreakWeight
287: : GoodBreakWeight;
288: }
289:
290: @Override
291: public View breakView(int axis, int start, float x,
292: float len) {
293: return createFragment(start, start
294: + (getEndOffset() - getStartOffset()) / 2);
295: }
296: };
297: int span = (int) views[i].getPreferredSpan(X_AXIS);
298: if (i == views.length - 1) {
299: span = span / 2;
300: }
301: view.layoutSpan += span;
302: }
303: row.replace(0, 0, views);
304: // layoutSpan includes the first and the second views entirely and
305: // only the half of the third.
306: // The view will break on the second one, that's why the number of
307: // children reduces from 3 to 2.
308: assertEquals(3, row.getViewCount());
309: strategy.adjustRow(view, 0, view.layoutSpan, 0);
310: assertEquals(2, row.getViewCount());
311: View child = row.getView(0);
312: assertEquals(0, child.getStartOffset());
313: assertEquals(17, child.getEndOffset());
314: child = row.getView(1);
315: assertEquals(17, child.getStartOffset());
316: assertEquals(24, child.getEndOffset());
317: assertEquals(0, row.getStartOffset());
318: assertEquals(24, row.getEndOffset());
319: }
320:
321: /**
322: * Adjust the missing row. (Causes expected exception.)
323: */
324: public void testAdjustRowNoRow() {
325: assertEquals(1, view.getViewCount());
326: try {
327: strategy.adjustRow(view, 1, view.layoutSpan, 0);
328: fail("NullPointerException or ArrayIndexOutOfBoundsException "
329: + "is expected");
330: } catch (NullPointerException e) {
331: } catch (ArrayIndexOutOfBoundsException e) {
332: }
333: }
334:
335: /**
336: * Layout the missing row. (Causes expected exception.)
337: */
338: public void testLayoutRowNoRow() {
339: assertEquals(1, view.getViewCount());
340: try {
341: strategy.layoutRow(view, 1, 0);
342: fail("NullPointerException or ArrayIndexOutOfBoundsException "
343: + "is expected");
344: } catch (NullPointerException e) {
345: } catch (ArrayIndexOutOfBoundsException e) {
346: }
347: }
348:
349: /**
350: * Tests the situation where the layoutSpan is exhausted.
351: */
352: public void testLayoutRowSpanExhausted() {
353: view.layoutSpan = (int) (view.layoutPool.getView(0)
354: .getPreferredSpan(X_AXIS) + view.layoutPool.getView(1)
355: .getPreferredSpan(X_AXIS) / 2);
356: assertEquals(98, view.layoutSpan);
357: int nextOffset = strategy.layoutRow(view, 0, 5);
358: assertEquals(2, row.getViewCount());
359: assertEquals(1, view.getViewCount());
360: assertEquals(2, viewsCreated.size());
361: final View child1 = row.getView(0);
362: assertSame(viewsCreated.get(0), child1);
363: assertEquals(5, child1.getStartOffset());
364: assertEquals(view.layoutPool.getView(0).getEndOffset(), child1
365: .getEndOffset());
366: assertSame(viewsCreated.get(1), view.layoutPool.getView(1));
367: final View child2 = row.getView(1);
368: assertNotSame(viewsCreated.get(1), child2);
369: assertEquals(child1.getEndOffset(), child2.getStartOffset());
370: assertEquals(27, child2.getEndOffset());
371: assertTrue(view.layoutSpan > child1.getPreferredSpan(X_AXIS)
372: + child2.getPreferredSpan(X_AXIS));
373: assertEquals(child2.getEndOffset(), nextOffset);
374: assertEquals(row.getEndOffset(), nextOffset);
375: }
376:
377: /**
378: * Tests the situation where <code>createView</code> returns
379: * <code>null</code>.
380: */
381: public void testLayoutRowNullReturned() {
382: strategy = view.strategy = new TestStrategy() {
383: private int count = 0;
384:
385: @Override
386: protected View createView(FlowView fv, int startOffset,
387: int spanLeft, int rowIndex) {
388: return count++ == 2 ? null : super .createView(fv,
389: startOffset, spanLeft, rowIndex);
390: }
391: };
392: assertEquals(Short.MAX_VALUE, view.layoutSpan);
393: int nextOffset = strategy.layoutRow(view, 0, 5);
394: assertEquals(2, row.getViewCount());
395: assertEquals(1, view.getViewCount());
396: assertEquals(2, viewsCreated.size());
397: final View child1 = row.getView(0);
398: assertSame(viewsCreated.get(0), child1);
399: assertEquals(5, child1.getStartOffset());
400: assertEquals(view.layoutPool.getView(0).getEndOffset(), child1
401: .getEndOffset());
402: assertSame(viewsCreated.get(1), view.layoutPool.getView(1));
403: final View child2 = row.getView(1);
404: assertSame(viewsCreated.get(1), child2);
405: assertEquals(child1.getEndOffset(), child2.getStartOffset());
406: assertEquals(view.layoutPool.getView(1).getEndOffset(), child2
407: .getEndOffset());
408: assertTrue(view.layoutSpan > child1.getPreferredSpan(X_AXIS)
409: + child2.getPreferredSpan(X_AXIS));
410: assertEquals(child2.getEndOffset(), nextOffset);
411: assertEquals(row.getEndOffset(), nextOffset);
412: }
413:
414: /**
415: * Tests the situation where a view requests a forced break.
416: */
417: public void testLayoutRowForcedBreak() {
418: strategy = view.strategy = new TestStrategy() {
419: private int count = 0;
420:
421: @Override
422: protected View createView(FlowView fv, int startOffset,
423: int spanLeft, int rowIndex) {
424: if (count++ == 1) {
425: Element e = fv.layoutPool.getView(count - 1)
426: .getElement();
427: return new PartView(e) {
428: private final int half = (getEndOffset() - getStartOffset()) / 2;
429: {
430: viewsCreated.add(this );
431: }
432:
433: @Override
434: public int getBreakWeight(int axis, float x,
435: float len) {
436: if (len > CHAR_WIDTH * half) {
437: return ForcedBreakWeight;
438: }
439: return super .getBreakWeight(axis, x, len);
440: }
441:
442: @Override
443: public View breakView(int axis,
444: int startOffset, float x, float len) {
445: if (len > CHAR_WIDTH * half) {
446: return createFragment(startOffset,
447: startOffset + half);
448: }
449: return super .breakView(axis, startOffset,
450: x, len);
451: }
452: };
453: }
454: return super .createView(fv, startOffset, spanLeft,
455: rowIndex);
456: }
457: };
458: assertEquals(Short.MAX_VALUE, view.layoutSpan);
459: int nextOffset = strategy.layoutRow(view, 0, 5);
460: assertEquals(2, row.getViewCount());
461: assertEquals(1, view.getViewCount());
462: assertEquals(2, viewsCreated.size());
463: final View child1 = row.getView(0);
464: assertSame(viewsCreated.get(0), child1);
465: assertEquals(5, child1.getStartOffset());
466: assertEquals(view.layoutPool.getView(0).getEndOffset(), child1
467: .getEndOffset());
468: assertNotSame(viewsCreated.get(1), view.layoutPool.getView(1));
469: final View forced = (View) viewsCreated.get(1);
470: assertEquals(view.layoutPool.getView(1).getStartOffset(),
471: forced.getStartOffset());
472: assertEquals(view.layoutPool.getView(1).getEndOffset(), forced
473: .getEndOffset());
474: assertNotSame(viewsCreated.get(1), row.getView(1));
475: final View child2 = row.getView(1);
476: assertEquals(child1.getEndOffset(), child2.getStartOffset());
477: assertEquals(
478: (view.layoutPool.getView(1).getEndOffset() + view.layoutPool
479: .getView(1).getStartOffset()) / 2, child2
480: .getEndOffset());
481: assertTrue(view.layoutSpan > child1.getPreferredSpan(X_AXIS)
482: + child2.getPreferredSpan(X_AXIS));
483: assertEquals(child2.getEndOffset(), nextOffset);
484: assertEquals(row.getEndOffset(), nextOffset);
485: }
486:
487: /**
488: * Tests how FlowView lays out the second paragraph (with very long words).
489: */
490: public void testLayout() {
491: view = new FlowViewImplWithFactory(root.getElement(1), Y_AXIS,
492: new PartFactory());
493: strategy = view.strategy = new TestStrategy();
494: view.loadChildren(null);
495: view.layoutSpan = PartView.CHAR_WIDTH * 13;
496: strategy.layout(view);
497: String[] text = new String[] { "Long words: ", "international",
498: "ization, ", "localization\n" };
499: assertEquals(text.length, view.getViewCount());
500: for (int i = 0; i < view.getViewCount(); i++) {
501: final View row = view.getView(i);
502: assertEquals("i = " + i + " " + row, 1, row.getViewCount());
503: assertEquals(text[i], getText(row.getView(0)));
504: }
505: }
506:
507: /**
508: * Tests that "old" children contained are removed from
509: * the <code>FlowView</code> before layout is performed.
510: */
511: public void testLayoutRemoved() {
512: View[] dummy = new View[] { new PlainView(p1L),
513: new PlainView(p1) };
514: view.replace(0, view.getViewCount(), dummy);
515: assertEquals(2, view.getViewCount());
516: view.layoutSpan = Integer.MAX_VALUE;
517: view.strategy.layout(view);
518: assertEquals(1, view.getViewCount());
519: }
520:
521: public void testGetLogicalView() {
522: assertSame(view.layoutPool, view.strategy.getLogicalView(view));
523: }
524:
525: public void testCreateView() {
526: assertSame(view.layoutPool.getView(0), strategy.createView(
527: view, 0, view.layoutSpan, 0));
528: assertSame(view.layoutPool.getView(0), strategy.createView(
529: view, 0, view.layoutSpan, -1));
530: assertSame(view.layoutPool.getView(0), strategy.createView(
531: view, 0, -1, 0));
532: View created = strategy.createView(view, 3, view.layoutSpan, 0);
533: View logical = view.layoutPool.getView(0);
534: assertNotSame(logical, created);
535: assertEquals(3, created.getStartOffset());
536: assertEquals(logical.getEndOffset(), created.getEndOffset());
537: created = strategy.createView(view, 3, 2, 0);
538: assertEquals(3, created.getStartOffset());
539: assertEquals(logical.getEndOffset(), created.getEndOffset());
540: created = strategy.createView(view, logical.getEndOffset(), 2,
541: 0);
542: logical = view.layoutPool.getView(1);
543: assertSame(logical, created);
544: created = strategy.createView(view,
545: logical.getStartOffset() + 2, 2, 0);
546: assertNotSame(logical, created);
547: assertEquals(logical.getStartOffset() + 2, created
548: .getStartOffset());
549: assertEquals(logical.getEndOffset(), created.getEndOffset());
550: logical = view.layoutPool.getView(view.layoutPool
551: .getViewCount() - 1);
552: created = strategy.createView(view, logical.getEndOffset() - 1,
553: 2, 0);
554: assertEquals(logical.getEndOffset() - 1, created
555: .getStartOffset());
556: assertEquals(logical.getEndOffset(), created.getEndOffset());
557: try {
558: assertNull(strategy.createView(view,
559: logical.getEndOffset(), 2, 0));
560: } catch (ArrayIndexOutOfBoundsException e) {
561: if (BasicSwingTestCase.isHarmony()) {
562: fail("ArrayIndexOutOfBoundsException is unexpected");
563: }
564: }
565: }
566:
567: private static String getText(View v) {
568: return ((GlyphView) v).getText(v.getStartOffset(),
569: v.getEndOffset()).toString();
570: }
571: }
|