Public: Simple implementation of ConstraintViolation, ConstraintDescriptor, Path and Node. Review at http://gwt-code-reviews.appspot.com/735802 Review by: rjrjr@google.com git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@8505 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/user/src/com/google/gwt/validation/client/ConstraintViolationImpl.java b/user/src/com/google/gwt/validation/client/ConstraintViolationImpl.java new file mode 100644 index 0000000..e42daf9 --- /dev/null +++ b/user/src/com/google/gwt/validation/client/ConstraintViolationImpl.java
@@ -0,0 +1,170 @@ +/* + * Copyright 2010 Google Inc. + * + * Licensed 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 com.google.gwt.validation.client; + +import java.io.Serializable; + +import javax.validation.ConstraintViolation; +import javax.validation.Path; +import javax.validation.metadata.ConstraintDescriptor; + +/** + * An implementation of {@link ConstraintViolation}. + * + * @param <T> the type of bean validated. + */ +public class ConstraintViolationImpl<T> implements ConstraintViolation<T>, + Serializable { + + /** + * Builder for ConstraintViolations; + * + * @param <T> the type of bean validated. + */ + public static class Builder<T> { + private String message; + private String messageTemplate; + private T rootBean; + private Class<T> rootBeanClass; + private Object leafBean; + private Path propertyPath; + private Object invalidValue; + private ConstraintDescriptor<?> constraintDescriptor; + + public ConstraintViolationImpl<T> build() { + return new ConstraintViolationImpl<T>( + message, + messageTemplate, + rootBean, + rootBeanClass, + leafBean, + propertyPath, + invalidValue, + constraintDescriptor); + } + + public Builder<T> setConstraintDescriptor( + ConstraintDescriptor<?> constraintDescriptor) { + this.constraintDescriptor = constraintDescriptor; + return this; + } + + public Builder<T> setInvalidValue(Object invalidValue) { + this.invalidValue = invalidValue; + return this; + } + + public Builder<T> setLeafBean(Object leafBean) { + this.leafBean = leafBean; + return this; + } + + public void setMessage(String message) { + this.message = message; + } + + public Builder<T> setMessageTemplate(String messageTemplate) { + this.messageTemplate = messageTemplate; + return this; + } + + public Builder<T> setPropertyPath(Path propertyPath) { + this.propertyPath = propertyPath; + return this; + } + + public Builder<T> setRootBean(T rootBean) { + this.rootBean = rootBean; + return this; + } + + public Builder<T> setRootBeanClass(Class<T> rootBeanClass) { + this.rootBeanClass = rootBeanClass; + return this; + } + } + + private static final long serialVersionUID = 1L; + + public static <T> Builder<T> builder() { + return new Builder<T>(); + } + + private final String message; + private final String messageTemplate; + private final T rootBean; + private final Class<T> rootBeanClass; + private final Object leafBean; + private final Path propertyPath; + private final Object invalidValue; + private final ConstraintDescriptor<?> constraintDescriptor; + + /** + * @param message + * @param messageTemplate + * @param rootBean + * @param rootBeanClass + * @param leafBean + * @param propertyPath + * @param invalidValue + * @param constraintDescriptor + */ + private ConstraintViolationImpl(String message, String messageTemplate, + T rootBean, Class<T> rootBeanClass, Object leafBean, Path propertyPath, + Object invalidValue, ConstraintDescriptor<?> constraintDescriptor) { + super(); + this.message = message; + this.messageTemplate = messageTemplate; + this.rootBean = rootBean; + this.rootBeanClass = rootBeanClass; + this.leafBean = leafBean; + this.propertyPath = propertyPath; + this.invalidValue = invalidValue; + this.constraintDescriptor = constraintDescriptor; + } + + public ConstraintDescriptor<?> getConstraintDescriptor() { + return constraintDescriptor; + } + + public Object getInvalidValue() { + return invalidValue; + } + + public Object getLeafBean() { + return leafBean; + } + + public String getMessage() { + return message; + } + + public String getMessageTemplate() { + return messageTemplate; + } + + public Path getPropertyPath() { + return propertyPath; + } + + public T getRootBean() { + return rootBean; + } + + public Class<T> getRootBeanClass() { + return rootBeanClass; + } +}
diff --git a/user/src/com/google/gwt/validation/client/NodeImpl.java b/user/src/com/google/gwt/validation/client/NodeImpl.java new file mode 100644 index 0000000..d890ea6 --- /dev/null +++ b/user/src/com/google/gwt/validation/client/NodeImpl.java
@@ -0,0 +1,132 @@ +/* + * Copyright 2010 Google Inc. + * + * Licensed 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 com.google.gwt.validation.client; + +import java.io.Serializable; + +import javax.validation.Path.Node; + +/** + * An immutable GWT safe implementation of {@link Node} + */ +class NodeImpl implements Node, Serializable { + + private static final long serialVersionUID = 1L; + public static final Node ROOT_NODE = new NodeImpl(null); + + private final String name; + private final Integer index; + private final Object key; + + /** + * Create a non iterable node. + * + * @param name the possibly <code>null</code> name. + */ + public NodeImpl(String name) { + this.name = name; + this.index = null; + this.key = null; + } + + /** + * Create an iterable node with an index. + * + * @param name the possibly <code>null</code> name. + * @param index the zero based index. + */ + public NodeImpl(String name, int index) { + if (index < 0) { + throw new IllegalArgumentException("Index can not be negative."); + } + this.name = name; + this.index = Integer.valueOf(index); + this.key = null; + } + + /** + * Create an iterable node with a key. + * + * @param name the possibly <code>null</code> name. + * @param key the lookup key for this node. + */ + public NodeImpl(String name, Object key) { + if (key == null) { + throw new NullPointerException(); + } + this.name = name; + this.index = null; + this.key = key; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof NodeImpl)) { + return false; + } + NodeImpl that = (NodeImpl) obj; + return (this.name == null ? that.name == null : this.name == that.name) + && (this.index == null ? that.index == null : this.index == that.index) + && (this.key == null ? that.key == null : this.key == that.key); + } + + public Integer getIndex() { + return index; + } + + public Object getKey() { + return key; + } + + public String getName() { + return name; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((index == null) ? 0 : index.hashCode()); + result = prime * result + ((key == null) ? 0 : key.hashCode()); + result = prime * result + ((name == null) ? 0 : name.hashCode()); + return result; + } + + public boolean isInIterable() { + return index != null || key != null; + } + + @Override + public String toString() { + StringBuffer sb = new StringBuffer(); + if (name != null) { + sb.append(name); + } + if (isInIterable()) { + sb.append('['); + if (key != null) { + sb.append(key); + } else { + sb.append(index); + } + sb.append(']'); + } + return sb.toString(); + } +}
diff --git a/user/src/com/google/gwt/validation/client/PathImpl.java b/user/src/com/google/gwt/validation/client/PathImpl.java new file mode 100644 index 0000000..970d306 --- /dev/null +++ b/user/src/com/google/gwt/validation/client/PathImpl.java
@@ -0,0 +1,114 @@ +/* + * Copyright 2010 Google Inc. + * + * Licensed 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 com.google.gwt.validation.client; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +import javax.validation.Path; + +/** + * An immutable GWT safe implementation of {@link Path}. + */ +public class PathImpl implements Path, Serializable { + + private static final long serialVersionUID = 1L; + + private final List<Node> nodes = new ArrayList<Node>(); + + /** + * Creates a new path containing only the root (<code>null</code>) + * {@link Node}. + */ + public PathImpl() { + nodes.add(NodeImpl.ROOT_NODE); + } + + private PathImpl(PathImpl originalPath, Node node) { + nodes.addAll(originalPath.nodes); + nodes.add(node); + } + + /** + * Create a new path with a node named <code>name</code> appended to the + * existing path. + * + * @param name + * @return The new path with appended node. + */ + public PathImpl append(String name) { + return new PathImpl(this, new NodeImpl(name)); + } + + /** + * Create a new path with a indexed node named <code>name</code> appended to + * the existing path. + * + * @param name + * @param key + * @return The new path with appended node. + */ + public PathImpl appendIndex(String name, int index) { + return new PathImpl(this, new NodeImpl(name, index)); + } + + /** + * Create a new path with a keyed node named <code>name</code> appended to the + * existing path. + * + * @param name + * @param key + * @return The new path with appended node. + */ + public PathImpl appendKey(String name, Object key) { + return new PathImpl(this, new NodeImpl(name, key)); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof PathImpl)) { + return false; + } + PathImpl that = (PathImpl) obj; + return this.nodes.equals(that.nodes); + } + + @Override + public int hashCode() { + return nodes.hashCode(); + } + + public Iterator<Node> iterator() { + return nodes.iterator(); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + for (Node n : nodes) { + if (sb.length() > 0) { + sb.append('.'); + } + sb.append(n); + } + return sb.toString(); + } +}
diff --git a/user/src/com/google/gwt/validation/metadata/ConstraintDescriptorImpl.java b/user/src/com/google/gwt/validation/metadata/ConstraintDescriptorImpl.java new file mode 100644 index 0000000..c371744 --- /dev/null +++ b/user/src/com/google/gwt/validation/metadata/ConstraintDescriptorImpl.java
@@ -0,0 +1,164 @@ +/* + * Copyright 2010 Google Inc. + * + * Licensed 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 com.google.gwt.validation.metadata; + +import java.lang.annotation.Annotation; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import javax.validation.ConstraintValidator; +import javax.validation.Payload; +import javax.validation.metadata.ConstraintDescriptor; + +/** + * A immutable GWT implementation of {@link ConstraintDescriptor}. + * + * @param <T> the constraint annotation to describe. + */ +public class ConstraintDescriptorImpl<T extends Annotation> implements + ConstraintDescriptor<T> { + + /** + * Builder for {@link ConstraintDescriptorImpl} + * + * @param <T> the constraint annotation to describe. + */ + public static class Builder<T extends Annotation> { + private T annotation; + private Set<Class<?>> groups; + private Set<Class<? extends Payload>> payload; + private List<Class<? extends ConstraintValidator<T, ?>>> constraintValidatorClasses; + private Map<String, Object> attributes; + private Set<ConstraintDescriptor<?>> composingConstraints; + private boolean reportAsSingleViolation; + + public ConstraintDescriptorImpl<T> build() { + return new ConstraintDescriptorImpl<T>( + annotation, + groups, + payload, + constraintValidatorClasses, + attributes, + composingConstraints, + reportAsSingleViolation); + } + + public Builder<T> setAnnotation(T annotation) { + this.annotation = annotation; + return this; + } + + public Builder<T> setAttributes(Map<String, Object> attributes) { + this.attributes = attributes; + return this; + } + + public Builder<T> setComposingConstraints( + Set<ConstraintDescriptor<?>> composingConstraints) { + this.composingConstraints = composingConstraints; + return this; + } + + public Builder<T> setConstraintValidatorClasses( + List<Class<? extends ConstraintValidator<T, ?>>> constraintValidatorClasses) { + this.constraintValidatorClasses = constraintValidatorClasses; + return this; + } + + public Builder<T> setGroups(Set<Class<?>> groups) { + this.groups = groups; + return this; + } + + public Builder<T> setPayload(Set<Class<? extends Payload>> payload) { + this.payload = payload; + return this; + } + + public Builder<T> setReportAsSingleViolation(boolean reportAsSingleViolation) { + this.reportAsSingleViolation = reportAsSingleViolation; + return this; + } + } + + public static <T extends Annotation> Builder<T> builder() { + return new Builder<T>(); + } + + private final T annotation; + private final Set<Class<?>> groups; + private final Set<Class<? extends Payload>> payload; + private final List<Class<? extends ConstraintValidator<T, ?>>> constraintValidatorClasses; + private final Map<String, Object> attributes; + private final Set<ConstraintDescriptor<?>> composingConstraints; + private final boolean reportAsSingleViolation; + + /** + * @param annotation + * @param groups + * @param payload + * @param constraintValidatorClasses + * @param attributes + * @param composingConstraints + * @param reportAsSingleViolation + */ + private ConstraintDescriptorImpl( + T annotation, + Set<Class<?>> groups, + Set<Class<? extends Payload>> payload, + List<Class<? extends ConstraintValidator<T, ?>>> constraintValidatorClasses, + Map<String, Object> attributes, + Set<ConstraintDescriptor<?>> composingConstraints, + boolean reportAsSingleViolation) { + super(); + this.annotation = annotation; + this.groups = groups; + this.payload = payload; + this.constraintValidatorClasses = constraintValidatorClasses; + this.attributes = attributes; + this.composingConstraints = composingConstraints; + this.reportAsSingleViolation = reportAsSingleViolation; + } + + public T getAnnotation() { + return annotation; + } + + public Map<String, Object> getAttributes() { + return attributes; + } + + public Set<ConstraintDescriptor<?>> getComposingConstraints() { + return composingConstraints; + } + + public List<Class<? extends ConstraintValidator<T, ?>>> getConstraintValidatorClasses() { + return constraintValidatorClasses; + } + + public Set<Class<?>> getGroups() { + return groups; + } + + public Set<Class<? extends Payload>> getPayload() { + return payload; + } + + public boolean isReportAsSingleViolation() { + return reportAsSingleViolation; + } +}
diff --git a/user/test/com/google/gwt/validation/client/NodeImplTest.java b/user/test/com/google/gwt/validation/client/NodeImplTest.java new file mode 100644 index 0000000..6e5ebd2 --- /dev/null +++ b/user/test/com/google/gwt/validation/client/NodeImplTest.java
@@ -0,0 +1,52 @@ +/* + * Copyright 2010 Google Inc. + * + * Licensed 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 com.google.gwt.validation.client; + +import junit.framework.TestCase; + +import javax.validation.Path.Node; + +/** + * Tests for {@link NodeImpl}. + */ +public class NodeImplTest extends TestCase { + + public void testRoot() throws Exception { + assertNode(NodeImpl.ROOT_NODE, null, false, null, null); + } + + public void testFoo() throws Exception { + assertNode(new NodeImpl("foo"), "foo", false, null, null); + } + + public void testFoo1() throws Exception { + assertNode(new NodeImpl("foo", 1), "foo", true, null, + Integer.valueOf(1)); + } + + public void testFooBar() throws Exception { + assertNode(new NodeImpl("foo", "bar"), "foo", true, "bar", null); + } + + protected void assertNode(Node node, String expectedName, + boolean expectedInIterator, Object expectedKey, Integer expectedIndex) { + assertEquals(node + " name", expectedName, node.getName()); + assertEquals(node + " isInIterator", expectedInIterator, + node.isInIterable()); + assertEquals(node + " key", expectedKey, node.getKey()); + assertEquals(node + " index", expectedIndex, node.getIndex()); + } +}
diff --git a/user/test/com/google/gwt/validation/client/PathImplTest.java b/user/test/com/google/gwt/validation/client/PathImplTest.java new file mode 100644 index 0000000..f71f208 --- /dev/null +++ b/user/test/com/google/gwt/validation/client/PathImplTest.java
@@ -0,0 +1,77 @@ +/* + * Copyright 2010 Google Inc. + * + * Licensed 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 com.google.gwt.validation.client; + +import junit.framework.TestCase; + +/** + * Tests for {@link PathImpl}. + */ +public class PathImplTest extends TestCase { + + public void testEquals_root() { + PathImpl root = new PathImpl(); + PathImpl rootCopy = new PathImpl(); + assertEqualsAndHash(root, rootCopy); + } + + public void testEquals_foo() { + PathImpl foo = new PathImpl().append("foo"); + PathImpl fooCopy = new PathImpl().append("foo"); + assertEqualsAndHash(foo, fooCopy); + } + + public void testEqual_fooBarKey() { + PathImpl fooBarKey = new PathImpl().append("foo").appendKey("bar", "key"); + PathImpl fooBarKeyCopy = new PathImpl().append("foo").appendKey("bar", + "key"); + assertEqualsAndHash(fooBarKey, fooBarKeyCopy); + } + + public void testEquals_fooBar1() { + PathImpl fooBar1 = new PathImpl().append("foo").appendIndex("bar", 1); + PathImpl fooBar1Copy = new PathImpl().append("foo").appendIndex("bar", 1); + assertEqualsAndHash(fooBar1, fooBar1Copy); + } + + public void testEquals_not() { + PathImpl root = new PathImpl(); + PathImpl foo = new PathImpl().append("foo"); + assertNotEqual(root, foo); + + PathImpl fooBarKey = new PathImpl().append("foo").appendKey("bar", "key"); + PathImpl fooBarNote = new PathImpl().append("foo").appendKey("bar", "note"); + assertNotEqual(root, fooBarKey); + assertNotEqual(foo, fooBarKey); + assertNotEqual(fooBarNote, fooBarKey); + + PathImpl fooBar1 = new PathImpl().append("foo").appendIndex("bar", 1); + PathImpl fooBar2 = new PathImpl().append("foo").appendIndex("bar", 2); + assertNotEqual(root, fooBar1); + assertNotEqual(foo, fooBar1); + assertNotEqual(fooBarKey, fooBar1); + assertNotEqual(fooBar2, fooBar1); + } + + protected void assertNotEqual(Object lhs, Object rhs) { + assertFalse(lhs + "should not equal " + rhs, lhs.equals(rhs)); + } + + protected void assertEqualsAndHash(Object lhs, Object rhs) { + assertEquals(lhs, rhs); + assertEquals("hashCode", lhs.hashCode(), rhs.hashCode()); + } +}
diff --git a/user/test/com/google/gwt/validation/client/SimpleSampleTest.java b/user/test/com/google/gwt/validation/client/SimpleSampleTest.java index 34e6a16..322fca9 100644 --- a/user/test/com/google/gwt/validation/client/SimpleSampleTest.java +++ b/user/test/com/google/gwt/validation/client/SimpleSampleTest.java
@@ -1,12 +1,12 @@ /* * Copyright 2010 Google Inc. - * + * * Licensed 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 @@ -17,6 +17,9 @@ import com.google.gwt.junit.client.GWTTestCase; +import javax.validation.ConstraintViolation; +import javax.validation.constraints.NotNull; + /** * Tests for {@link SimpleSample}. */ @@ -33,6 +36,15 @@ assertEquals(null, sample.getName()); } + public void testConstraintViolation_compiles() throws Exception { + // Only tests that Violation Compiles which will also compile Path, Node + // and ConstraintDescriptor + ConstraintViolation<NotNull> violation = ConstraintViolationImpl.<NotNull> builder(). + setLeafBean(sample). + build(); + assertEquals(sample, violation.getLeafBean()); + } + @Override public String getModuleName() { return "com.google.gwt.validation.Validation";
diff --git a/user/test/com/google/gwt/validation/ValidationGwtSuite.java b/user/test/com/google/gwt/validation/client/ValidationClientGwtSuite.java similarity index 84% rename from user/test/com/google/gwt/validation/ValidationGwtSuite.java rename to user/test/com/google/gwt/validation/client/ValidationClientGwtSuite.java index d366007..03e87ad 100644 --- a/user/test/com/google/gwt/validation/ValidationGwtSuite.java +++ b/user/test/com/google/gwt/validation/client/ValidationClientGwtSuite.java
@@ -13,17 +13,16 @@ * License for the specific language governing permissions and limitations under * the License. */ -package com.google.gwt.validation; +package com.google.gwt.validation.client; import com.google.gwt.junit.tools.GWTTestSuite; -import com.google.gwt.validation.client.SimpleSampleTest; import junit.framework.Test; /** - * All validation tests. + * All validation client GWT tests. */ -public class ValidationGwtSuite { +public class ValidationClientGwtSuite { public static Test suite() { GWTTestSuite suite = new GWTTestSuite( @@ -31,5 +30,4 @@ suite.addTestSuite(SimpleSampleTest.class); return suite; } - }
diff --git a/user/test/com/google/gwt/validation/ValidationGwtSuite.java b/user/test/com/google/gwt/validation/client/ValidationClientJreSuite.java similarity index 65% copy from user/test/com/google/gwt/validation/ValidationGwtSuite.java copy to user/test/com/google/gwt/validation/client/ValidationClientJreSuite.java index d366007..b50b7c4 100644 --- a/user/test/com/google/gwt/validation/ValidationGwtSuite.java +++ b/user/test/com/google/gwt/validation/client/ValidationClientJreSuite.java
@@ -13,23 +13,21 @@ * License for the specific language governing permissions and limitations under * the License. */ -package com.google.gwt.validation; - -import com.google.gwt.junit.tools.GWTTestSuite; -import com.google.gwt.validation.client.SimpleSampleTest; +package com.google.gwt.validation.client; import junit.framework.Test; +import junit.framework.TestSuite; /** - * All validation tests. + * All validation client non GWT tests. */ -public class ValidationGwtSuite { +public class ValidationClientJreSuite { public static Test suite() { - GWTTestSuite suite = new GWTTestSuite( - "Test suite for all validation code."); - suite.addTestSuite(SimpleSampleTest.class); + TestSuite suite = new TestSuite( + "Test suite for validation client code that does not require GWT."); + suite.addTestSuite(PathImplTest.class); + suite.addTestSuite(NodeImplTest.class); return suite; } - }