GNU Classpath (0.91) | |
Frames | No Frames |
1: /* CompositeView.java -- An abstract view that manages child views 2: Copyright (C) 2005, 2006 Free Software Foundation, Inc. 3: 4: This file is part of GNU Classpath. 5: 6: GNU Classpath is free software; you can redistribute it and/or modify 7: it under the terms of the GNU General Public License as published by 8: the Free Software Foundation; either version 2, or (at your option) 9: any later version. 10: 11: GNU Classpath is distributed in the hope that it will be useful, but 12: WITHOUT ANY WARRANTY; without even the implied warranty of 13: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14: General Public License for more details. 15: 16: You should have received a copy of the GNU General Public License 17: along with GNU Classpath; see the file COPYING. If not, write to the 18: Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 19: 02110-1301 USA. 20: 21: Linking this library statically or dynamically with other modules is 22: making a combined work based on this library. Thus, the terms and 23: conditions of the GNU General Public License cover the whole 24: combination. 25: 26: As a special exception, the copyright holders of this library give you 27: permission to link this library with independent modules to produce an 28: executable, regardless of the license terms of these independent 29: modules, and to copy and distribute the resulting executable under 30: terms of your choice, provided that you also meet, for each linked 31: independent module, the terms and conditions of the license of that 32: module. An independent module is a module which is not derived from 33: or based on this library. If you modify this library, you may extend 34: this exception to your version of the library, but you are not 35: obligated to do so. If you do not wish to do so, delete this 36: exception statement from your version. */ 37: 38: 39: package javax.swing.text; 40: 41: import java.awt.Insets; 42: import java.awt.Rectangle; 43: import java.awt.Shape; 44: 45: import javax.swing.SwingConstants; 46: 47: /** 48: * An abstract base implementation of {@link View} that manages child 49: * <code>View</code>s. 50: * 51: * @author Roman Kennke (roman@kennke.org) 52: */ 53: public abstract class CompositeView 54: extends View 55: { 56: 57: /** 58: * The child views of this <code>CompositeView</code>. 59: */ 60: View[] children; 61: 62: /** 63: * The allocation of this <code>View</code> minus its insets. This is 64: * initialized in {@link #getInsideAllocation} and reused and modified in 65: * {@link #childAllocation(int, Rectangle)}. 66: */ 67: Rectangle insideAllocation; 68: 69: /** 70: * The insets of this <code>CompositeView</code>. This is initialized 71: * in {@link #setInsets}. 72: */ 73: Insets insets; 74: 75: /** 76: * Creates a new <code>CompositeView</code> for the given 77: * <code>Element</code>. 78: * 79: * @param element the element that is rendered by this CompositeView 80: */ 81: public CompositeView(Element element) 82: { 83: super(element); 84: children = new View[0]; 85: insets = new Insets(0, 0, 0, 0); 86: } 87: 88: /** 89: * Loads the child views of this <code>CompositeView</code>. This method 90: * is called from {@link #setParent} to initialize the child views of 91: * this composite view. 92: * 93: * @param f the view factory to use for creating new child views 94: * 95: * @see #setParent 96: */ 97: protected void loadChildren(ViewFactory f) 98: { 99: Element el = getElement(); 100: int count = el.getElementCount(); 101: View[] newChildren = new View[count]; 102: for (int i = 0; i < count; ++i) 103: { 104: Element child = el.getElement(i); 105: View view = f.create(child); 106: newChildren[i] = view; 107: } 108: replace(0, getViewCount(), newChildren); 109: } 110: 111: /** 112: * Sets the parent of this <code>View</code>. 113: * In addition to setting the parent, this calls {@link #loadChildren}, if 114: * this <code>View</code> does not already have its children initialized. 115: * 116: * @param parent the parent to set 117: */ 118: public void setParent(View parent) 119: { 120: super.setParent(parent); 121: if (parent != null && ((children == null) || children.length == 0)) 122: loadChildren(getViewFactory()); 123: } 124: 125: /** 126: * Returns the number of child views. 127: * 128: * @return the number of child views 129: */ 130: public int getViewCount() 131: { 132: return children.length; 133: } 134: 135: /** 136: * Returns the child view at index <code>n</code>. 137: * 138: * @param n the index of the requested child view 139: * 140: * @return the child view at index <code>n</code> 141: */ 142: public View getView(int n) 143: { 144: return children[n]; 145: } 146: 147: /** 148: * Replaces child views by some other child views. If there are no views to 149: * remove (<code>length == 0</code>), the result is a simple insert, if 150: * there are no children to add (<code>view == null</code>) the result 151: * is a simple removal. 152: * 153: * @param offset the start offset from where to remove children 154: * @param length the number of children to remove 155: * @param views the views that replace the removed children 156: */ 157: public void replace(int offset, int length, View[] views) 158: { 159: // Check for null views to add. 160: for (int i = 0; i < views.length; ++i) 161: if (views[i] == null) 162: throw new NullPointerException("Added views must not be null"); 163: 164: int endOffset = offset + length; 165: 166: // First we set the parent of the removed children to null. 167: for (int i = offset; i < endOffset; ++i) 168: children[i].setParent(null); 169: 170: View[] newChildren = new View[children.length - length + views.length]; 171: System.arraycopy(children, 0, newChildren, 0, offset); 172: System.arraycopy(views, 0, newChildren, offset, views.length); 173: System.arraycopy(children, offset + length, newChildren, 174: offset + views.length, 175: children.length - (offset + length)); 176: children = newChildren; 177: 178: // Finally we set the parent of the added children to this. 179: for (int i = 0; i < views.length; ++i) 180: views[i].setParent(this); 181: } 182: 183: /** 184: * Returns the allocation for the specified child <code>View</code>. 185: * 186: * @param index the index of the child view 187: * @param a the allocation for this view 188: * 189: * @return the allocation for the specified child <code>View</code> 190: */ 191: public Shape getChildAllocation(int index, Shape a) 192: { 193: Rectangle r = getInsideAllocation(a); 194: childAllocation(index, r); 195: return r; 196: } 197: 198: /** 199: * Maps a position in the document into the coordinate space of the View. 200: * The output rectangle usually reflects the font height but has a width 201: * of zero. 202: * 203: * @param pos the position of the character in the model 204: * @param a the area that is occupied by the view 205: * @param bias either {@link Position.Bias#Forward} or 206: * {@link Position.Bias#Backward} depending on the preferred 207: * direction bias. If <code>null</code> this defaults to 208: * <code>Position.Bias.Forward</code> 209: * 210: * @return a rectangle that gives the location of the document position 211: * inside the view coordinate space 212: * 213: * @throws BadLocationException if <code>pos</code> is invalid 214: * @throws IllegalArgumentException if b is not one of the above listed 215: * valid values 216: */ 217: public Shape modelToView(int pos, Shape a, Position.Bias bias) 218: throws BadLocationException 219: { 220: int childIndex = getViewIndex(pos, bias); 221: if (childIndex == -1) 222: throw new BadLocationException("Position " + pos + " is not represented by view.", pos); 223: 224: Shape ret = null; 225: 226: View child = getView(childIndex); 227: Shape childAlloc = getChildAllocation(childIndex, a); 228: 229: if (childAlloc == null) 230: ret = createDefaultLocation(a, bias); 231: 232: Shape result = child.modelToView(pos, childAlloc, bias); 233: 234: if (result != null) 235: ret = result; 236: else 237: ret = createDefaultLocation(a, bias); 238: 239: return ret; 240: } 241: 242: /** 243: * A helper method for {@link #modelToView(int, Position.Bias, int, 244: * Position.Bias, Shape)}. This creates a default location when there is 245: * no child view that can take responsibility for mapping the position to 246: * view coordinates. Depending on the specified bias this will be the 247: * left or right edge of this view's allocation. 248: * 249: * @param a the allocation for this view 250: * @param bias the bias 251: * 252: * @return a default location 253: */ 254: private Shape createDefaultLocation(Shape a, Position.Bias bias) 255: { 256: Rectangle alloc = a.getBounds(); 257: Rectangle location = new Rectangle(alloc.x, alloc.y, 1, alloc.height); 258: if (bias == Position.Bias.Forward) 259: location.x = alloc.x + alloc.width; 260: return location; 261: } 262: 263: /** 264: * Maps a region in the document into the coordinate space of the View. 265: * 266: * @param p1 the beginning position inside the document 267: * @param b1 the direction bias for the beginning position 268: * @param p2 the end position inside the document 269: * @param b2 the direction bias for the end position 270: * @param a the area that is occupied by the view 271: * 272: * @return a rectangle that gives the span of the document region 273: * inside the view coordinate space 274: * 275: * @throws BadLocationException if <code>p1</code> or <code>p2</code> are 276: * invalid 277: * @throws IllegalArgumentException if b1 or b2 is not one of the above 278: * listed valid values 279: */ 280: public Shape modelToView(int p1, Position.Bias b1, 281: int p2, Position.Bias b2, Shape a) 282: throws BadLocationException 283: { 284: // TODO: This is most likely not 100% ok, figure out what else is to 285: // do here. 286: return super.modelToView(p1, b1, p2, b2, a); 287: } 288: 289: /** 290: * Maps coordinates from the <code>View</code>'s space into a position 291: * in the document model. 292: * 293: * @param x the x coordinate in the view space, x >= 0 294: * @param y the y coordinate in the view space, y >= 0 295: * @param a the allocation of this <code>View</code> 296: * @param b the bias to use 297: * 298: * @return the position in the document that corresponds to the screen 299: * coordinates <code>x, y</code> >= 0 300: */ 301: public int viewToModel(float x, float y, Shape a, Position.Bias[] b) 302: { 303: if (x >= 0 && y >= 0) 304: { 305: Rectangle r = getInsideAllocation(a); 306: View view = getViewAtPoint((int) x, (int) y, r); 307: return view.viewToModel(x, y, r, b); 308: } 309: return 0; 310: } 311: 312: /** 313: * Returns the next model location that is visible in eiter north / south 314: * direction or east / west direction. This is used to determine the placement 315: * of the caret when navigating around the document with the arrow keys. This 316: * is a convenience method for {@link #getNextNorthSouthVisualPositionFrom} 317: * and {@link #getNextEastWestVisualPositionFrom}. 318: * 319: * @param pos 320: * the model position to start search from 321: * @param b 322: * the bias for <code>pos</code> 323: * @param a 324: * the allocated region for this view 325: * @param direction 326: * the direction from the current position, can be one of the 327: * following: 328: * <ul> 329: * <li>{@link SwingConstants#WEST}</li> 330: * <li>{@link SwingConstants#EAST}</li> 331: * <li>{@link SwingConstants#NORTH}</li> 332: * <li>{@link SwingConstants#SOUTH}</li> 333: * </ul> 334: * @param biasRet 335: * the bias of the return value gets stored here 336: * @return the position inside the model that represents the next visual 337: * location 338: * @throws BadLocationException 339: * if <code>pos</code> is not a valid location inside the document 340: * model 341: * @throws IllegalArgumentException 342: * if <code>direction</code> is invalid 343: */ 344: public int getNextVisualPositionFrom(int pos, Position.Bias b, Shape a, 345: int direction, Position.Bias[] biasRet) 346: throws BadLocationException 347: { 348: int retVal = -1; 349: switch (direction) 350: { 351: case SwingConstants.WEST: 352: case SwingConstants.EAST: 353: retVal = getNextEastWestVisualPositionFrom(pos, b, a, direction, 354: biasRet); 355: break; 356: case SwingConstants.NORTH: 357: case SwingConstants.SOUTH: 358: retVal = getNextNorthSouthVisualPositionFrom(pos, b, a, direction, 359: biasRet); 360: break; 361: default: 362: throw new IllegalArgumentException("Illegal value for direction."); 363: } 364: return retVal; 365: } 366: 367: /** 368: * Returns the index of the child view that represents the specified 369: * model location. 370: * 371: * @param pos the model location for which to determine the child view index 372: * @param b the bias to be applied to <code>pos</code> 373: * 374: * @return the index of the child view that represents the specified 375: * model location 376: */ 377: public int getViewIndex(int pos, Position.Bias b) 378: { 379: if (b == Position.Bias.Backward && pos != 0) 380: pos -= 1; 381: return getViewIndexAtPosition(pos); 382: } 383: 384: /** 385: * Returns <code>true</code> if the specified point lies before the 386: * given <code>Rectangle</code>, <code>false</code> otherwise. 387: * 388: * "Before" is typically defined as being to the left or above. 389: * 390: * @param x the X coordinate of the point 391: * @param y the Y coordinate of the point 392: * @param r the rectangle to test the point against 393: * 394: * @return <code>true</code> if the specified point lies before the 395: * given <code>Rectangle</code>, <code>false</code> otherwise 396: */ 397: protected abstract boolean isBefore(int x, int y, Rectangle r); 398: 399: /** 400: * Returns <code>true</code> if the specified point lies after the 401: * given <code>Rectangle</code>, <code>false</code> otherwise. 402: * 403: * "After" is typically defined as being to the right or below. 404: * 405: * @param x the X coordinate of the point 406: * @param y the Y coordinate of the point 407: * @param r the rectangle to test the point against 408: * 409: * @return <code>true</code> if the specified point lies after the 410: * given <code>Rectangle</code>, <code>false</code> otherwise 411: */ 412: protected abstract boolean isAfter(int x, int y, Rectangle r); 413: 414: /** 415: * Returns the child <code>View</code> at the specified location. 416: * 417: * @param x the X coordinate 418: * @param y the Y coordinate 419: * @param r the inner allocation of this <code>BoxView</code> on entry, 420: * the allocation of the found child on exit 421: * 422: * @return the child <code>View</code> at the specified location 423: */ 424: protected abstract View getViewAtPoint(int x, int y, Rectangle r); 425: 426: /** 427: * Computes the allocation for a child <code>View</code>. The parameter 428: * <code>a</code> stores the allocation of this <code>CompositeView</code> 429: * and is then adjusted to hold the allocation of the child view. 430: * 431: * @param index the index of the child <code>View</code> 432: * @param a the allocation of this <code>CompositeView</code> before the 433: * call, the allocation of the child on exit 434: */ 435: protected abstract void childAllocation(int index, Rectangle a); 436: 437: /** 438: * Returns the child <code>View</code> that contains the given model 439: * position. The given <code>Rectangle</code> gives the parent's allocation 440: * and is changed to the child's allocation on exit. 441: * 442: * @param pos the model position to query the child <code>View</code> for 443: * @param a the parent allocation on entry and the child allocation on exit 444: * 445: * @return the child view at the given model position 446: */ 447: protected View getViewAtPosition(int pos, Rectangle a) 448: { 449: int i = getViewIndexAtPosition(pos); 450: View view = children[i]; 451: childAllocation(i, a); 452: return view; 453: } 454: 455: /** 456: * Returns the index of the child <code>View</code> for the given model 457: * position. 458: * 459: * @param pos the model position for whicht the child <code>View</code> is 460: * queried 461: * 462: * @return the index of the child <code>View</code> for the given model 463: * position 464: */ 465: protected int getViewIndexAtPosition(int pos) 466: { 467: int index = -1; 468: for (int i = 0; i < children.length; i++) 469: { 470: if (children[i].getStartOffset() <= pos 471: && children[i].getEndOffset() > pos) 472: { 473: index = i; 474: break; 475: } 476: } 477: return index; 478: } 479: 480: /** 481: * Returns the allocation that is given to this <code>CompositeView</code> 482: * minus this <code>CompositeView</code>'s insets. 483: * 484: * Also this translates from an immutable allocation to a mutable allocation 485: * that is typically reused and further narrowed, like in 486: * {@link #childAllocation}. 487: * 488: * @param a the allocation given to this <code>CompositeView</code> 489: * 490: * @return the allocation that is given to this <code>CompositeView</code> 491: * minus this <code>CompositeView</code>'s insets or 492: * <code>null</code> if a was <code>null</code> 493: */ 494: protected Rectangle getInsideAllocation(Shape a) 495: { 496: if (a == null) 497: return null; 498: 499: Rectangle alloc = a.getBounds(); 500: // Initialize the inside allocation rectangle. This is done inside 501: // a synchronized block in order to avoid multiple threads creating 502: // this instance simultanously. 503: Rectangle inside; 504: synchronized(this) 505: { 506: inside = insideAllocation; 507: if (inside == null) 508: { 509: inside = new Rectangle(); 510: insideAllocation = inside; 511: } 512: } 513: inside.x = alloc.x + insets.left; 514: inside.y = alloc.y + insets.top; 515: inside.width = alloc.width - insets.left - insets.right; 516: inside.height = alloc.height - insets.top - insets.bottom; 517: return inside; 518: } 519: 520: /** 521: * Sets the insets defined by attributes in <code>attributes</code>. This 522: * queries the attribute keys {@link StyleConstants#SpaceAbove}, 523: * {@link StyleConstants#SpaceBelow}, {@link StyleConstants#LeftIndent} and 524: * {@link StyleConstants#RightIndent} and calls {@link #setInsets} to 525: * actually set the insets on this <code>CompositeView</code>. 526: * 527: * @param attributes the attributes from which to query the insets 528: */ 529: protected void setParagraphInsets(AttributeSet attributes) 530: { 531: Float l = (Float) attributes.getAttribute(StyleConstants.LeftIndent); 532: short left = 0; 533: if (l != null) 534: left = l.shortValue(); 535: Float r = (Float) attributes.getAttribute(StyleConstants.RightIndent); 536: short right = 0; 537: if (r != null) 538: right = r.shortValue(); 539: Float t = (Float) attributes.getAttribute(StyleConstants.SpaceAbove); 540: short top = 0; 541: if (t != null) 542: top = t.shortValue(); 543: Float b = (Float) attributes.getAttribute(StyleConstants.SpaceBelow); 544: short bottom = 0; 545: if (b != null) 546: bottom = b.shortValue(); 547: setInsets(top, left, bottom, right); 548: } 549: 550: /** 551: * Sets the insets of this <code>CompositeView</code>. 552: * 553: * @param top the top inset 554: * @param left the left inset 555: * @param bottom the bottom inset 556: * @param right the right inset 557: */ 558: protected void setInsets(short top, short left, short bottom, short right) 559: { 560: insets.top = top; 561: insets.left = left; 562: insets.bottom = bottom; 563: insets.right = right; 564: } 565: 566: /** 567: * Returns the left inset of this <code>CompositeView</code>. 568: * 569: * @return the left inset of this <code>CompositeView</code> 570: */ 571: protected short getLeftInset() 572: { 573: return (short) insets.left; 574: } 575: 576: /** 577: * Returns the right inset of this <code>CompositeView</code>. 578: * 579: * @return the right inset of this <code>CompositeView</code> 580: */ 581: protected short getRightInset() 582: { 583: return (short) insets.right; 584: } 585: 586: /** 587: * Returns the top inset of this <code>CompositeView</code>. 588: * 589: * @return the top inset of this <code>CompositeView</code> 590: */ 591: protected short getTopInset() 592: { 593: return (short) insets.top; 594: } 595: 596: /** 597: * Returns the bottom inset of this <code>CompositeView</code>. 598: * 599: * @return the bottom inset of this <code>CompositeView</code> 600: */ 601: protected short getBottomInset() 602: { 603: return (short) insets.bottom; 604: } 605: 606: /** 607: * Returns the next model location that is visible in north or south 608: * direction. 609: * This is used to determine the 610: * placement of the caret when navigating around the document with 611: * the arrow keys. 612: * 613: * @param pos the model position to start search from 614: * @param b the bias for <code>pos</code> 615: * @param a the allocated region for this view 616: * @param direction the direction from the current position, can be one of 617: * the following: 618: * <ul> 619: * <li>{@link SwingConstants#NORTH}</li> 620: * <li>{@link SwingConstants#SOUTH}</li> 621: * </ul> 622: * @param biasRet the bias of the return value gets stored here 623: * 624: * @return the position inside the model that represents the next visual 625: * location 626: * 627: * @throws BadLocationException if <code>pos</code> is not a valid location 628: * inside the document model 629: * @throws IllegalArgumentException if <code>direction</code> is invalid 630: */ 631: protected int getNextNorthSouthVisualPositionFrom(int pos, Position.Bias b, 632: Shape a, int direction, 633: Position.Bias[] biasRet) 634: throws BadLocationException 635: { 636: // TODO: It is unknown to me how this method has to be implemented and 637: // there is no specification telling me how to do it properly. Therefore 638: // the implementation was done for cases that are known. 639: // 640: // If this method ever happens to act silly for your particular case then 641: // it is likely that it is a cause of not knowing about your case when it 642: // was implemented first. You are free to fix the behavior. 643: // 644: // Here are the assumptions that lead to the implementation: 645: // If direction is NORTH chose the View preceding the one that contains the 646: // offset 'pos' (imagine the views are stacked on top of each other where 647: // the top is 0 and the bottom is getViewCount()-1. 648: // Consecutively when the direction is SOUTH the View following the one 649: // the offset 'pos' lies in is questioned. 650: // 651: // This limitation is described as PR 27345. 652: int index = getViewIndex(pos, b); 653: View v = null; 654: 655: if (index == -1) 656: return pos; 657: 658: switch (direction) 659: { 660: case NORTH: 661: // If we cannot calculate a proper offset return the one that was 662: // provided. 663: if (index <= 0) 664: return pos; 665: 666: v = getView(index - 1); 667: break; 668: case SOUTH: 669: // If we cannot calculate a proper offset return the one that was 670: // provided. 671: if (index >= getViewCount() - 1) 672: return pos; 673: 674: v = getView(index + 1); 675: break; 676: default: 677: throw new IllegalArgumentException(); 678: } 679: 680: return v.getNextVisualPositionFrom(pos, b, a, direction, biasRet); 681: } 682: 683: /** 684: * Returns the next model location that is visible in east or west 685: * direction. 686: * This is used to determine the 687: * placement of the caret when navigating around the document with 688: * the arrow keys. 689: * 690: * @param pos the model position to start search from 691: * @param b the bias for <code>pos</code> 692: * @param a the allocated region for this view 693: * @param direction the direction from the current position, can be one of 694: * the following: 695: * <ul> 696: * <li>{@link SwingConstants#EAST}</li> 697: * <li>{@link SwingConstants#WEST}</li> 698: * </ul> 699: * @param biasRet the bias of the return value gets stored here 700: * 701: * @return the position inside the model that represents the next visual 702: * location 703: * 704: * @throws BadLocationException if <code>pos</code> is not a valid location 705: * inside the document model 706: * @throws IllegalArgumentException if <code>direction</code> is invalid 707: */ 708: protected int getNextEastWestVisualPositionFrom(int pos, Position.Bias b, 709: Shape a, int direction, 710: Position.Bias[] biasRet) 711: throws BadLocationException 712: { 713: // TODO: It is unknown to me how this method has to be implemented and 714: // there is no specification telling me how to do it properly. Therefore 715: // the implementation was done for cases that are known. 716: // 717: // If this method ever happens to act silly for your particular case then 718: // it is likely that it is a cause of not knowing about your case when it 719: // was implemented first. You are free to fix the behavior. 720: // 721: // Here are the assumptions that lead to the implementation: 722: // If direction is EAST increase the offset by one and ask the View to 723: // which that index belong to calculate the 'next visual position'. 724: // If the direction is WEST do the same with offset 'pos' being decreased 725: // by one. 726: // This behavior will fail in a right-to-left or bidi environment! 727: // 728: // This limitation is described as PR 27346. 729: int index; 730: 731: View v = null; 732: 733: switch (direction) 734: { 735: case EAST: 736: index = getViewIndex(pos + 1, b); 737: // If we cannot calculate a proper offset return the one that was 738: // provided. 739: if (index == -1) 740: return pos; 741: 742: v = getView(index); 743: break; 744: case WEST: 745: index = getViewIndex(pos - 1, b); 746: // If we cannot calculate a proper offset return the one that was 747: // provided. 748: if (index == -1) 749: return pos; 750: 751: v = getView(index); 752: break; 753: default: 754: throw new IllegalArgumentException(); 755: } 756: 757: return v.getNextVisualPositionFrom(pos, 758: b, 759: a, 760: direction, 761: biasRet); 762: } 763: 764: /** 765: * Determines if the next view in horinzontal direction is located to 766: * the east or west of the view at position <code>pos</code>. Usually 767: * the <code>View</code>s are laid out from the east to the west, so 768: * we unconditionally return <code>false</code> here. Subclasses that 769: * support bidirectional text may wish to override this method. 770: * 771: * @param pos the position in the document 772: * @param bias the bias to be applied to <code>pos</code> 773: * 774: * @return <code>true</code> if the next <code>View</code> is located 775: * to the EAST, <code>false</code> otherwise 776: */ 777: protected boolean flipEastAndWestAtEnds(int pos, Position.Bias bias) 778: { 779: return false; 780: } 781: }
GNU Classpath (0.91) |