| MockContext.java |
package org.mockejb.jndi;
import java.util.*;
import javax.naming.*;
import org.apache.commons.logging.*;
import org.mockejb.MethodNotImplementedException;
/**
* Provides implementation of <code>javax.naming.Context</code> interface for
* hierarchical in memory single-namespace naming system.
* A name in the <code>MockContext</code> namespace is a sequence of one or more
* atomic names, relative to a root initial context.
* When a name consist of more than one atomic names it is a <code>CompoundName</code>
* where atomic names are separated with separator character - '/' or '.'.
* It is possible to use both separator characters in the same name. In such cases
* any occurrences of '.' are replaced with '/' before parsing.
* <p>
* Leading and terminal components of a <code>CompoundName</code> can not be empty -
* for example "name1/name2/name3" is a valid name, while the following names are
* not valid - "/name1/name2/name3", "name1/name2/name3/", "/name1/name2/name3/".
* If such name is passed, all empty leading/terminal components will be removed
* before the name is actually used (this will not affect the original value) -
* from the above three examples the actual name will be "name1/name2/name3".
* If a name contains intermediate empty components (for example "a//b") then
* <code>InvalidNameException</code> will be thrown.
* <p>
* Composite names (instances of <code>CompositeName</code>) must contain zero or one
* component from the <code>MockContext</code> namespace.
* <p>
* The namespace of <code>MockContext</code> can be represented as a tree of atomic names.
* Each name is bound to an instance of MockContext (subcontext) or to an arbitrary object.
* Each subcontext has collection of names bound to other subcontexts or arbitrary objects.
* <p>
* When instance of <code>Name</code> is used as parameter to any of the
* MockContext methods, if the object is not <code>CompositeName</code> then
* it is assumed that it is <code>CompoundName</code>
* <p>
* Example:
* <pre><code>
* myContext = initialContext.lookup("foo");
* myObject = myContext.lookup("bar");
* </code>
* is equivalent to
* <code>myObject = initialContext.lookup("foo/bar");</code>
* </pre>
* <p>
* Instances of <code>MockContext</code> are created only through
* <code>MockContextFactory</code>, when <code>InitialContext</code> is instantiated.
* <p>
* If a remote context is provided, this class will search in that remote context if the
* object is not found locally.
* <p>
* For overloaded methods that accept name as <code>String</code> or
* <code>Name</code> only the version for <code>Name</code> is documented.
* The <code>String</code> version creates <code>CompoundName</code>, from
* the string name passed as parameter, and calls the <code>Name</code> version of
* the same method.
*
* @author Alexander Ananiev
* @author Dimitar Gospodinov
*/
public class MockContext implements Context {
private static final String ROOT_CONTEXT_NAME = "ROOT";
// MockContext supports single naming scheme and all instances use the same parser.
private static final NameParser nameParser = new MockContextNameParser();
// logger for this class
private static Log logger =
LogFactory.getLog(MockContext.class.getName());
/**
* Map of objects registered for this context representing the local context
*/
private final Map objects = Collections.synchronizedMap(new HashMap());
// Parent Context of this Context
private MockContext parent;
// Atomic name of this Context
private String contextName;
/**
* Container context used for delegated lookups
*/
private Context remoteContext;
// Shows if this context has been destroyed
private boolean isDestroyed;
/**
* Creates a new instance of the context.
* This class can only be instantiated by its factory.
* @param remoteContext remote context that MockContext will
* delegate to if it fails to lookup an object locally
*/
protected MockContext( Context remoteContext ) {
this(remoteContext, null, ROOT_CONTEXT_NAME);
}
/**
* Creates new instance of <code>MockContext</code>.
* @param remoteContext remote context that MockContext will
* delegate to if it fails to lookup an object locally
* @param parent parent context of this context. <code>null</code> if
* this is the root context.
* @param name atomic name for this context
*/
private MockContext(Context remoteContext, MockContext parent, String name) {
this.remoteContext = remoteContext;
this.parent = parent;
this.contextName = name;
this.isDestroyed = false;
}
/**
* Not implemented
* @see javax.naming.Context#addToEnvironment(java.lang.String, java.lang.Object)
*/
public Object addToEnvironment(String arg0, Object arg1)
throws NamingException {
throwMethodNotImplemented("addToEnvironment( String, Object)");
return null;
}
/**
* Binds object <code>obj</code> to name <code>name</code> in this context.
* Intermediate contexts that do not exist will be created.
*
* @param name name of the object to bind
* @param obj object to bind. Can be <code>null</code>.
*
* @throws NoPermissionException if this context has been destroyed
* @throws InvalidNameException if <code>name</code> is empty or is
* <code>CompositeName</code> that spans more than one naming system
* @throws NotContextException if <code>name</code> has more than one
* atomic name and intermediate atomic name is bound to object that is
* not context.
*
* @see javax.naming.Context#bind(javax.naming.Name, java.lang.Object)
*/
public void bind(Name name, Object obj) throws NamingException {
checkIsDestroyed();
// Do not check for already bound name. Simply replace the existing value.
rebind(name, obj);
}
/**
* Binds object <code>obj</code> to name <code>name</code> in this context.
* @param name name of the object to add
* @param obj object to bind
* @throws NamingException if naming error occurs
* @see #bind(Name, Object)
*/
public void bind(String name, Object obj) throws NamingException {
bind(nameParser.parse(name), obj);
}
/**
* Does nothing.
*/
public void close() throws NamingException {
}
/**
* Returns composition of <code>prefix</code> and <code>name</code>.
* @param name name relative to this context
* @param prefix name of this context
* @see javax.naming.Context#composeName(javax.naming.Name, javax.naming.Name)
*/
public Name composeName( Name name, Name prefix ) throws NamingException {
checkIsDestroyed();
/*
* We do not want to modify any of the parameters (JNDI requirement).
* Clone <code>prefix</code> to satisfy the requirement.
*/
Name parsedPrefix = getParsedName((Name)prefix.clone());
Name parsedName = getParsedName(name);
return parsedPrefix.addAll(parsedName);
}
/**
* Composes the name of this context with a name relative to this context.
* Given a name (name) relative to this context, and the name (prefix)
* of this context relative to one of its ancestors, this method returns
* the composition of the two names using the syntax appropriate for
* the naming system(s) involved.
* Example:
* composeName("a","b") b/a
* composeName("a","") a
* @param name name relative to this context
* @param prefix name of this context
* @see javax.naming.Context#composeName(java.lang.String, java.lang.String)
*/
public String composeName( String name, String prefix )
throws NamingException {
checkIsDestroyed();
return composeName(nameParser.parse(name), nameParser.parse(prefix)).toString();
}
/**
* Creates subcontext with name <code>name</code>, relative to this Context.
* @param name subcontext name.
* @return new subcontext named <code>name</code> relative to this context
* @throws NoPermissionException if this context has been destroyed
* @throws InvalidNameException if <code>name</code> is empty or is
* <code>CompositeName</code> that spans more than one naming system
* @throws NameAlreadyBoundException if <code>name</code> is already bound in this Context
* @throws NotContextException if any intermediate name from <code>name</code>
* is not bound to instance of <code>javax.naming.Context</code>
* @see javax.naming.Context#createSubcontext(javax.naming.Name)
*/
public Context createSubcontext(Name name)
throws NamingException {
checkIsDestroyed();
Name parsedName = getParsedName(name);
if (parsedName.size() == 0 || parsedName.get(0).length() == 0) {
throw new InvalidNameException("Name can not be empty!");
}
String subContextName = parsedName.get(0);
Object boundObject = objects.get(parsedName.get(0));
if (parsedName.size() == 1) {
// Check if <code>name</code> is already in use
if (boundObject == null) {
Context subContext =
new MockContext(remoteContext, this, subContextName);
objects.put(subContextName, subContext);
return subContext;
} else {
throw new NameAlreadyBoundException(
"Name " + subContextName + " is already bound!");
}
} else {
if (boundObject instanceof Context) {
// Let the subcontext create new subcontext
return ((Context) boundObject).createSubcontext(
parsedName.getSuffix(1));
} else {
throw new NotContextException(
"Expected Context but found " + boundObject);
}
}
}
/**
* Creates subcontext with name <code>name</code>, relative to this Context.
* @param name subcontext name
* @return new subcontext named <code>name</code> relative to this context
* @throws NamingException if naming error occurs
* @see #createSubcontext(javax.naming.Name)
*/
public Context createSubcontext(String name) throws NamingException {
return createSubcontext(nameParser.parse(name));
}
/**
* Destroys subcontext with name <code>name</code>
* The subcontext must be empty otherwise <code>ContextNotEmptyException</code>
* is thrown.
* <p>
* Once a context is destroyed, the instance should not be used.
* @param name subcontext to destroy
* @throws NoPermissionException if this context has been destroyed
* @throws InvalidNameException if <code>name</code> is empty or is
* <code>CompositeName</code> that spans more than one naming system
* @throws ContextNotEmptyException if Context <code>name</code> is not empty
* @throws NameNotFoundException if subcontext with name <code>name</code> can not be found
* @throws NotContextException if <code>name</code> is not bound to instance of
* <code>MockContext</code>
* @see javax.naming.Context#destroySubcontext(javax.naming.Name)
*/
public void destroySubcontext(Name name) throws NamingException {
checkIsDestroyed();
Name parsedName = getParsedName(name);
if (parsedName.size() == 0 || parsedName.get(0).length() == 0) {
throw new InvalidNameException("Name can not be empty!");
}
String subContextName = parsedName.get(0);
Object boundObject = objects.get(subContextName);
if (boundObject == null) {
throw new NameNotFoundException(
"Name " + subContextName + "not found in the context!");
}
if (!(boundObject instanceof MockContext)) {
throw new NotContextException();
}
MockContext contextToDestroy = (MockContext) boundObject;
if (parsedName.size() == 1) {
/*
* Check if the Context to be destroyed is empty.
* Can not destroy non-empty Context.
*/
if (contextToDestroy.objects.size() == 0) {
objects.remove(subContextName);
contextToDestroy.destroyInternal();
} else {
throw new ContextNotEmptyException("Can not destroy non-empty Context!");
}
} else {
// Let the subcontext destroy the context
((MockContext) boundObject).destroySubcontext(
parsedName.getSuffix(1));
}
}
/**
* Destroys subcontext with name <code>name</code>
* @param name name of subcontext to destroy
* @throws NamingException if naming error occurs
* @see #destroySubcontext(javax.naming.Name)
*/
public void destroySubcontext(String name) throws NamingException {
destroySubcontext(nameParser.parse(name));
}
/**
* Not implemented
* @see javax.naming.Context#getEnvironment()
*/
public Hashtable getEnvironment() throws NamingException {
throwMethodNotImplemented("getEnvironment()");
return null;
}
/**
* Not implemented
* @see javax.naming.Context#getNameInNamespace()
*/
public String getNameInNamespace() throws NamingException {
throwMethodNotImplemented("getNameInNamespace()");
return null;
}
/**
* Retrieves name parser used to parse context with name <code>name</code>.
* @param name context name
* @return <code>NameParser</code>
* @throws NoPermissionException if this context has been destroyed
* @throws NamingException if any other naming error occurs
* @see javax.naming.Context#getNameParser(javax.naming.Name)
*/
public NameParser getNameParser(Name name) throws NamingException {
checkIsDestroyed();
return nameParser;
}
/**
* Retrieves name parser used to parse context with name <code>name</code>.
* @param name context name
* @return <code>NameParser</code>
* @throws NamingException if naming error occurs
* @see #getNameParser(javax.naming.Name)
*/
public NameParser getNameParser(String name) throws NamingException {
checkIsDestroyed();
return nameParser;
}
/**
* The same as <code>listBindings(String)</code>
* @param name name of Context, relative to this Context
* @return <code>NamingEnumeration</code> of all name-class pairs. Each
* element from the enumeration is instance of <code>NameClassPair</code>
* @throws NamingException if naming error occurs
* @see #listBindings(javax.naming.Name)
*/
public NamingEnumeration list(Name name) throws NamingException {
return listBindings(name);
}
/**
* The same as <code>listBindings(String)</code>
* @param name name of Context, relative to this Context
* @return <code>NamingEnumeration</code> of all name-class pairs. Each
* element from the enumeration is instance of <code>NameClassPair</code>
* @throws NamingException if naming error occurs
* @see #listBindings(java.lang.String)
*/
public NamingEnumeration list(String name) throws NamingException {
return list(nameParser.parse(name));
}
/**
* Lists all bindings for Context with name <code>name</code>.
* If <code>name</code> is empty then this Context is assumed.
* @param name name of Context, relative to this Context
* @return <code>NamingEnumeration</code> of all name-object pairs. Each
* element from the enumeration is instance of <code>Binding</code>
* @throws NoPermissionException if this context has been destroyed
* @throws InvalidNameException if <code>name</code> is <code>CompositeName</code>
* that spans more than one naming system
* @throws NameNotFoundException if <code>name</code> can not be found
* @throws NotContextException component of <code>name</code> is not bound to instance of
* <code>MockContext</code>, when <code>name</code> is not an atomic name
* @throws NamingException if any other naming error occurs
* @see javax.naming.Context#listBindings(javax.naming.Name)
*/
public NamingEnumeration listBindings(Name name) throws NamingException {
checkIsDestroyed();
Name parsedName = getParsedName(name);
if (parsedName.size() == 0) {
Vector bindings = new Vector();
Iterator iterat = objects.keySet().iterator();
while (iterat.hasNext()) {
String bindingName = (String) iterat.next();
bindings.addElement(
new Binding(bindingName, objects.get(bindingName)));
}
return new NamingEnumerationImpl(bindings);
} else {
Object subContext = objects.get(parsedName.get(0));
if (subContext instanceof Context) {
return ((Context) subContext).list(parsedName.getSuffix(1));
}
if (subContext == null
&& !objects.containsKey(parsedName.get(0))) {
throw new NameNotFoundException("Name " + name + " not found");
} else {
throw new NotContextException(
"Expected Context but found " + subContext);
}
}
}
/**
* Lists all bindings for Context with name <code>name</code>.
* If <code>name</code> is empty then this Context is assumed.
* @param name name of Context, relative to this Context
* @return <code>NamingEnumeration</code> of all name-object pairs. Each
* element from the enumeration is instance of <code>Binding</code>
* @throws NamingException if naming error occurs
* @see #listBindings(javax.naming.Name)
*/
public NamingEnumeration listBindings(String name) throws NamingException {
return listBindings(nameParser.parse(name));
}
/**
* Looks up object with name <code>name</code> in this context. If the object is not
* found and the remote context was provided, calls the remote
* context to lookup the object.
* @param name name to look up
* @return object reference bound to name <code>name</code>
* @throws NoPermissionException if this context has been destroyed
* @throws InvalidNameException if <code>name</code> is <code>CompositeName</code>
* that spans more than one naming system
* @throws NameNotFoundException if <code>name</code> can not be found
* @throws NotContextException component of <code>name</code> is not bound to instance of
* <code>MockContext</code>, when <code>name</code> is not atomic name.
* @throws NamingException if any other naming error occurs
* @see javax.naming.Context#lookup(javax.naming.Name)
*/
public Object lookup(Name name) throws NamingException {
checkIsDestroyed();
try {
return lookupInternal(name);
} catch (NameNotFoundException ex) {
// Shall we delegate?
if ( remoteContext != null ) {
logger.debug(
"Was not able to find name "
+ name
+ " in the local context. Will try remote context.");
return remoteContext.lookup(name);
} else {
throw new NameNotFoundException("Name " + name + " not found. ");
}
}
}
private Object lookupInternal(Name name) throws NamingException {
Name parsedName = getParsedName(name);
String nameComponent = parsedName.get(0);
Object res = objects.get( nameComponent );
// if not found
if ( ! objects.containsKey( nameComponent ) ) {
throw new NameNotFoundException("Name " + name + " not found.");
}
// if this is a compound name
else if ( parsedName.size() > 1 ){
if ( res instanceof MockContext) {
res = ((MockContext) res).lookupInternal(parsedName.getSuffix(1));
} else {
throw new NotContextException(
"Expected MockContext but found " + res);
}
}
return res;
}
/**
* Looks up the object in this context. If the object is not
* found and the remote context was provided, calls the remote
* context to lookup the object.
* @param name object to search
* @return object reference bound to name <code>name</code>
* @throws NamingException if naming error occurs
* @see #lookup(javax.naming.Name)
*/
public Object lookup(String name) throws NamingException {
checkIsDestroyed();
try {
return lookupInternal(name);
} catch (NameNotFoundException ex) {
// Shall we delegate?
if ( remoteContext != null ) {
logger.debug(
"Was not able to find name "
+ name
+ " in the local context. Will try remote context.");
return remoteContext.lookup(name);
} else {
throw new NameNotFoundException("Name " + name + " not found. ");
}
}
}
private Object lookupInternal(String name) throws NamingException {
return lookupInternal(nameParser.parse(name));
}
/**
* Not implemented
* @see javax.naming.Context#lookupLink(javax.naming.Name)
*/
public Object lookupLink(Name arg0) throws NamingException {
throwMethodNotImplemented("lookupLink(Name)");
return null;
}
/**
* Not implemented
* @see javax.naming.Context#lookupLink(java.lang.String)
*/
public Object lookupLink(String arg0) throws NamingException {
throwMethodNotImplemented("lookupLink(String)");
return null;
}
/**
* Rebinds object <code>obj</code> to name <code>name</code>.
* If there is existing binding it will be overwritten.
* @param name name of the object to rebind
* @param obj object to add. Can be <code>null</code>
* @throws NoPermissionException if this context has been destroyed
* @throws InvalidNameException if <code>name</code> is empty or is
* <code>CompositeName</code> that spans more than one naming system
* @throws NotContextException if <code>name</code> has more than one
* atomic name and intermediate context is not found
* @throws NamingException if any other naming error occurs
* @see javax.naming.Context#rebind(javax.naming.Name, java.lang.Object)
*/
public void rebind(Name name, Object obj) throws NamingException {
checkIsDestroyed();
Name parsedName = getParsedName(name);
if (parsedName.size() == 0 || parsedName.get(0).length() == 0) {
throw new InvalidNameException("Name can not be empty!");
}
String nameToBind = parsedName.get(0);
if (parsedName.size() == 1) {
objects.put(nameToBind, obj);
logger.debug("Bound object " + obj + " to the name " + nameToBind);
} else {
Object boundObject = objects.get(nameToBind);
if (boundObject instanceof Context) {
/*
* Let the subcontext bind the object.
*/
((Context)boundObject).bind(parsedName.getSuffix(1), obj);
} else {
if (boundObject == null) {
// Create new subcontext and let it do the binding
Context sub = createSubcontext(nameToBind);
sub.bind(parsedName.getSuffix(1), obj);
} else {
throw new NotContextException("Expected Context but found " + boundObject);
}
}
}
}
/**
* Same as bind except that if <code>name</code> is already bound in
* the context, it will be re-bound to object <code>obj</code>
* @param name name of the object to rebind
* @param obj object to add. Can be <code>null</code>
* @throws NamingException if naming error occurs
* @see #rebind(javax.naming.Name, Object)
*/
public void rebind(String name, Object obj) throws NamingException {
rebind(nameParser.parse(name), obj);
}
/**
* Not implemented
* @see javax.naming.Context#removeFromEnvironment(java.lang.String)
*/
public Object removeFromEnvironment(String arg0) throws NamingException {
throwMethodNotImplemented("removeFromEnvironment(String)");
return null;
}
/**
* Not implemented
* @see javax.naming.Context#rename(javax.naming.Name, javax.naming.Name)
*/
public void rename(Name arg0, Name arg1) throws NamingException {
throwMethodNotImplemented("rename(Name arg0, Name arg1)");
}
/**
* Not implemented
* @see javax.naming.Context#rename(java.lang.String, java.lang.String)
*/
public void rename(String arg0, String arg1) throws NamingException {
throwMethodNotImplemented("rename(String arg0, String arg1)");
}
/**
* Removes <code>name</code> and its associated object from the context.
* @param name name to remove
* @throws NoPermissionException if this context has been destroyed
* @throws InvalidNameException if <code>name</code> is empty or is
* <code>CompositeName</code> that spans more than one naming system
* @throws NameNotFoundException if intermediate context can not be found
* @throws NotContextException if <code>name</code> has more than one
* atomic name and intermediate context is not found
* @throws NamingException if any other naming exception occurs
* @see javax.naming.Context#unbind(javax.naming.Name)
*/
public void unbind(Name name) throws NamingException {
checkIsDestroyed();
Name parsedName = getParsedName(name);
if (parsedName.size() == 0 || parsedName.get(0).length() == 0) {
throw new InvalidNameException("Name can not be empty!");
}
String nameToRemove = parsedName.get(0);
if (parsedName.size() == 1) {
objects.remove(nameToRemove);
logger.debug("Unbound name " + name);
} else {
Object boundObject = objects.get(nameToRemove);
if (boundObject instanceof Context) {
/*
* Let the subcontext do the unbind
*/
((Context) boundObject).unbind(parsedName.getSuffix(1));
} else {
if (!objects.containsKey(nameToRemove)) {
throw new NameNotFoundException("Can not find " + name);
}
throw new NotContextException(
"Expected Context but found " + boundObject);
}
}
}
/**
* Removes object from the object map
* @param name object to remove
* @throws NamingException if naming error occurs
* @see #unbind(javax.naming.Name)
*/
public void unbind(String name) throws NamingException {
unbind(nameParser.parse(name));
}
// ** Non-standard methods
/**
* Checks if this context has been destroyed.
* <code>isDestroyed</code> is set to <code>true</code> when a context is
* destroyed by calling <code>destroySubcontext</code> method.
* @throws NoPermissionException if this context has been destroyed
*/
private void checkIsDestroyed() throws NamingException {
if (isDestroyed) {
throw new NoPermissionException("Can not invoke operations on destroyed context!");
}
}
/**
* Marks this context as destroyed.
* Method called only by <code>destroySubcontext</code>.
*/
private void destroyInternal() {
isDestroyed = true;
}
/**
* Parses <code>name</code> which is <code>CompositeName</code> or <code>CompoundName</code>.
* If <code>name</code> is not <code>CompositeName</code> then it is
* assumed to be <code>CompoundName</code>.
* <p>
* If the name contains leading and/or terminal empty components, they will not
* be included in the result.
* @param name <code>Name</code> to parse
* @return parsed name as instance of <code>CompoundName</code>
* @throws InvalidNameException if <code>name</code> is
* <code>CompositeName</code> and spans more than one name space
* @throws NamingException if any other naming exception occurs
*/
private Name getParsedName(Name name) throws NamingException {
Name result = null;
if (name instanceof CompositeName) {
if (name.size() == 0) {
// Return empty CompositeName
result = nameParser.parse("");
} else if (name.size() > 1) {
throw new InvalidNameException("Multiple name systems are not supported!");
}
result = nameParser.parse(name.get(0));
} else {
result = (Name)name.clone();
}
while (!result.isEmpty()) {
if (result.get(0).length() == 0) {
result.remove(0);
continue;
}
if (result.get(result.size()-1).length() == 0) {
result.remove(result.size()-1);
continue;
}
break;
}
// Validate that there are not intermediate empty components.
// Skip first and last element, they are valid
for (int i = 1; i < result.size() - 1; i++) {
if (result.get(i).length() == 0) {
throw new InvalidNameException("Empty intermediate components are not supported!");
}
}
return result;
}
/**
* Returns the compound string name of this context.
* Suppose a/b/c/d is the full name and this context is "c".
* It's compound string name is a/b/c
*
* @return compound string name of the context
*/
String getCompoundStringName() throws NamingException {
//StringBuffer compositeName = new StringBuffer();
String compositeName="";
MockContext curCtx = this;
while( ! curCtx.isRootContext() ) {
compositeName = composeName( compositeName, curCtx.getAtomicName() );
curCtx = curCtx.getParentContext();
}
return compositeName;
}
/**
* Returns parent context of this context
*/
MockContext getParentContext() {
return parent;
}
/**
* Returns the "atomic" (as opposed to "composite") name of the context.
* @return name of the context
*/
String getAtomicName(){
return contextName;
}
/**
* Returns true if this context is the root context
* @return true if the context is the root context
*/
boolean isRootContext(){
return getParentContext() == null;
}
private void throwMethodNotImplemented(String methodName) {
throw new MethodNotImplementedException(
methodName,
this.getClass().getName());
}
private class NamingEnumerationImpl implements NamingEnumeration {
private Vector elements;
private int currentElement;
NamingEnumerationImpl(Vector elements) {
this.elements = elements;
this.currentElement = 0;
}
public void close() {
currentElement = 0;
elements.clear();
}
public boolean hasMore() {
return hasMoreElements();
}
public boolean hasMoreElements() {
if (currentElement < elements.size()) {
return true;
}
close();
return false;
}
public Object next() {
return nextElement();
}
public Object nextElement() {
if (hasMoreElements()) {
return elements.get(currentElement++);
}
throw new NoSuchElementException();
}
}
}