Skip to content
This repository has been archived by the owner on May 15, 2023. It is now read-only.

Commit

Permalink
RecordingResponse.getAnchors() returns a custom multimap. (#88)
Browse files Browse the repository at this point in the history
The AnchorMap class is consistent with Guava's LinkedListMultimap.
It has an additional keyList() method to fully support the
LinkedListMultimap semantics of ordered keys and values.
Document that returned collections are views rather than copies.
  • Loading branch information
wiarlawd authored Dec 14, 2017
1 parent 7d7dad5 commit 9e31188
Show file tree
Hide file tree
Showing 4 changed files with 232 additions and 10 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,11 @@
/**
* A fake implementation of {@link DocIdPusher} that simply records
* the values it receives. This implementation is not thread-safe.
* <p>
* Methods that return collections all return unmodifiable views of
* the recorded values. The collections cannot be changed directly,
* but they will reflect changes to the recorded values that are made
* through the {@code DocIdPusher} interface.
*/
public class RecordingDocIdPusher extends AbstractDocIdPusher {
// If unspecified, the default group source name is the value of the feed.name
Expand Down
124 changes: 115 additions & 9 deletions src/com/google/enterprise/adaptor/testing/RecordingResponse.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

import static java.util.Collections.unmodifiableList;
import static java.util.Collections.unmodifiableMap;
import static java.util.Collections.unmodifiableSet;

import com.google.enterprise.adaptor.Acl;
import com.google.enterprise.adaptor.Metadata;
Expand All @@ -26,18 +27,25 @@
import java.io.IOException;
import java.io.OutputStream;
import java.net.URI;
import java.util.AbstractMap.SimpleEntry;
import java.util.AbstractMap.SimpleImmutableEntry;
import java.util.ArrayList;
import java.util.Date;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;

/**
* A fake implementation of {@link Response} that simply records the
* values it receives, and implements the interface semantics of the
* methods that must be called last. This implementation is not
* thread-safe.
* <p>
* Methods that return collections all return unmodifiable views of
* the recorded values. The collections cannot be changed directly,
* but they will reflect changes to the recorded values that are made
* through the {@code Response} interface.
*/
public class RecordingResponse implements Response {
/**
Expand Down Expand Up @@ -176,7 +184,7 @@ public void addAnchor(URI uri, String text) {
if (uri == null) {
throw new NullPointerException();
}
anchors.add(new SimpleEntry<String, URI>(text, uri));
anchors.add(new SimpleImmutableEntry<String, URI>(text, uri));
}

@Override
Expand Down Expand Up @@ -227,7 +235,7 @@ public void setLock(boolean lock) {
this.lock = lock;
}

// TODO(bmj): @Override
@Override
public void setForcedTransmissionDecision(TransmissionDecision decision) {
if (state != State.SETUP) {
throw new IllegalStateException("Already responded " + state);
Expand Down Expand Up @@ -268,6 +276,12 @@ public Acl getAcl() {
return acl;
}

/**
* Gets an unmodifiable view of the named resources as a {@code Map}
* from URI fragments to ACLs.
*
* @return an unmodifiable view of the named resources
*/
public Map<String, Acl> getNamedResources() {
return unmodifiableMap(namedResources);
}
Expand All @@ -277,12 +291,104 @@ public boolean isSecure() {
}

/**
* Gets an unmodifiable list of the accumulated anchors.
* Gets an unmodifiable view of the accumulated anchors as a multimap.
*
* @return a unmodifiable list of the accumulated anchors
* @return a unmodifiable view of the accumulated anchors
*/
public AnchorMap getAnchors() {
return new AnchorMap(anchors);
}

/**
* An unmodifiable view of the accumulated anchors as a multimap
* that supports duplicate, ordered key-value pairs. The anchor
* texts are the keys, and the anchor URIs are the values.
* <p>
* This class is consistent with Guava's {@code LinkedListMultimap},
* although it does not extend that class, or implement the
* {@code ListMultimap} interface or all of its methods.
*/
public List<Map.Entry<String, URI>> getAnchors() {
return unmodifiableList(anchors);
public static class AnchorMap {
private final List<Map.Entry<String, URI>> anchors;

private AnchorMap(List<Map.Entry<String, URI>> anchors) {
this.anchors = unmodifiableList(anchors);
}

@Override
public String toString() {
return anchors.toString();
}

public boolean isEmpty() {
return anchors.isEmpty();
}

public int size() {
return anchors.size();
}

/**
* Gets an unmodifiable list of the anchors. The list is in
* insertion order, and may be empty, but will never be
* {@code null}. The keys (anchor texts) may be {@code null}.
* The values (anchor URIs) will not be {@code null}.
*
* @return an unmodifiable list of the accumulated anchors
*/
public List<Map.Entry<String, URI>> entries() {
return anchors;
}

/**
* Gets an unmodifiable list of the anchor texts. The list is in
* insertion order and may contain duplicates and nulls.
*
* @return a unmodifiable list of the accumulated anchor texts
*/
public List<String> keyList() {
List<String> texts = new ArrayList<String>(anchors.size());
for (Map.Entry<String, URI> entry : anchors) {
texts.add(entry.getKey());
}
return unmodifiableList(texts);
}

/**
* Gets an unmodifiable set of the unique anchor texts. The set is
* in insertion order based on the first appearance of each key,
* and may contain {@code null}.
*
* @return a unmodifiable set of the accumulated anchor texts
*/
public Set<String> keySet() {
Set<String> texts = new LinkedHashSet<String>(anchors.size());
for (Map.Entry<String, URI> entry : anchors) {
texts.add(entry.getKey());
}
return unmodifiableSet(texts);
}

/**
* Gets an unmodifiable list of the accumulated anchor URIs
* that match the given text. The text may be {@code null}. The
* list is in insertion order, and may be empty, but will
* never be {@code null}. The URIs will not be {@code null}.
*
* @param text the anchor text to get the URIs for
* @return a unmodifiable list of the accumulated anchor URIs
*/
public List<URI> get(String text) {
List<URI> uris = new ArrayList<URI>();
for (Map.Entry<String, URI> entry : anchors) {
// TODO(jlacey): This is just Objects.equals in Java 7.
if ((text == null && entry.getKey() == null)
|| (text != null && text.equals(entry.getKey()))) {
uris.add(entry.getValue());
}
}
return unmodifiableList(uris);
}
}

public boolean isNoIndex() {
Expand Down Expand Up @@ -314,9 +420,9 @@ public TransmissionDecision getForcedTransmissionDecision() {
}

/**
* Gets an unmodifiable map of the accumulated params.
* Gets an unmodifiable view of the accumulated params as a {@code Map}.
*
* @return an unmodifiable map of the accumulated params
* @return an unmodifiable view of the accumulated params
*/
public Map<String, String> getParams() {
return unmodifiableMap(params);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,7 @@ public void testRetriever() throws IOException {
assertEquals(true, response.isSecure());
assertEquals(singletonList(new SimpleEntry<String, URI>(
"It is an example", URI.create("http://example.com/doc"))),
response.getAnchors());
response.getAnchors().entries());
assertEquals(true, response.isNoIndex());
assertEquals(true, response.isNoFollow());
assertEquals(true, response.isNoArchive());
Expand Down
111 changes: 111 additions & 0 deletions test/com/google/enterprise/adaptor/testing/RecordingResponseTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
// Copyright 2017 Google Inc. All Rights Reserved.
//
// 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.enterprise.adaptor.testing;

import static java.util.Arrays.asList;
import static java.util.Collections.emptyList;
import static java.util.Collections.emptySet;
import static java.util.Collections.singleton;
import static java.util.Collections.singletonList;
import static org.junit.Assert.assertEquals;

import com.google.common.collect.ImmutableList;

import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;

import java.net.URI;
import java.util.AbstractMap.SimpleImmutableEntry;
import java.util.ArrayList;
import java.util.HashSet;

/**
* Tests for {@link RecordingResponse}.
*/
public class RecordingResponseTest {
@Rule
public ExpectedException thrown = ExpectedException.none();

@Test
public void testAnchors_empty() {
RecordingResponse response = new RecordingResponse();
RecordingResponse.AnchorMap anchors = response.getAnchors();
assertEquals(emptyList(), anchors.entries());
assertEquals(emptyList(), anchors.keyList());
assertEquals(emptySet(), anchors.keySet());
assertEquals(emptyList(), anchors.get("foo"));
}

@Test
public void testAnchors_nullUri() {
thrown.expect(NullPointerException.class);
new RecordingResponse().addAnchor(null, "hello");
}

@Test
public void testAnchors_nullText() {
RecordingResponse response = new RecordingResponse();
response.addAnchor(URI.create("http://localhost/"), null);
RecordingResponse.AnchorMap anchors = response.getAnchors();
assertEquals(
singletonList(
new SimpleImmutableEntry<String, URI>(null,
URI.create("http://localhost/"))),
anchors.entries());
assertEquals(singletonList((String) null), anchors.keyList());
assertEquals(singleton((String) null), anchors.keySet());
assertEquals(singletonList(URI.create("http://localhost/")),
anchors.get(null));
assertEquals(emptyList(), anchors.get("foo"));
}

@Test
public void testAnchors_duplicates() {
RecordingResponse response = new RecordingResponse();
response.addAnchor(URI.create("http://localhost/a"), "a");
response.addAnchor(URI.create("http://localhost/b"), null);
response.addAnchor(URI.create("http://localhost/c"), "a");
response.addAnchor(URI.create("http://localhost/b"), "d");
RecordingResponse.AnchorMap anchors = response.getAnchors();
assertEquals(
ImmutableList.of(
new SimpleImmutableEntry<String, URI>("a",
URI.create("http://localhost/a")),
new SimpleImmutableEntry<String, URI>(null,
URI.create("http://localhost/b")),
new SimpleImmutableEntry<String, URI>("a",
URI.create("http://localhost/c")),
new SimpleImmutableEntry<String, URI>("d",
URI.create("http://localhost/b"))),
anchors.entries());
assertEquals(asList("a", null, "a", "d"), anchors.keyList());
assertEquals(
new HashSet<String>(asList("d", "a", null)), // Out of order as a test.
anchors.keySet());
// The keySet() should be ordered the same as the keyList() or entries().
assertEquals(
asList("a", null, "d"),
new ArrayList<String>(anchors.keySet()));
assertEquals(
asList(
URI.create("http://localhost/a"),
URI.create("http://localhost/c")),
anchors.get("a"));
assertEquals(
asList(URI.create("http://localhost/b")),
anchors.get(null));
}
}

0 comments on commit 9e31188

Please sign in to comment.