JDK-5080151 : (cs) 1.4.2_0X: IllegalStateException: recursive invocation on non-english locales
  • Type: Bug
  • Component: core-libs
  • Sub-Component: java.nio
  • Affected Version: 1.4.2
  • Priority: P2
  • Status: Closed
  • Resolution: Duplicate
  • OS: solaris_9
  • CPU: sparc
  • Submitted: 2004-07-29
  • Updated: 2005-03-03
  • Resolved: 2005-03-03
Related Reports
Duplicate :  
Relates :  
Relates :  
Relates :  
Description

Name: rv122619			Date: 07/29/2004

When we replace the System classloader without our own using the approved java.lang.class.loader method the VM fails to start. This is caused by the UnixFileSystem.list method constructing a String. The String construction is failing trying to find the system charset before the SystemClassloader is finished being constructed. This happens on most non-english locales. Specificly JA. This happens on Solaris as well as Linux. I have not been able to try this on Windows.
Included are 3 source files and a config file. compile and run with the following script:
echo "compiling"
javac loader/src/com/jim/*.java
jar cf loader.jar -C loader/src .

mv loader.jar loader/dist

/bigdisk/suwall/jdk1.5.0/bin/javac -source 1.4 test.java
/bigdisk/suwall/jdk1.5.0/bin/jar cf test.jar *.class
export LC_ALL=ja
locale
echo ""
for x in /usr/j2re1.4.1_02/bin 
do
${x}/java -version
${x}/java -debug -cp "loader/dist/loader.jar" -Dsas.app.class.dirs="." -Djava.system.class.loader=com.jim.AppClassLoader test
=com/jim/AppClassLoader.java========================================
package com.jim;
import java.io.IOException;
import java.io.File;
import java.net.URL;
import java.net.MalformedURLException;
import java.net.URLStreamHandler;
import java.util.Vector;
import java.net.URLStreamHandlerFactory;
import java.util.StringTokenizer;
import sun.net.www.ParseUtil;

public class AppClassLoader
	extends java.net.URLClassLoader
{

	/**
	 * The AppClassLoader is the start of the SAS Application level classloaders.
	 * This classloader defines the location for the classes used by the application.
	 * The system property "sas.app.class.dirs" points to a set of directories that will
	 * be scanned for jar files. This acts much like the extensions classloader. The property
	 * sas.app.class.path points to a list of classes or jars that actually define the application.
	 * This acts much like the classpath and are prepended to the list of jars found using the
	 * "sas.app.class.dirs" property.
	 */

	public AppClassLoader(ClassLoader parent)
	{
		super(getFileURLs("sas.app.class.dirs", "sas.app.class.path"), new ExtClassLoader(parent)); //$NON-NLS-1$ //$NON-NLS-2$
	}

	/**
	 * scans the System property for directories. Each directory is scanned for files.
	 * Each file is then returned as an URL. This has the effect of added every jar and
	 * zip in each directory to the list. This has the side effect of adding and sub-directory
	 * as a directory of classes to the list.
	 */

    private static URL[] getFileURLs(String jarProperty, String pathProperty)
	{
		// create an array of path names from the webaf.class.path property
		File[] dirs = getDirs(jarProperty);
		File[] path = getPath(pathProperty);
		// translate these pathnames in URLs
	    Vector urls = new Vector();
		int i;
		// first do the path if it exists
		for(i=0;i<path.length;++i)
		{
			if (path[i] != null)
				urls.add(getFileURL(path[i]));
		}

		// next do the jar directories
	    for (i = 0; i < dirs.length; i++)
		{
			String[] files = dirs[i].list();
			if (files != null)
			{
			    for (int j = 0; j < files.length; j++)
				{
					File f = new File(dirs[i], files[j]);
					urls.add(getFileURL(f));
			    }
			}
	    }
	    URL[] ua = new URL[urls.size()];
	    urls.copyInto(ua);
	    return ua;
	}

	/**
	 *  scans the System property for a list of directories.
	 */

	private static File[] getDirs(String property)
	{
		String s = System.getProperty(property);
		File[] dirs;
		if (s != null)
		{
			StringTokenizer st =
				new StringTokenizer(s, File.pathSeparator);
			int count = st.countTokens();
			dirs = new File[count];
			for (int i = 0; i < count; i++)
			{
				dirs[i] = new File(st.nextToken());
			}
		}
		else
		{
			dirs = new File[0];
		}
		return dirs;
	}

	/**
	 *  scans the System property for a list of files.
	 */

	private static File[] getPath(String property)
	{
		String s = System.getProperty(property);
		File[] dirs;
		if (s != null)
		{
			StringTokenizer st =
				new StringTokenizer(s, File.pathSeparator);
			int count = st.countTokens();
			dirs = new File[count];
			for (int i = 0; i < count; i++)
			{
				dirs[i] = new File(st.nextToken());
			}
		}
		else
		{
			dirs = new File[0];
		}
		return dirs;
	}

	/**
	 * Translated the passed in File into an URL. Handles spaces in file names.
	 * @param file The File to be translated into an URL
	 * @return URL The file location translated into an URL
	 */
    private static URL getFileURL(File file)
	{
		try
		{
 			file = file.getCanonicalFile();
		}
		catch (IOException e) {}

        try
		{
            return ParseUtil.fileToEncodedURL(file);
        }
		catch (MalformedURLException e)
		{
			// Should never happen since we specify the protocol...
			throw new InternalError();
		}
    }


	/**
	 * Appends the specified URL to the list of URLs to search for classes and resources.
	 *
	 * @param url the URL to be added to the search path of URLs
	 **/
	public void add(URL u)
	{
		addURL(u);
	}
}
=com/jim/ExtClassLoader.java===================================================
package com.jim;
import java.io.IOException;
import java.io.File;
import java.net.URL;
import java.net.MalformedURLException;
import java.net.URLStreamHandler;
import java.util.Vector;
import java.net.URLStreamHandlerFactory;
import java.io.FileInputStream;
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.InputStream;
import java.io.FileNotFoundException;
import java.text.MessageFormat;
import java.util.ResourceBundle;
import java.util.Locale;


public class ExtClassLoader
	extends java.net.URLClassLoader
{

	/*
	 * The ExtClassLoader scans the normal extensions classloader
	 * for a known set of URLs. These URLs are identified from the contents
	 * of a config file. The default config is com.sas.app.JavaEXTs.config.
	 * an alternate file will be used if it is speicified in the System property
	 * "sas.java.config"
	 */
	public ExtClassLoader(ClassLoader parent)
	{
		super(getFilteredURLs(parent), null);	// skip the extensions classloader
	}

	/**
	 * getFileredURLs scans the extensions classloader for its known URLs and only uses
	 * a subset as defined by the contents of JavaEXTs.config. The config file is a list of jar
	 * names. Absolute names are not used. Each URL is scanned to determine if it points to
	 * a jar named in the config file. Any matches are selected and returned by this method.
	 * All jar names must be complete names. Fragments will not match.
	 */

	private static URL[] getFilteredURLs(ClassLoader parent)
	{
		ClassLoaderUtil clu = new ClassLoaderUtil();
		Vector v = new Vector();
		int num = 0;
		// read the config file. This handles finding the config via the System Property
		// first then looking near the class.
		String configLocation=null;
		try
		{
			configLocation = System.getProperty("sas.ext.config"); //$NON-NLS-1$
			InputStream in=null;
			if (configLocation != null)
			{
				in=new FileInputStream(configLocation);
			}
			else
			{
				configLocation=clu.getConfigName();
				in = clu.getConfig();

				//if the input stream is null, then throw a FileNotFoundException like the user-specified
				//config strategy will throw and catch below to print out message.
				if (in==null)
					throw new FileNotFoundException();
			}

			BufferedReader br=new BufferedReader(new java.io.InputStreamReader(in, "ISO_8859-1:1987"));

			boolean done = false;
			while(!done)
			{
				String line = br.readLine();
				if (line == null)
					done = true;
				else
				{
					v.addElement(line);
					++num;
				}
			}
			br.close();
		}
		catch(FileNotFoundException e1)
		{
			//I'm having to do the ResourceBundle work manually since the Launcher jar shouldn't depend on any other jars.
			ResourceBundle resourceBundle=ResourceBundle.getBundle("com.sas.app.Resources", //$NON-NLS-1$
                                                         Locale.getDefault(),
                                                         ExtClassLoader.class.getClassLoader());
			MessageFormat format=new MessageFormat(resourceBundle.getString("ExtClassLoader.ConfigLocationError.txt")); //$NON-NLS-1$
			Object[] messageArgs=new Object[1];
			messageArgs[0]=configLocation;
			System.err.println(format.format(messageArgs));
		}
		catch(IOException e2)
		{
			e2.printStackTrace();
		}

		// grab the urls from the extensions classloader and see which ones match the jars from the
		// list in the config file. We prepend a slash to the jar name so we don't match partials.
		String[] filter = (String[])v.toArray(new String[num]);
		ClassLoader loader = parent.getParent(); // hope this is the extensions classloader
		if (loader instanceof java.net.URLClassLoader)
		{
			URL[] u = ((java.net.URLClassLoader)loader).getURLs();
			v.removeAllElements();
			int i;
			for(i=0;i<u.length;++i)
			{
				int j;
				for(j=0;j<filter.length;++j)
				{
					if (u[i].getFile().endsWith("/"+filter[j])) //$NON-NLS-1$
						v.addElement(u[i]);
				}
			}
			return (URL[])v.toArray(new URL[0]);
		}
		return null;
	}


}


/**
 * ClassLoaderUtil is here only to load the config file as a resource. This allows the config to
 * reside inside the jar file.
 */
class ClassLoaderUtil
{
	public ClassLoaderUtil()
	{
	}
	public String getConfigName()
	{
		return "JavaEXTs.config"; //$NON-NLS-1$
	}
	public InputStream getConfig()
	{
		return getClass().getResourceAsStream(getConfigName());
	}
}

=com/jim/JavaEXTs.config========================================
dnsns.jar
ldapsec.jar
localedata.jar
sunjce_provider.jar
=test.java==================================================


public class test
{
	public static void main(String[] args)
	{
		System.getProperties().list(System.out);
		System.out.println("test");
		System.exit(0);
	}
}

======================================================================

Comments
EVALUATION This bug appears to have been fixed in 1.4.2_08. It is still reproducible under 1.4.2_07. I've requested a list of bugs which have been fixed in the _08 release to determine what changes caused this. ###@###.### 2005-2-28 23:11:07 GMT This bug is no longer reproducible as a result of the changes for this bug: 6196407: J2SE NIO: eucJP-open failed to be looked up Unless there are any further concerns, I expect to close this bug. ###@###.### 2005-03-01 22:18:22 GMT Verified as duplicate of 6196407 by CTE. Closing this report as a duplicate. ###@###.### 2005-03-03 19:09:17 GMT
28-02-2005

WORK AROUND Name: rv122619 Date: 07/29/2004 The only workaround I have found is to move AppClassLoader and ExtClassLoader into the default package. That makes no sense to me. ======================================================================
03-08-2004

SUGGESTED FIX ###@###.### 07/29/04 This happens only in 1.4.2. It does not happen in 1.4.1 or 1.5. It happens in all flavors of 1.4.2 that we have tried. Maybe I should file an escalation for CTE to look into it. thanks, Raghu Raghunath Verabelli wrote: Thanks Jerry and Martin for responding. I visited SAS yesterday, they raised about these classloader issues during the meeting. It is a high priority/high impact issue for them. Iris, can you please look into it and let me know your comments/suggestions? It seems it happens not just with Japaneese, but some of the european characters also and like I said removing simple package statement solves the problem. Please let me know if you need any additional details. I can file a bug report if needed. thanks, Raghu Martin Buchholz wrote: Hi Ragunath, Ian Little has left the company. I have inherited some of Ian's work, but I am not yet familiar with the earlier work. I believe Iris Garcia may have reviewed the changes for 4892738: Migrate PCK and eucJP-Open converters to use NIO charset API and she also knows about ClassLoaders, so she is more likely to be able to answer your question. Offhand, it looks like the scenario below is a basic dependency loop, where the initialization of a classloader eventually requires the classloader to have already been initialized. The use of the nio mechanism may trigger this. Perhaps we have a deep bug that makes it very difficult for a user to define their own system classloader using the -Djava.system.class.loader mechanism. Martin Raghunath Verabelli wrote: Something else to consider. Changing the code to live in the default package makes the problem go away. Jerry Driscoll wrote: Martin, Could you help Raghu? Jerry Raghunath Verabelli wrote: Hi Jerry, I have a question related to bug 4892738, looks like it is fixed in tiger, but there is no RE, so not sure whom to ask the below question. I see ###@###.### comments, but not sure if this a valid email id. please let me know. thanks, Raghu Would the bug 4892738 be the cause of the following problem: Use -Djava.system.class.loader to set a new system classloader that uses the code from sun.misc.launcher to define a new classloader. Thus the new classloader uses File.list to get a list of files and then creates URLs for them and uses them in an URLClassLoader. In the japenese locale on Solaris in 1.4.2 (all versions), this throws an "IllegalStateException: recursive invocation" trying to create a string in the UnixFileSystem.list function. This is because the system classloader has not been set and the system is trying to lookup nio charset support which calls GetSystemClassloader().
03-08-2004