Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -18,31 +18,84 @@
*/
package org.elasticsearch.search.aggregations;

import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Objects;

import static java.util.Collections.unmodifiableMap;

/**
* Represents a set of computed addAggregation.
* Represents a set of {@link Aggregation}s
*/
public interface Aggregations extends Iterable<Aggregation> {
public abstract class Aggregations implements Iterable<Aggregation> {

protected List<? extends Aggregation> aggregations = Collections.emptyList();
protected Map<String, Aggregation> aggregationsAsMap;

protected Aggregations() {
}

protected Aggregations(List<? extends Aggregation> aggregations) {
this.aggregations = aggregations;
}

/**
* Iterates over the {@link Aggregation}s.
*/
@Override
public final Iterator<Aggregation> iterator() {
return aggregations.stream().map((p) -> (Aggregation) p).iterator();
}

/**
* The list of {@link Aggregation}s.
*/
List<Aggregation> asList();
public final List<Aggregation> asList() {
return Collections.unmodifiableList(aggregations);
}

/**
* Returns the {@link Aggregation}s keyed by aggregation name.
*/
Map<String, Aggregation> asMap();
public final Map<String, Aggregation> asMap() {
return getAsMap();
}

/**
* Returns the {@link Aggregation}s keyed by aggregation name.
*/
Map<String, Aggregation> getAsMap();
public final Map<String, Aggregation> getAsMap() {
if (aggregationsAsMap == null) {
Map<String, Aggregation> newAggregationsAsMap = new HashMap<>(aggregations.size());
for (Aggregation aggregation : aggregations) {
newAggregationsAsMap.put(aggregation.getName(), aggregation);
}
this.aggregationsAsMap = unmodifiableMap(newAggregationsAsMap);
}
return aggregationsAsMap;
}

/**
* Returns the aggregation that is associated with the specified name.
*/
<A extends Aggregation> A get(String name);
@SuppressWarnings("unchecked")
public final <A extends Aggregation> A get(String name) {
return (A) asMap().get(name);
}

@Override
public final boolean equals(Object obj) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since we don't plan to implement equals/hashCode() for the ParsedAggregations at this point, wouldn't it make sense to leave this in InternalAggregations?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know, does it hurt here? it is based on members that have been moved to the base class after all.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doesn't hurt, I guess, but it might obfuscate the fact that all the parsed aggregations don't implement equals/hashCode. At a quick glance people might think all subclasses properly implement equals()...

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

right that is a good point. seems like it could hurt then, I will move it back.

if (obj == null || getClass() != obj.getClass()) {
return false;
}
return aggregations.equals(((Aggregations) obj).aggregations);
}

@Override
public final int hashCode() {
return Objects.hash(getClass(), aggregations);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,83 +27,26 @@

import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;

import static java.util.Collections.emptyMap;
import static java.util.Collections.unmodifiableMap;
/**
* An internal implementation of {@link Aggregations}.
*/
public class InternalAggregations implements Aggregations, ToXContent, Streamable {
public final class InternalAggregations extends Aggregations implements ToXContent, Streamable {

public static final InternalAggregations EMPTY = new InternalAggregations();

private List<InternalAggregation> aggregations = Collections.emptyList();

private Map<String, Aggregation> aggregationsAsMap;

private InternalAggregations() {
}

/**
* Constructs a new addAggregation.
*/
public InternalAggregations(List<InternalAggregation> aggregations) {
this.aggregations = aggregations;
}

/**
* Iterates over the {@link Aggregation}s.
*/
@Override
public Iterator<Aggregation> iterator() {
return aggregations.stream().map((p) -> (Aggregation) p).iterator();
}

/**
* The list of {@link Aggregation}s.
*/
@Override
public List<Aggregation> asList() {
return aggregations.stream().map((p) -> (Aggregation) p).collect(Collectors.toList());
}

/**
* Returns the {@link Aggregation}s keyed by map.
*/
@Override
public Map<String, Aggregation> asMap() {
return getAsMap();
}

/**
* Returns the {@link Aggregation}s keyed by map.
*/
@Override
public Map<String, Aggregation> getAsMap() {
if (aggregationsAsMap == null) {
Map<String, InternalAggregation> newAggregationsAsMap = new HashMap<>();
for (InternalAggregation aggregation : aggregations) {
newAggregationsAsMap.put(aggregation.getName(), aggregation);
}
this.aggregationsAsMap = unmodifiableMap(newAggregationsAsMap);
}
return aggregationsAsMap;
}

/**
* @return the aggregation of the specified name.
*/
@SuppressWarnings("unchecked")
@Override
public <A extends Aggregation> A get(String name) {
return (A) asMap().get(name);
super(aggregations);
}

/**
Expand All @@ -118,21 +61,16 @@ public static InternalAggregations reduce(List<InternalAggregations> aggregation
}

// first we collect all aggregations of the same type and list them together

Map<String, List<InternalAggregation>> aggByName = new HashMap<>();
for (InternalAggregations aggregations : aggregationsList) {
for (InternalAggregation aggregation : aggregations.aggregations) {
List<InternalAggregation> aggs = aggByName.get(aggregation.getName());
if (aggs == null) {
aggs = new ArrayList<>(aggregationsList.size());
aggByName.put(aggregation.getName(), aggs);
}
aggs.add(aggregation);
for (Aggregation aggregation : aggregations.aggregations) {
List<InternalAggregation> aggs = aggByName.computeIfAbsent(
aggregation.getName(), k -> new ArrayList<>(aggregationsList.size()));
aggs.add((InternalAggregation)aggregation);
}
}

// now we can use the first aggregation of each list to handle the reduce of its list

List<InternalAggregation> reducedAggregations = new ArrayList<>();
for (Map.Entry<String, List<InternalAggregation>> entry : aggByName.entrySet()) {
List<InternalAggregation> aggregations = entry.getValue();
Expand All @@ -142,41 +80,33 @@ public static InternalAggregations reduce(List<InternalAggregations> aggregation
return new InternalAggregations(reducedAggregations);
}

/** The fields required to write this addAggregation to xcontent */
static class Fields {
public static final String AGGREGATIONS = "aggregations";
}

@Override
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since we are planning to implement toXContent equivalents for the ParsedAggregations, would it make sense to pull this (and maybe toXContentInternal()) to the abstract class?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am working on this, I was doing it but it's controversial. That's why I took it off this PR for now, I prefer doing it in small steps.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Specifically, moving the ToXContent part requires to adjust the Aggregation (singular) part too. The short solution is making Aggregation extend ToXContent which is not the cleanest, yet I am not sure any of the other solutions are feasible as they involve generics and complicate things quite a bit.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, I was just wondering

if (aggregations.isEmpty()) {
return builder;
}
builder.startObject(Fields.AGGREGATIONS);
builder.startObject("aggregations");
toXContentInternal(builder, params);
return builder.endObject();
}

/**
* Directly write all the addAggregation without their bounding object. Used by sub-addAggregation (non top level addAggregation)
* Directly write all the aggregations without their bounding object. Used by sub-aggregations (non top level aggs)
*/
public XContentBuilder toXContentInternal(XContentBuilder builder, Params params) throws IOException {
for (Aggregation aggregation : aggregations) {
((InternalAggregation) aggregation).toXContent(builder, params);
((InternalAggregation)aggregation).toXContent(builder, params);
}
return builder;
}


public static InternalAggregations readAggregations(StreamInput in) throws IOException {
InternalAggregations result = new InternalAggregations();
result.readFrom(in);
return result;
}

public static InternalAggregations readOptionalAggregations(StreamInput in) throws IOException {
return in.readOptionalStreamable(InternalAggregations::new);
}

@Override
public void readFrom(StreamInput in) throws IOException {
aggregations = in.readList(stream -> in.readNamedWriteable(InternalAggregation.class));
Expand All @@ -186,20 +116,8 @@ public void readFrom(StreamInput in) throws IOException {
}

@Override
@SuppressWarnings("unchecked")
public void writeTo(StreamOutput out) throws IOException {
out.writeNamedWriteableList(aggregations);
}

@Override
public boolean equals(Object obj) {
if (obj == null || getClass() != obj.getClass()) {
return false;
}
return aggregations.equals(((InternalAggregations) obj).aggregations);
}

@Override
public int hashCode() {
return Objects.hash(getClass(), aggregations);
out.writeNamedWriteableList((List<InternalAggregation>)aggregations);
}
}