Skip to content

Commit

Permalink
Add lazy headers implementation
Browse files Browse the repository at this point in the history
Work toward #198.
  • Loading branch information
Ozzie Fallick authored and sjudd committed Mar 4, 2015
1 parent 4a8c58f commit fee6ed6
Show file tree
Hide file tree
Showing 4 changed files with 230 additions and 69 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
package com.bumptech.glide.load.model;

import android.text.TextUtils;

import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

/**
* A wrapper class for a set of headers to be included in a Glide request.
*/
public final class BasicHeaders implements Headers {

private final Map<String, Set<String>> headers;
private volatile Map<String, String> combinedHeaders;

BasicHeaders(Map<String, Set<String>> headers) {
this.headers = Collections.unmodifiableMap(headers);
}

public Map<String, String> getHeaders() {
if (combinedHeaders == null) {
synchronized (this) {
if (combinedHeaders == null) {
this.combinedHeaders = generateCombinedHeaders();
}
}
}

return combinedHeaders;
}

private Map<String, String> generateCombinedHeaders() {
Map<String, String> combinedHeaders = new HashMap<String, String>();
for (Map.Entry<String, Set<String>> entry : headers.entrySet()) {
combinedHeaders.put(entry.getKey(), TextUtils.join(",", entry.getValue()));
}
return Collections.unmodifiableMap(combinedHeaders);
}

@Override
public boolean equals(Object o) {
if (o instanceof BasicHeaders) {
BasicHeaders other = (BasicHeaders) o;
return headers.equals(other.headers);
}
return false;
}

@Override
public int hashCode() {
return headers.hashCode();
}

/**
* Builder class for {@link Headers}.
*/
public static final class Builder {
private final Map<String, Set<String>> headers = new HashMap<String, Set<String>>();

public void addHeader(String key, String value) {
if (headers.containsKey(key)) {
headers.get(key).add(value);
} else {
Set<String> values = new HashSet<String>();
values.add(value);
headers.put(key, values);
}
}

public BasicHeaders build() {
return new BasicHeaders(headers);
}
}
}
75 changes: 6 additions & 69 deletions library/src/main/java/com/bumptech/glide/load/model/Headers.java
Original file line number Diff line number Diff line change
@@ -1,79 +1,16 @@
package com.bumptech.glide.load.model;

import android.text.TextUtils;

import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

/**
* A wrapper class for a set of headers to be included in a Glide request.
* An interface for a wrapper for a set of headers to be included in a Glide request.
* Implementations must implement equals() and hashcode().
*/
public final class Headers {

public static final Headers NONE = new Builder().build();

private final Map<String, Set<String>> headers;
private volatile Map<String, String> combinedHeaders;

Headers(Map<String, Set<String>> headers) {
this.headers = Collections.unmodifiableMap(headers);
}

public Map<String, String> getHeaders() {
if (combinedHeaders == null) {
synchronized (this) {
if (combinedHeaders == null) {
this.combinedHeaders = generateCombinedHeaders();
}
}
}

return combinedHeaders;
}

private Map<String, String> generateCombinedHeaders() {
Map<String, String> combinedHeaders = new HashMap<String, String>();
for (Map.Entry<String, Set<String>> entry : headers.entrySet()) {
combinedHeaders.put(entry.getKey(), TextUtils.join(",", entry.getValue()));
}
return Collections.unmodifiableMap(combinedHeaders);
}

/**
* Builder class for {@link Headers}.
*/
public static final class Builder {
private final Map<String, Set<String>> headers = new HashMap<String, Set<String>>();

public void addHeader(String key, String value) {
if (headers.containsKey(key)) {
headers.get(key).add(value);
} else {
Set<String> values = new HashSet<String>();
values.add(value);
headers.put(key, values);
}
}
public interface Headers {

public Headers build() {
return new Headers(headers);
}
}
/** An empty Headers object that can be used if users don't want to provide headers. */
Headers NONE = new BasicHeaders.Builder().build();

@Override
public boolean equals(Object o) {
if (o instanceof Headers) {
Headers other = (Headers) o;
return headers.equals(other.headers);
}
return false;
}
Map<String, String> getHeaders();

@Override
public int hashCode() {
return headers.hashCode();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.bumptech.glide.load.model;

/**
* An interface for lazily creating headers that allows expensive to calculate headers (oauth for
* example) to be generated in the background during the first fetch.
*
* <p> Implementations should implement equals() and hashcode() </p> .
*/
public interface LazyHeaderFactory {

String buildHeader();

}
134 changes: 134 additions & 0 deletions library/src/main/java/com/bumptech/glide/load/model/LazyHeaders.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
package com.bumptech.glide.load.model;

import android.text.TextUtils;

import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

/**
* A wrapper class for a set of headers to be included in a Glide request, allowing headers to be
* constructed lazily.
*
* <p> Should be used instead of BasicHeaders when constructing headers requires I/O. </p>
*/
public final class LazyHeaders implements Headers {
private final Map<String, Set<String>> eagerHeaders;
private final Map<String, Set<LazyHeaderFactory>> lazyHeaders;
private volatile Map<String, String> combinedHeaders;

LazyHeaders(Map<String, Set<String>> eagerHeaders,
Map<String, Set<LazyHeaderFactory>> lazyHeaders) {
this.eagerHeaders = Collections.unmodifiableMap(eagerHeaders);
this.lazyHeaders = Collections.unmodifiableMap(lazyHeaders);
}

@Override
public Map<String, String> getHeaders() {
if (combinedHeaders == null) {
synchronized (this) {
if (combinedHeaders == null) {
this.combinedHeaders = Collections.unmodifiableMap(generateHeaders());
}
}
}

return combinedHeaders;
}

private Map<String, String> generateHeaders() {
Map<String, String> combinedHeaders = new HashMap<String, String>();
Set<String> combinedKeys = new HashSet<String>(eagerHeaders.keySet());
combinedKeys.addAll(lazyHeaders.keySet());

for (String key : combinedKeys) {
Set<String> values = new HashSet<String>();
if (eagerHeaders.containsKey(key)) {
values.addAll(eagerHeaders.get(key));
}
if (lazyHeaders.containsKey(key)) {
for (LazyHeaderFactory factory : lazyHeaders.get(key)) {
values.add(factory.buildHeader());
}
}
combinedHeaders.put(key, TextUtils.join(",", values));
}

return combinedHeaders;
}

@Override
public String toString() {
Set<String> combinedKeys = new HashSet<String>(eagerHeaders.keySet());
combinedKeys.addAll(lazyHeaders.keySet());

StringBuilder stringBuilder = new StringBuilder();
for (String key : combinedKeys) {
stringBuilder.append(key)
.append(": ");
if (eagerHeaders.containsKey(key)) {
stringBuilder.append(TextUtils.join(",", eagerHeaders.get(key)));
}
if (lazyHeaders.containsKey(key)) {
for (LazyHeaderFactory factory : lazyHeaders.get(key)) {
stringBuilder.append(factory.toString());
stringBuilder.append(',');
}
}
stringBuilder.append('\n');
}
return stringBuilder.toString();
}

@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}

LazyHeaders otherHeaders = (LazyHeaders) o;
return eagerHeaders.equals(otherHeaders.eagerHeaders)
&& lazyHeaders.equals(otherHeaders.lazyHeaders);
}

@Override
public int hashCode() {
return eagerHeaders.hashCode() + 31 * lazyHeaders.hashCode();
}

/**
* Builder class for {@link BasicHeaders}.
*/
public static final class Builder {
private final Map<String, Set<String>> eagerHeaders = new HashMap<String, Set<String>>();
private final Map<String, Set<LazyHeaderFactory>> lazyHeaders =
new HashMap<String, Set<LazyHeaderFactory>>();

public void addHeader(String key, String value) {
Set<String> values = eagerHeaders.get(key);
if (values == null) {
values = new HashSet<String>();
eagerHeaders.put(key, values);
}
values.add(value);
}

public void addHeader(String key, LazyHeaderFactory factory) {
Set<LazyHeaderFactory> factories = lazyHeaders.get(key);
if (factories == null) {
factories = new HashSet<LazyHeaderFactory>();
lazyHeaders.put(key, factories);
}
factories.add(factory);
}

public LazyHeaders build() {
return new LazyHeaders(eagerHeaders, lazyHeaders);
}
}
}

2 comments on commit fee6ed6

@TWiStErRob
Copy link
Collaborator

Choose a reason for hiding this comment

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

Think about this: Sets VS Lists in the Maps. I didn't read the full header spec, but there are some orderings appear in it: search for "the order" in http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html. Most used multivalued headers seems to be using the quality qualifier ;q= though for ordering.

@sjudd
Copy link
Collaborator

@sjudd sjudd commented on fee6ed6 Mar 4, 2015

Choose a reason for hiding this comment

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

Good point, thanks for looking as always, filed #361.

Please sign in to comment.