/*
 * @(#)JobAttributes.java	1.9 03/12/19
 *
 * Copyright 2004 Sun Microsystems, Inc. All rights reserved.
 * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 */

package java.awt;

/**
 * A set of attributes which control a print job.
 * <p>
 * Instances of this class control the number of copies, default selection,
 * destination, print dialog, file and printer names, page ranges, multiple
 * document handling (including collation), and multi-page imposition (such
 * as duplex) of every print job which uses the instance. Attribute names are
 * compliant with the Internet Printing Protocol (IPP) 1.1 where possible.
 * Attribute values are partially compliant where possible.
 * <p>
 * To use a method which takes an inner class type, pass a reference to
 * one of the constant fields of the inner class. Client code cannot create
 * new instances of the inner class types because none of those classes
 * has a public constructor. For example, to set the print dialog type to
 * the cross-platform, pure Java print dialog, use the following code:
 * <pre>
 * import java.awt.JobAttributes;
 *
 * public class PureJavaPrintDialogExample {
 *     public void setPureJavaPrintDialog(JobAttributes jobAttributes) {
 *         jobAttributes.setDialog(JobAttributes.DialogType.COMMON);
 *     }
 * }
 * </pre>
 * <p>
 * Every IPP attribute which supports an <i>attributeName</i>-default value
 * has a corresponding <code>set<i>attributeName</i>ToDefault</code> method.
 * Default value fields are not provided.
 *
 * @version	1.9, 12/19/03
 * @author	David Mendenhall
 */
public final class JobAttributes implements Cloneable {
    /**
     * A type-safe enumeration of possible default selection states.
     */
    public static final class DefaultSelectionType extends AttributeValue {
        private static final int I_ALL = 0;
        private static final int I_RANGE = 1;
        private static final int I_SELECTION = 2;

        private static final String NAMES[] = {
	    "all", "range", "selection"
	};

        /**
	 * The <code>DefaultSelectionType</code> instance to use for
         * specifying that all pages of the job should be printed.
	 */
        public static final DefaultSelectionType ALL =
           new DefaultSelectionType(I_ALL);
        /**
	 * The <code>DefaultSelectionType</code> instance to use for
         * specifying that a range of pages of the job should be printed.
	 */
        public static final DefaultSelectionType RANGE =
           new DefaultSelectionType(I_RANGE);
        /**
	 * The <code>DefaultSelectionType</code> instance to use for
         * specifying that the current selection should be printed.
	 */
        public static final DefaultSelectionType SELECTION =
           new DefaultSelectionType(I_SELECTION);

        private DefaultSelectionType(int type) {
	    super(type, NAMES);
	}
    }

    /**
     * A type-safe enumeration of possible job destinations.
     */
    public static final class DestinationType extends AttributeValue {
        private static final int I_FILE = 0;
        private static final int I_PRINTER = 1;

        private static final String NAMES[] = {
	    "file", "printer"
	};

        /**
	 * The <code>DestinationType</code> instance to use for
         * specifying print to file.
	 */
        public static final DestinationType FILE =
            new DestinationType(I_FILE);
        /**
	 * The <code>DestinationType</code> instance to use for
         * specifying print to printer.
	 */
        public static final DestinationType PRINTER =
            new DestinationType(I_PRINTER);

        private DestinationType(int type) {
	    super(type, NAMES);
	}
    }

    /**
     * A type-safe enumeration of possible dialogs to display to the user.
     */
    public static final class DialogType extends AttributeValue {
        private static final int I_COMMON = 0;
        private static final int I_NATIVE = 1;
        private static final int I_NONE = 2;

        private static final String NAMES[] = {
	    "common", "native", "none"
	};

        /**
	 * The <code>DialogType</code> instance to use for
         * specifying the cross-platform, pure Java print dialog.
	 */
        public static final DialogType COMMON = new DialogType(I_COMMON);
        /**
	 * The <code>DialogType</code> instance to use for
         * specifying the platform's native print dialog.
	 */
        public static final DialogType NATIVE = new DialogType(I_NATIVE);
        /**
	 * The <code>DialogType</code> instance to use for
         * specifying no print dialog.
	 */
        public static final DialogType NONE = new DialogType(I_NONE);

        private DialogType(int type) {
	    super(type, NAMES);
	}
    }

    /**
     * A type-safe enumeration of possible multiple copy handling states.
     * It is used to control how the sheets of multiple copies of a single
     * document are collated.
     */
    public static final class MultipleDocumentHandlingType extends
                                                               AttributeValue {
        private static final int I_SEPARATE_DOCUMENTS_COLLATED_COPIES = 0;
        private static final int I_SEPARATE_DOCUMENTS_UNCOLLATED_COPIES = 1;

        private static final String NAMES[] = {
	    "separate-documents-collated-copies",
	    "separate-documents-uncollated-copies"
	};

        /**
	 * The <code>MultipleDocumentHandlingType</code> instance to use for specifying
	 * that the job should be divided into separate, collated copies.
	 */
        public static final MultipleDocumentHandlingType
            SEPARATE_DOCUMENTS_COLLATED_COPIES =
                new MultipleDocumentHandlingType(
                    I_SEPARATE_DOCUMENTS_COLLATED_COPIES);
        /**
	 * The <code>MultipleDocumentHandlingType</code> instance to use for specifying
	 * that the job should be divided into separate, uncollated copies.
	 */
        public static final MultipleDocumentHandlingType
            SEPARATE_DOCUMENTS_UNCOLLATED_COPIES =
                new MultipleDocumentHandlingType(
                    I_SEPARATE_DOCUMENTS_UNCOLLATED_COPIES);

        private MultipleDocumentHandlingType(int type) {
	    super(type, NAMES);
	}
    }

    /**
     * A type-safe enumeration of possible multi-page impositions. These
     * impositions are in compliance with IPP 1.1.
     */
    public static final class SidesType extends AttributeValue {
        private static final int I_ONE_SIDED = 0;
        private static final int I_TWO_SIDED_LONG_EDGE = 1;
        private static final int I_TWO_SIDED_SHORT_EDGE = 2;

        private static final String NAMES[] = {
	    "one-sided", "two-sided-long-edge", "two-sided-short-edge"
	};

        /**
	 * The <code>SidesType</code> instance to use for specifying that 
	 * consecutive job pages should be printed upon the same side of
         * consecutive media sheets.
	 */
        public static final SidesType ONE_SIDED = new SidesType(I_ONE_SIDED);
        /**
	 * The <code>SidesType</code> instance to use for specifying that
         * consecutive job pages should be printed upon front and back sides
         * of consecutive media sheets, such that the orientation of each pair
         * of pages on the medium would be correct for the reader as if for
         * binding on the long edge.
	 */
        public static final SidesType TWO_SIDED_LONG_EDGE =
            new SidesType(I_TWO_SIDED_LONG_EDGE);
        /**
	 * The <code>SidesType</code> instance to use for specifying that
         * consecutive job pages should be printed upon front and back sides
         * of consecutive media sheets, such that the orientation of each pair
         * of pages on the medium would be correct for the reader as if for
         * binding on the short edge.
	 */
        public static final SidesType TWO_SIDED_SHORT_EDGE =
            new SidesType(I_TWO_SIDED_SHORT_EDGE);

        private SidesType(int type) {
	    super(type, NAMES);
	}
    }

    private int copies;
    private DefaultSelectionType defaultSelection;
    private DestinationType destination;
    private DialogType dialog;
    private String fileName;
    private int fromPage;
    private int maxPage;
    private int minPage;
    private MultipleDocumentHandlingType multipleDocumentHandling;
    private int[][] pageRanges;
    private int prFirst;
    private int prLast;
    private String printer;
    private SidesType sides;
    private int toPage;

    /**
     * Constructs a <code>JobAttributes</code> instance with default
     * values for every attribute.  The dialog defaults to
     * <code>DialogType.NATIVE</code>.  Min page defaults to
     * <code>1</code>.  Max page defaults to <code>Integer.MAX_VALUE</code>.
     * Destination defaults to <code>DestinationType.PRINTER</code>.
     * Selection defaults to <code>DefaultSelectionType.ALL</code>.
     * Number of copies defaults to <code>1</code>. Multiple document handling defaults
     * to <code>MultipleDocumentHandlingType.SEPARATE_DOCUMENTS_UNCOLLATED_COPIES</code>.
     * Sides defaults to <code>SidesType.ONE_SIDED</code>. File name defaults
     * to <code>null</code>.
     */
    public JobAttributes() {
        setCopiesToDefault();
	setDefaultSelection(DefaultSelectionType.ALL);
	setDestination(DestinationType.PRINTER);
	setDialog(DialogType.NATIVE);
	setMaxPage(Integer.MAX_VALUE);
	setMinPage(1);
	setMultipleDocumentHandlingToDefault();
	setSidesToDefault();
    }

    /**
     * Constructs a <code>JobAttributes</code> instance which is a copy
     * of the supplied <code>JobAttributes</code>.
     *
     * @param	obj the <code>JobAttributes</code> to copy
     */
    public JobAttributes(JobAttributes obj) {
        set(obj);
    }

    /**
     * Constructs a <code>JobAttributes</code> instance with the
     * specified values for every attribute.
     *
     * @param	copies an integer greater than 0
     * @param	defaultSelection <code>DefaultSelectionType.ALL</code>,
     *		<code>DefaultSelectionType.RANGE</code>, or
     *          <code>DefaultSelectionType.SELECTION</code>
     * @param	destination <code>DesintationType.FILE</code> or
     *          <code>DesintationType.PRINTER</code>
     * @param	dialog <code>DialogType.COMMON</code>,
     *          <code>DialogType.NATIVE</code>, or
     *		<code>DialogType.NONE</code>
     * @param	fileName the possibly <code>null</code> file name
     * @param	maxPage an integer greater than zero and greater than or equal
     *		to <i>minPage</i>
     * @param	minPage an integer greater than zero and less than or equal
     *		to <i>maxPage</i>
     * @param	multipleDocumentHandling
     *     <code>MultipleDocumentHandlingType.SEPARATE_DOCUMENTS_COLLATED_COPIES</code> or
     *     <code>MultipleDocumentHandlingType.SEPARATE_DOCUMENTS_UNCOLLATED_COPIES</code>
     * @param	pageRanges an array of integer arrays of two elements; an array
     *		is interpreted as a range spanning all pages including and
     *		between the specified pages; ranges must be in ascending
     *		order and must not overlap; specified page numbers cannot be
     *		less than <i>minPage</i> nor greater than <i>maxPage</i>;
     *		for example:
     *          <pre>
     *		(new int[][] { new int[] { 1, 3 }, new int[] { 5, 5 },
     *		               new int[] { 15, 19 } }),
     *          </pre>
     *		specifies pages 1, 2, 3, 5, 15, 16, 17, 18, and 19. Note that
     *		(<code>new int[][] { new int[] { 1, 1 }, new int[] { 1, 2 } }</code>),
     *		is an invalid set of page ranges because the two ranges
     *		overlap
     * @param	printer the possibly <code>null</code> printer name
     * @param	sides <code>SidesType.ONE_SIDED</code>,
     *          <code>SidesType.TWO_SIDED_LONG_EDGE</code>, or
     *		<code>SidesType.TWO_SIDED_SHORT_EDGE</code>
     * @throws	IllegalArgumentException if one or more of the above
     *		conditions is violated
     */
    public JobAttributes(int copies, DefaultSelectionType defaultSelection,
			 DestinationType destination, DialogType dialog,
			 String fileName, int maxPage, int minPage,
			 MultipleDocumentHandlingType multipleDocumentHandling,
			 int[][] pageRanges, String printer, SidesType sides) {
        setCopies(copies);
	setDefaultSelection(defaultSelection);
	setDestination(destination);
	setDialog(dialog);
	setFileName(fileName);
	setMaxPage(maxPage);
	setMinPage(minPage);
	setMultipleDocumentHandling(multipleDocumentHandling);
	setPageRanges(pageRanges);
	setPrinter(printer);
	setSides(sides);
    }

    /**
     * Creates and returns a copy of this <code>JobAttributes</code>.
     *
     * @return	the newly created copy; it is safe to cast this Object into
     *		a <code>JobAttributes</code>
     */
    public Object clone() {
        try {
	    return super.clone();
	} catch (CloneNotSupportedException e) {
	    // Since we implement Cloneable, this should never happen
	    throw new InternalError();
	}
    }

    /**
     * Sets all of the attributes of this <code>JobAttributes</code> to
     * the same values as the attributes of obj.
     *
     * @param	obj the <code>JobAttributes</code> to copy
     */
    public void set(JobAttributes obj) {
        copies = obj.copies;
	defaultSelection = obj.defaultSelection;
	destination = obj.destination;
	dialog = obj.dialog;
	fileName = obj.fileName;
	fromPage = obj.fromPage;
	maxPage = obj.maxPage;
	minPage = obj.minPage;
	multipleDocumentHandling = obj.multipleDocumentHandling;
	// okay because we never modify the contents of pageRanges
	pageRanges = obj.pageRanges;
	prFirst = obj.prFirst;
	prLast = obj.prLast;
	printer = obj.printer;
	sides = obj.sides;
	toPage = obj.toPage;
    }

    /**
     * Returns the number of copies the application should render for jobs
     * using these attributes. This attribute is updated to the value chosen
     * by the user.
     *
     * @return	an integer greater than 0.
     */
    public int getCopies() {
        return copies;
    }

    /**
     * Specifies the number of copies the application should render for jobs
     * using these attributes. Not specifying this attribute is equivalent to
     * specifying <code>1</code>.
     *
     * @param	copies an integer greater than 0
     * @throws	IllegalArgumentException if <code>copies</code> is less than
     *      or equal to 0
     */
    public void setCopies(int copies) {
        if (copies <= 0) {
	    throw new IllegalArgumentException("Invalid value for attribute "+
					       "copies");
	}
	this.copies = copies;
    }

    /**
     * Sets the number of copies the application should render for jobs using
     * these attributes to the default. The default number of copies is 1.
     */
    public void setCopiesToDefault() {
        setCopies(1);
    }

    /**
     * Specifies whether, for jobs using these attributes, the application
     * should print all pages, the range specified by the return value of
     * <code>getPageRanges</code>, or the current selection. This attribute
     * is updated to the value chosen by the user.
     *
     * @return	DefaultSelectionType.ALL, DefaultSelectionType.RANGE, or
     *		DefaultSelectionType.SELECTION
     */
    public DefaultSelectionType getDefaultSelection() {
        return defaultSelection;
    }

    /**
     * Specifies whether, for jobs using these attributes, the application
     * should print all pages, the range specified by the return value of
     * <code>getPageRanges</code>, or the current selection. Not specifying
     * this attribute is equivalent to specifying DefaultSelectionType.ALL.
     *
     * @param	defaultSelection DefaultSelectionType.ALL,
     *		DefaultSelectionType.RANGE, or DefaultSelectionType.SELECTION.
     * @throws	IllegalArgumentException if defaultSelection is <code>null</code>
     */
    public void setDefaultSelection(DefaultSelectionType defaultSelection) {
        if (defaultSelection == null) {
	    throw new IllegalArgumentException("Invalid value for attribute "+
					       "defaultSelection");
	}
        this.defaultSelection = defaultSelection;
    }

    /**
     * Specifies whether output will be to a printer or a file for jobs using
     * these attributes. This attribute is updated to the value chosen by the
     * user.
     *
     * @return	DesintationType.FILE or DesintationType.PRINTER
     */
    public DestinationType getDestination() {
        return destination;
    }

    /**
     * Specifies whether output will be to a printer or a file for jobs using
     * these attributes. Not specifying this attribute is equivalent to
     * specifying DesintationType.PRINTER.
     *
     * @param	destination DesintationType.FILE or DesintationType.PRINTER.
     * @throws	IllegalArgumentException if destination is null.
     */
    public void setDestination(DestinationType destination) {
        if (destination == null) {
	    throw new IllegalArgumentException("Invalid value for attribute "+
					       "destination");
	}
        this.destination = destination;
    }

    /**
     * Returns whether, for jobs using these attributes, the user should see
     * a print dialog in which to modify the print settings, and which type of
     * print dialog should be displayed. DialogType.COMMON denotes a cross-
     * platform, pure Java print dialog. DialogType.NATIVE denotes the
     * platform's native print dialog. If a platform does not support a native
     * print dialog, the pure Java print dialog is displayed instead.
     * DialogType.NONE specifies no print dialog (i.e., background printing).
     * This attribute cannot be modified by, and is not subject to any
     * limitations of, the implementation or the target printer.
     *
     * @return	<code>DialogType.COMMON</code>, <code>DialogType.NATIVE</code>, or
     *		<code>DialogType.NONE</code>
     */
    public DialogType getDialog() {
        return dialog;
    }

    /**
     * Specifies whether, for jobs using these attributes, the user should see
     * a print dialog in which to modify the print settings, and which type of
     * print dialog should be displayed. DialogType.COMMON denotes a cross-
     * platform, pure Java print dialog. DialogType.NATIVE denotes the
     * platform's native print dialog. If a platform does not support a native
     * print dialog, the pure Java print dialog is displayed instead.
     * DialogType.NONE specifies no print dialog (i.e., background printing).
     * Not specifying this attribute is equivalent to specifying
     * DialogType.NATIVE.
     *
     * @param	dialog DialogType.COMMON, DialogType.NATIVE, or
     *		DialogType.NONE.
     * @throws	IllegalArgumentException if dialog is null.
     */
    public void setDialog(DialogType dialog) {
        if (dialog == null) {
	    throw new IllegalArgumentException("Invalid value for attribute "+
					       "dialog");
	}
        this.dialog = dialog;
    }

    /**
     * Specifies the file name for the output file for jobs using these
     * attributes. This attribute is updated to the value chosen by the user.
     *
     * @return	the possibly <code>null</code> file name
     */
    public String getFileName() {
        return fileName;
    }

    /**
     * Specifies the file name for the output file for jobs using these
     * attributes. Default is platform-dependent and implementation-defined.
     *
     * @param	fileName the possibly null file name.
     */
    public void setFileName(String fileName) {
        this.fileName = fileName;
    }

    /**
     * Returns, for jobs using these attributes, the first page to be
     * printed, if a range of pages is to be printed. This attribute is
     * updated to the value chosen by the user. An application should ignore
     * this attribute on output, unless the return value of the <code>
     * getDefaultSelection</code> method is DefaultSelectionType.RANGE. An
     * application should honor the return value of <code>getPageRanges</code>
     * over the return value of this method, if possible.
     *
     * @return	an integer greater than zero and less than or equal to
     *          <i>toPage</i> and greater than or equal to <i>minPage</i> and
     *		less than or equal to <i>maxPage</i>.
     */
    public int getFromPage() {
        if (fromPage != 0) {
	    return fromPage;
	} else if (toPage != 0) {
	    return getMinPage();
	} else if (pageRanges != null) {
	    return prFirst;
	} else {
	    return getMinPage();
	}
    }

    /**
     * Specifies, for jobs using these attributes, the first page to be
     * printed, if a range of pages is to be printed. If this attribute is not
     * specified, then the values from the pageRanges attribute are used. If
     * pageRanges and either or both of fromPage and toPage are specified,
     * pageRanges takes precedence. Specifying none of pageRanges, fromPage,
     * or toPage is equivalent to calling
     * setPageRanges(new int[][] { new int[] { <i>minPage</i> } });
     *
     * @param	fromPage an integer greater than zero and less than or equal to
     *          <i>toPage</i> and greater than or equal to <i>minPage</i> and
     *		less than or equal to <i>maxPage</i>.
     * @throws	IllegalArgumentException if one or more of the above
     *		conditions is violated.
     */
    public void setFromPage(int fromPage) {
        if (fromPage <= 0 ||
	    (toPage != 0 && fromPage > toPage) ||
	    fromPage < minPage ||
	    fromPage > maxPage) {
	    throw new IllegalArgumentException("Invalid value for attribute "+
					       "fromPage");
	}
	this.fromPage = fromPage;
    }

    /**
     * Specifies the maximum value the user can specify as the last page to
     * be printed for jobs using these attributes. This attribute cannot be
     * modified by, and is not subject to any limitations of, the
     * implementation or the target printer.
     *
     * @return	an integer greater than zero and greater than or equal
     *		to <i>minPage</i>.
     */
    public int getMaxPage() {
        return maxPage;
    }

    /**
     * Specifies the maximum value the user can specify as the last page to
     * be printed for jobs using these attributes. Not specifying this
     * attribute is equivalent to specifying <code>Integer.MAX_VALUE</code>.
     *
     * @param	maxPage an integer greater than zero and greater than or equal
     *		to <i>minPage</i>
     * @throws	IllegalArgumentException if one or more of the above
     *		conditions is violated
     */
    public void setMaxPage(int maxPage) {
        if (maxPage <= 0 || maxPage < minPage) {
	    throw new IllegalArgumentException("Invalid value for attribute "+
					       "maxPage");
	}
	this.maxPage = maxPage;
    }

    /**
     * Specifies the minimum value the user can specify as the first page to
     * be printed for jobs using these attributes. This attribute cannot be
     * modified by, and is not subject to any limitations of, the
     * implementation or the target printer.
     *
     * @return	an integer greater than zero and less than or equal
     *		to <i>maxPage</i>.
     */
    public int getMinPage() {
        return minPage;
    }

    /**
     * Specifies the minimum value the user can specify as the first page to
     * be printed for jobs using these attributes. Not specifying this
     * attribute is equivalent to specifying <code>1</code>.
     *
     * @param	minPage an integer greater than zero and less than or equal
     *		to <i>maxPage</i>.
     * @throws	IllegalArgumentException if one or more of the above
     *		conditions is violated.
     */
    public void setMinPage(int minPage) {
        if (minPage <= 0 || minPage > maxPage) {
	    throw new IllegalArgumentException("Invalid value for attribute "+
					       "minPage");
	}
	this.minPage = minPage;
    }

    /**
     * Specifies the handling of multiple copies, including collation, for
     * jobs using these attributes. This attribute is updated to the value
     * chosen by the user.
     *
     * @return
     *     MultipleDocumentHandlingType.SEPARATE_DOCUMENTS_COLLATED_COPIES or
     *     MultipleDocumentHandlingType.SEPARATE_DOCUMENTS_UNCOLLATED_COPIES.
     */
    public MultipleDocumentHandlingType getMultipleDocumentHandling() {
        return multipleDocumentHandling;
    }

    /**
     * Specifies the handling of multiple copies, including collation, for
     * jobs using these attributes. Not specifying this attribute is equivalent
     * to specifying
     * MultipleDocumentHandlingType.SEPARATE_DOCUMENTS_UNCOLLATED_COPIES.
     *
     * @param	multipleDocumentHandling
     *     MultipleDocumentHandlingType.SEPARATE_DOCUMENTS_COLLATED_COPIES or
     *     MultipleDocumentHandlingType.SEPARATE_DOCUMENTS_UNCOLLATED_COPIES.
     * @throws	IllegalArgumentException if multipleDocumentHandling is null.
     */
    public void setMultipleDocumentHandling(MultipleDocumentHandlingType
					    multipleDocumentHandling) {
        if (multipleDocumentHandling == null) {
	    throw new IllegalArgumentException("Invalid value for attribute "+
					       "multipleDocumentHandling");
	}
        this.multipleDocumentHandling = multipleDocumentHandling;
    }

    /**
     * Sets the handling of multiple copies, including collation, for jobs
     * using these attributes to the default. The default handling is
     * MultipleDocumentHandlingType.SEPARATE_DOCUMENTS_UNCOLLATED_COPIES.
     */
    public void setMultipleDocumentHandlingToDefault() {
        setMultipleDocumentHandling(
            MultipleDocumentHandlingType.SEPARATE_DOCUMENTS_UNCOLLATED_COPIES);
    }

    /**
     * Specifies, for jobs using these attributes, the ranges of pages to be
     * printed, if a range of pages is to be printed. All range numbers are
     * inclusive. This attribute is updated to the value chosen by the user.
     * An application should ignore this attribute on output, unless the
     * return value of the <code>getDefaultSelection</code> method is
     * DefaultSelectionType.RANGE.
     *
     * @return	an array of integer arrays of 2 elements. An array
     *		is interpreted as a range spanning all pages including and
     *		between the specified pages. Ranges must be in ascending
     *		order and must not overlap. Specified page numbers cannot be
     *		less than <i>minPage</i> nor greater than <i>maxPage</i>.
     *		For example:
     *		(new int[][] { new int[] { 1, 3 }, new int[] { 5, 5 },
     *		               new int[] { 15, 19 } }),
     *		specifies pages 1, 2, 3, 5, 15, 16, 17, 18, and 19.
     */
    public int[][] getPageRanges() {
        if (pageRanges != null) {
	    // Return a copy because otherwise client code could circumvent the
	    // the checks made in setPageRanges by modifying the returned
	    // array.
	    int[][] copy = new int[pageRanges.length][2];
	    for (int i = 0; i < pageRanges.length; i++) {
		copy[i][0] = pageRanges[i][0];
		copy[i][1] = pageRanges[i][1];
	    }
	    return copy;
	} else if (fromPage != 0 || toPage != 0) {
	    int fromPage = getFromPage();
	    int toPage = getToPage();
	    return new int[][] { new int[] { fromPage, toPage } };
	} else {
	    int minPage = getMinPage();
	    return new int[][] { new int[] { minPage, minPage } };
	}
    }

    /**
     * Specifies, for jobs using these attributes, the ranges of pages to be
     * printed, if a range of pages is to be printed. All range numbers are
     * inclusive. If this attribute is not specified, then the values from the
     * fromPage and toPages attributes are used. If pageRanges and either or
     * both of fromPage and toPage are specified, pageRanges takes precedence.
     * Specifying none of pageRanges, fromPage, or toPage is equivalent to
     * calling setPageRanges(new int[][] { new int[] { <i>minPage</i>, 
     *                                                 <i>minPage</i> } });
     *
     * @param	pageRanges an array of integer arrays of 2 elements. An array
     *		is interpreted as a range spanning all pages including and
     *		between the specified pages. Ranges must be in ascending
     *		order and must not overlap. Specified page numbers cannot be
     *		less than <i>minPage</i> nor greater than <i>maxPage</i>.
     *		For example:
     *		(new int[][] { new int[] { 1, 3 }, new int[] { 5, 5 },
     *		               new int[] { 15, 19 } }),
     *		specifies pages 1, 2, 3, 5, 15, 16, 17, 18, and 19. Note that
     *		(new int[][] { new int[] { 1, 1 }, new int[] { 1, 2 } }),
     *		is an invalid set of page ranges because the two ranges
     *		overlap.
     * @throws	IllegalArgumentException if one or more of the above
     *		conditions is violated.
     */
    public void setPageRanges(int[][] pageRanges) {
        String xcp = "Invalid value for attribute pageRanges";
	int first = 0;
        int last = 0;

	if (pageRanges == null) {
	    throw new IllegalArgumentException(xcp);
	}

        for (int i = 0; i < pageRanges.length; i++) {
	    if (pageRanges[i] == null ||
		pageRanges[i].length != 2 ||
		pageRanges[i][0] <= last ||
		pageRanges[i][1] < pageRanges[i][0]) {
	            throw new IllegalArgumentException(xcp);
	    }
	    last = pageRanges[i][1];
	    if (first == 0) {
	        first = pageRanges[i][0];
	    }
	}

	if (first < minPage || last > maxPage) {
	    throw new IllegalArgumentException(xcp);
	}

        // Store a copy because otherwise client code could circumvent the
        // the checks made above by holding a reference to the array and
	// modifying it after calling setPageRanges.
        int[][] copy = new int[pageRanges.length][2];
	for (int i = 0; i < pageRanges.length; i++) {
	    copy[i][0] = pageRanges[i][0];
	    copy[i][1] = pageRanges[i][1];
	}
	this.pageRanges = copy;
	this.prFirst = first;
	this.prLast = last;
    }

    /**
     * Returns the destination printer for jobs using these attributes. This
     * attribute is updated to the value chosen by the user.
     *
     * @return	the possibly null printer name.
     */
    public String getPrinter() {
        return printer;
    }

    /**
     * Specifies the destination printer for jobs using these attributes.
     * Default is platform-dependent and implementation-defined.
     *
     * @param	printer the possibly null printer name.
     */
    public void setPrinter(String printer) {
        this.printer = printer;
    }

    /**
     * Returns how consecutive pages should be imposed upon the sides of the
     * print medium for jobs using these attributes. SidesType.ONE_SIDED
     * imposes each consecutive page upon the same side of consecutive media
     * sheets. This imposition is sometimes called <i>simplex</i>.
     * SidesType.TWO_SIDED_LONG_EDGE imposes each consecutive pair of pages
     * upon front and back sides of consecutive media sheets, such that the
     * orientation of each pair of pages on the medium would be correct for
     * the reader as if for binding on the long edge. This imposition is
     * sometimes called <i>duplex</i>. SidesType.TWO_SIDED_SHORT_EDGE imposes
     * each consecutive pair of pages upon front and back sides of consecutive
     * media sheets, such that the orientation of each pair of pages on the
     * medium would be correct for the reader as if for binding on the short
     * edge. This imposition is sometimes called <i>tumble</i>. This attribute
     * is updated to the value chosen by the user.
     *
     * @return	SidesType.ONE_SIDED, SidesType.TWO_SIDED_LONG_EDGE, or
     *		SidesType.TWO_SIDED_SHORT_EDGE.
     */
    public SidesType getSides() {
        return sides;
    }

    /**
     * Specifies how consecutive pages should be imposed upon the sides of the
     * print medium for jobs using these attributes. SidesType.ONE_SIDED
     * imposes each consecutive page upon the same side of consecutive media
     * sheets. This imposition is sometimes called <i>simplex</i>.
     * SidesType.TWO_SIDED_LONG_EDGE imposes each consecutive pair of pages
     * upon front and back sides of consecutive media sheets, such that the
     * orientation of each pair of pages on the medium would be correct for
     * the reader as if for binding on the long edge. This imposition is
     * sometimes called <i>duplex</i>. SidesType.TWO_SIDED_SHORT_EDGE imposes
     * each consecutive pair of pages upon front and back sides of consecutive
     * media sheets, such that the orientation of each pair of pages on the
     * medium would be correct for the reader as if for binding on the short
     * edge. This imposition is sometimes called <i>tumble</i>. Not specifying
     * this attribute is equivalent to specifying SidesType.ONE_SIDED.
     *
     * @param	sides SidesType.ONE_SIDED, SidesType.TWO_SIDED_LONG_EDGE, or
     *		SidesType.TWO_SIDED_SHORT_EDGE.
     * @throws	IllegalArgumentException if sides is null.
     */
    public void setSides(SidesType sides) {
        if (sides == null) {
	    throw new IllegalArgumentException("Invalid value for attribute "+
					       "sides");
	}
        this.sides = sides;
    }

    /**
     * Sets how consecutive pages should be imposed upon the sides of the
     * print medium for jobs using these attributes to the default. The
     * default imposition is SidesType.ONE_SIDED.
     */
    public void setSidesToDefault() {
        setSides(SidesType.ONE_SIDED);
    }

    /**
     * Returns, for jobs using these attributes, the last page (inclusive)
     * to be printed, if a range of pages is to be printed. This attribute is
     * updated to the value chosen by the user. An application should ignore
     * this attribute on output, unless the return value of the <code>
     * getDefaultSelection</code> method is DefaultSelectionType.RANGE. An
     * application should honor the return value of <code>getPageRanges</code>
     * over the return value of this method, if possible.
     *
     * @return	an integer greater than zero and greater than or equal
     *		to <i>toPage</i> and greater than or equal to <i>minPage</i>
     *		and less than or equal to <i>maxPage</i>.
     */
    public int getToPage() {
        if (toPage != 0) {
	    return toPage;
	} else if (fromPage != 0) {
	    return fromPage;
	} else if (pageRanges != null) {
	    return prLast;
	} else {
	    return getMinPage();
	}
    }

    /**
     * Specifies, for jobs using these attributes, the last page (inclusive)
     * to be printed, if a range of pages is to be printed.
     * If this attribute is not specified, then the values from the pageRanges
     * attribute are used. If pageRanges and either or both of fromPage and
     * toPage are specified, pageRanges takes precedence. Specifying none of
     * pageRanges, fromPage, or toPage is equivalent to calling
     * setPageRanges(new int[][] { new int[] { <i>minPage</i> } });
     *
     * @param	toPage an integer greater than zero and greater than or equal
     *		to <i>fromPage</i> and greater than or equal to <i>minPage</i>
     *		and less than or equal to <i>maxPage</i>.
     * @throws	IllegalArgumentException if one or more of the above
     *		conditions is violated.
     */
    public void setToPage(int toPage) {
        if (toPage <= 0 ||
	    (fromPage != 0 && toPage < fromPage) ||
	    toPage < minPage ||
	    toPage > maxPage) {
	    throw new IllegalArgumentException("Invalid value for attribute "+
					       "toPage");
	}
	this.toPage = toPage;
    }

    /**
     * Determines whether two JobAttributes are equal to each other.
     * <p>
     * Two JobAttributes are equal if and only if each of their attributes are
     * equal. Attributes of enumeration type are equal if and only if the
     * fields refer to the same unique enumeration object. A set of page
     * ranges is equal if and only if the sets are of equal length, each range
     * enumerates the same pages, and the ranges are in the same order.
     *
     * @param	obj the object whose equality will be checked.
     * @return	whether obj is equal to this JobAttribute according to the
     *		above criteria.
     */
    public boolean equals(Object obj) {
        if (!(obj instanceof JobAttributes)) {
	    return false;
	}
	JobAttributes rhs = (JobAttributes)obj;

	if (fileName == null) {
	    if (rhs.fileName != null) {
	        return false;
	    }
	} else {
	    if (!fileName.equals(rhs.fileName)) {
	        return false;
	    }
	}

	if (pageRanges == null) {
	    if (rhs.pageRanges != null) {
	        return false;
	    }
	} else {
	    if (rhs.pageRanges == null ||
		    pageRanges.length != rhs.pageRanges.length) {
	        return false;
	    }
	    for (int i = 0; i < pageRanges.length; i++) {
	        if (pageRanges[i][0] != rhs.pageRanges[i][0] ||
		    pageRanges[i][1] != rhs.pageRanges[i][1]) {
		    return false;
		}
	    }
	}

	if (printer == null) {
	    if (rhs.printer != null) {
	        return false;
	    }
	} else {
	    if (!printer.equals(rhs.printer)) {
	        return false;
	    }
	}

	return (copies == rhs.copies &&
		defaultSelection == rhs.defaultSelection &&
		destination == rhs.destination &&
		dialog == rhs.dialog &&
		fromPage == rhs.fromPage &&
		maxPage == rhs.maxPage &&
		minPage == rhs.minPage &&
		multipleDocumentHandling == rhs.multipleDocumentHandling &&
		prFirst == rhs.prFirst &&
		prLast == rhs.prLast &&
		sides == rhs.sides &&
		toPage == rhs.toPage);
    }

    /**
     * Returns a hash code value for this JobAttributes.
     *
     * @return	the hash code.
     */
    public int hashCode() {
	int rest = ((copies + fromPage + maxPage + minPage + prFirst + prLast +
		     toPage) * 31) << 21;
	if (pageRanges != null) {
	    int sum = 0;
	    for (int i = 0; i < pageRanges.length; i++) {
	        sum += pageRanges[i][0] + pageRanges[i][1];
	    }
	    rest ^= (sum * 31) << 11;
	}
	if (fileName != null) {
	    rest ^= fileName.hashCode();
	}
	if (printer != null) {
	    rest ^= printer.hashCode();
	}
	return (defaultSelection.hashCode() << 6 ^
		destination.hashCode() << 5 ^
		dialog.hashCode() << 3 ^
		multipleDocumentHandling.hashCode() << 2 ^
		sides.hashCode() ^
		rest);
    }

    /**
     * Returns a string representation of this JobAttributes.
     *
     * @return	the string representation.
     */
    public String toString() {
        int[][] pageRanges = getPageRanges();
	String prStr = "[";
	boolean first = true;
	for (int i = 0; i < pageRanges.length; i++) {
	    if (first) {
	        first = false;
	    } else {
	        prStr += ",";
	    }
	    prStr += pageRanges[i][0] + ":" + pageRanges[i][1];
	}
	prStr += "]";

        return "copies=" + getCopies() + ",defaultSelection=" + 
	    getDefaultSelection() + ",destination=" + getDestination() +
	    ",dialog=" + getDialog() + ",fileName=" + getFileName() +
	    ",fromPage=" + getFromPage() + ",maxPage=" + getMaxPage() +
	    ",minPage=" + getMinPage() + ",multiple-document-handling=" +
	    getMultipleDocumentHandling() + ",page-ranges=" + prStr +
	    ",printer=" + getPrinter() + ",sides=" + getSides() + ",toPage=" +
	    getToPage();
    }
}
