JDK-4214785 : Plug-in doesn't load images from JAR files
  • Type: Bug
  • Component: deploy
  • Sub-Component: plugin
  • Affected Version: 1.1,1.2.0,1.2.2
  • Priority: P4
  • Status: Closed
  • Resolution: Not an Issue
  • OS:
    solaris_2.5,solaris_2.6,windows_95,windows_98,windows_nt solaris_2.5,solaris_2.6,windows_95,windows_98,windows_nt
  • CPU: generic,x86,sparc
  • Submitted: 1999-02-25
  • Updated: 2005-02-10
  • Resolved: 2005-02-10
Related Reports
Duplicate :  
Duplicate :  
Relates :  
Description
At least in Netscape Navigator on Solaris, 
Java Plug-in does not load images from JAR
files.

For the tutorial, we've worked around this
(PARTLY) by including the image files alongside
the JAR files.  But this wastes disk space and
bandwidth, and it DOES NOT WORK across firewalls.
(I think the firewall is what prevents image files
from being loaded separately, but the cause might
be something else; the result is the same, though.)

This is a major bug that degrades applet performance
and discourages the use of images in applets.  Worse,
since it doesn't show up all the time, developers 
might miss this bug until they've deployed their
applets, which is likely to make them very angry
at us.

Name: gsC80088			Date: 02/26/99


I have an applet that works fine under Java 1.1.6 but not
under 1.2. When the applet starts it first displays a splash
screen. The splash screen also allows the user to enter their
user name and password in two TextField objects that are placed
on top of the graphic. Labels are drawn beside these text fields
while they are visible. As soon as the OK button is pressed 
the OK and Cancel button that appear under the text fields are
hidden, as are the text fields.

The class file for the applet is in one Jar file
(TstProg5.jar) and the code for displaying the splash screen
is part of a set of support classes in another Jar file
(Exodus.jar). The splash screen graphic is in the same Jar
file as the splash screen class. Both components are in the
directory 'Exodus' within the Exodus.jar file.

The splash screen image, together with an animated activity
bar are loaded using the following code:

  private void imageInit()
  {
       MediaTracker mt = new MediaTracker(this);

       System.out.println("Loading graphics");

       splash = parent.getImage(parent.getDocumentBase(), "Exodus/Splash.jpg");
       anim = parent.getImage(parent.getDocumentBase(), "Exodus/Animbar.jpg");

       mt.addImage(splash, 0);
       mt.addImage(anim, 0);
       buffer = createImage(481, 320);
       gc = buffer.getGraphics();

       try
       {
	   mt.waitForAll();
       }
       catch (InterruptedException e)
       {
           System.out.println("Interrupted waiting for creation of image");
       }

       System.out.println("Splash = " + splash.toString());

  } // End loadImage

The System.out.println call at the end of the method displays
the following:

    Splash = sun.awt.windows.WImage@f981df62

so the image is instantiated to something. Also there is a
delay between the "Loading graphics" message and the "Splash ="
message that is appropriately long for the size of the graphics
and the speed of our network. When the paint routine is called
(which happens in a loop to animate the activity bar) no
graphics appear. The paint routine is shown below:

  public void paint(Graphics g)
  {
    try
    {
       gc.drawImage(splash, 0, 0, this);
       if(displayComps)
       {
           gc.setFont(scrnFont);
           gc.setColor(Color.white);
           gc.drawString("Login Id:", 80, 160);
           gc.drawString("Password:", 80, 195);
       }
       else
       if(animating)
       {
           gc.drawImage(anim, animXpos + 468, 294, this);
           gc.drawImage(anim, animXpos, 294, this);
           if(animXpos < 455)
               gc.drawImage(anim, animXpos + 936, 294, this);
       }
       g.drawImage(buffer, iset.left, iset.top, this);
    }
    catch (NullPointerException e) {}

  } // End paint

Again, this works fine under JDK 1.1.6 and the JRE run time
using the Java Plugin.
======================================================================

Name: rlT66838			Date: 03/02/2000


Java(TM) Plug-in: Version 1.2.2.p001
Using JRE version 1.2.2
  User home directory = D:\WINNT\Profiles\prd
Proxy Configuration: no proxy

JAR cache enabled.
Opening http://ip200/cursbug.jar no proxy
CacheHandler file name: D:\WINNT\Profiles\prd\Temporary Internet
Files\G97J7G22\cursbug.jar

1. a)place cursbug.jar and CursorBug.htm on Web Server
   b)Open CursorBug.htm in Web Browser
   c)Cursor disappears when entering JApplet
*Make sure to remove CustomCursorBug.class and jarImage.class from you local
path or bug will not appear.
**The Cursor.gif file is CompuServer Graphics Interchange file version 89a
Noninterlaced (Size:32x32) 4bit(16 Color) with a transparent background.
2.
a)FILE:CustomCursorBug.java
import javax.swing.JApplet;
import java.awt.Cursor;
import java.awt.Image;
import java.awt.Toolkit;
import java.awt.Point;
public class CustomCursorBug extends JApplet{
  public CustomCursorBug(){
  }
  public void init(){
    super.init();
    jarImage ji = new jarImage();
    Image cursorImage = ji.getImageFromJAR("images/Cursor.gif");
    Cursor customCursor =
  Toolkit.getDefaultToolkit().createCustomCursor(cursorImage,new
Point(1,1),"Custom");
		setCursor(customCursor);
  }
}
b)jarImage.java
import java.awt.*;
import java.io.InputStream;

public class jarImage {
    public jarImage() { }
    // returns an Image retrieved from a jar file
    public Image getImageFromJAR(String fileName){
      if( fileName == null )
        return null;
      Image image = null;
      byte[] tn = null;
      Toolkit toolkit = Toolkit.getDefaultToolkit();
      InputStream in = getClass().getResourceAsStream(fileName);
      try{ int length = in.available();
        tn = new byte[length];
        in.read( tn);
        image = toolkit.createImage( tn);
      }
      catch(Exception exc){
        System.out.println( exc +" getting resource " +fileName );
        return null;
      }
      return image;
    }
  }
c)FILE:CursorBug.htm
<html>
<head>
<title>Cursor Bug</title>
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
</head>
<body bgcolor="#FFFFFF">
<object classid="clsid:8AD9C840-044E-11D1-B3E9-00805F499D93"
    width = 100
    height = 100
  
codebase="http://java.sun.com/products/plugin/1.2.2/jinstall-1_2_2-win.cab#Versi
on=1,2,2,0">
  <param name=CODE value = CustomCursorBug.class >
  <param name=ARCHIVE value="cursbug.jar">
  <param name="type" value="application/x-java-applet;version=1.2">
  <param name="scriptable" value="false">
  <embed
    type="application/x-java-applet;version=1.2"
    code = CustomCursorBug.class
    archive="cursbug.jar"
    width=100
    height=100
    pluginspage="http://java.sun.com/products/plugin/1.2.2/plugin-install.html"
    scriptable=false>
    <noembed></COMMENT> alt="Your browser understands the &lt;APPLET&gt; tag but
    isn't running the applet." Your browser is completely ignoring
    the &lt;APPLET&gt; tag! </noembed>
  </embed>
</object>
</body>
</html>
3)No error reported
4)N/A
5)N/A
(Review ID: 101296)
======================================================================

Comments
EVALUATION I was able to reproduce the problem according to the customer source codes. The problem did not appear when running with JDK 1.1.x. However, when running in JDK 1.2, it appears in both AppletViewer and Java Plug-in on both Win32 and Solaris. Since Java Plug-in is basically using AppletViewer interally for getting the image, the problem is in the AppletContext.getImage() in the AppletViewer. I will reassign this bug to the appletviewer group. stanley.ho@Eng 1999-03-04 There is a bug in the inner class defined in ZipFile.getInputStream, if close is called more than once on the same stream, then the same inflater will get recycled and put in the free list more than once. Later on, when new inflater streams are created, the same Inflater may be allocated to different streams, causing at least one stream to crash. JLS did not define the behaviour of calling close multiple times on the same stream, the current implementation implies calling close on an already closed stream will have no effect. The fix is pretty straight forward. In the close method, we need to check to see if it has been called before, if not, then the Inflater will be recycled, otherwise, close will be a no op. ###@###.### 1999-03-12 AT&T has checked out the latest Cricket build, but they still observe the same problem, so apparently this bug has not been entirely fixed. I also try the test on my machine, and it fails as well. I will set this bug status back to evaluate, so the bug can be evaluated again. This bug occurs in appletviewer, so it is a JDK bug. stanley.ho@Eng 1999-04-22 The evaluation I put on 3/12 fixed part of the problem which lies in java.util.zip. The other part of the problem lies in appletviewer, that is how the applet loads resources. For the test case shown in the bug report, user used getDocumentBase to get the base url, then try to load resources relative to that url. If code base is /home/foo, then the url constructed for the resource will be a file url: /home/foo/Exodus/splash.gif, which at least for 1.2, this url is not valid and the image will not get load when all the classes and resource files are bundled in a jar file. If instead I do the following: URLClassLoader cl = (URLClassLoader)this.getClass().getClassLoader(); System.out.println("AppletClassLoader: " + cl); url = cl.findResource("Exodus/Splash.gif"); if (url != null) System.out.println("Resource URL: " + url); splash = getImage(url); then the image will get loaded correctly. I don't think the problem is loading image from jars, the problem is how the appletviewer use the right path the load resources. I have attached the test case. I will reassign this bug to the appletviewer group. ###@###.### 1999-04-28 Basically, the fact that this worked at all in JDK1.1.x is purely accidental. The current behaviour conforms to the spec (which has not changed between 1.1.x and 1.2/1.3). Applet.getImage() requires "an absolute URL giving the location of the image". In the provided test case, the passed in URL which has been constructed with Applet.getDocumentBase() does not point to the JAR file so the image is not located. The constructed URL has not changed between 1.1.x and 1.2/1.3. What has changed is the location mechanism. In 1.1.x, AppletViewer used non-applet-specific code from sun.net to locate the .gif. It appears as though when JAR files were added in 1.1, this sun.net code was modified to look in multiple places for the gif file. One of these places happened to be the JAR files where the .class file was found. Due to the re-write of sun.net in 1.2, we now search only in the provided URL. It is possible to change the behaviour of AppletViewer's implementation of getImage() to emulate what users have always seen/expected prior to 1.2. Doing so would require some rather extensive modifications to the spec for Applet.getImage() and AppletContext.getImage() to define the search algorithm. The functionality provided by sun.net code which preformed the search would have to be entirely written within sun.applet. This approach is not advisable. A more desirable solution would be to insist that users provide a valid URL for the image so that getImage() does not have to take a hit for searching. If the same JAR file contains the .class file and the .gif file, we could then advise users to determine the image's URL via Applet.getCodeBase(). [ Note that this is currently not possible in AppletViewer due to bug 4274668. ] A similar problem occurs for Applet.getAudioClip(). iris.garcia@eng 1999-09-22 I verified in a different customer case that ClassLoader.findResource works with jdk1.2.2 Related bugs: 4141523 ClassLoader.getResource fails for local disk JAR files 4157932 Images can't be loaded by Applet in JAR file using URL constructor. 4159155 JApplet cannot import images from JAR file in browser 4182189 Images loading differerntly from jar vs on disk 4198073 compressed gif not loading from jar file 4214785 Plug-in doesn't load images from JAR files 4238086 loading images from jar application 4251549 sun.awt.image.FileImageSource implementation need to be clarified I have attached working source. Let's stop the madness on this issue. mark.chamness@Eng 1999-09-30 Here is the original code: splash = parent.getImage(parent.getDocumentBase(), "Exodus/Splash.jpg"); anim = parent.getImage(parent.getDocumentBase(), "Exodus/Animbar.jpg"); When java.applet.Applet.getImage(URL, String) is called, it would use the URL and the path to construct a new URL to download the image directly. It will NOT search through JAR file to look up the image. The correct approach is to use ClassLoader.getResourceAsStream(). However, the submitter code also has bugs: Image image = null; byte[] tn = null; Toolkit toolkit = Toolkit.getDefaultToolkit(); InputStream in = getClass().getResourceAsStream(fileName); try{ int length = in.available(); tn = new byte[length]; in.read( tn); image = toolkit.createImage( tn); } The bug is in this line: int length = in.available(); Basically, InputStream.available() returns the number of bytes available to read(), but it is not equivalent to the total length of the InputStream. In this case, if the total length of the image file is longer than the number returned from InputStream.available(), the image will be only read partially, and that explains the image corruption problem. The correct approach is to read the input stream in a loop until EOF: InputStream in = getClass().getResourceAsStream(fileName); try { BufferedInputStream bis = new BufferedInputStream(is); ByteArrayOutputStream bos = new ByteArrayOutputStream(); byte[] buffer = new byte[8192]; int byteRead = 0; // Read the stream until it is EOF while ((byteRead = bis.read(buffer, 0, 8192)) != -1) bos.write(buffer, 0, byteRead); // Close input stream bis.close(); // Convert to byte array byte[] data = bos.toByteArray(); // Return image only if data length is not zero if (data != null && data.length > 0) return Toolkit.getDefaultToolkit().createImage(data); } } catch( ...) This is not a bug in plugin, but an user error. ###@###.### 2005-2-10 08:23:34 GMT ###@###.### 2005-2-10 08:24:37 GMT
10-02-2005

WORK AROUND It is possible to access images stored in JAR files by going through the class loader mechanism. The following code from zhenghua@eng's evaluation will successfully load an image. URLClassLoader cl = (URLClassLoader)this.getClass().getClassLoader(); System.out.println("AppletClassLoader: " + cl); url = cl.findResource("Exodus/Splash.gif"); if (url != null) System.out.println("Resource URL: " + url); splash = getImage(url); This paradigm can be followed to access audio clips as well. iris.garcia@eng 1999-09-22
22-09-1999