/*
* Copyright (C) 2012 The Android Open Source Project
*
* 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.android.sdklib.internal.repository;
import com.android.annotations.NonNull;
import com.android.annotations.Nullable;
import com.android.utils.Pair;
import org.apache.http.Header;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.ProtocolVersion;
import org.apache.http.message.BasicHttpResponse;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.TreeMap;
/** A mock UpdaterData that simply records what would have been installed. */
public class MockDownloadCache extends DownloadCache {
private final File mCacheRoot;
/** Map url => payload bytes, http code response.
* If the payload pair is null, an exception such as FNF is thrown. */
private final Map<String, Payload> mDirectPayloads = new HashMap<String, Payload>();
/** Map url => payload bytes, http code response.
* If the payload pair is null, an exception such as FNF is thrown. */
private final Map<String, Payload> mCachedPayloads = new HashMap<String, Payload>();
private final Map<String, Integer> mDirectHits = new TreeMap<String, Integer>();
private final Map<String, Integer> mCachedHits = new TreeMap<String, Integer>();
private Strategy mOverrideStrategy;
public static final int THROW_FNF = -1;
/**
* Creates a download cache with a {@code DIRECT} strategy and
* no root {@code $HOME/.android} folder, which effectively disables the cache.
*/
public MockDownloadCache() {
super(DownloadCache.Strategy.DIRECT);
mCacheRoot = null;
}
/**
* Creates a download with the given strategy and the given cache root.
*/
public MockDownloadCache(DownloadCache.Strategy strategy, File cacheRoot) {
super(strategy);
mCacheRoot = cacheRoot;
}
@Override
protected File initCacheRoot() {
return mCacheRoot;
}
/**
* Override the {@link DownloadCache.Strategy} of the cache.
* This lets us set it temporarily to {@link DownloadCache.Strategy#ONLY_CACHE},
* which will force {@link #openCachedUrl(String, ITaskMonitor)} to throw an FNF,
* essentially simulating an empty cache at first.
* <p/>
* Setting it back to null reverts the behavior to its default.
*/
public void overrideStrategy(DownloadCache.Strategy strategy) {
mOverrideStrategy = strategy;
}
/**
* Register a direct payload response.
*
* @param url The URL to match.
* @param httpCode The expected response code.
* Use {@link #THROW_FNF} to mean an FNF should be thrown (which is what the
* httpClient stack seems to return instead of {@link HttpStatus#SC_NOT_FOUND}.)
* @param content The payload to return.
* As a shortcut a null will be replaced by an empty byte array.
*/
public void registerDirectPayload(String url, int httpCode, byte[] content) {
mDirectPayloads.put(url, new Payload(httpCode, content));
}
/**
* Register a cached payload response.
*
* @param url The URL to match.
* @param content The payload to return or null to throw a FNF.
*/
public void registerCachedPayload(String url, byte[] content) {
mCachedPayloads.put(url,
new Payload(content == null ? THROW_FNF : HttpStatus.SC_OK, content));
}
public String[] getDirectHits() {
ArrayList<String> list = new ArrayList<String>();
synchronized (mDirectHits) {
for (Entry<String, Integer> entry : mDirectHits.entrySet()) {
list.add(String.format("<%1$s : %2$d>",
entry.getKey(), entry.getValue().intValue()));
}
}
return list.toArray(new String[list.size()]);
}
public String[] getCachedHits() {
ArrayList<String> list = new ArrayList<String>();
synchronized (mCachedHits) {
for (Entry<String, Integer> entry : mCachedHits.entrySet()) {
list.add(String.format("<%1$s : %2$d>",
entry.getKey(), entry.getValue().intValue()));
}
}
return list.toArray(new String[list.size()]);
}
public void clearDirectHits() {
synchronized (mDirectHits) {
mDirectHits.clear();
}
}
public void clearCachedHits() {
synchronized (mCachedHits) {
mCachedHits.clear();
}
}
/**
* Override openDirectUrl to return one of the registered payloads or throw a FNF exception.
* This totally ignores the cache's {@link DownloadCache.Strategy}.
*/
@Override
public Pair<InputStream, HttpResponse> openDirectUrl(
@NonNull String urlString,
@Nullable Header[] headers,
@NonNull ITaskMonitor monitor) throws IOException, CanceledByUserException {
synchronized (mDirectHits) {
Integer count = mDirectHits.get(urlString);
mDirectHits.put(urlString, (count == null ? 0 : count.intValue()) + 1);
}
Payload payload = mDirectPayloads.get(urlString);
if (payload == null || payload.mHttpCode == THROW_FNF) {
throw new FileNotFoundException(urlString);
}
byte[] content = payload.mContent;
if (content == null) {
content = new byte[0];
}
InputStream is = new ByteArrayInputStream(content);
HttpResponse hr = new BasicHttpResponse(
new ProtocolVersion("HTTP", 1, 1),
payload.mHttpCode,
"Http-Code-" + payload.mHttpCode);
return Pair.of(is, hr);
}
/**
* Override openCachedUrl to return one of the registered payloads or throw a FNF exception.
* This totally ignores the cache's {@link DownloadCache.Strategy}.
* It will however throw a FNF if {@link #overrideStrategy(Strategy)} is set to
* {@link DownloadCache.Strategy#ONLY_CACHE}.
*/
@Override
public InputStream openCachedUrl(String urlString, ITaskMonitor monitor)
throws IOException, CanceledByUserException {
synchronized (mCachedHits) {
Integer count = mCachedHits.get(urlString);
mCachedHits.put(urlString, (count == null ? 0 : count.intValue()) + 1);
}
if (Strategy.ONLY_CACHE.equals(mOverrideStrategy)) {
// Override the cache to read only "local cached" data.
// In this first phase, we assume there's nothing cached.
// TODO register first-pass files later.
throw new FileNotFoundException(urlString);
}
Payload payload = mCachedPayloads.get(urlString);
if (payload == null || payload.mHttpCode != HttpStatus.SC_OK) {
throw new FileNotFoundException(urlString);
}
byte[] content = payload.mContent;
if (content == null) {
content = new byte[0];
}
return new ByteArrayInputStream(content);
}
private static class Payload {
final byte[] mContent;
final int mHttpCode;
Payload(int httpCode, byte[] content) {
mHttpCode = httpCode;
mContent = content;
}
}
}