/*
* Copyright (c) 2012 Data Harmonisation Panel
*
* All rights reserved. This program and the accompanying materials are made
* available under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation, either version 3 of the License,
* or (at your option) any later version.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this distribution. If not, see <http://www.gnu.org/licenses/>.
*
* Contributors:
* HUMBOLDT EU Integrated Project #030962
* Data Harmonisation Panel <http://www.dhpanel.eu>
*/
package eu.esdihumboldt.hale.io.xsd.reader;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.util.Collection;
import java.util.Date;
import java.util.Iterator;
import javax.xml.XMLConstants;
import javax.xml.namespace.QName;
import org.junit.Test;
import com.google.common.base.Objects;
import com.google.common.base.Predicate;
import com.google.common.collect.Collections2;
import eu.esdihumboldt.hale.common.core.io.IOProviderConfigurationException;
import eu.esdihumboldt.hale.common.core.io.report.IOReport;
import eu.esdihumboldt.hale.common.core.io.supplier.DefaultInputSupplier;
import eu.esdihumboldt.hale.common.core.io.supplier.LocatableInputSupplier;
import eu.esdihumboldt.hale.common.schema.model.ChildDefinition;
import eu.esdihumboldt.hale.common.schema.model.GroupPropertyDefinition;
import eu.esdihumboldt.hale.common.schema.model.PropertyDefinition;
import eu.esdihumboldt.hale.common.schema.model.Schema;
import eu.esdihumboldt.hale.common.schema.model.TypeDefinition;
import eu.esdihumboldt.hale.common.schema.model.constraint.property.Cardinality;
import eu.esdihumboldt.hale.common.schema.model.constraint.property.ChoiceFlag;
import eu.esdihumboldt.hale.common.schema.model.constraint.property.NillableFlag;
import eu.esdihumboldt.hale.common.schema.model.constraint.type.Binding;
import eu.esdihumboldt.hale.common.schema.model.constraint.type.HasValueFlag;
import eu.esdihumboldt.hale.common.schema.model.constraint.type.MappableFlag;
import eu.esdihumboldt.hale.common.schema.model.constraint.type.MappingRelevantFlag;
import eu.esdihumboldt.hale.common.schema.model.impl.DefaultTypeIndex;
import eu.esdihumboldt.hale.io.xsd.constraint.XmlElements;
import eu.esdihumboldt.hale.io.xsd.model.XmlElement;
import eu.esdihumboldt.hale.io.xsd.model.XmlIndex;
/**
* Tests for XML schema reading
*
* @author Simon Templer
*/
public class XmlSchemaReaderTest {
/**
* Test reading a simple XML schema that contains one big element. Focuses
* on structure, simple type bindings and cardinalities.
*
* @throws Exception if reading the schema fails
*/
@Test
public void testRead_shiporder_one() throws Exception {
URI location = getClass().getResource("/testdata/shiporder/shiporder-one.xsd").toURI();
LocatableInputSupplier<? extends InputStream> input = new DefaultInputSupplier(location);
XmlIndex schema = (XmlIndex) readSchema(input);
String ns = "http://www.example.com";
assertEquals(ns, schema.getNamespace());
// shiporder element
Collection<XmlElement> elements = getElementsWithNS(ns, schema.getElements().values());
assertEquals(1, elements.size());
XmlElement shiporder = elements.iterator().next();
testShiporderStructure(shiporder, ns);
}
/**
* Test reading a simple XML schema that contains one big element and where
* elementFormDefault/attributeFromDefault is set to unqualified and no
* target namespace is set. Focuses on structure, simple type bindings and
* cardinalities.
*
* @throws Exception if reading the schema fails
*/
@Test
public void testRead_shiporder_unqualified() throws Exception {
URI location = getClass().getResource("/testdata/shiporder/shiporder-unqualified.xsd")
.toURI();
LocatableInputSupplier<? extends InputStream> input = new DefaultInputSupplier(location);
XmlIndex schema = (XmlIndex) readSchema(input);
String ns = XMLConstants.NULL_NS_URI;
assertEquals(ns, schema.getNamespace());
// shiporder element
Collection<XmlElement> elements = getElementsWithNS(ns, schema.getElements().values());
assertEquals(1, elements.size());
XmlElement shiporder = elements.iterator().next();
// XXX use null namespace XXX not sure how to work with unqualified form
// FIXME target namespace no effect?! should the target namespace always
// be injected?
testShiporderStructure(shiporder, ns);
}
/**
* Test reading a simple XML schema that contains several elements
*
* @throws Exception if reading the schema fails
*/
@Test
public void testRead_shiporder_divided() throws Exception {
URI location = getClass().getResource("/testdata/shiporder/shiporder-divided.xsd").toURI();
LocatableInputSupplier<? extends InputStream> input = new DefaultInputSupplier(location);
XmlIndex schema = (XmlIndex) readSchema(input);
String ns = "http://www.example.com";
assertEquals(ns, schema.getNamespace());
// element count
Collection<XmlElement> elements = getElementsWithNS(ns, schema.getElements().values());
assertEquals(12, elements.size());
// shiporder element
XmlElement shiporder = schema.getElements().get(new QName(ns, "shiporder"));
testShiporderStructure(shiporder, ns);
}
/**
* Test reading a simple XML schema that uses several custom named types
*
* @throws Exception if reading the schema fails
*/
@Test
public void testRead_shiporder_types() throws Exception {
URI location = getClass().getResource("/testdata/shiporder/shiporder-types.xsd").toURI();
LocatableInputSupplier<? extends InputStream> input = new DefaultInputSupplier(location);
XmlIndex schema = (XmlIndex) readSchema(input);
String ns = "http://www.example.com";
assertEquals(ns, schema.getNamespace());
// shiporder element
Collection<XmlElement> elements = getElementsWithNS(ns, schema.getElements().values());
assertEquals(1, elements.size());
XmlElement shiporder = elements.iterator().next();
testShiporderStructure(shiporder, ns);
}
/**
* Test reading a simple XML schema that uses several custom named types and
* has a cycle.
*
* @throws Exception if reading the schema fails
*/
@Test
public void testRead_shiporder_types_cycle() throws Exception {
URI location = getClass().getResource("/testdata/shiporder/shiporder-types-cycle.xsd")
.toURI();
LocatableInputSupplier<? extends InputStream> input = new DefaultInputSupplier(location);
XmlIndex schema = (XmlIndex) readSchema(input);
String ns = "http://www.example.com";
assertEquals(ns, schema.getNamespace());
// shiporder element
Collection<XmlElement> elements = getElementsWithNS(ns, schema.getElements().values());
assertEquals(1, elements.size());
XmlElement shiporder = elements.iterator().next();
assertNotNull(shiporder);
TypeDefinition type = shiporder.getType();
assertEquals(5, type.getChildren().size());
// contained shiporder element
PropertyDefinition s2 = type.getChild(new QName(ns, "shiporder")).asProperty();
assertNotNull(s2);
assertEquals(type, s2.getPropertyType());
}
/**
* Test reading a simple XML schema that uses several custom named types.
* The types are referenced before they are declared.
*
* @throws Exception if reading the schema fails
*/
@Test
public void testRead_shiporder_types_reverse() throws Exception {
URI location = getClass().getResource("/testdata/shiporder/shiporder-types-r.xsd").toURI();
LocatableInputSupplier<? extends InputStream> input = new DefaultInputSupplier(location);
XmlIndex schema = (XmlIndex) readSchema(input);
String ns = "http://www.example.com";
assertEquals(ns, schema.getNamespace());
// shiporder element
Collection<XmlElement> elements = getElementsWithNS(ns, schema.getElements().values());
assertEquals(1, elements.size());
XmlElement shiporder = elements.iterator().next();
testShiporderStructure(shiporder, ns);
}
/**
* Test reading a simple XML schema that uses an attribute group and an
* attribute with xs:date type.
*
* @throws Exception if reading the schema fails
*/
@Test
public void testRead_definitive_attributegroup() throws Exception {
URI location = getClass().getResource("/testdata/definitive/attributegroup.xsd").toURI();
LocatableInputSupplier<? extends InputStream> input = new DefaultInputSupplier(location);
XmlIndex schema = (XmlIndex) readSchema(input);
// ShirtType
TypeDefinition type = schema.getType(new QName("ShirtType"));
assertNotNull(type);
// not there any more because it is flattened away
// // IdentifierGroup
// GroupPropertyDefinition group = type.getChild(new QName("IdentifierGroup")).asGroup();
// assertNotNull(group);
// // not a choice
// assertFalse(group.getConstraint(ChoiceFlag.class).isEnabled());
// id
PropertyDefinition id = type.getChild(new QName("id")).asProperty();
assertNotNull(id);
// property type must be a simple type
assertTrue(id.getPropertyType().getConstraint(HasValueFlag.class).isEnabled());
// binding must be string
assertEquals(String.class, id.getPropertyType().getConstraint(Binding.class).getBinding());
// required
Cardinality cc = id.getConstraint(Cardinality.class);
assertEquals(1, cc.getMinOccurs());
assertEquals(1, cc.getMaxOccurs());
// version
PropertyDefinition version = type.getChild(new QName("version")).asProperty();
assertNotNull(version);
// property type must be a simple type
assertTrue(version.getPropertyType().getConstraint(HasValueFlag.class).isEnabled());
// effDate
PropertyDefinition effDate = type.getChild(new QName("effDate")).asProperty();
assertNotNull(effDate);
// binding must be compatible to Date
assertTrue(Date.class.isAssignableFrom(
effDate.getPropertyType().getConstraint(Binding.class).getBinding()));
}
/**
* Test reading a simple XML schema that is split into several files. Tests
* also the {@link XmlElements} and {@link MappingRelevantFlag} constraints
*
* @throws Exception if reading the schema fails
*/
@Test
public void testRead_definitive_chapter03() throws Exception {
URI location = getClass().getResource("/testdata/definitive/chapter03env.xsd").toURI();
LocatableInputSupplier<? extends InputStream> input = new DefaultInputSupplier(location);
XmlIndex schema = (XmlIndex) readSchema(input);
// envelope element
XmlElement envelope = schema.getElements()
.get(new QName("http://example.org/ord", "envelope"));
assertNotNull(envelope);
TypeDefinition envType = envelope.getType();
// mappable
assertTrue(envType.getConstraint(MappingRelevantFlag.class).isEnabled());
// XmlElements
Collection<? extends XmlElement> elements = envType.getConstraint(XmlElements.class)
.getElements();
assertEquals(1, elements.size());
assertEquals(envelope, elements.iterator().next());
// order
PropertyDefinition order = envType.getChild(new QName("http://example.org/ord", "order"))
.asProperty();
assertNotNull(order);
TypeDefinition orderType = order.getPropertyType();
// mappable
assertTrue(orderType.getConstraint(MappingRelevantFlag.class).isEnabled());
// number
PropertyDefinition number = orderType
.getChild(new QName("http://example.org/ord", "number")).asProperty();
assertNotNull(number);
// binding must be string
assertEquals(String.class,
number.getPropertyType().getConstraint(Binding.class).getBinding());
// items
PropertyDefinition items = orderType.getChild(new QName("http://example.org/ord", "items"))
.asProperty();
assertNotNull(items);
// not mappable
assertFalse(items.getPropertyType().getConstraint(MappingRelevantFlag.class).isEnabled());
// no elements
assertTrue(
items.getPropertyType().getConstraint(XmlElements.class).getElements().isEmpty());
// SpecialOrderType
// extension to OrderType, should be mappable using xsi:type
TypeDefinition specialOrderType = schema
.getType(new QName("http://example.org/ord", "SpecialOrderType"));
assertNotNull(specialOrderType);
// number of declared children
assertEquals(1, specialOrderType.getDeclaredChildren().size());
// number of children
assertEquals(3, specialOrderType.getChildren().size());
// mappable
assertTrue(specialOrderType.getConstraint(MappableFlag.class).isEnabled());
// no elements
assertTrue(specialOrderType.getConstraint(XmlElements.class).getElements().isEmpty());
// overall mappable types
Collection<? extends TypeDefinition> mt = schema.getMappingRelevantTypes();
assertEquals(2, mt.size()); // envelope, order, special order
}
/**
* Test reading a simple XML schema with choices and complex types.
*
* @throws Exception if reading the schema fails
*/
@Test
public void testRead_definitive_choice() throws Exception {
URI location = getClass().getResource("/testdata/definitive/choice_complex.xsd").toURI();
LocatableInputSupplier<? extends InputStream> input = new DefaultInputSupplier(location);
XmlIndex schema = (XmlIndex) readSchema(input);
// ItemsType
TypeDefinition itemsType = schema.getType(new QName("ItemsType"));
assertNotNull(itemsType);
Collection<? extends ChildDefinition<?>> children = itemsType.getChildren();
assertEquals(1, children.size());
// choice
GroupPropertyDefinition choice = children.iterator().next().asGroup();
assertNotNull(choice);
// cardinality
Cardinality cc = choice.getConstraint(Cardinality.class);
assertEquals(0, cc.getMinOccurs());
assertEquals(Cardinality.UNBOUNDED, cc.getMaxOccurs());
// choice flag
assertTrue(choice.getConstraint(ChoiceFlag.class).isEnabled());
// children
assertEquals(3, choice.getDeclaredChildren().size());
// shirt
PropertyDefinition shirt = choice.getChild(new QName("shirt")).asProperty();
assertNotNull(shirt);
// hat
PropertyDefinition hat = choice.getChild(new QName("hat")).asProperty();
assertNotNull(hat);
// umbrella
PropertyDefinition umbrella = choice.getChild(new QName("umbrella")).asProperty();
assertNotNull(umbrella);
// TODO extend with advanced complex type tests?
}
/**
* Test reading a simple XML schema with sequences that have to be grouped.
*
* @throws Exception if reading the schema fails
*/
@Test
public void testRead_definitive_sequencegroup() throws Exception {
URI location = getClass().getResource("/testdata/definitive/sequencegroup.xsd").toURI();
LocatableInputSupplier<? extends InputStream> input = new DefaultInputSupplier(location);
XmlIndex schema = (XmlIndex) readSchema(input);
// ItemsType
TypeDefinition itemsType = schema.getType(new QName("ItemsType"));
assertNotNull(itemsType);
assertEquals(1, itemsType.getChildren().size());
// sequence group
GroupPropertyDefinition sequence = itemsType.getChildren().iterator().next().asGroup();
assertNotNull(sequence);
// cardinality
Cardinality cc = sequence.getConstraint(Cardinality.class);
assertEquals(1, cc.getMinOccurs());
assertEquals(Cardinality.UNBOUNDED, cc.getMaxOccurs());
// choice flag (not a choice)
assertFalse(sequence.getConstraint(ChoiceFlag.class).isEnabled());
Iterator<? extends ChildDefinition<?>> it = sequence.getDeclaredChildren().iterator();
// name
PropertyDefinition name = it.next().asProperty();
assertNotNull(name);
assertEquals("name", name.getName().getLocalPart());
// id
PropertyDefinition id = it.next().asProperty();
assertNotNull(id);
assertEquals("id", id.getName().getLocalPart());
// choice
GroupPropertyDefinition choice = it.next().asGroup();
assertNotNull(choice);
// cardinality
cc = choice.getConstraint(Cardinality.class);
assertEquals(1, cc.getMinOccurs());
assertEquals(1, cc.getMaxOccurs());
// choice flag
assertTrue(choice.getConstraint(ChoiceFlag.class).isEnabled());
it = choice.getDeclaredChildren().iterator();
// choice sequence
GroupPropertyDefinition seqGroup = it.next().asGroup();
assertNotNull(seqGroup);
// choice flag (not a choice)
assertFalse(seqGroup.getConstraint(ChoiceFlag.class).isEnabled());
// sequence elements
// one
PropertyDefinition one = seqGroup.getChild(new QName("one")).asProperty();
assertNotNull(one);
// two
PropertyDefinition two = seqGroup.getChild(new QName("two")).asProperty();
assertNotNull(two);
// choice element
PropertyDefinition single = it.next().asProperty();
assertNotNull(single);
assertEquals("single", single.getName().getLocalPart());
}
/**
* Test reading a simple XML schema with an annotated element.
*
* @throws Exception if reading the schema fails
*/
@Test
public void testRead_definitive_annotated() throws Exception {
URI location = getClass().getResource("/testdata/definitive/documentation_ex.xsd").toURI();
LocatableInputSupplier<? extends InputStream> input = new DefaultInputSupplier(location);
XmlIndex schema = (XmlIndex) readSchema(input);
// product element
XmlElement product = schema.getElements().get(new QName("product"));
assertNotNull(product);
assertTrue(product.getDescription().contains("This element represents a product."));
}
/**
* Test reading a simple XML schema containing groups and group references.
*
* @throws Exception if reading the schema fails
*/
@Test
public void testRead_definitive_groups() throws Exception {
URI location = getClass().getResource("/testdata/definitive/groups.xsd").toURI();
LocatableInputSupplier<? extends InputStream> input = new DefaultInputSupplier(location);
XmlIndex schema = (XmlIndex) readSchema(input);
// ShirtType
TypeDefinition shirtType = schema.getType(new QName("ShirtType"));
assertNotNull(shirtType);
assertEquals(5, shirtType.getChildren().size());
Iterator<? extends ChildDefinition<?>> it = shirtType.getChildren().iterator();
// ProductPropertyGroup
GroupPropertyDefinition prodGroup = it.next().asGroup();
// cardinality
Cardinality cc = prodGroup.getConstraint(Cardinality.class);
assertEquals(0, cc.getMinOccurs());
assertEquals(1, cc.getMaxOccurs());
// name
assertEquals("ProductPropertyGroup", prodGroup.getName().getLocalPart());
assertEquals(4, prodGroup.getDeclaredChildren().size());
Iterator<? extends ChildDefinition<?>> itProd = prodGroup.getDeclaredChildren().iterator();
// not there any more because it is flattened away
// // DescriptionGroup
// GroupPropertyDefinition descGroup = itProd.next().asGroup();
// assertNotNull(descGroup);
// // cardinality
// cc = descGroup.getConstraint(Cardinality.class);
// assertEquals(1, cc.getMinOccurs());
// assertEquals(1, cc.getMaxOccurs());
//
// assertEquals(2, descGroup.getDeclaredChildren().size());
// Iterator<? extends ChildDefinition<?>> itDesc = descGroup.getDeclaredChildren().iterator();
// description
PropertyDefinition description = itProd.next().asProperty();
assertNotNull(description);
assertEquals("description", description.getName().getLocalPart());
// comment
PropertyDefinition comment = itProd.next().asProperty();
assertNotNull(comment);
assertEquals("comment", comment.getName().getLocalPart());
// number
PropertyDefinition number = itProd.next().asProperty();
assertNotNull(number);
assertEquals("number", number.getName().getLocalPart());
// name
PropertyDefinition name = itProd.next().asProperty();
assertNotNull(name);
assertEquals("name", name.getName().getLocalPart());
// size
PropertyDefinition size = it.next().asProperty();
assertNotNull(size);
assertEquals("size", size.getName().getLocalPart());
}
/**
* Test reading a simple XML schema containing substitution groups.
*
* @throws Exception if reading the schema fails
*/
@Test
public void testRead_definitive_substitution() throws Exception {
URI location = getClass().getResource("/testdata/definitive/substgroups.xsd").toURI();
LocatableInputSupplier<? extends InputStream> input = new DefaultInputSupplier(location);
XmlIndex schema = (XmlIndex) readSchema(input);
// shirt element
XmlElement shirt = schema.getElements().get(new QName("shirt"));
assertNotNull(shirt);
assertEquals(new QName("product"), shirt.getSubstitutionGroup());
// TODO extend
}
// /**
// * Test reading a simple XML schema containing union and list types.
// * @throws Exception if reading the schema fails
// */
// @Test
// public void testRead_definitive_unionlist() throws Exception {
// URI location = getClass().getResource("/testdata/definitive/unionlist.xsd").toURI();
// LocatableInputSupplier<? extends InputStream> input = new DefaultInputSupplier(location );
// XmlIndex schema = (XmlIndex) readSchema(input);
//
// //TODO create tests
// }
private Collection<XmlElement> getElementsWithNS(final String ns,
Collection<XmlElement> values) {
return Collections2.filter(values, new Predicate<XmlElement>() {
@Override
public boolean apply(XmlElement input) {
return Objects.equal(ns, input.getName().getNamespaceURI());
}
});
}
/**
* Reads a schema
*
* @param input the input supplier
* @return the schema
* @throws IOProviderConfigurationException if the configuration of the
* reader is invalid
* @throws IOException if reading the schema fails
*/
public static Schema readSchema(LocatableInputSupplier<? extends InputStream> input)
throws IOProviderConfigurationException, IOException {
XmlSchemaReader reader = new XmlSchemaReader();
// reader.setContentType(XMLSchemaIO.XSD_CT);
reader.setSharedTypes(new DefaultTypeIndex());
reader.setSource(input);
reader.validate();
IOReport report = reader.execute(null);
assertTrue(report.isSuccess());
assertTrue("Errors are contained in the report", report.getErrors().isEmpty());
return reader.getSchema();
}
/**
* Test the shiporder structure
*
* @param shiporder the shiporder element
* @param ns the namespace
*/
private void testShiporderStructure(XmlElement shiporder, String ns) {
assertNotNull(shiporder);
assertEquals("shiporder", shiporder.getName().getLocalPart());
// shiporder type
TypeDefinition shiporderType = shiporder.getType();
assertNotNull(shiporderType);
Collection<? extends ChildDefinition<?>> properties = shiporderType.getChildren();
assertEquals(4, properties.size());
// orderperson
PropertyDefinition orderperson = shiporderType.getChild(new QName(ns, "orderperson"))
.asProperty();
assertNotNull(orderperson);
// property type must be a simple type
assertTrue(orderperson.getPropertyType().getConstraint(HasValueFlag.class).isEnabled());
// binding must be string
assertEquals(String.class,
orderperson.getPropertyType().getConstraint(Binding.class).getBinding());
// cardinality
Cardinality cc = orderperson.getConstraint(Cardinality.class);
assertEquals(1, cc.getMinOccurs());
assertEquals(1, cc.getMaxOccurs());
// not nillable
assertFalse(orderperson.getConstraint(NillableFlag.class).isEnabled());
// shipto
PropertyDefinition shipto = shiporderType.getChild(new QName(ns, "shipto")).asProperty();
assertNotNull(shipto);
// property type must be a complex type
assertFalse(shipto.getPropertyType().getConstraint(HasValueFlag.class).isEnabled());
// item
PropertyDefinition item = shiporderType.getChild(new QName(ns, "item")).asProperty();
assertNotNull(item);
// property type must be a complex type
assertFalse(item.getPropertyType().getConstraint(HasValueFlag.class).isEnabled());
// item cardinality
cc = item.getConstraint(Cardinality.class);
assertEquals(1, cc.getMinOccurs());
assertEquals(Cardinality.UNBOUNDED, cc.getMaxOccurs());
// item properties
TypeDefinition itemType = item.getPropertyType();
Collection<? extends ChildDefinition<?>> itemProps = itemType.getChildren();
assertEquals(4, itemProps.size());
// title
assertNotNull(itemType.getChild(new QName(ns, "title")).asProperty());
// note
PropertyDefinition note = itemType.getChild(new QName(ns, "note")).asProperty();
assertNotNull(note);
cc = note.getConstraint(Cardinality.class);
assertEquals(0, cc.getMinOccurs());
assertEquals(1, cc.getMaxOccurs());
// quantity
PropertyDefinition quantity = itemType.getChild(new QName(ns, "quantity")).asProperty();
assertNotNull(quantity);
assertTrue(quantity.getPropertyType().getConstraint(HasValueFlag.class).isEnabled());
assertTrue(Number.class.isAssignableFrom(
quantity.getPropertyType().getConstraint(Binding.class).getBinding()));
// price
PropertyDefinition price = itemType.getChild(new QName(ns, "price")).asProperty();
assertNotNull(price);
assertTrue(price.getPropertyType().getConstraint(HasValueFlag.class).isEnabled());
assertTrue(Number.class.isAssignableFrom(
price.getPropertyType().getConstraint(Binding.class).getBinding()));
// orderid
PropertyDefinition orderid = shiporderType.getChild(new QName(ns, "orderid")).asProperty();
assertNotNull(orderid);
// binding must be string
assertEquals(String.class,
orderid.getPropertyType().getConstraint(Binding.class).getBinding());
// required
cc = orderid.getConstraint(Cardinality.class);
assertEquals(1, cc.getMinOccurs());
assertEquals(1, cc.getMaxOccurs());
}
}