Browse Source
Minor tweaks to Parameter and Parameters to remove some JPA implications (1-based position indexes). Introduced PartTree abstraction that results from query parsing and allows simple API to traverse the query parts. Added AbstractQueryCreator to implement general traversing logic on PartTree and provide typed callback hooks for persistence technology specific creation of a criteria query.pull/2/head
10 changed files with 780 additions and 11 deletions
@ -0,0 +1,142 @@
@@ -0,0 +1,142 @@
|
||||
/* |
||||
* Copyright 2008-2010 the original author or authors. |
||||
* |
||||
* 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 org.springframework.data.repository.query; |
||||
|
||||
import org.springframework.data.domain.Pageable; |
||||
import org.springframework.data.domain.Sort; |
||||
import org.springframework.util.Assert; |
||||
|
||||
|
||||
/** |
||||
* {@link SimpleParameterAccessor} is used to bind method parameters. |
||||
* |
||||
* @author Oliver Gierke |
||||
*/ |
||||
public class SimpleParameterAccessor { |
||||
|
||||
private final Parameters parameters; |
||||
private final Object[] values; |
||||
|
||||
|
||||
/** |
||||
* Creates a new {@link SimpleParameterAccessor}. |
||||
* |
||||
* @param parameters |
||||
* @param values |
||||
*/ |
||||
public SimpleParameterAccessor(Parameters parameters, Object[] values) { |
||||
|
||||
Assert.notNull(parameters); |
||||
Assert.notNull(values); |
||||
|
||||
Assert.isTrue(parameters.getNumberOfParameters() == values.length, |
||||
"Invalid number of parameters given!"); |
||||
|
||||
this.parameters = parameters; |
||||
this.values = values; |
||||
} |
||||
|
||||
|
||||
/** |
||||
* Returns the {@link Pageable} of the parameters, if available. Returns |
||||
* {@code null} otherwise. |
||||
* |
||||
* @return |
||||
*/ |
||||
public Pageable getPageable() { |
||||
|
||||
if (!parameters.hasPageableParameter()) { |
||||
return null; |
||||
} |
||||
|
||||
return (Pageable) values[parameters.getPageableIndex()]; |
||||
} |
||||
|
||||
|
||||
/** |
||||
* Returns the sort instance to be used for query creation. Will use a |
||||
* {@link Sort} parameter if available or the {@link Sort} contained in a |
||||
* {@link Pageable} if available. Returns {@code null} if no {@link Sort} |
||||
* can be found. |
||||
* |
||||
* @return |
||||
*/ |
||||
public Sort getSort() { |
||||
|
||||
if (parameters.hasSortParameter()) { |
||||
return (Sort) values[parameters.getSortIndex()]; |
||||
} |
||||
|
||||
if (parameters.hasPageableParameter() && getPageable() != null) { |
||||
return getPageable().getSort(); |
||||
} |
||||
|
||||
return null; |
||||
} |
||||
|
||||
|
||||
private Object getBindableValue(int index) { |
||||
|
||||
int bindableCount = 0; |
||||
|
||||
for (Parameter parameter : parameters) { |
||||
|
||||
if (parameter.isBindable() && bindableCount == index) { |
||||
return values[parameter.getParameterIndex()]; |
||||
} |
||||
|
||||
if (parameter.isBindable()) { |
||||
bindableCount++; |
||||
} |
||||
} |
||||
|
||||
throw new IllegalArgumentException(); |
||||
} |
||||
|
||||
|
||||
/** |
||||
* Returns a {@link BindableParameterIterator} to traverse all bindable |
||||
* parameters. |
||||
* |
||||
* @return |
||||
*/ |
||||
public BindableParameterIterator iterator() { |
||||
|
||||
return new BindableParameterIterator(); |
||||
} |
||||
|
||||
/** |
||||
* Iterator class to allow traversing all bindable parameters inside the |
||||
* accessor. |
||||
* |
||||
* @author Oliver Gierke |
||||
*/ |
||||
public class BindableParameterIterator { |
||||
|
||||
private int currentIndex = 0; |
||||
|
||||
|
||||
/** |
||||
* Returns the next bindable parameter. |
||||
* |
||||
* @return |
||||
*/ |
||||
public Object next() { |
||||
|
||||
return getBindableValue(currentIndex++); |
||||
} |
||||
} |
||||
} |
||||
@ -0,0 +1,140 @@
@@ -0,0 +1,140 @@
|
||||
/* |
||||
* Copyright 2008-2010 the original author or authors. |
||||
* |
||||
* 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 org.springframework.data.repository.query.parser; |
||||
|
||||
import org.springframework.data.domain.Sort; |
||||
import org.springframework.data.repository.query.SimpleParameterAccessor; |
||||
import org.springframework.data.repository.query.SimpleParameterAccessor.BindableParameterIterator; |
||||
import org.springframework.data.repository.query.parser.PartTree.OrPart; |
||||
import org.springframework.util.Assert; |
||||
|
||||
|
||||
/** |
||||
* Base class for query creators that create criteria based queries from a |
||||
* {@link PartTree}. |
||||
* |
||||
* @param T the actual query type to be created |
||||
* @param S the intermediate criteria type |
||||
* @author Oliver Gierke |
||||
*/ |
||||
public abstract class AbstractQueryCreator<T, S> { |
||||
|
||||
private final SimpleParameterAccessor parameters; |
||||
private final PartTree tree; |
||||
|
||||
|
||||
/** |
||||
* Creates a new {@link AbstractQueryCreator} for the given {@link PartTree} |
||||
* and {@link SimpleParameterAccessor}. |
||||
* |
||||
* @param tree |
||||
* @param parameters |
||||
*/ |
||||
public AbstractQueryCreator(PartTree tree, |
||||
SimpleParameterAccessor parameters) { |
||||
|
||||
Assert.notNull(tree); |
||||
Assert.notNull(parameters); |
||||
|
||||
this.tree = tree; |
||||
this.parameters = parameters; |
||||
} |
||||
|
||||
|
||||
/** |
||||
* Creates the actual query object. |
||||
* |
||||
* @return |
||||
*/ |
||||
public T createQuery() { |
||||
|
||||
return finalize(createCriteria(tree), tree.getSort()); |
||||
} |
||||
|
||||
|
||||
/** |
||||
* Actual query building logic. Traverses the {@link PartTree} and invokes |
||||
* callback methods to delegate actual criteria creation and concatenation. |
||||
* |
||||
* @param tree |
||||
* @return |
||||
*/ |
||||
private S createCriteria(PartTree tree) { |
||||
|
||||
S base = null; |
||||
BindableParameterIterator iterator = parameters.iterator(); |
||||
|
||||
for (OrPart node : tree) { |
||||
|
||||
S criteria = null; |
||||
|
||||
for (Part part : node) { |
||||
|
||||
criteria = |
||||
criteria == null ? create(part, iterator) : and(part, |
||||
criteria, iterator); |
||||
} |
||||
|
||||
base = base == null ? criteria : or(base, criteria); |
||||
} |
||||
|
||||
return base; |
||||
} |
||||
|
||||
|
||||
/** |
||||
* Creates a new atomic instance of the criteria object. |
||||
* |
||||
* @param part |
||||
* @param iterator |
||||
* @return |
||||
*/ |
||||
protected abstract S create(Part part, BindableParameterIterator iterator); |
||||
|
||||
|
||||
/** |
||||
* Creates a new criteria object from the given part and and-concatenates it |
||||
* to the given base criteria. |
||||
* |
||||
* @param part |
||||
* @param base will never be {@literal null}. |
||||
* @param iterator |
||||
* @return |
||||
*/ |
||||
protected abstract S and(Part part, S base, |
||||
BindableParameterIterator iterator); |
||||
|
||||
|
||||
/** |
||||
* Or-concatenates the given base criteria to the given new criteria. |
||||
* |
||||
* @param base |
||||
* @param criteria |
||||
* @return |
||||
*/ |
||||
protected abstract S or(S base, S criteria); |
||||
|
||||
|
||||
/** |
||||
* Actually creates the query object applying the given criteria object and |
||||
* {@link Sort} definition. |
||||
* |
||||
* @param criteria |
||||
* @param sort |
||||
* @return |
||||
*/ |
||||
protected abstract T finalize(S criteria, Sort sort); |
||||
} |
||||
@ -0,0 +1,188 @@
@@ -0,0 +1,188 @@
|
||||
/* |
||||
* Copyright 2008-2010 the original author or authors. |
||||
* |
||||
* 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 org.springframework.data.repository.query.parser; |
||||
|
||||
import static java.lang.String.*; |
||||
import static java.util.regex.Pattern.*; |
||||
|
||||
import java.util.ArrayList; |
||||
import java.util.Iterator; |
||||
import java.util.List; |
||||
import java.util.regex.Pattern; |
||||
|
||||
import org.springframework.data.domain.Sort; |
||||
import org.springframework.data.repository.query.parser.PartTree.OrPart; |
||||
import org.springframework.util.Assert; |
||||
|
||||
|
||||
/** |
||||
* Class to parse a {@link String} into a tree or {@link OrPart}s consisting of |
||||
* simple {@link Part} instances in turn. Takes a domain class as well to |
||||
* validate that each of the {@link Part}s are refering to a property of the |
||||
* domain class. The {@link PartTree} can then be used to build queries based on |
||||
* its API instead of parsing the method name for each query execution. |
||||
* |
||||
* @author Oliver Gierke |
||||
*/ |
||||
public class PartTree implements Iterable<OrPart> { |
||||
|
||||
private static final String ORDER_BY = "OrderBy"; |
||||
private static final String[] PREFIXES = new String[] { "findBy", "find", |
||||
"readBy", "read", "getBy", "get" }; |
||||
private static final String PREFIX_TEMPLATE = "^%s(?=[A-Z]).*"; |
||||
private static final String KEYWORD_TEMPLATE = "(%s)(?=[A-Z])"; |
||||
|
||||
private final OrderBySource orderBySource; |
||||
private final List<OrPart> nodes = new ArrayList<PartTree.OrPart>(); |
||||
|
||||
|
||||
/** |
||||
* Creates a new {@link PartTree} by parsing the given {@link String} |
||||
* |
||||
* @param source the {@link String} to parse |
||||
* @param domainClass the domain class to check indiviual parts against to |
||||
* ensure they refer to a property of the class
|
||||
*/ |
||||
public PartTree(String source, Class<?> domainClass) { |
||||
|
||||
Assert.notNull(source); |
||||
Assert.notNull(domainClass); |
||||
|
||||
String foo = strip(source); |
||||
String[] parts = split(foo, ORDER_BY); |
||||
|
||||
if (parts.length > 2) { |
||||
throw new IllegalArgumentException( |
||||
"OrderBy must not be used more than once in a method name!"); |
||||
} |
||||
|
||||
buildTree(parts[0], domainClass); |
||||
this.orderBySource = |
||||
parts.length == 2 ? new OrderBySource(parts[1]) : null; |
||||
|
||||
} |
||||
|
||||
|
||||
/* |
||||
* (non-Javadoc) |
||||
* |
||||
* @see java.lang.Iterable#iterator() |
||||
*/ |
||||
public Iterator<OrPart> iterator() { |
||||
|
||||
return nodes.iterator(); |
||||
} |
||||
|
||||
|
||||
private void buildTree(String source, Class<?> domainClass) { |
||||
|
||||
String[] split = split(source, "Or"); |
||||
|
||||
for (String part : split) { |
||||
nodes.add(new OrPart(part, domainClass)); |
||||
} |
||||
} |
||||
|
||||
|
||||
/** |
||||
* Returns the {@link Sort} specification parsed from the source. |
||||
* |
||||
* @return |
||||
*/ |
||||
public Sort getSort() { |
||||
|
||||
return orderBySource == null ? null : orderBySource.toSort(); |
||||
} |
||||
|
||||
|
||||
/** |
||||
* Splits the given text at the given keywords. Expects camelcase style to |
||||
* only match concrete keywords and not derivatives of it. |
||||
* |
||||
* @param text |
||||
* @param keyword |
||||
* @return |
||||
*/ |
||||
private String[] split(String text, String keyword) { |
||||
|
||||
String regex = format(KEYWORD_TEMPLATE, keyword); |
||||
|
||||
Pattern pattern = compile(regex); |
||||
return pattern.split(text); |
||||
} |
||||
|
||||
|
||||
/** |
||||
* Strips a prefix from the given method name if it starts with one of |
||||
* {@value #PREFIXES}. |
||||
* |
||||
* @param methodName |
||||
* @return |
||||
*/ |
||||
private String strip(String methodName) { |
||||
|
||||
for (String prefix : PREFIXES) { |
||||
|
||||
String regex = format(PREFIX_TEMPLATE, prefix); |
||||
if (methodName.matches(regex)) { |
||||
return methodName.substring(prefix.length()); |
||||
} |
||||
} |
||||
|
||||
return methodName; |
||||
} |
||||
|
||||
/** |
||||
* A part of the parsed source that results from splitting up the resource |
||||
* ar {@literal Or} keywords. Consists of {@link Part}s that have to be |
||||
* concatenated by {@literal And}. |
||||
* |
||||
* @author Oliver Gierke |
||||
*/ |
||||
public class OrPart implements Iterable<Part> { |
||||
|
||||
private final List<Part> children = new ArrayList<Part>(); |
||||
|
||||
|
||||
/** |
||||
* Creates a new {@link OrPart}. |
||||
* |
||||
* @param source the source to split up into {@literal And} parts in |
||||
* turn. |
||||
* @param domainClass the domain class to check the resulting |
||||
* {@link Part}s against. |
||||
*/ |
||||
OrPart(String source, Class<?> domainClass) { |
||||
|
||||
String[] split = split(source, "And"); |
||||
|
||||
for (String part : split) { |
||||
children.add(new Part(part, domainClass)); |
||||
} |
||||
} |
||||
|
||||
|
||||
/* |
||||
* (non-Javadoc) |
||||
* |
||||
* @see java.lang.Iterable#iterator() |
||||
*/ |
||||
public Iterator<Part> iterator() { |
||||
|
||||
return children.iterator(); |
||||
} |
||||
} |
||||
} |
||||
@ -0,0 +1,143 @@
@@ -0,0 +1,143 @@
|
||||
/* |
||||
* Copyright 2008-2010 the original author or authors. |
||||
* |
||||
* 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 org.springframework.data.repository.query; |
||||
|
||||
import static org.hamcrest.CoreMatchers.*; |
||||
import static org.junit.Assert.*; |
||||
|
||||
import org.junit.Before; |
||||
import org.junit.Test; |
||||
import org.springframework.data.domain.PageRequest; |
||||
import org.springframework.data.domain.Pageable; |
||||
import org.springframework.data.domain.Sort; |
||||
|
||||
|
||||
/** |
||||
* Unit tests for {@link SimpleParameterAccessor}. |
||||
* |
||||
* @author Oliver Gierke |
||||
*/ |
||||
public class SimpleParameterAccessorUnitTests { |
||||
|
||||
Parameters parameters, sortParameters, pageableParameters; |
||||
|
||||
|
||||
@Before |
||||
public void setUp() throws SecurityException, NoSuchMethodException { |
||||
|
||||
parameters = |
||||
new Parameters(Sample.class.getMethod("sample", String.class)); |
||||
sortParameters = |
||||
new Parameters(Sample.class.getMethod("sample1", String.class, |
||||
Sort.class)); |
||||
pageableParameters = |
||||
new Parameters(Sample.class.getMethod("sample2", String.class, |
||||
Pageable.class)); |
||||
} |
||||
|
||||
|
||||
@Test |
||||
public void testname() throws Exception { |
||||
|
||||
new SimpleParameterAccessor(parameters, new Object[] { "test" }); |
||||
} |
||||
|
||||
|
||||
@Test(expected = IllegalArgumentException.class) |
||||
public void rejectsNullParameters() throws Exception { |
||||
|
||||
new SimpleParameterAccessor(null, new Object[0]); |
||||
} |
||||
|
||||
|
||||
@Test(expected = IllegalArgumentException.class) |
||||
public void rejectsNullValues() throws Exception { |
||||
|
||||
new SimpleParameterAccessor(parameters, null); |
||||
} |
||||
|
||||
|
||||
@Test(expected = IllegalArgumentException.class) |
||||
public void rejectsTooLittleNumberOfArguments() throws Exception { |
||||
|
||||
new SimpleParameterAccessor(parameters, new Object[0]); |
||||
} |
||||
|
||||
|
||||
@Test(expected = IllegalArgumentException.class) |
||||
public void rejectsTooManyArguments() throws Exception { |
||||
|
||||
new SimpleParameterAccessor(parameters, new Object[] { "test", "test" }); |
||||
} |
||||
|
||||
|
||||
@Test |
||||
public void returnsNullForPageableAndSortIfNoneAvailable() throws Exception { |
||||
|
||||
SimpleParameterAccessor accessor = |
||||
new SimpleParameterAccessor(parameters, new Object[] { "test" }); |
||||
assertThat(accessor.getPageable(), is(nullValue())); |
||||
assertThat(accessor.getSort(), is(nullValue())); |
||||
} |
||||
|
||||
|
||||
@Test |
||||
public void returnsSortIfAvailable() { |
||||
|
||||
Sort sort = new Sort("foo"); |
||||
SimpleParameterAccessor accessor = |
||||
new SimpleParameterAccessor(sortParameters, new Object[] { |
||||
"test", sort }); |
||||
assertThat(accessor.getSort(), is(sort)); |
||||
assertThat(accessor.getPageable(), is(nullValue())); |
||||
} |
||||
|
||||
|
||||
@Test |
||||
public void returnsPageableIfAvailable() { |
||||
|
||||
Pageable pageable = new PageRequest(0, 10); |
||||
SimpleParameterAccessor accessor = |
||||
new SimpleParameterAccessor(pageableParameters, new Object[] { |
||||
"test", pageable }); |
||||
assertThat(accessor.getPageable(), is(pageable)); |
||||
assertThat(accessor.getSort(), is(nullValue())); |
||||
} |
||||
|
||||
|
||||
@Test |
||||
public void returnsSortFromPageableIfAvailable() throws Exception { |
||||
|
||||
Sort sort = new Sort("foo"); |
||||
Pageable pageable = new PageRequest(0, 10, sort); |
||||
SimpleParameterAccessor accessor = |
||||
new SimpleParameterAccessor(pageableParameters, new Object[] { |
||||
"test", pageable }); |
||||
assertThat(accessor.getPageable(), is(pageable)); |
||||
assertThat(accessor.getSort(), is(sort)); |
||||
} |
||||
|
||||
interface Sample { |
||||
|
||||
void sample(String firstname); |
||||
|
||||
|
||||
void sample1(String firstname, Sort sort); |
||||
|
||||
|
||||
void sample2(String firstname, Pageable pageable); |
||||
} |
||||
} |
||||
@ -0,0 +1,118 @@
@@ -0,0 +1,118 @@
|
||||
/* |
||||
* Copyright 2008-2010 the original author or authors. |
||||
* |
||||
* 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 org.springframework.data.repository.query.parser; |
||||
|
||||
import static org.hamcrest.Matchers.*; |
||||
import static org.junit.Assert.*; |
||||
|
||||
import java.util.Iterator; |
||||
|
||||
import org.junit.Test; |
||||
import org.springframework.data.domain.Sort; |
||||
import org.springframework.data.domain.Sort.Direction; |
||||
import org.springframework.data.repository.query.parser.PartTree.OrPart; |
||||
|
||||
|
||||
/** |
||||
* Unit tests for {@link PartTree}. |
||||
* |
||||
* @author Oliver Gierke |
||||
*/ |
||||
public class PartTreeUnitTests { |
||||
|
||||
@Test(expected = IllegalArgumentException.class) |
||||
public void rejectsNullSource() throws Exception { |
||||
|
||||
new PartTree(null, getClass()); |
||||
} |
||||
|
||||
|
||||
@Test(expected = IllegalArgumentException.class) |
||||
public void rejectsNullDomainClass() throws Exception { |
||||
|
||||
new PartTree("test", null); |
||||
} |
||||
|
||||
|
||||
@Test(expected = IllegalArgumentException.class) |
||||
public void rejectsMultipleOrderBy() throws Exception { |
||||
|
||||
new PartTree("firstnameOrderByLastnameOrderByFirstname", User.class); |
||||
} |
||||
|
||||
|
||||
@Test |
||||
public void parsesSimplePropertyCorrectly() throws Exception { |
||||
|
||||
PartTree partTree = new PartTree("firstname", User.class); |
||||
assertPart(partTree, new Part[] { new Part("firstname", User.class) }); |
||||
} |
||||
|
||||
|
||||
@Test |
||||
public void parsesAndPropertiesCorrectly() throws Exception { |
||||
|
||||
PartTree partTree = new PartTree("firstnameAndLastname", User.class); |
||||
assertPart(partTree, new Part[] { new Part("firstname", User.class), |
||||
new Part("lastname", User.class) }); |
||||
assertThat(partTree.getSort(), is(nullValue())); |
||||
} |
||||
|
||||
|
||||
@Test |
||||
public void parsesOrPropertiesCorrectly() throws Exception { |
||||
|
||||
PartTree partTree = new PartTree("firstnameOrLastname", User.class); |
||||
assertPart(partTree, new Part[] { new Part("firstname", User.class) }, |
||||
new Part[] { new Part("lastname", User.class) }); |
||||
assertThat(partTree.getSort(), is(nullValue())); |
||||
} |
||||
|
||||
|
||||
@Test |
||||
public void hasSortIfOrderByIsGiven() throws Exception { |
||||
|
||||
PartTree partTree = |
||||
new PartTree("firstnameOrderByLastnameDesc", User.class); |
||||
assertThat(partTree.getSort(), is(new Sort(Direction.DESC, "lastname"))); |
||||
} |
||||
|
||||
|
||||
private void assertPart(PartTree tree, Part[]... parts) { |
||||
|
||||
Iterator<OrPart> iterator = tree.iterator(); |
||||
for (Part[] part : parts) { |
||||
assertThat(iterator.hasNext(), is(true)); |
||||
Iterator<Part> partIterator = iterator.next().iterator(); |
||||
for (int k = 0; k < part.length; k++) { |
||||
assertThat(String.format("Expected %d parts but have %d", |
||||
part.length, k + 1), partIterator.hasNext(), is(true)); |
||||
Part next = partIterator.next(); |
||||
assertThat( |
||||
String.format("Expected %s but got %s!", part[k], next), |
||||
part[k], is(next)); |
||||
} |
||||
assertThat("Too many parts!", partIterator.hasNext(), is(false)); |
||||
} |
||||
assertThat("Too many or parts!", iterator.hasNext(), is(false)); |
||||
} |
||||
|
||||
class User { |
||||
|
||||
String firstname; |
||||
String lastname; |
||||
} |
||||
} |
||||
Loading…
Reference in new issue