Java tutorial
/** * The utillib library. * More information is available at http://www.jinchess.com/. * Copyright (C) 2002, 2003 Alexander Maryanovsky. * All rights reserved. * * The utillib library is free software; you can redistribute * it and/or modify it under the terms of the GNU Lesser General Public License * as published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * The utillib library is distributed in the hope that it will * be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser * General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with utillib library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ import java.awt.Component; import java.awt.Container; import java.awt.Dimension; import java.awt.Insets; import java.awt.LayoutManager2; import java.util.Enumeration; import java.util.Hashtable; import java.util.NoSuchElementException; import java.util.Vector; /** * A LayoutManager which lays out the components in a table-like structure. * Unlike <code>GridLayout</code>, the sizes of the rows and columns * are dynamic, although properly aligned. The cell sizes are determined * according to the preferred sizes of the components and each component is * sized to either its maximum size or the cell size. Components are positioned * within their cells according to their X and Y alignments. * When a new component is added, it is placed in the first empty cell, in * lexigraphic order. A new row is created if necessary. * To create an empty cell, simply add blank component. */ public class TableLayout implements LayoutManager2 { /** * The amount of columns in the table. */ private final int columnCount; /** * The gap between columns, in pixels. */ private final int xGap; /** * The gap between rows, in pixels. */ private final int yGap; /** * A Vector of rows where each row is a Component array holding the components * in that row. */ private final Vector rows = new Vector(); /** * Creates a new TableLayout with the specified amount of columns, * horizontal/vertical gaps between columns/cells. */ public TableLayout(int columnCount, int xGap, int yGap) { if (columnCount <= 0) throw new IllegalArgumentException("The amount of columns must be positive"); if (xGap < 0) throw new IllegalArgumentException("The horizontal gap may not be negative: " + xGap); if (yGap < 0) throw new IllegalArgumentException("The vertical gap may not be negative: " + yGap); this.columnCount = columnCount; this.xGap = xGap; this.yGap = yGap; } /** * Creates a new TableLayout with the specified amount of columns. */ public TableLayout(int columnCount) { this(columnCount, 0, 0); } /** * Adds the specified component to the layout. */ public void addLayoutComponent(Component component, Object constraints) { synchronized (component.getTreeLock()) { int rowCount = rows.size(); for (int i = 0; i < rowCount; i++) { Component[] row = (Component[]) rows.elementAt(i); for (int j = 0; j < row.length; j++) { if (row[j] == null) { row[j] = component; return; } } } Component[] newRow = new Component[columnCount]; newRow[0] = component; rows.addElement(newRow); } } /** * Throws an exception. */ public void addLayoutComponent(String name, Component component) { throw new UnsupportedOperationException("deprecated addLayoutComponent(String, Component)"); } /** * Removes the specified component from the layout. */ public void removeLayoutComponent(Component component) { synchronized (component.getTreeLock()) { int rowCount = rows.size(); outer: for (int i = 0; i < rowCount; i++) { Component[] row = (Component[]) rows.elementAt(i); for (int j = 0; j < row.length; j++) { if (row[j] == component) { row[j] = null; break outer; } } } // Remove any empty rows at the end. for (int i = rowCount - 1; i >= 0; i--) { Component[] row = (Component[]) rows.elementAt(i); boolean isEmpty = true; for (int j = 0; j < row.length; j++) { if (row[j] != null) { isEmpty = false; break; } } if (isEmpty) rows.removeElementAt(i); else break; } } } /** * Returns a matrix of Dimension objects specifying the preferred sizes of the * components we are going to layout. */ private Dimension[][] getPreferredSizes(Container parent) { int rowCount = rows.size(); Dimension[][] prefSizes = new Dimension[rowCount][columnCount]; for (int i = 0; i < rowCount; i++) { Component[] row = (Component[]) rows.elementAt(i); for (int j = 0; j < columnCount; j++) { Component component = row[j]; // Can only happen on the last line when all the remaining components are null as well if (component == null) break; if (component.getParent() != parent) throw new IllegalStateException("Bad parent specified"); prefSizes[i][j] = component.getPreferredSize(); } } return prefSizes; } /** * Calculates and returns a Pair where the first object is an array holding * the column widths of our layout and the second is the rowHeights. */ private Pair calculateLayout(Dimension[][] prefSizes) { int rowCount = rows.size(); int[] columnWidths = new int[columnCount]; int[] rowHeights = new int[rowCount]; // Find the maximum preferred row heights and column widths. for (int i = 0; i < rowCount; i++) { for (int j = 0; j < columnCount; j++) { Dimension prefSize = prefSizes[i][j]; // Can only happen on the last line when all the remaining components are null as well if (prefSize == null) break; columnWidths[j] = Math.max(columnWidths[j], prefSize.width); rowHeights[i] = Math.max(rowHeights[i], prefSize.height); } } return new Pair(columnWidths, rowHeights); } /** * Lays out the specified container. Throws an * <code>IllegalStateException</code> if any of the components added via the * <code>addLayoutComponent</code> method have a different parent than the * specified Container. */ public void layoutContainer(Container parent) { synchronized (parent.getTreeLock()) { int rowCount = rows.size(); Insets parentInsets = parent.getInsets(); // Collect the preferred sizes. Dimension[][] prefSizes = getPreferredSizes(parent); Pair layout = calculateLayout(prefSizes); int[] columnWidths = (int[]) layout.getFirst(); int[] rowHeights = (int[]) layout.getSecond(); Dimension prefParentSize = calculatePreferredLayoutSize(parent, columnWidths, rowHeights); Dimension parentSize = parent.getSize(); Dimension layoutSize = new Dimension( parentSize.width - xGap * (rowCount - 1) - parentInsets.left - parentInsets.right, parentSize.height - yGap * (columnCount - 1) - parentInsets.top - parentInsets.bottom); Dimension prefLayoutSize = new Dimension( prefParentSize.width - xGap * (rowCount - 1) - parentInsets.left - parentInsets.right, prefParentSize.height - yGap * (columnCount - 1) - parentInsets.top - parentInsets.bottom); // Layout the components. int y = parentInsets.top; for (int i = 0; i < rowCount; i++) { int x = parentInsets.left; int cellHeight = (rowHeights[i] * layoutSize.height) / prefLayoutSize.height; Component[] row = (Component[]) rows.elementAt(i); for (int j = 0; j < row.length; j++) { int cellWidth = (columnWidths[j] * layoutSize.width) / prefLayoutSize.width; Component component = row[j]; // Can only happen on the last line when all the remaining components are null as well if (component == null) break; Dimension maxSize = component.getMaximumSize(); int compWidth = Math.min(maxSize.width, cellWidth); int compHeight = Math.min(maxSize.height, cellHeight); int compX = x + (int) ((cellWidth - compWidth) * component.getAlignmentX()); int compY = y + (int) ((cellHeight - compHeight) * component.getAlignmentY()); component.setBounds(compX, compY, compWidth, compHeight); x += cellWidth + xGap; } y += cellHeight + yGap; } } } /** * We're not caching anything yet, so this call is ignored. */ public void invalidateLayout(Container parent) { } /** * Returns the preferred layout for the specified parent container. */ public Dimension preferredLayoutSize(Container parent) { synchronized (parent.getTreeLock()) { Dimension[][] prefSizes = getPreferredSizes(parent); Pair layout = calculateLayout(prefSizes); int[] columnWidths = (int[]) layout.getFirst(); int[] rowHeights = (int[]) layout.getSecond(); return calculatePreferredLayoutSize(parent, columnWidths, rowHeights); } } /** * Calculates the preferred layout size for the specified preferred column * widths and row heights. */ private Dimension calculatePreferredLayoutSize(Container parent, int[] columnWidths, int[] rowHeights) { int prefWidth = 0; int prefHeight = 0; for (int i = 0; i < columnWidths.length; i++) prefWidth += columnWidths[i]; for (int i = 0; i < rowHeights.length; i++) prefHeight += rowHeights[i]; // Add the gaps prefWidth += xGap * (columnWidths.length - 1); prefHeight += yGap * (rowHeights.length - 1); // Add parent insets Insets parentInsets = parent.getInsets(); prefWidth += parentInsets.left + parentInsets.right; prefHeight += parentInsets.top + parentInsets.bottom; return new Dimension(prefWidth, prefHeight); } /** * Returns the same as <code>preferredLayoutSize</code>. */ public Dimension minimumLayoutSize(Container parent) { return preferredLayoutSize(parent); } /** * Returns a dimension with maximum possible values. */ public Dimension maximumLayoutSize(Container parent) { return new Dimension(Integer.MAX_VALUE, Integer.MAX_VALUE); } /** * Returns <code>CENTER_ALIGNMENT</code>; */ public float getLayoutAlignmentX(Container parent) { return Component.CENTER_ALIGNMENT; } /** * Returns <code>CENTER_ALIGNMENT</code>; */ public float getLayoutAlignmentY(Container parent) { return Component.CENTER_ALIGNMENT; } } /** * The utillib library. * More information is available at http://www.jinchess.com/. * Copyright (C) 2002 Alexander Maryanovsky. * All rights reserved. * * The utillib library is free software; you can redistribute * it and/or modify it under the terms of the GNU Lesser General Public License * as published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * The utillib library is distributed in the hope that it will * be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser * General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with utillib library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ /** * A wrapper for any two other given objects. */ final class Pair { /** * The first object. */ private final Object first; /** * The second object. */ private final Object second; /** * Creates a new <code>Pair</code> with the two given objects. Either of the * objects may be <code>null</code>. */ public Pair(Object first, Object second) { this.first = first; this.second = second; } /** * Returns the first object. */ public Object getFirst() { return first; } /** * Returns the second object. */ public Object getSecond() { return second; } /** * Returns a hashcode combined from the hashcodes of the two target objects. */ public int hashCode() { int hash1 = (first == null ? 0 : first.hashCode()); int hash2 = (second == null ? 0 : second.hashCode()); return hash1 ^ hash2; } /** * Returns true iff the given Object is a Pair, and its two objects are the * same as this one's (comparison done via <code>Utilities.areEqual</code>) */ public boolean equals(Object o) { if (o == this) return true; if (!(o instanceof Pair)) return false; Pair pair = (Pair) o; return Utilities.areEqual(pair.first, first) && Utilities.areEqual(pair.second, second); } } /** * The utillib library. * More information is available at http://www.jinchess.com/. * Copyright (C) 2002, 2003 Alexander Maryanovsky. * All rights reserved. * * The utillib library is free software; you can redistribute * it and/or modify it under the terms of the GNU Lesser General Public License * as published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * The utillib library is distributed in the hope that it will * be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser * General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with utillib library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA */ /** * A collection of general utility methods. */ class Utilities { /** * A 0-length Object array. */ public static final Object[] EMPTY_ARRAY = new Object[0]; /** * A 0-length long array. */ public static final long[] EMPTY_LONG_ARRAY = new long[0]; /** * A 0-length int array. */ public static final int[] EMPTY_INT_ARRAY = new int[0]; /** * A 0-length short array. */ public static final short[] EMPTY_SHORT_ARRAY = new short[0]; /** * A 0-length byte array. */ public static final byte[] EMPTY_BYTE_ARRAY = new byte[0]; /** * A 0-length char array. */ public static final char[] EMPTY_CHAR_ARRAY = new char[0]; /** * A 0-length double array. */ public static final double[] EMPTY_DOUBLE_ARRAY = new double[0]; /** * A 0-length float array. */ public static final float[] EMPTY_FLOAT_ARRAY = new float[0]; /** * A 0-length String array. */ public static final String[] EMPTY_STRING_ARRAY = new String[0]; /** * An empty enumeration. */ public static final Enumeration EMPTY_ENUM = new Enumeration() { public boolean hasMoreElements() { return false; } public Object nextElement() { throw new NoSuchElementException(); } }; /** * Returns <code>true</code> if the two specified objects are the same. * Returns <code>false</code> otherwise. To be considered the same, the two * references must either both be null or invoking <code>equals</code> on one * of them with the other must return <code>true</code>. */ public static boolean areEqual(Object obj1, Object obj2) { return (obj1 == obj2) || (obj1 == null ? false : obj1.equals(obj2)); } /** * Maps the specified key to the specified value in the specified * <code>Hashtable</code>. If the specified value is <code>null</code> any * existing mapping of the specified key is removed from the * <code>Hashtable</code>. The old value mapped to the specified key * is returned, or <code>null</code> if no value was mapped to the key. */ public static Object put(Hashtable table, Object key, Object value) { return value == null ? table.remove(key) : table.put(key, value); } /** * Returns <code>true</code> if the specified object is an element of the * specified array. The specified array may not be <code>null</code>. The * specified object may be <code>null</code>, in which case this method will * return <code>true</code> iff one of the indices in the array is empty * (contains <code>null</code>). */ public static boolean contains(Object[] array, Object item) { return (indexOf(array, item) != -1); } /** * Returns the index of the first occurrance of specified object in the * specified array, or -1 if the specified object is not an element of the * specified array. The specified object may be <code>null</code> in which * case the returned index will be the index of the first <code>null</code> * in the array. */ public static int indexOf(Object[] array, Object item) { if (array == null) throw new IllegalArgumentException("The specified array may not be null"); for (int i = 0; i < array.length; i++) if (areEqual(item, array[i])) return i; return -1; } /** * Returns the index of the first occurrance of specified integer in the * specified array, or -1 if the specified integer is not an element of the * specified array. */ public static int indexOf(int[] arr, int val) { if (arr == null) throw new IllegalArgumentException("The specified array may not be null"); for (int i = 0; i < arr.length; i++) if (arr[i] == val) return i; return -1; } /** * Converts the specified array into a string by appending all its elements * separated by a semicolon. */ public static String arrayToString(Object[] arr) { StringBuffer buf = new StringBuffer(); for (int i = 0; i < arr.length; i++) { buf.append(arr[i]); buf.append("; "); } if (arr.length > 0) buf.setLength(buf.length() - 2); // get rid of the extra "; " return buf.toString(); } /** * Converts the specified <code>Hashtable</code> into a string by putting * each key and value on a separate line (separated by '\n') and an arrow * (" -> ") between them. */ public static String hashtableToString(Hashtable hashtable) { StringBuffer buf = new StringBuffer(); Enumeration keys = hashtable.keys(); while (keys.hasMoreElements()) { Object key = keys.nextElement(); Object value = hashtable.get(key); buf.append(key.toString()); buf.append(" -> "); buf.append(value.toString()); buf.append("\n"); } return buf.toString(); } /** * Returns the maximum element in the specified integer array. */ public static int max(int[] arr) { if (arr == null) throw new IllegalArgumentException("The specified array may not be null"); if (arr.length == 0) throw new IllegalArgumentException("The specified array must have at least one element"); int n = arr[0]; for (int i = 1; i < arr.length; i++) if (arr[i] > n) n = arr[i]; return n; } /** * Returns a hash code for the specified double value. */ public static int hashCode(double val) { return hashCode(Double.doubleToLongBits(val)); } /** * Returns a hash code for the specified long value. */ public static int hashCode(long val) { return (int) (val ^ (val >>> 32)); } /** * Returns the name of the package of the specified class. */ public static String getPackageName(Class c) { return getPackageName(c.getName()); } /** * Returns the name of the package of the class with the specified (full) name. */ public static String getPackageName(String className) { int lastDotIndex = className.lastIndexOf("."); return lastDotIndex == -1 ? "" : className.substring(0, lastDotIndex); } /** * Returns the short name (excluding the package name) of the specified class. */ public static String getClassName(Class c) { return getClassName(c.getName()); } /** * Returns the short name (excluding the package name) of the class with the * specified fully qualified name. */ public static String getClassName(String className) { int lastDotIndex = className.lastIndexOf("."); return lastDotIndex == -1 ? className : className.substring(lastDotIndex + 1); } }