Skip to content

Commit

Permalink
json-stream: Improve performance. Fix #442
Browse files Browse the repository at this point in the history
  • Loading branch information
minborg committed May 25, 2017
1 parent c43dea2 commit dba8b97
Show file tree
Hide file tree
Showing 4 changed files with 241 additions and 46 deletions.
5 changes: 5 additions & 0 deletions plugin-parent/json-stream/pom.xml
Expand Up @@ -120,6 +120,11 @@
<artifactId>junit</artifactId> <artifactId>junit</artifactId>
</dependency> </dependency>


<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
</dependency>

<dependency> <dependency>
<groupId>org.hamcrest</groupId> <groupId>org.hamcrest</groupId>
<artifactId>hamcrest-core</artifactId> <artifactId>hamcrest-core</artifactId>
Expand Down
Expand Up @@ -17,47 +17,43 @@
package com.speedment.plugins.json; package com.speedment.plugins.json;


import com.speedment.plugins.json.internal.JsonCollectorImpl; import com.speedment.plugins.json.internal.JsonCollectorImpl;
import java.util.List;
import static java.util.Objects.requireNonNull; import static java.util.Objects.requireNonNull;
import java.util.StringJoiner;
import java.util.stream.Collector; import java.util.stream.Collector;
import static java.util.stream.Collectors.joining; import static java.util.stream.Collectors.joining;


/** /**
* A specialized java {@link Collector} that converts streams of Speedment * A specialized java {@link Collector} that converts streams of Speedment
* entities into JSON arrays. * entities into JSON arrays.
* <p> * <p>
* Example usage: * Example usage: <code>
* <code>
* app.getOrThrow(EmployeeManager.class).stream() * app.getOrThrow(EmployeeManager.class).stream()
* .filter(Employee.AGE.greaterThan(35)) * .filter(Employee.AGE.greaterThan(35))
* .filter(Employee.NAME.startsWith("B")) * .filter(Employee.NAME.startsWith("B"))
* .collect(JsonCollector.toJson( * .collect(JsonCollector.toJson(
* jsonComponent.allOf(employees) * jsonComponent.allOf(employees)
* )); * ));
* </code> * </code>
* *
* @param <ENTITY> the entity type * @param <ENTITY> the entity type
* *
* @author Emil Forslund * @author Emil Forslund
* @since 1.0.0 * @since 1.0.0
*/ */
public interface JsonCollector<ENTITY> extends Collector<ENTITY, List<String>, String> { public interface JsonCollector<ENTITY> extends Collector<ENTITY, StringJoiner, String> {


/** /**
* Returns a collector that calls the specified encoder for each element in * Returns a collector that calls the specified encoder for each element in
* the stream and joins the resuling stream separated by commas and * the stream and joins the resulting stream separated by commas and
* surrounded by square brackets. Each element is also formatted using the * surrounded by square brackets. Each element is also formatted using the
* specified {@link JsonEncoder}. * specified {@link JsonEncoder}.
* *
* @param <ENTITY> the type of the stream * @param <ENTITY> the type of the stream
* @param encoder the enocder to use * @param encoder the enocder to use
* @return the json string * @return the json string
*/ */
static <ENTITY> JsonCollector<ENTITY> toJson(JsonEncoder<ENTITY> encoder) { static <ENTITY> JsonCollector<ENTITY> toJson(JsonEncoder<ENTITY> encoder) {
requireNonNull(encoder); requireNonNull(encoder);

return JsonCollectorImpl.collect(encoder::apply);
return JsonCollectorImpl.collect(
encoder::apply, l -> "[" + l.stream().collect(joining(",")) + "]"
);
} }
} }
Expand Up @@ -17,63 +17,57 @@
package com.speedment.plugins.json.internal; package com.speedment.plugins.json.internal;


import com.speedment.plugins.json.JsonCollector; import com.speedment.plugins.json.JsonCollector;

import java.util.*; import java.util.*;
import static java.util.Objects.requireNonNull;
import java.util.function.BiConsumer; import java.util.function.BiConsumer;
import java.util.function.BinaryOperator; import java.util.function.BinaryOperator;
import java.util.function.Function; import java.util.function.Function;
import java.util.function.Supplier; import java.util.function.Supplier;
import java.util.stream.Collector; import java.util.stream.Collector;


import static java.util.Objects.requireNonNull;
import static java.util.stream.Collector.Characteristics.CONCURRENT;

/** /**
* *
* @author Emil Forslund * @author Emil Forslund
* @since 1.0.0 * @param <ENTITY> type of the entity
* @since 1.0.0
*/ */
public final class JsonCollectorImpl<ENTITY> implements JsonCollector<ENTITY> { public final class JsonCollectorImpl<ENTITY> implements JsonCollector<ENTITY> {

public static <ENTITY> JsonCollector<ENTITY> collect(Function<ENTITY, String> converter, Function<List<String>, String> merger) { public static <ENTITY> JsonCollector<ENTITY> collect(Function<ENTITY, String> converter) {
return new JsonCollectorImpl<>(converter, merger); return new JsonCollectorImpl<>(converter);
} }

@Override @Override
public Supplier<List<String>> supplier() { public Supplier<StringJoiner> supplier() {
return () -> Collections.synchronizedList(new ArrayList<>()); return () -> new StringJoiner(",", "[", "]");
} }


@Override @Override
public BiConsumer<List<String>, ENTITY> accumulator() { public BiConsumer<StringJoiner, ENTITY> accumulator() {
return (l, t) -> { return (sj, t) -> sj.add(converter.apply(t));
l.add(converter.apply(t));
};
} }


@Override @Override
public BinaryOperator<List<String>> combiner() { public BinaryOperator<StringJoiner> combiner() {
return (l1, l2) -> { return StringJoiner::merge;
l1.addAll(l2);
return l1;
};
} }


@Override @Override
public Function<List<String>, String> finisher() { public Function<StringJoiner, String> finisher() {
return merger::apply; return StringJoiner::toString;
} }


private static final Set<Collector.Characteristics> CHARACTERISTICS
= Collections.unmodifiableSet(EnumSet.noneOf(Characteristics.class));

@Override @Override
public Set<Collector.Characteristics> characteristics() { public Set<Collector.Characteristics> characteristics() {
return EnumSet.of(CONCURRENT); return CHARACTERISTICS;
} }

private JsonCollectorImpl(Function<ENTITY, String> converter, Function<List<String>, String> merger) { private JsonCollectorImpl(Function<ENTITY, String> converter) {
this.converter = requireNonNull(converter); this.converter = requireNonNull(converter);
this.merger = requireNonNull(merger);
} }

private final Function<ENTITY, String> converter; private final Function<ENTITY, String> converter;
private final Function<List<String>, String> merger; }
}
@@ -0,0 +1,200 @@
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package com.speedment.plugins.json.internal;

import com.speedment.common.injector.Injector;
import com.speedment.common.injector.exception.NoDefaultConstructorException;
import com.speedment.plugins.json.JsonBundle;
import com.speedment.plugins.json.JsonComponent;
import com.speedment.plugins.json.JsonEncoder;
import com.speedment.runtime.config.Project;
import com.speedment.runtime.config.identifier.ColumnIdentifier;
import com.speedment.runtime.config.identifier.TableIdentifier;
import com.speedment.runtime.config.internal.ProjectImpl;
import com.speedment.runtime.core.component.ProjectComponent;
import com.speedment.runtime.core.manager.Manager;
import com.speedment.runtime.field.IntField;
import com.speedment.runtime.field.StringField;
import com.speedment.runtime.typemapper.TypeMapper;
import java.util.HashMap;
import java.util.Map;
import static java.util.Objects.requireNonNull;
import java.util.stream.IntStream;
import java.util.stream.Stream;
import org.junit.Before;
import org.junit.Test;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

/**
*
* @author Per Minborg
*/
public class JsonComponentImplTest {

private JsonComponent jsonComponent;
private Manager<Person> persons;

@Before
@SuppressWarnings("unchecked")
public void init() {
jsonComponent = newComponent();
persons = (Manager<Person>) mock(Manager.class);
final Stream<Person> personStream = IntStream.range(0, 8).mapToObj(i -> new Person(i));
when(persons.stream()).thenReturn(personStream);
when(persons.fields()).thenReturn(Stream.of(Person.ID, Person.NAME));
}

@Test
public void testNoneOf() {
final JsonEncoder<Person> result = jsonComponent.noneOf(persons);
final String json = persons.stream().collect(result.collector());
System.out.println(json);
}

// @Test
// public void testAllOf() {
// System.out.println("allOf");
// final JsonEncoder<Person> result = jsonComponent.allOf(persons);
// final String json = persons.stream().collect(result.collector());
// System.out.println(json);
// }
//
// @Test
// public void testOf() {
// System.out.println("of");
// @SuppressWarnings("unchecked")
// final JsonEncoder<Person> result = jsonComponent.of(persons, Person.ID, Person.NAME);
// final String json = persons.stream().collect(result.collector());
// System.out.println(json);
// }

private static class Person {

public static IntField<Person, Integer> ID = IntField.create(
Identifier.ID,
Person::getId,
Person::setId,
TypeMapper.primitive(),
true
);
/**
* This Field corresponds to the {@link Human} field that can be
* obtained using the {@link Human#getName()} method.
*/
public static StringField<Person, String> NAME = StringField.create(
Identifier.NAME,
Person::getName,
Person::setName,
TypeMapper.identity(),
false
);

private int id;
private String name;

public Person(int id) {
this.id = id;
this.name = "name" + id;
}

public int getId() {
return id;
}

public Person setId(int id) {
this.id = id;
return this;
}

public String getName() {
return name;
}

public Person setName(String name) {
this.name = name;
return this;
}

enum Identifier implements ColumnIdentifier<Person> {

ID("id"),
NAME("name");

private final String columnName;
private final TableIdentifier<Person> tableIdentifier;

Identifier(String columnName) {
this.columnName = columnName;
this.tableIdentifier = TableIdentifier.of(getDbmsName(),
getSchemaName(),
getTableName());
}

@Override
public String getDbmsName() {
return "db0";
}

@Override
public String getSchemaName() {
return "schema";
}

@Override
public String getTableName() {
return "person";
}

@Override
public String getColumnName() {
return this.columnName;
}

@Override
public TableIdentifier<Person> asTableIdentifier() {
return this.tableIdentifier;
}
}

}

private JsonComponent newComponent() {
try {
Injector injector = Injector.builder()
.withComponent(MyProjectComponent.class)
.withBundle(JsonBundle.class)
.build();

return injector.getOrThrow(JsonComponent.class);

} catch (InstantiationException | NoDefaultConstructorException e) {
throw new RuntimeException(e);
}
}

private static class MyProjectComponent implements ProjectComponent {

private Project project;

public MyProjectComponent() {
final Project project = mock(Project.class);
setProject(project);
}

@Override
public Project getProject() {
return project;
}

@Override
public void setProject(Project project) {
this.project = requireNonNull(project);
}

}

}

0 comments on commit dba8b97

Please sign in to comment.