/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package javax.naming.spi;
import java.net.URL;
import java.net.URLClassLoader;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.StringTokenizer;
import javax.naming.CannotProceedException;
import javax.naming.Context;
import org.apache.harmony.jndi.internal.EnvironmentReader;
import org.apache.harmony.jndi.internal.UrlParser;
import org.apache.harmony.jndi.internal.nls.Messages;
import javax.naming.Name;
import javax.naming.NamingException;
import javax.naming.NoInitialContextException;
import javax.naming.RefAddr;
import javax.naming.Reference;
import javax.naming.Referenceable;
import javax.naming.StringRefAddr;
/**
* The <code>NamingManager</code> class should not be instantiated although it
* can be extended by classes within the <code>javax.naming.spi</code> package -
* see {@link DirectoryManager}. All its methods are static.
* <p>
* The methods are used by service providers for accessing object and state
* factories and for determining continuation contexts. Many of the methods
* create objects. These may be <code>Context</code> objects or objects
* referred to by the naming service.
* </p>
* <p>
* The <code>Name</code> and <code>Hashtable</code> arguments passed to the
* <code>NamingManager</code> methods remain owned purely by the calling
* method. They must not be changed or referenced.
* </p>
* <p>
* It should be noted that it is possible for an application to access a
* namespace other than that supplied by the default <code>InitialContext</code>
* (as specified by <code>Context.INITIAL_CONTEXT_FACTORY</code>). It is
* possible to call the following <code>InitialContext</code> methods passing
* a URL string either as the <code>String</code> or <code>Name</code>
* parameter: <code>lookup, bin, rebind, unbind, rename, list, listBindings,
* destroySubcontext, createSubcontext, lookupLink, getNameParser</code>.
* This allows you to have one <code>InitialContext</code> object where these
* methods usually use the default initial context but access a URL
* <code>Context</code> instead when invoked with a URL string.
* </p>
* <p>
* A URL string is of the format abc:\nnnnnn where abc is the scheme of the URL.
* (See <code>InitialContext</code> where it refers to RFC1738.) When a URL
* string is supplied to those <code>InitialContext</code> methods, a URL
* context is used instead of the default initial context when performing that
* method. URL context factories are used to create URL contexts. A URL context
* factory is really just a service provider's implementation of an
* <code>ObjectFactory</code>. It is not essential that a service provider
* supplies one if they do not wish to support URL <code>Contexts.</code>
* </p>
* <p>
* See the <code>getURLContext</code> method for a description of how a URL
* context factory is located.
* </p>
* <p>
* Please note that multithreaded access to this class must be safe. For
* example, for thread safety, it should not be possible for one thread to read
* the installed <code>InitialContextFactoryBuilder</code> or
* <code>ObjectFactoryBuilder</code> while another thread is in the process of
* setting it.
* </p>
* <p>
* Also note that privileges should be granted to get the context classloader
* and to read the resource files.
* </p>
*
* @see DirectoryManager
*/
public class NamingManager {
/**
* The property name of <code>CannotProceedException</code> in a context's
* environment.
*/
public static final String CPE = "java.naming.spi.CannotProceedException"; //$NON-NLS-1$
static InitialContextFactoryBuilder icfb;
static ObjectFactoryBuilder ofb;
NamingManager() {
super();
// package private to prevent it being instanced but make it can be
// subclassed by DirectoryManager
}
/**
* Install an <code>InitialContextFactoryBuilder</code>. Once this has
* been set it cannot be reset. Attempts to do so cause an
* <code>IllegalStateException</code>. The builder can only be installed
* if the security policy allows the setting of the factory.
*
* @param icfb
* the builder to be installed - can be null, but then no builder
* is installed.
* @throws IllegalStateException
* if an builder has already been installed.
* @throws SecurityException
* is a security error prevents the installation.
* @throws NamingException
* for other errors encountered.
*/
public static void setInitialContextFactoryBuilder(
InitialContextFactoryBuilder icfb) throws IllegalStateException,
SecurityException, NamingException {
// check security access
SecurityManager sm = System.getSecurityManager();
if (null != sm) {
sm.checkSetFactory();
}
synchronized (NamingManager.class) {
if (null != NamingManager.icfb) {
// jndi.1E=InitialContextFactoryBuilder cannot be reset
throw new IllegalStateException(Messages.getString("jndi.1E")); //$NON-NLS-1$
}
NamingManager.icfb = icfb;
}
}
/**
* Returns true when an <code>InitialContextFactoryBuilder</code> has been
* installed.
*
* @return true when an <code>InitialContextFactoryBuilder</code> has been
* installed.
*/
public static boolean hasInitialContextFactoryBuilder() {
return null != icfb;
}
/**
* Install an <code>ObjectFactoryBuilder</code>. Once this has been set
* it cannot be reset. Attempts to do so cause an
* <code>IllegalStateException</code>. The builder can only be installed
* if the security policy allows the setting of the factory.
*
* @param ofb
* the <code>ObjectFactoryBuilder</code> to be installed - can
* be null, but then no builder is installed.
* @throws IllegalStateException
* if an <code>ObjectFactoryBuilder</code> has already been
* installed.
* @throws SecurityException
* is a security error prevents the installation.
* @throws NamingException
* for other errors encountered.
*/
public static synchronized void setObjectFactoryBuilder(
ObjectFactoryBuilder ofb) throws IllegalStateException,
SecurityException, NamingException {
if (null != NamingManager.ofb) {
// jndi.1F=ObjectFactoryBuilder cannot be reset
throw new IllegalStateException(Messages.getString("jndi.1F")); //$NON-NLS-1$
}
// check security access
SecurityManager sm = System.getSecurityManager();
if (null != sm) {
sm.checkSetFactory();
}
NamingManager.ofb = ofb;
}
/**
* Create an <code>InitialContext</code> from either a previously
* installed <code>InitialContextFactoryBuilder</code> or from the
* <code>Context.INITIAL_CONTEXT_FACTORY</code> property in the supplied
* <code> Hashtable h</code> if no builder is installed. An installed
* <code>InitialContextFactoryBuilder</code> can generate a factory which
* can be used to create the <code>InitialContext</code>. The
* <code>Context.INITIAL_CONTEXT_FACTORY</code> property contains the
* class of a factory which can be used to create the
* <code>InitialContext</code>.
*
* @param h
* a hashtable containing properties and values - may be null
* @return an <code>InitialContext</code>
* @throws NoInitialContextException
* if the <code>InitialContext</code> cannot be created.
* @throws NamingException
*/
public static Context getInitialContext(Hashtable<?, ?> h)
throws NoInitialContextException, NamingException {
// if InitialContextFactoryBuilder is set
if (null != icfb) {
// create InitialContext using builder
return icfb.createInitialContextFactory(h).getInitialContext(h);
}
// create InitialContext using factory specified in hashtable
try {
// get factory class name
String factoryClassName = (String) h
.get(Context.INITIAL_CONTEXT_FACTORY);
// new factory instance
Class<?> factoryClass = classForName(factoryClassName);
InitialContextFactory factory = (InitialContextFactory) factoryClass
.newInstance();
// create initial context instance using the factory
return factory.getInitialContext(h);
} catch (NamingException e) {
// throw NamingException
throw e;
} catch (Exception e) {
// failed, throw NoInitialContextException
// jndi.20=Failed to create InitialContext using factory specified
// in hashtable {0}
NamingException nex = new NoInitialContextException(Messages
.getString("jndi.20", h)); //$NON-NLS-1$
nex.setRootCause(e);
throw nex;
}
}
/**
* Create an object from either a previously installed
* <code>ObjectFactoryBuilder</code> or from a supplied reference or from
* the <code>Context.OBJECT_FACTORIES</code> property in the supplied
* <code>Hashtable h</code>.
* <p>
* An installed <code>ObjectFactoryBuilder</code> can generate a factory
* which can be used to create the object instance to return to caller. Any
* encountered exceptions are thrown.
* </p>
* <p>
* If an <code>ObjectFactoryBuilder</code> has not been installed then the
* supplied <code>Object o</code> may provide a <code>Reference</code>
* or <code>Referenceable</code> object. If so, then that
* <code>Object o</code> may have an associated class in a factory which
* could be loaded and used to create the object instance. If the factory
* class cannot be loaded then the <code>URLClassLoader</code> may be able
* to load a class from the list of URLs specified in the reference's
* factory class location. Any exceptions encountered are passed up.
* </p>
* <p>
* If a reference is supplied but no factory class can be loaded from it
* then this method returns the supplied object <code>o</code>.
* </p>
* <p>
* If a factory class loads successfully and can then be used to create an
* object instance then that instance is returned to the caller.
* </p>
* <p>
* If no factory name was associated with the <code>Reference</code>
* object <code>o</code> then see whether the <code>Reference</code> or
* <code>Referenceable</code> object has any <code>StringRefAddrs</code>
* of address type URL or url in its address list. For each entry in the
* list, in the order they appear in the list, it may be possible to use the
* URL factory to create the object. A URL in a <code>StringRefAddr</code>
* should have a scheme which can be used to locate the associated URL
* context factory in the same way as in the <code>getURLContext</code>
* method. (The scheme is the part which comes before :\. For example the
* URL http://www.apache.org has the scheme http.) A URL with no scheme
* would be ignored for these purposes.
* </p>
* <p>
* If no <code>ObjectFactoryBuilder</code> was installed, no factory class
* name is supplied with a <code>Reference</code> and no URL contexts
* succeeded in creating an <code>Object</code> then try the factories in
* <code>Context.OBJECT_FACTORIES</code> for this environment. Also try
* the provider resource file belonging to the context <code>c</code>.
* (See <code>Context</code> description for details of Provider resource
* files.) If any factory throws an exception then pass that back to the
* caller - no further factories are tried.
* </p>
* <p>
* If all factories fail to load or create the <code>Object</code> then
* return the argument object <code>o</code> as the returned object.
* </p>
*
* @param o
* an object which may provide reference or location information.
* May be null.
* @param n
* The name of the <code>Object</code> relative to the default
* initial context(or relative to the Context c if it is
* supplied)
* @param c
* the <code>Context</code> to which the <code>Name</code> is
* relative
* @param h
* a <code>Hashtable</code> containing environment properties
* and values - may be null
* @return a new <code>Object</code> or the supplied <code>Object o</code>
* if one cannot be created.
* @throws NamingException
* if one is encountered
* @throws Exception
* if any other exception is encountered
*/
public static Object getObjectInstance(Object o, Name n, Context c,
Hashtable<?, ?> h) throws NamingException, Exception {
// 1. try ObjectFactoryBuilder, if it is set
if (null != ofb) {
// use the builder to create an object factory
ObjectFactory factory = ofb.createObjectFactory(o, h);
// get object instance using the factory and return
return factory.getObjectInstance(o, n, c, h);
}
// 2. see whether o is a Referenceable or a Reference
Reference ref = null;
if (o instanceof Referenceable) {
ref = ((Referenceable) o).getReference();
}
if (o instanceof Reference) {
ref = (Reference) o;
}
// if o is a Referenceable or a Reference
if (null != ref) {
// if a factory class name is supplied by the reference, use it to
// create
if (null != ref.getFactoryClassName()) {
return getObjectInstanceByFactoryInReference(ref, o, n, c, h);
}
// see if ref has any StringRefAddrs of address type URL,
Object result = getObjectInstanceByUrlRefAddr(n, c, h, ref);
// if success, return it
if (null != result) {
return result;
}
}
// 3. try Context.OBJECT_FACTORIES
Object result = getObjectInstanceByObjectFactory(o, n, c, h);
if (null != result) {
return result;
}
// all failed, just return o
return o;
}
private static Object getObjectInstanceByObjectFactory(Object o, Name n,
Context c, Hashtable<?, ?> h) throws NamingException, Exception {
// obtain object factories from hashtable and service provider resource
// file
String fnames[] = EnvironmentReader
.getFactoryNamesFromEnvironmentAndProviderResource(h, c,
Context.OBJECT_FACTORIES);
for (String element : fnames) {
// new factory instance by its class name
ObjectFactory factory = null;
try {
factory = (ObjectFactory) classForName(element).newInstance();
} catch (Exception e) {
continue;
}
// create object using factory
Object obj = factory.getObjectInstance(o, n, c, h);
if (null != obj) {
return obj;
}
}
// no object factory succeeded, return null
return null;
}
private static Object getObjectInstanceByUrlRefAddr(Name n, Context c,
Hashtable<?, ?> h, Reference ref) throws NamingException {
// obtain pkg prefixes from hashtable and service provider resource file
String pkgPrefixes[] = EnvironmentReader
.getFactoryNamesFromEnvironmentAndProviderResource(h, c,
Context.URL_PKG_PREFIXES);
// for each RefAddr
Enumeration<RefAddr> enumeration = ref.getAll();
while (enumeration.hasMoreElements()) {
RefAddr addr = enumeration.nextElement();
// if it is StringRefAddr and type is URL
if (addr instanceof StringRefAddr
&& addr.getType().equalsIgnoreCase("URL")) { //$NON-NLS-1$
// get the url address
String url = (String) ((StringRefAddr) addr).getContent();
// try create using url context factory
Object obj = getObjectInstanceByUrlContextFactory(url, n, c, h,
pkgPrefixes, UrlParser.getScheme(url));
// if success, return the created obj
if (null != obj) {
return obj;
}
}
}
// failed to create using any StringRefAddr of address type URL, return
// null
return null;
}
private static Object getObjectInstanceByUrlContextFactory(String url,
Name n, Context c, Hashtable<?, ?> h, String pkgPrefixes[],
String schema) throws NamingException {
// if schema is empty or null, fail, return null
if (null == schema || 0 == schema.length()) {
return null;
}
for (String element : pkgPrefixes) {
ObjectFactory factory = null;
try {
// create url context factory instance
String clsName = element + "." //$NON-NLS-1$
+ schema + "." //$NON-NLS-1$
+ schema + "URLContextFactory"; //$NON-NLS-1$
factory = (ObjectFactory) classForName(clsName).newInstance();
} catch (Exception e) {
// failed to create factory, continue trying
continue;
}
try {
// create obj using url context factory
Object obj = factory.getObjectInstance(url, n, c, h);
// if create success, return it
if (null != obj) {
return obj;
}
} catch (Exception e) {
// throw NamingException, if factory fails
if (e instanceof NamingException) {
throw (NamingException) e;
}
// jndi.21=Failed to create object instance
NamingException nex = new NamingException(Messages
.getString("jndi.21")); //$NON-NLS-1$
nex.setRootCause(e);
throw nex;
}
}
// fail to create using url context factory, return null
return null;
}
private static Object getObjectInstanceByFactoryInReference(Reference ref,
Object o, Name n, Context c, Hashtable<?, ?> h) throws Exception {
ObjectFactory factory = null;
// try load the factory by its class name
try {
factory = (ObjectFactory) classForName(ref.getFactoryClassName())
.newInstance();
} catch (ClassNotFoundException e) {
// Ignore.
}
// try load the factory from its class location
if (null == factory && null != ref.getFactoryClassLocation()) {
factory = (ObjectFactory) loadFactoryFromLocation(ref
.getFactoryClassName(), ref.getFactoryClassLocation());
}
// if factory cannot be loaded
if (null == factory) {
// return o
return o;
}
// get object instance using the factory and return it
return factory.getObjectInstance(ref, n, c, h);
}
/*
* If cannot load class, return null. Throws any exceptions except
* ClassNotFoundException
*/
private static Object loadFactoryFromLocation(String clsName,
String location) throws Exception {
// convert location into an array of URL, separated by ' '
StringTokenizer st = new StringTokenizer(location, " "); //$NON-NLS-1$
URL urls[] = new URL[st.countTokens()];
for (int i = 0; i < urls.length; i++) {
urls[i] = new URL(st.nextToken());
}
// new a URLClassLoader from the URLs
URLClassLoader l = new URLClassLoader(urls);
// try load factory by URLClassLoader
try {
// return the new instance
return l.loadClass(clsName).newInstance();
} catch (ClassNotFoundException e) {
// return null if class loading failed
return null;
}
}
/**
* Get the state of an Object.
* <p>
* The <code>Context.STATE_FACTORIES</code> property from the
* <code>Hashtable h</code> together with the
* <code>Context.STATE_FACTORIES</code> property from the provider
* resource file of the <code>Context c</code> provides the list of
* factories tried to get an object's state.
* </p>
* <p>
* Each factory in the list is attempted to be loaded using the context
* class loader. Once a class is loaded then it can be used to create a new
* instance of it to obtain the factory which can then use its
* <code>getStateToBind</code> to find the return object. Once an object
* is found then it is not necessary to examine further factories and the
* object is returned it as the return parameter.
* </p>
* <p>
* If no factory is loaded or all loaded factories fail to return an object
* then return the supplied <code>Object o</code> as the return param.
* </p>
* <p>
* Note for service provider implementors: Classes which implement the
* <code>StateFactory</code> interface must be public with a public
* constructor that has no parameters.
* </p>
*
* @param o
* an object which may provide reference or location information.
* May be null.
* @param n
* the name of the <code>Object</code> relative to the default
* initial context (or relative to the Context c if it is
* supplied)
* @param c
* the <code>Context</code> to which the <code>Name</code> is
* relative
* @param h
* a <code>Hashtable</code> containing environment properties
* and values - may be null
* @return the state of the specified object
* @throws NamingException
* if one is encountered
*/
public static Object getStateToBind(Object o, Name n, Context c,
Hashtable<?, ?> h) throws NamingException {
// obtain state factories from hashtable and service provider resource
// file
String fnames[] = EnvironmentReader
.getFactoryNamesFromEnvironmentAndProviderResource(h, c,
Context.STATE_FACTORIES);
for (String element : fnames) {
// new factory instance by its class name
StateFactory factory = null;
try {
factory = (StateFactory) classForName(element).newInstance();
} catch (Exception e) {
continue;
}
// try obtain state using the factory
Object state = factory.getStateToBind(o, n, c, h);
// if a state obtained successfully, return it
if (null != state) {
return state;
}
}
// all factories failed, return the input argument o
return o;
}
/**
* Creates a URL <code>Context</code> which can subsequently be used to
* resolve any URLs with the URL scheme s. A <code>URLContextFactory</code>
* is a type of <code>ObjectFactory</code> used to create a
* <code>URLContext</code> when <code>getObjectInstance</code> is
* invoked with the <code>Object o</code> set to null (see the description
* of <code>ObjectFactory</code>).
* <p>
* This <code>getURLContext</code> method tries to locate the
* <code>URLContextFactory</code> based on the
* <code>Context.URL_PKG_PREFIXES</code> property which contains the
* prefixes to be tried as the start of the package name. (See
* <code>Context</code>).
* </p>
* <p>
* Each package prefix entry (and finally the default value) are tried to
* find the class which can be used to create the Context.
* </p>
* <p>
* A full class name is derived as
* <code>packageprefix.s.sURLContextFactory</code> where <code>s</code>
* is the scheme.
* </p>
* <p>
* For example if a scheme is abc and the package prefix to try is com.ibm
* then the factory class to try is
* <code>com.ibm.abc.abcURLContextFactory</code>. Once a factory is
* created then a <code>Context</code> is created using the special use of
* <code>ObjectFactory.getObjectInstance</code>.
* </p>
* <p>
* Once a first factory is created, it is used to create the context, and NO
* further attempts will be made on other pkg prefixes.
* </p>
*
* @param schema
* the URL scheme to which the Context will relate
* @param envmt
* a <code>Hashtable</code> containing environment properties
* and values - may be null
* @return the URL <code>Context</code> or null if no
* <code>URLContextFactory</code> instance can be created and
* therefore a Context cannot be created.
* @throws NamingException
* if one is encountered.
*/
public static Context getURLContext(String schema, Hashtable<?, ?> envmt)
throws NamingException {
if (null == schema || 0 == schema.length()) {
return null;
}
// obtain pkg prefixes from hashtable
String pkgPrefixes[] = EnvironmentReader
.getFactoryNamesFromEnvironmentAndProviderResource(envmt, null,
Context.URL_PKG_PREFIXES);
for (String element : pkgPrefixes) {
// create factory instance
ObjectFactory factory;
try {
String clsName = element + "." //$NON-NLS-1$
+ schema + "." //$NON-NLS-1$
+ schema + "URLContextFactory"; //$NON-NLS-1$
factory = (ObjectFactory) classForName(clsName).newInstance();
} catch (Exception ex) {
// fail to create factory, continue to try another
continue;
}
try {
// create url context using the factory, and return it
return (Context) factory.getObjectInstance(null, null, null,
envmt);
} catch (NamingException e) {
// find NamingException, throw it
throw e;
} catch (Exception e) {
// other exception, throw as NamingException
// jndi.22=other exception happens: {0}
NamingException nex = new NamingException(Messages.getString(
"jndi.22", e.toString())); //$NON-NLS-1$
nex.setRootCause(e);
throw nex;
}
}
// cannot create context instance from any pkg prefixes, return null
return null;
}
/**
* Create the next context when using federation. All the information
* required to do this is contained in the
* <code>CannotProceedException</code> <code>e</code>. If the resolved
* object is null then throw the supplied
* <code>CannotProceedException</code> <code>e</code> using the stack
* details from this thread. The resolved object in <code>e</code> may
* already be a <code>Context</code>. This is the case where the service
* provider gives an explicit pointer to the next naming system. A Context
* object is returned as the continuation context, but need not be the same
* object instance as the resolved object.
* <p>
* If the resolved object is not already a <code>Context</code> then it is
* necessary to use the resolved object together with the
* <code>altName</code> name, the <code>altNameCtx</code> context and
* the environment hashtable to get an instance of the object. This should
* then be a context which is returned as the continuation context. If an
* instance cannot be obtained then throw the supplied
* <code>CannotProceedException</code> using the stack details from this
* thread.
* </p>
* <p>
* This method is responsible for setting the property denoted by the
* <code>CPE</code> string to be the supplied
* <code>CannotProceedException</code> for the exception <code>e</code>
* environment. The continuation context should then inherit this property.
* </p>
*
* @param cpe
* the <code>CannotProceedException</code> generated by the
* context of the previous naming system when it can proceed no
* further.
* @return the next Context when using federation
* @throws NamingException
* if the resolved object is null or if a context cannot be
* obtained from it either directly or indirectly.
*/
@SuppressWarnings("unchecked")
public static Context getContinuationContext(CannotProceedException cpe)
throws NamingException {
Context ctx = null;
// set CPE property of the env
if (cpe.getEnvironment() == null) {
cpe.setEnvironment(new Hashtable<String, CannotProceedException>());
}
((Hashtable<String, CannotProceedException>) cpe.getEnvironment()).put(
CPE, cpe);
// if resolved object is null
if (null == cpe.getResolvedObj()) {
// re-throw cpe
cpe.fillInStackTrace();
throw cpe;
}
// if cpe's resolved obj is Context
if (cpe.getResolvedObj() instanceof Context) {
// accept it as the continuation context
ctx = (Context) cpe.getResolvedObj();
} else {
// otherwise, call getObjectInstance() to get a context instance
try {
ctx = (Context) getObjectInstance(cpe.getResolvedObj(), cpe
.getAltName(), cpe.getAltNameCtx(), cpe
.getEnvironment());
} catch (Exception ex) {
// throw back CPE in case of any exception
throw cpe;
}
// if ctx cannot be obtained
if (null == ctx) {
// re-throw CPE
cpe.fillInStackTrace();
throw cpe;
}
}
// return the continuation context
return ctx;
}
private static Class<?> classForName(final String className)
throws ClassNotFoundException {
Class<?> cls = AccessController
.doPrivileged(new PrivilegedAction<Class<?>>() {
public Class<?> run() {
// try thread context class loader first
try {
return Class.forName(className, true, Thread
.currentThread().getContextClassLoader());
} catch (ClassNotFoundException e) {
// Ignored.
}
// try system class loader second
try {
return Class.forName(className, true, ClassLoader
.getSystemClassLoader());
} catch (ClassNotFoundException e1) {
// Ignored.
}
// return null, if fail to load class
return null;
}
});
if (cls == null) {
// jndi.1C=class {0} not found
throw new ClassNotFoundException(Messages.getString(
"jndi.1C", className)); //$NON-NLS-1$
}
return cls;
}
}