United StatesChange Country, Oracle Worldwide Web Sites Communities I am a... I want to...
JDK-6278300 : sun.print.Win32MediaSize deserialization error

Details
Type:
Bug
Submit Date:
2005-05-31
Status:
Open
Updated Date:
2016-08-02
Project Name:
JDK
Resolved Date:
Component:
client-libs
OS:
windows_xp
Sub-Component:
2d
CPU:
x86
Priority:
P5
Resolution:
Unresolved
Affected Versions:
5.0
Targeted Versions:

Related Reports

Sub Tasks

Description
FULL PRODUCT VERSION :
java version "1.5.0_02"
Java(TM) 2 Runtime Environment, Standard Edition (build 1.5.0_02-b09)
Java HotSpot(TM) Client VM (build 1.5.0_02-b09, mixed mode, sharing)

ADDITIONAL OS VERSION INFORMATION :
Microsoft Windows XP [Version 5.1.2600]

(Does not appear to be specific to particular Windows version.)

A DESCRIPTION OF THE PROBLEM :
Deserialization of a javax.print.attribute.standard.MediaPrintableArea printer attribute fails when the serialization happened in a prior invocation of the program. Unclear what the nature of the initialization problem is, but it works during the same invocation of the program as the serialization. Exception message suggests a problem with the implementation of the javax.print.attribute.EnumSyntax.readResolve method call in sun.print.Win32MediaSize.

The stack trace suggests that the valid range test in readResolve is not functioning properly because of the fact that the range is in descending rather than ascending order--but that's just our guess. That is, it says zero is not a valid value because it doesn't fall in the range 0 to -1:

java.io.InvalidObjectException: Integer value = 0 not in valid range 0..-1for cl
ass class sun.print.Win32MediaSize
        at javax.print.attribute.EnumSyntax.readResolve(Unknown Source)
(see Error Messages for full stack trace)

My code is attempting to deserialize a javax.print.attribute.standard.MediaPrintableArea object. Depending upon the specific attributes, this either succeeds or fails (in our case, with the stack trace shown; there may be other similar problems we haven't encountered yet). The problem appears when there is a non-standard page size involved (in our test case with a Canon S750 driver and the 4 x 6 size [a size supplied with the Canon driver because of its potential use to print on expensive Canon photo paper]).

Note that this only seems to happen when the deserialization occurs as the program is initializing. In the course of a single program invocation, the deserialization operates correctly. If the serialization sits in Preferences when the program starts, it fails (odd--and frustrating since that's sort of the whole point of saving the media size).



STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
You'll need a Windoze box (we tested on both XP and 2K w/ sp4 so either should be an okay testbed).

Get the Canon driver for the S750 from this URL:

http://consumer.usa.canon.com/ir/controller?act=SupportSearchResultsAct&keyword=S750&Submit.x=39&Submit.y=11

Click on Drivers and get the one for Win2K or XP.

Install it (you won't actually need a printer to see the problem).

Run the test program. When the printer dialog comes up, go to the  Page Setup tab; choose the 4 x 6 Media and change the margins. Press the Print button (this serializes the attributes of the printer and saves them as Preferences).

The program now redisplays the Print Dialog with the deserialized attributes shown (you can verify this by checking the Page Setup tab). When you press Print or Cancel, the program terminates.

So far so good. We've successfully deserialized the media object.

Rerun the program.

When the program attempts to deserialize the attributes, it fails as described.

At this point you can actually go to the registry and find the Preferences on this path:

My Computer\HKEY_CURRENT_USER\Software\JavaSoft\Prefs\<test program package name  | unnamed>\/<printer name here>

If you delete the entry for media, you'll note that the program can initialize and display the print dialog. That is to say, the media-printable-area entry deserializes correctly.

If you want to get back to the initial state, you can run the program with the parameter:

reinit

Or, just delete the entries from the printer's folder in the registry.

Finally, if you choose one of the standard media sizes (for which there's a static class value in the MediaPrintableArea class), there doesn't seem to be a problem. Custom page sizes cause problems too.



EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
Printer Dialog should display, with the last settings, without an error regardless of the paper/media setting.
ACTUAL -
When you use a non-standard paper size for the media value, deserialization of the MediaPrintableArea instance fails upon initialization of the program (oddly not when it happens in the same invocation of the program as the serialization).

ERROR MESSAGES/STACK TRACES THAT OCCUR :
java.io.InvalidObjectException: Integer value = 0 not in valid range 0..-1for class class sun.print.Win32MediaSize
        at javax.print.attribute.EnumSyntax.readResolve(EnumSyntax.java:182)
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
        at java.lang.reflect.Method.invoke(Method.java:324)
        at java.io.ObjectStreamClass.invokeReadResolve(ObjectStreamClass.java:925)
        at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1655)
        at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1274)
        at java.io.ObjectInputStream.readObject(ObjectInputStream.java:324)
        at PrtAttribDeserializer.loadAttributes(PrtAttribDeserializer.java:106)
        at PrtAttribDeserializer.<init>(PrtAttribDeserializer.java:40)
        at PrtAttribDeserializer.main(PrtAttribDeserializer.java:140)
java.io.InvalidObjectException: Integer value = 0 not in valid range 0..-1for class class sun.print.Win32MediaSize
        at javax.print.attribute.EnumSyntax.readResolve(EnumSyntax.java:182)
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
        at java.lang.reflect.Method.invoke(Method.java:324)
        at java.io.ObjectStreamClass.invokeReadResolve(ObjectStreamClass.java:925)
        at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1655)
        at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1274)
        at java.io.ObjectInputStream.readObject(ObjectInputStream.java:324)
        at PrtAttribDeserializer.loadAttributes(PrtAttribDeserializer.java:106)
        at PrtAttribDeserializer.<init>(PrtAttribDeserializer.java:40)
        at PrtAttribDeserializer.main(PrtAttribDeserializer.java:140)
Exception in thread "main"


REPRODUCIBILITY :
This bug can be reproduced always.

---------- BEGIN SOURCE ----------
/*
 * PrtAttribDeserializer.java
 *
 * Created on May 27, 2005, 5:14 PM
 */

import java.awt.print.*;

import java.io.*;

import javax.print.*;
import javax.print.attribute.*;

import java.util.prefs.*;

/**
 * Demonstrates the apparent bug in deserialization of some printer attribute set
 * values.
 *
 * @author  John M Craig
 */
public class PrtAttribDeserializer {
    
    private PrintService dfltPrtSrvc = null;
    
    private String       printerName = null;
    
    private PrinterJob   printerJob  = null;
    
    private HashPrintRequestAttributeSet attributeSet
        = new HashPrintRequestAttributeSet();
    
    /** Creates a new instance of PrtAttribDeserializer */
    public PrtAttribDeserializer( final boolean doReinit ) throws Exception {
        this.dfltPrtSrvc = PrintServiceLookup.lookupDefaultPrintService();
        this.printerName = dfltPrtSrvc.getName();
        this.printerJob  = PrinterJob.getPrinterJob();
        this.printerJob.setPrintService( dfltPrtSrvc );
        if ( ! doReinit ) {
            this.attributeSet = this.loadAttributes( printerName );
        } else {
            this.saveAttributes( printerName, null );
        }
    }
    
    private void saveAttributes( final String                       printerName
                               , final HashPrintRequestAttributeSet attributeSet
                               ) throws Exception {
            Preferences prefs
                = Preferences.userNodeForPackage( this.getClass() );
            Preferences prtPrefs = prefs.node( printerName );
            // if there's nothing to write, zero out everything for the printer
            // in question
            if ( attributeSet == null || attributeSet.isEmpty() ) {
                prtPrefs.clear();
                prtPrefs.flush();
                return;
            }
            // OK, got stuff to save
            ByteArrayOutputStream byteOutput = new ByteArrayOutputStream( 1024 );
        
            ObjectOutputStream objOutput = null;
            // objOutput puts a header into the byteOutput; set a mark after it
            Attribute[] attribs = attributeSet.toArray();
            for ( int i = 0; i < attribs.length; i++ ) {
                byteOutput.reset();
                // objOutput puts a header in the byteOutput buffer; recreation required
                objOutput = new ObjectOutputStream ( byteOutput );

                objOutput .writeObject( attribs[ i ] );
                objOutput .flush();
                byte[] theBytes = byteOutput.toByteArray();
                prtPrefs.putByteArray( attribs[ i ].getName()
                                     , theBytes );
                objOutput .close();
            }
            prtPrefs  .flush();
            objOutput .close();
            byteOutput.close(); // note this is a no-op; if switch implementations may be required
        
    // saveAttributes
    }
    
    private HashPrintRequestAttributeSet loadAttributes( final String printerName
                                                       ) throws Exception {
        HashPrintRequestAttributeSet attributeSet = new HashPrintRequestAttributeSet();

            Preferences prefs
                = Preferences.userNodeForPackage( this.getClass() );
            Preferences prtPrefs = prefs.node( printerName );
            byte[]               theBytes  = null;
            ByteArrayInputStream byteInput = null;
            ObjectInputStream    objInput  = null;
            String[] keys = prtPrefs.keys();
            for ( int idx = 0; idx < keys.length; idx++ ) {
                theBytes = prtPrefs.getByteArray( keys[ idx ], null );
                if ( theBytes == null || theBytes.length == 0 ) {
                    // nothing for attribute in question
                    continue;
                }
                Object attribute = null;
                try {
                    byteInput = new ByteArrayInputStream( theBytes );
                    objInput
                        = new ObjectInputStream ( byteInput );
                    attribute = objInput.readObject();
                } catch ( Exception eX ) {
                    eX.printStackTrace();
                    throw eX;
                }
                attributeSet.add( (Attribute) attribute );
                objInput .close();
                objInput  = null;
                byteInput.close(); // no-op; if modify implementation may be important
                byteInput = null;
            } // for each child node
            
        return attributeSet;
    // loadAttributes
    }

    
    public void displayDialog() throws Exception {
        if ( this.attributeSet == null ) {
            this.attributeSet = new HashPrintRequestAttributeSet();
        }
        this.printerJob.printDialog( this.attributeSet );
        this.saveAttributes( this.printerName, this.attributeSet );
    }

    
    /**
     * @param args the command line arguments
     * use 'reinit' to clear out Preferences entry
     */
    public static void main(String[] args) throws Exception {
        boolean doReinit = args.length > 0
                        && args[ 0 ].equalsIgnoreCase( "reinit" );
          
        PrtAttribDeserializer attribDeserial = new PrtAttribDeserializer( doReinit );

        System.out.println("When the dialog displays, open the Page Setup tab.");
        System.out.println("\t Change the Media to 4 x 6 & press Print." );
        System.out.println("\t Note that the dialog displays the next time with the deserialized value shown.");
        System.out.println("\t Allow the program to exit normally (press Print or Cancel); rerun to see failure.");
        System.out.println("\t To reset run with parameter: \"reinit\" " );

        attribDeserial.displayDialog();
        
        attribDeserial = new PrtAttribDeserializer( false );
        
        attribDeserial.displayDialog();

    // main
    }
    
// class PrtAttribSerializer
}

---------- END SOURCE ----------

CUSTOMER SUBMITTED WORKAROUND :
I assume that if I disect the MediaPrintableArea so that I'm actually saving the x, y, h, w, and units properties, I could create a new one. Haven't had time to try that, but that's the plan.
###@###.### 2005-05-31 17:37:53 GMT

Additional customer info :
Some of the information was a bit confused. Several times, I mistakenly referred to the MediaPageSize class when I meant the Media class. It's the form/media that's the problem. But it may be a limitation of the design: there doesn't seem to be a way to create a Media instance that refers to a non-standard paper size in a way that looks like it could deserialize correctly--it's odd that it'll do it within the same JVM instance.
###@###.### 2005-07-13 18:44:30 GMT

                                    

Comments
Windows defines standard paper sizes and we recognise those and will always serialise
the "Media" as one of the built-in java pre-defined Media.

But windows printer drivers very  typically define additional driver-specific papers,
so at runtime JDK dynamically creates new instances as these are discovered.

They will be instances of Win32MediaSize and their IDs (enum int values) will
be entirely dependent on the order they "happened" to be added to the list.

When you serialise this out, all you serialise is
(a) the type (Win32MediaSize) and (b) the int value (see the serialized form docs
for EnumSyntax).

When you come to *deserialize* then what happens depends on whether the
code that adds those to the list has been run (or not). If it has, you may well
be lucky and get the right intended paper.

If it has not - which is inevitable if you load the preferences in main(..)
before any printing code has run - then the code that initialises the table
has not been run and the int value is meaningless. 

It seems like there should be a readResolve() for Win32MediaSize that
first initialises the table.

And it is only *this simple* (yes, I mean that) because you only have one
printer involved. Get 4 printers with custom IDs and you don't even know
in which order these printers had their IDs initialised.

The  documented serialised form leaves you with essentially insufficient information.
It seems like Win32MediaSize needs a serialised form that includes the extra info.
But if you "delete" the printer and run the app you now have useless serialised data
and need to esstablish a policy around that.

Also it seems likely we have the same issue in the CUPS code ..

This is a non-trivial bug and will likely take a lot of careful work to
figure out all the spec/compat/behavioural issues.
                                     
2016-08-02



Hardware and Software, Engineered to Work Together