org.jvnet.hk2.internal.SystemDescriptor Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of jersey-all Show documentation
Show all versions of jersey-all Show documentation
jersey-all is a rebundled verison of Jersey as one OSGi bundle.
The newest version!
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright (c) 2012-2015 Oracle and/or its affiliates. All rights reserved.
*
* The contents of this file are subject to the terms of either the GNU
* General Public License Version 2 only ("GPL") or the Common Development
* and Distribution License("CDDL") (collectively, the "License"). You
* may not use this file except in compliance with the License. You can
* obtain a copy of the License at
* https://glassfish.dev.java.net/public/CDDL+GPL_1_1.html
* or packager/legal/LICENSE.txt. See the License for the specific
* language governing permissions and limitations under the License.
*
* When distributing the software, include this License Header Notice in each
* file and include the License file at packager/legal/LICENSE.txt.
*
* GPL Classpath Exception:
* Oracle designates this particular file as subject to the "Classpath"
* exception as provided by Oracle in the GPL Version 2 section of the License
* file that accompanied this code.
*
* Modifications:
* If applicable, add the following below the License Header, with the fields
* enclosed by brackets [] replaced by your own identifying information:
* "Portions Copyright [year] [name of copyright owner]"
*
* Contributor(s):
* If you wish your version of this file to be governed by only the CDDL or
* only the GPL Version 2, indicate your decision by adding "[Contributor]
* elects to include this software in this distribution under the [CDDL or GPL
* Version 2] license." If you don't indicate a single choice of license, a
* recipient has the option to distribute your version of this file under
* either the CDDL, the GPL Version 2 or to extend the choice of license to
* its licensees as provided above. However, if you add GPL Version 2 code
* and therefore, elected the GPL Version 2 license, then the option applies
* only if the new code is made subject to such option by the copyright
* holder.
*/
package org.jvnet.hk2.internal;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.glassfish.hk2.api.ActiveDescriptor;
import org.glassfish.hk2.api.Descriptor;
import org.glassfish.hk2.api.DescriptorType;
import org.glassfish.hk2.api.DescriptorVisibility;
import org.glassfish.hk2.api.ErrorInformation;
import org.glassfish.hk2.api.ErrorService;
import org.glassfish.hk2.api.ErrorType;
import org.glassfish.hk2.api.Factory;
import org.glassfish.hk2.api.Filter;
import org.glassfish.hk2.api.HK2Loader;
import org.glassfish.hk2.api.IndexedFilter;
import org.glassfish.hk2.api.Injectee;
import org.glassfish.hk2.api.InstanceLifecycleEvent;
import org.glassfish.hk2.api.InstanceLifecycleEventType;
import org.glassfish.hk2.api.InstanceLifecycleListener;
import org.glassfish.hk2.api.MultiException;
import org.glassfish.hk2.api.PerLookup;
import org.glassfish.hk2.api.Proxiable;
import org.glassfish.hk2.api.ServiceHandle;
import org.glassfish.hk2.api.Unproxiable;
import org.glassfish.hk2.api.ValidationService;
import org.glassfish.hk2.utilities.BuilderHelper;
import org.glassfish.hk2.utilities.DescriptorImpl;
import org.glassfish.hk2.utilities.reflection.ParameterizedTypeImpl;
import org.glassfish.hk2.utilities.reflection.Pretty;
import org.glassfish.hk2.utilities.reflection.ReflectionHelper;
import org.glassfish.hk2.utilities.reflection.ScopeInfo;
/**
* @author jwells
* @param The type from the cache
*/
public class SystemDescriptor implements ActiveDescriptor, Closeable {
private final Descriptor baseDescriptor;
private final Long id;
private final ActiveDescriptor activeDescriptor;
private final ServiceLocatorImpl sdLocator;
private volatile boolean reified;
private boolean reifying = false; // Am I currently reifying
private boolean preAnalyzed = false;
private volatile boolean closed = false;
private final Object cacheLock = new Object();
private boolean cacheSet = false;
private T cachedValue;
// These are used when we are doing the reifying ourselves
private Class implClass;
private Annotation scopeAnnotation;
private Class scope;
private Set contracts;
private Set qualifiers;
private Creator creator;
private Long factoryLocatorId;
private Long factoryServiceId;
private final HashMap validationServiceCache =
new HashMap();
private final List instanceListeners =
new LinkedList();
private final Set myLists = new HashSet();
private int singletonGeneration = Integer.MAX_VALUE;
/* package */ @SuppressWarnings("unchecked")
SystemDescriptor(Descriptor baseDescriptor, boolean requiresDeepCopy, ServiceLocatorImpl locator, Long serviceId) {
if (requiresDeepCopy) {
this.baseDescriptor = BuilderHelper.deepCopyDescriptor(baseDescriptor);
}
else {
this.baseDescriptor = baseDescriptor;
}
this.sdLocator = locator;
this.id = serviceId;
if (baseDescriptor instanceof ActiveDescriptor) {
ActiveDescriptor active = (ActiveDescriptor) baseDescriptor;
if (active.isReified()) {
activeDescriptor = active;
reified = true;
if (active instanceof AutoActiveDescriptor) {
((AutoActiveDescriptor) active).setHK2Parent(this);
}
}
else {
activeDescriptor = null;
preAnalyzed = true;
implClass = active.getImplementationClass();
scopeAnnotation = active.getScopeAsAnnotation();
scope = active.getScopeAnnotation();
contracts = Collections.unmodifiableSet(active.getContractTypes());
qualifiers = Collections.unmodifiableSet(active.getQualifierAnnotations());
}
}
else {
activeDescriptor = null;
}
}
/* (non-Javadoc)
* @see org.glassfish.hk2.api.Descriptor#getImplementation()
*/
@Override
public String getImplementation() {
return baseDescriptor.getImplementation();
}
/* (non-Javadoc)
* @see org.glassfish.hk2.api.Descriptor#getAdvertisedContracts()
*/
@Override
public Set getAdvertisedContracts() {
return baseDescriptor.getAdvertisedContracts();
}
/* (non-Javadoc)
* @see org.glassfish.hk2.api.Descriptor#getScope()
*/
@Override
public String getScope() {
return baseDescriptor.getScope();
}
/* (non-Javadoc)
* @see org.glassfish.hk2.api.Descriptor#getNames()
*/
@Override
public String getName() {
return baseDescriptor.getName();
}
/* (non-Javadoc)
* @see org.glassfish.hk2.api.Descriptor#getQualifiers()
*/
@Override
public Set getQualifiers() {
return baseDescriptor.getQualifiers();
}
/* (non-Javadoc)
* @see org.glassfish.hk2.api.Descriptor#getDescriptorType()
*/
@Override
public DescriptorType getDescriptorType() {
return baseDescriptor.getDescriptorType();
}
/* (non-Javadoc)
* @see org.glassfish.hk2.api.Descriptor#getDescriptorType()
*/
@Override
public DescriptorVisibility getDescriptorVisibility() {
return baseDescriptor.getDescriptorVisibility();
}
/* (non-Javadoc)
* @see org.glassfish.hk2.api.Descriptor#getMetadata()
*/
@Override
public Map> getMetadata() {
return baseDescriptor.getMetadata();
}
/* (non-Javadoc)
* @see org.glassfish.hk2.api.Descriptor#getLoader()
*/
@Override
public HK2Loader getLoader() {
return baseDescriptor.getLoader();
}
/* (non-Javadoc)
* @see org.glassfish.hk2.api.Descriptor#getRanking()
*/
@Override
public int getRanking() {
return baseDescriptor.getRanking();
}
/* (non-Javadoc)
* @see org.glassfish.hk2.api.Descriptor#isProxiable()
*/
@Override
public Boolean isProxiable() {
return baseDescriptor.isProxiable();
}
/* (non-Javadoc)
* @see org.glassfish.hk2.api.Descriptor#isProxyForSameScope()
*/
@Override
public Boolean isProxyForSameScope() {
return baseDescriptor.isProxyForSameScope();
}
@Override
public String getClassAnalysisName() {
return baseDescriptor.getClassAnalysisName();
}
/* (non-Javadoc)
* @see org.glassfish.hk2.api.Descriptor#setRanking(int)
*/
@Override
public int setRanking(int ranking) {
// do NOT change this without the write lock, can cause
// all sorts of problems with ConcurrentModificationExceptions
return sdLocator.unsortIndexes(ranking, this, myLists);
}
/* package */ int setRankWithLock(int ranking) {
return baseDescriptor.setRanking(ranking);
}
/* package */ void addList(IndexedListData indexedList) {
myLists.add(indexedList);
}
/* package */ void removeList(IndexedListData indexedList) {
myLists.remove(indexedList);
}
/* (non-Javadoc)
* @see org.glassfish.hk2.api.Descriptor#getServiceId()
*/
@Override
public Long getServiceId() {
return id;
}
/* (non-Javadoc)
* @see org.glassfish.hk2.api.SingleCache#getCache()
*/
@Override
public T getCache() {
return cachedValue;
}
/* (non-Javadoc)
* @see org.glassfish.hk2.api.SingleCache#isCacheSet()
*/
@Override
public boolean isCacheSet() {
return cacheSet;
}
/* (non-Javadoc)
* @see org.glassfish.hk2.api.SingleCache#setCache(java.lang.Object)
*/
@Override
public void setCache(T cacheMe) {
synchronized (cacheLock) {
cachedValue = cacheMe;
cacheSet = true;
}
}
/* (non-Javadoc)
* @see org.glassfish.hk2.api.SingleCache#releaseCache()
*/
@Override
public void releaseCache() {
synchronized (cacheLock) {
cacheSet = false;
cachedValue = null;
}
}
/* (non-Javadoc)
* @see org.glassfish.hk2.api.ActiveDescriptor#isReified()
*/
@Override
public boolean isReified() {
// This is safe because once a descriptor is
// reified it is never un-reified
if (reified) return true;
synchronized (this) {
return reified;
}
}
/* (non-Javadoc)
* @see org.glassfish.hk2.api.ActiveDescriptor#getImplementationClass()
*/
@Override
public Class getImplementationClass() {
checkState();
if (activeDescriptor != null) {
return activeDescriptor.getImplementationClass();
}
return implClass;
}
/* (non-Javadoc)
* @see org.glassfish.hk2.api.ActiveDescriptor#getContractTypes()
*/
@Override
public Set getContractTypes() {
checkState();
if (activeDescriptor != null) {
return activeDescriptor.getContractTypes();
}
return contracts;
}
@Override
public Annotation getScopeAsAnnotation() {
checkState();
return scopeAnnotation;
}
/* (non-Javadoc)
* @see org.glassfish.hk2.api.ActiveDescriptor#getScopeAnnotation()
*/
@Override
public Class getScopeAnnotation() {
checkState();
if (activeDescriptor != null) {
return activeDescriptor.getScopeAnnotation();
}
return scope;
}
/* (non-Javadoc)
* @see org.glassfish.hk2.api.ActiveDescriptor#getQualifierAnnotations()
*/
@Override
public Set getQualifierAnnotations() {
checkState();
if (activeDescriptor != null) {
return activeDescriptor.getQualifierAnnotations();
}
return qualifiers;
}
/* (non-Javadoc)
* @see org.glassfish.hk2.api.ActiveDescriptor#getInjectees()
*/
@Override
public List getInjectees() {
checkState();
if (activeDescriptor != null) {
return activeDescriptor.getInjectees();
}
return creator.getInjectees();
}
public Long getFactoryServiceId() {
if (activeDescriptor != null) {
return activeDescriptor.getFactoryServiceId();
}
return factoryServiceId;
}
public Long getFactoryLocatorId() {
if (activeDescriptor != null) {
return activeDescriptor.getFactoryLocatorId();
}
return factoryLocatorId;
}
/* package */ void setFactoryIds(Long factoryLocatorId, Long factoryServiceId) {
this.factoryLocatorId = factoryLocatorId;
this.factoryServiceId = factoryServiceId;
}
/* package */ void invokeInstanceListeners(InstanceLifecycleEvent event) {
for (InstanceLifecycleListener listener : instanceListeners) {
listener.lifecycleEvent(event);
}
}
/* (non-Javadoc)
* @see org.glassfish.hk2.api.ActiveDescriptor#create(org.glassfish.hk2.api.ServiceHandle)
*/
@Override
public T create(ServiceHandle root) {
checkState();
try {
T retVal;
if (activeDescriptor != null) {
if (!(activeDescriptor instanceof AutoActiveDescriptor)) {
// An auto-active descriptor will do the event in its create method
invokeInstanceListeners(new InstanceLifecycleEventImpl(InstanceLifecycleEventType.PRE_PRODUCTION, null, this));
}
retVal = activeDescriptor.create(root);
if (!(activeDescriptor instanceof AutoActiveDescriptor)) {
// An auto-active descriptor will do the event in its create method
invokeInstanceListeners(new InstanceLifecycleEventImpl(InstanceLifecycleEventType.POST_PRODUCTION, retVal, this));
}
}
else {
retVal = creator.create(root, this);
}
return retVal;
}
catch (Throwable re) {
if (!(re instanceof MultiException)) {
re = new MultiException(re);
}
MultiException reported = (MultiException) re;
if (!reported.getReportToErrorService()) {
// Specifically told to NOT report this error to error handlers
throw (RuntimeException) re;
}
LinkedList errorHandlers = sdLocator.getErrorHandlers();
for (ErrorService es : errorHandlers) {
ErrorInformation ei = new ErrorInformationImpl(ErrorType.SERVICE_CREATION_FAILURE,
this,
null,
reported);
try {
es.onFailure(ei);
}
catch (Throwable th) {
// ignored
}
}
throw (RuntimeException) re;
}
}
/* (non-Javadoc)
* @see org.glassfish.hk2.api.ActiveDescriptor#dispose(java.lang.Object, org.glassfish.hk2.api.ServiceHandle)
*/
@Override
public void dispose(T instance) {
checkState();
InstanceLifecycleEventImpl event = new InstanceLifecycleEventImpl(
InstanceLifecycleEventType.PRE_DESTRUCTION,
instance, this);
// invoke listeners BEFORE destroying the instance
invokeInstanceListeners(event);
try {
if (activeDescriptor != null) {
activeDescriptor.dispose(instance);
return;
}
creator.dispose(instance);
}
catch (Throwable re) {
if (!(re instanceof MultiException)) {
re = new MultiException(re);
}
MultiException reported = (MultiException) re;
if (!reported.getReportToErrorService()) {
// Specifically told to NOT report this error to error handlers
throw (RuntimeException) re;
}
LinkedList errorHandlers = sdLocator.getErrorHandlers();
for (ErrorService es : errorHandlers) {
ErrorInformation ei = new ErrorInformationImpl(ErrorType.SERVICE_DESTRUCTION_FAILURE,
this,
null,
reported);
try {
es.onFailure(ei);
}
catch (Throwable th) {
// ignored
}
}
throw (RuntimeException) re;
}
}
private void checkState() {
if (reified) return;
synchronized(this) {
if (!reified) throw new IllegalStateException();
}
}
private ActiveDescriptor getFactoryDescriptor(Method provideMethod,
Type factoryProvidedType,
ServiceLocatorImpl locator,
Collector collector) {
if (factoryServiceId != null && factoryLocatorId != null) {
// In this case someone has told us the exact factory they want
final Long fFactoryServiceId = factoryServiceId;
final Long fFactoryLocatorId = factoryLocatorId;
ActiveDescriptor retVal = locator.getBestDescriptor(new IndexedFilter() {
@Override
public boolean matches(Descriptor d) {
if (d.getServiceId().longValue() != fFactoryServiceId.longValue()) return false;
if (d.getLocatorId().longValue() != fFactoryLocatorId.longValue()) return false;
return true;
}
@Override
public String getAdvertisedContract() {
return Factory.class.getName();
}
@Override
public String getName() {
return null;
}
});
if (retVal == null) {
collector.addThrowable(new IllegalStateException("Could not find a pre-determined factory service for " +
factoryProvidedType));
}
return retVal;
}
List> factoryHandles = locator.getAllServiceHandles(
new ParameterizedTypeImpl(Factory.class, factoryProvidedType));
ServiceHandle factoryHandle = null;
for (ServiceHandle candidate : factoryHandles) {
if (qualifiers.isEmpty()) {
// We do this before we reify in order to ensure we don't reify too much
factoryHandle = candidate;
break;
}
ActiveDescriptor descriptorUnderTest = candidate.getActiveDescriptor();
try {
descriptorUnderTest = locator.reifyDescriptor(descriptorUnderTest);
}
catch (MultiException me) {
collector.addThrowable(me);
continue;
}
Method candidateMethod = Utilities.getFactoryProvideMethod(
descriptorUnderTest.getImplementationClass());
Set candidateQualifiers =
Utilities.getAllQualifiers(
candidateMethod,
Utilities.getDefaultNameFromMethod(candidateMethod, collector),
collector);
if (ReflectionHelper.annotationContainsAll(candidateQualifiers, qualifiers)) {
factoryHandle = candidate;
break;
}
}
if (factoryHandle == null) {
collector.addThrowable(new IllegalStateException("Could not find a factory service for " +
factoryProvidedType));
return null;
}
ActiveDescriptor retVal = factoryHandle.getActiveDescriptor();
factoryServiceId = retVal.getServiceId();
factoryLocatorId = retVal.getLocatorId();
return retVal;
}
/* package */ void reify(Class implClass, Collector collector) {
if (reified) return;
synchronized(this) {
if (reified) return;
while (reifying) {
try {
this.wait();
}
catch (InterruptedException e) {
collector.addThrowable(e);
return;
}
}
if (reified) return;
reifying = true;
}
try {
// This call can NOT hold the SystemDescriptor lock
// because this method could be called with the ServiceLocatorImpl
// lock held, and if the other thread was also trying to
// reify this descriptor that could lead to a deadlock
internalReify(implClass, collector);
}
finally {
synchronized (this) {
reifying = false;
this.notifyAll();
if (!collector.hasErrors()) {
reified = true;
}
else {
collector.addThrowable(new IllegalArgumentException("Errors were discovered while reifying " + this));
}
}
}
}
/**
* The service locator must hold its lock for this cal
*
* @param implClass The impl class to reify
* @param collector An error collector for errors
*/
private void internalReify(Class implClass, Collector collector) {
if (!preAnalyzed) {
this.implClass = implClass;
}
else {
if (!implClass.equals(this.implClass)) {
collector.addThrowable(new IllegalArgumentException(
"During reification a class mistmatch was found " + implClass.getName() +
" is not the same as " + this.implClass.getName()));
}
}
if (getDescriptorType().equals(DescriptorType.CLASS)) {
if (!preAnalyzed) {
qualifiers = Collections.unmodifiableSet(
Utilities.getAllQualifiers(implClass,
baseDescriptor.getName(),
collector));
}
ClazzCreator myClazzCreator = new ClazzCreator(sdLocator, implClass);
myClazzCreator.initialize(this, collector);
creator = myClazzCreator;
if (!preAnalyzed) {
ScopeInfo si = Utilities.getScopeAnnotationType(implClass, baseDescriptor, collector);
scopeAnnotation = si.getScope();
scope = si.getAnnoType();
contracts = Collections.unmodifiableSet(ReflectionHelper.getTypeClosure(implClass,
baseDescriptor.getAdvertisedContracts()));
}
}
else {
Utilities.checkFactoryType(implClass, collector);
// For a factory base stuff off of the method, not the class
Method provideMethod = Utilities.getFactoryProvideMethod(implClass);
if (provideMethod == null) {
collector.addThrowable(new IllegalArgumentException("Could not find the provide method on the class " + implClass.getName()));
// Do not continue, all is lost
return;
}
if (!preAnalyzed) {
qualifiers = Collections.unmodifiableSet(
Utilities.getAllQualifiers(
provideMethod,
Utilities.getDefaultNameFromMethod(provideMethod, collector),
collector));
}
Type factoryProvidedType = provideMethod.getGenericReturnType();
if (factoryProvidedType instanceof TypeVariable) {
factoryProvidedType = Utilities.getFactoryProductionType(implClass);
}
ActiveDescriptor factoryDescriptor = getFactoryDescriptor(provideMethod,
factoryProvidedType,
sdLocator,
collector);
if (factoryDescriptor != null) {
creator = new FactoryCreator(sdLocator, factoryDescriptor);
}
if (!preAnalyzed) {
ScopeInfo si = Utilities.getScopeAnnotationType(provideMethod, baseDescriptor, collector);
scopeAnnotation = si.getScope();
scope = si.getAnnoType();
contracts = Collections.unmodifiableSet(ReflectionHelper.getTypeClosure(
factoryProvidedType,
baseDescriptor.getAdvertisedContracts()));
}
}
// Check the scope
if ((baseDescriptor.getScope() == null) && (scope == null)) {
scope = PerLookup.class;
}
if (baseDescriptor.getScope() != null && scope != null) {
String scopeName = scope.getName();
if (!scopeName.equals(baseDescriptor.getScope())) {
collector.addThrowable(new IllegalArgumentException("The scope name given in the descriptor (" +
baseDescriptor.getScope() +
") did not match the scope annotation on the class (" + scope.getName() +
") in class " + Pretty.clazz(implClass)));
}
}
if (scope.isAnnotationPresent(Proxiable.class) && scope.isAnnotationPresent(Unproxiable.class)) {
collector.addThrowable(new IllegalArgumentException("The scope " + scope.getName() +
" is marked both @Proxiable and @Unproxiable"));
}
if ((isProxiable() != null) && isProxiable().booleanValue() && Utilities.isUnproxiableScope(scope)) {
collector.addThrowable(new IllegalArgumentException("The descriptor is in an Unproxiable scope but has " +
" isProxiable set to true"));
}
}
/* (non-Javadoc)
* @see org.glassfish.hk2.api.Descriptor#getLocatorId()
*/
@Override
public Long getLocatorId() {
return sdLocator.getLocatorId();
}
/* (non-Javadoc)
* @see org.jvnet.hk2.internal.Closeable#close()
*/
@Override
public boolean close() {
if (closed) return true;
synchronized (this) {
if (closed) return true;
closed = true;
return false;
}
}
/* (non-Javadoc)
* @see org.jvnet.hk2.internal.Closeable#isClosed()
*/
@Override
public boolean isClosed() {
// This is safe because once a descriptor is
// closed it is never opened
if (closed) return true;
synchronized (this) {
return closed;
}
}
/**
* Gets the decision of the filter from this service. May use
* a cache
*
* @param service The service to get the isValidating decision from
* @return true if this validation service should validate this descriptor
*/
/* package */ boolean isValidating(ValidationService service) {
Boolean cachedResult = validationServiceCache.get(service);
if (cachedResult != null) {
return cachedResult.booleanValue();
}
boolean decision = true;
try {
decision = BuilderHelper.filterMatches(this, service.getLookupFilter());
}
catch (Throwable th) {
// If the filter fails we assume the decision is true
}
if (decision) {
validationServiceCache.put(service, Boolean.TRUE);
}
else {
validationServiceCache.put(service, Boolean.FALSE);
}
return decision;
}
/* package */ void reupInstanceListeners(List listeners) {
instanceListeners.clear();
for (InstanceLifecycleListener listener : listeners) {
Filter filter = listener.getFilter();
if (BuilderHelper.filterMatches(this, filter)) {
instanceListeners.add(listener);
}
}
}
/* package */ Class getPreAnalyzedClass() {
return implClass;
}
/* package */ int getSingletonGeneration() {
return singletonGeneration;
}
/* package */ void setSingletonGeneration(int gen) {
singletonGeneration = gen;
}
@Override
public int hashCode() {
int low32 = id.intValue();
int high32 = (int) (id.longValue() >> 32);
int locatorLow32 = (int) sdLocator.getLocatorId();
int locatorHigh32 = (int) (sdLocator.getLocatorId() >> 32);
return low32 ^ high32 ^ locatorLow32 ^ locatorHigh32;
}
@SuppressWarnings({ "rawtypes" })
@Override
public boolean equals(Object o) {
if (o == null) return false;
if (!(o instanceof SystemDescriptor)) return false;
SystemDescriptor sd = (SystemDescriptor) o;
if (!sd.getServiceId().equals(id)) return false;
return sd.getLocatorId().equals(sdLocator.getLocatorId());
}
@Override
public String toString() {
StringBuffer sb = new StringBuffer("SystemDescriptor(");
DescriptorImpl.pretty(sb, this);
sb.append("\n\treified=" + reified);
sb.append(")");
return sb.toString();
}
}