aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorRaphael Moll <ralf@android.com>2012-06-15 11:24:51 -0700
committerRaphael Moll <ralf@android.com>2012-06-15 12:54:21 -0700
commitf453507f61785ebb5594b5cdb867286f9e848fdb (patch)
treec90d3d10fe613830919f80a121a859754dd9cd67
parentd2e6f894cfe4df9ab8fdfb44114b245115e58645 (diff)
downloadsdk-f453507f61785ebb5594b5cdb867286f9e848fdb.zip
sdk-f453507f61785ebb5594b5cdb867286f9e848fdb.tar.gz
sdk-f453507f61785ebb5594b5cdb867286f9e848fdb.tar.bz2
SDK Manager fix.
This change the XML fetcher to cope with: - input streams that do not support mark/reset when the caller expects one as such. - the XML parser/validator closing the input stream when the caller still needs it open. Change-Id: I5af24b7b8545f9c5f60a13d5dc06ebfc4bb9b0dd
-rwxr-xr-xsdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/AddonsListFetcher.java42
-rwxr-xr-xsdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/DownloadCache.java25
-rwxr-xr-xsdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/SdkStats.java34
-rw-r--r--sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/UrlOpener.java82
-rwxr-xr-xsdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/sources/SdkRepoSource.java1
-rwxr-xr-xsdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/sources/SdkSource.java43
-rwxr-xr-xsdkmanager/libs/sdklib/src/com/android/sdklib/io/NonClosingInputStream.java104
7 files changed, 291 insertions, 40 deletions
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/AddonsListFetcher.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/AddonsListFetcher.java
index 0880645..55021a3 100755
--- a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/AddonsListFetcher.java
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/AddonsListFetcher.java
@@ -18,6 +18,8 @@ package com.android.sdklib.internal.repository;
import com.android.annotations.VisibleForTesting;
import com.android.annotations.VisibleForTesting.Visibility;
+import com.android.sdklib.io.NonClosingInputStream;
+import com.android.sdklib.io.NonClosingInputStream.CloseBehavior;
import com.android.sdklib.repository.SdkAddonsListConstants;
import com.android.sdklib.repository.SdkRepoConstants;
@@ -127,10 +129,11 @@ public class AddonsListFetcher {
defaultNames[i] = SdkAddonsListConstants.getDefaultName(version);
}
- InputStream xml = fetchUrl(url, cache, monitor.createSubMonitor(1), exception);
+ InputStream xml = fetchXmlUrl(url, cache, monitor.createSubMonitor(1), exception);
if (xml != null) {
int version = getXmlSchemaVersion(xml);
if (version == 0) {
+ closeStream(xml);
xml = null;
}
}
@@ -153,10 +156,11 @@ public class AddonsListFetcher {
if (newUrl.equals(url)) {
continue;
}
- xml = fetchUrl(newUrl, cache, subMonitor.createSubMonitor(1), exception);
+ xml = fetchXmlUrl(newUrl, cache, subMonitor.createSubMonitor(1), exception);
if (xml != null) {
int version = getXmlSchemaVersion(xml);
if (version == 0) {
+ closeStream(xml);
xml = null;
} else {
url = newUrl;
@@ -190,6 +194,7 @@ public class AddonsListFetcher {
} else if (version > SdkAddonsListConstants.NS_LATEST_VERSION) {
// The schema used is more recent than what is supported by this tool.
// We don't have an upgrade-path support yet, so simply ignore the document.
+ closeStream(xml);
return null;
}
}
@@ -223,6 +228,7 @@ public class AddonsListFetcher {
// Stop here if we failed to validate the XML. We don't want to load it.
if (validatedDoc == null) {
+ closeStream(xml);
return null;
}
@@ -239,13 +245,13 @@ public class AddonsListFetcher {
// done
monitor.incProgress(1);
+ closeStream(xml);
return result;
}
/**
* Fetches the document at the given URL and returns it as a stream. Returns
- * null if anything wrong happens. References: <br/>
- * URL Connection:
+ * null if anything wrong happens.
*
* @param urlString The URL to load, as a string.
* @param monitor {@link ITaskMonitor} related to this URL.
@@ -253,12 +259,17 @@ public class AddonsListFetcher {
* happens during the fetch.
* @see UrlOpener UrlOpener, which handles all URL logic.
*/
- private InputStream fetchUrl(String urlString,
+ private InputStream fetchXmlUrl(String urlString,
DownloadCache cache,
ITaskMonitor monitor,
Exception[] outException) {
try {
- return cache.openCachedUrl(urlString, monitor);
+ InputStream xml = cache.openCachedUrl(urlString, monitor);
+ if (xml != null) {
+ xml.mark(500000);
+ xml = new NonClosingInputStream(xml).setCloseBehavior(CloseBehavior.RESET);
+ }
+ return xml;
} catch (Exception e) {
if (outException != null) {
outException[0] = e;
@@ -269,6 +280,21 @@ public class AddonsListFetcher {
}
/**
+ * Closes the stream, ignore any exception from InputStream.close().
+ * If the stream is a NonClosingInputStream, sets it to CloseBehavior.CLOSE first.
+ */
+ private void closeStream(InputStream is) {
+ if (is != null) {
+ if (is instanceof NonClosingInputStream) {
+ ((NonClosingInputStream) is).setCloseBehavior(CloseBehavior.CLOSE);
+ }
+ try {
+ is.close();
+ } catch (IOException ignore) {}
+ }
+ }
+
+ /**
* Manually parses the root element of the XML to extract the schema version
* at the end of the xmlns:sdk="http://schemas.android.com/sdk/android/addons-list/$N"
* declaration.
@@ -285,6 +311,7 @@ public class AddonsListFetcher {
// Get an XML document
Document doc = null;
try {
+ assert xml.markSupported();
xml.reset();
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
@@ -323,6 +350,7 @@ public class AddonsListFetcher {
// Failed to create XML document builder
// Failed to parse XML document
// Failed to read XML document
+ //--For debug--System.err.println("getXmlSchemaVersion exception: " + e.toString());
}
if (doc == null) {
@@ -403,6 +431,7 @@ public class AddonsListFetcher {
validatorFound[0] = Boolean.TRUE;
// Reset the stream if it supports that operation.
+ assert xml.markSupported();
xml.reset();
// Validation throws a bunch of possible Exceptions on failure.
@@ -462,6 +491,7 @@ public class AddonsListFetcher {
factory.setNamespaceAware(true);
DocumentBuilder builder = factory.newDocumentBuilder();
+ assert xml.markSupported();
xml.reset();
Document doc = builder.parse(new InputSource(xml));
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/DownloadCache.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/DownloadCache.java
index 8d7b47f..2f154a6 100755
--- a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/DownloadCache.java
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/DownloadCache.java
@@ -254,7 +254,7 @@ public class DownloadCache {
* Instead the HttpClient library returns a progressive download stream.
* <p/>
* For details on realm authentication and user/password handling,
- * check the underlying {@link UrlOpener#openUrl(String, ITaskMonitor, Header[])}
+ * check the underlying {@link UrlOpener#openUrl(String, boolean, ITaskMonitor, Header[])}
* documentation.
*
* @param urlString the URL string to be opened.
@@ -271,8 +271,11 @@ public class DownloadCache {
if (DEBUG) {
System.out.println(String.format("%s : Direct download", urlString)); //$NON-NLS-1$
}
- Pair<InputStream, HttpResponse> result =
- UrlOpener.openUrl(urlString, monitor, null /*headers*/);
+ Pair<InputStream, HttpResponse> result = UrlOpener.openUrl(
+ urlString,
+ false /*needsMarkResetSupport*/,
+ monitor,
+ null /*headers*/);
return result.getFirst();
}
@@ -286,7 +289,7 @@ public class DownloadCache {
* method.
* <p/>
* For details on realm authentication and user/password handling,
- * check the underlying {@link UrlOpener#openUrl(String, ITaskMonitor, Header[])}
+ * check the underlying {@link UrlOpener#openUrl(String, boolean, ITaskMonitor, Header[])}
* documentation.
*
* @param urlString the URL string to be opened.
@@ -301,9 +304,14 @@ public class DownloadCache {
*/
public InputStream openCachedUrl(String urlString, ITaskMonitor monitor)
throws IOException, CanceledByUserException {
- // Don't cache in direct mode. Don't try to cache non-http URLs.
- if (mStrategy == Strategy.DIRECT || !urlString.startsWith("http")) { //$NON-NLS-1$
- return openDirectUrl(urlString, monitor);
+ // Don't cache in direct mode.
+ if (mStrategy == Strategy.DIRECT) {
+ Pair<InputStream, HttpResponse> result = UrlOpener.openUrl(
+ urlString,
+ true /*needsMarkResetSupport*/,
+ monitor,
+ null /*headers*/);
+ return result.getFirst();
}
File cached = new File(mCacheRoot, getCacheFilename(urlString));
@@ -561,7 +569,8 @@ public class DownloadCache {
byte[] result = new byte[inc];
try {
- Pair<InputStream, HttpResponse> r = UrlOpener.openUrl(urlString, monitor, headers);
+ Pair<InputStream, HttpResponse> r =
+ UrlOpener.openUrl(urlString, true /*needsMarkResetSupport*/, monitor, headers);
is = r.getFirst();
HttpResponse response = r.getSecond();
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/SdkStats.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/SdkStats.java
index 4958505..60bb89d 100755
--- a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/SdkStats.java
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/SdkStats.java
@@ -18,6 +18,8 @@ package com.android.sdklib.internal.repository;
import com.android.annotations.VisibleForTesting;
import com.android.annotations.VisibleForTesting.Visibility;
+import com.android.sdklib.io.NonClosingInputStream;
+import com.android.sdklib.io.NonClosingInputStream.CloseBehavior;
import com.android.sdklib.repository.SdkStatsConstants;
import com.android.sdklib.util.SparseArray;
@@ -159,7 +161,7 @@ public class SdkStats {
Document validatedDoc = null;
String validatedUri = null;
- InputStream xml = fetchUrl(url, cache, monitor.createSubMonitor(1), exception);
+ InputStream xml = fetchXmlUrl(url, cache, monitor.createSubMonitor(1), exception);
if (xml != null) {
monitor.setDescription("Validate XML");
@@ -181,6 +183,7 @@ public class SdkStats {
} else if (version > SdkStatsConstants.NS_LATEST_VERSION) {
// The schema used is more recent than what is supported by this tool.
// We don't have an upgrade-path support yet, so simply ignore the document.
+ closeStream(xml);
return;
}
}
@@ -214,6 +217,7 @@ public class SdkStats {
// Stop here if we failed to validate the XML. We don't want to load it.
if (validatedDoc == null) {
+ closeStream(xml);
return;
}
@@ -227,12 +231,12 @@ public class SdkStats {
// done
monitor.incProgress(1);
+ closeStream(xml);
}
/**
* Fetches the document at the given URL and returns it as a stream. Returns
- * null if anything wrong happens. References: <br/>
- * URL Connection:
+ * null if anything wrong happens.
*
* @param urlString The URL to load, as a string.
* @param monitor {@link ITaskMonitor} related to this URL.
@@ -240,12 +244,17 @@ public class SdkStats {
* happens during the fetch.
* @see UrlOpener UrlOpener, which handles all URL logic.
*/
- private InputStream fetchUrl(String urlString,
+ private InputStream fetchXmlUrl(String urlString,
DownloadCache cache,
ITaskMonitor monitor,
Exception[] outException) {
try {
- return cache.openCachedUrl(urlString, monitor);
+ InputStream xml = cache.openCachedUrl(urlString, monitor);
+ if (xml != null) {
+ xml.mark(500000);
+ xml = new NonClosingInputStream(xml).setCloseBehavior(CloseBehavior.RESET);
+ }
+ return xml;
} catch (Exception e) {
if (outException != null) {
outException[0] = e;
@@ -256,6 +265,21 @@ public class SdkStats {
}
/**
+ * Closes the stream, ignore any exception from InputStream.close().
+ * If the stream is a NonClosingInputStream, sets it to CloseBehavior.CLOSE first.
+ */
+ private void closeStream(InputStream is) {
+ if (is != null) {
+ if (is instanceof NonClosingInputStream) {
+ ((NonClosingInputStream) is).setCloseBehavior(CloseBehavior.CLOSE);
+ }
+ try {
+ is.close();
+ } catch (IOException ignore) {}
+ }
+ }
+
+ /**
* Manually parses the root element of the XML to extract the schema version
* at the end of the xmlns:sdk="http://schemas.android.com/sdk/android/addons-list/$N"
* declaration.
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/UrlOpener.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/UrlOpener.java
index d31a286..5da92e9 100644
--- a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/UrlOpener.java
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/UrlOpener.java
@@ -41,6 +41,7 @@ import org.apache.http.protocol.BasicHttpContext;
import org.apache.http.protocol.HttpContext;
import java.io.BufferedInputStream;
+import java.io.ByteArrayInputStream;
import java.io.FileNotFoundException;
import java.io.FilterInputStream;
import java.io.IOException;
@@ -61,7 +62,7 @@ import java.util.Properties;
/**
* This class holds methods for adding URLs management.
- * @see #openUrl(String, ITaskMonitor, Header[])
+ * @see #openUrl(String, boolean, ITaskMonitor, Header[])
*/
public class UrlOpener {
@@ -121,13 +122,21 @@ public class UrlOpener {
* available in the memory cache.
*
* @param url the URL string to be opened.
- * @param monitor {@link ITaskMonitor} which is related to this URL
- * fetching.
+ * @param needsMarkResetSupport Indicates the caller <em>must</em> have an input stream that
+ * supports the mark/reset operations (as indicated by {@link InputStream#markSupported()}.
+ * Implementation detail: If the original stream does not, it will be fetched and wrapped
+ * into a {@link ByteArrayInputStream}. This can only work sanely if the resource is a
+ * small file that can fit in memory. It also means the caller has no chance of showing
+ * a meaningful download progress. If unsure, callers should set this to false.
+ * @param monitor {@link ITaskMonitor} to output status.
* @param headers An optional array of HTTP headers to use in the GET request.
- * @return Returns an {@link InputStream} holding the URL content and
- * the HttpResponse (locale, headers and an status line).
- * This never returns null; an exception is thrown instead in case of
- * error or if the user canceled an authentication dialog.
+ * @return Returns a {@link Pair} with {@code first} holding an {@link InputStream}
+ * and {@code second} holding an {@link HttpResponse}.
+ * The input stream can be null. The response is never null and contains
+ * at least a code; for http requests that provide them the response
+ * also contains locale, headers and an status line.
+ * The returned pair is never null.
+ * The caller must only accept the stream if the response code is 200 or similar.
* @throws IOException Exception thrown when there are problems retrieving
* the URL or its content.
* @throws CanceledByUserException Exception thrown if the user cancels the
@@ -135,6 +144,7 @@ public class UrlOpener {
*/
static @NonNull Pair<InputStream, HttpResponse> openUrl(
@NonNull String url,
+ boolean needsMarkResetSupport,
@NonNull ITaskMonitor monitor,
@Nullable Header[] headers)
throws IOException, CanceledByUserException {
@@ -154,15 +164,60 @@ public class UrlOpener {
}
}
+ // If the caller requires an InputStream that supports mark/reset, let's
+ // make sure we have such a stream.
+ if (result != null && needsMarkResetSupport) {
+ InputStream is = result.getFirst();
+ if (is != null) {
+ if (!is.markSupported()) {
+ try {
+ // Consume the whole input stream and offer a byte array stream instead.
+ // This can only work sanely if the resource is a small file that can
+ // fit in memory. It also means the caller has no chance of showing
+ // a meaningful download progress.
+ InputStream is2 = toByteArrayInputStream(is);
+ if (is2 != null) {
+ result = Pair.of(is2, result.getSecond());
+ try {
+ is.close();
+ } catch (Exception ignore) {}
+ }
+ } catch (Exception e3) {
+ // Ignore. If this can't work, caller will fail later.
+ }
+ }
+ }
+ }
+
if (result == null) {
HttpResponse outResponse = new BasicHttpResponse(
- new ProtocolVersion("HTTP", 1, 0),
- 404, ""); //$NON-NLS-1$;
+ new ProtocolVersion("HTTP", 1, 0), //$NON-NLS-1$
+ 424, ""); //$NON-NLS-1$; // 424=Method Failure
result = Pair.of(null, outResponse);
}
+
return result;
}
+ // ByteArrayInputStream is the duct tape of input streams.
+ private static InputStream toByteArrayInputStream(InputStream is) throws IOException {
+ int inc = 4096;
+ int curr = 0;
+ byte[] result = new byte[inc];
+
+ int n;
+ while ((n = is.read(result, curr, result.length - curr)) != -1) {
+ curr += n;
+ if (curr == result.length) {
+ byte[] temp = new byte[curr + inc];
+ System.arraycopy(result, 0, temp, 0, curr);
+ result = temp;
+ }
+ }
+
+ return new ByteArrayInputStream(result, 0, curr);
+ }
+
private static Pair<InputStream, HttpResponse> openWithUrl(
String url,
Header[] inHeaders) throws IOException {
@@ -257,9 +312,10 @@ public class UrlOpener {
if (DEBUG) {
try {
+ URI uri = new URI(url);
ProxySelector sel = routePlanner.getProxySelector();
- if (sel != null) {
- List<Proxy> list = sel.select(new URI(url));
+ if (sel != null && uri.getScheme().startsWith("httP")) { //$NON-NLS-1$
+ List<Proxy> list = sel.select(uri);
System.out.printf(
"SdkLib.UrlOpener:\n Connect to: %s\n Proxy List: %s\n", //$NON-NLS-1$
url,
@@ -267,7 +323,7 @@ public class UrlOpener {
}
} catch (Exception e) {
System.out.printf(
- "SdkLib.UrlOpener: Failed to get proxy info for %s: %s\n", //$NON-NLS-1$
+ "SdkLib.UrlOpener: Failed to get proxy info for %s: %s\n", //$NON-NLS-1$
url, e.toString());
}
}
@@ -280,7 +336,7 @@ public class UrlOpener {
int statusCode = response.getStatusLine().getStatusCode();
if (DEBUG) {
- System.out.printf(" Status: %d", statusCode); //$NON-NLS-1$
+ System.out.printf(" Status: %d\n", statusCode); //$NON-NLS-1$
}
// check whether any authentication is required
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/sources/SdkRepoSource.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/sources/SdkRepoSource.java
index 9fe5574..3ad809a 100755
--- a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/sources/SdkRepoSource.java
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/sources/SdkRepoSource.java
@@ -181,6 +181,7 @@ public class SdkRepoSource extends SdkSource {
}
// Reset the stream if it supports that operation.
+ assert xml.markSupported();
xml.reset();
// Get an XML document
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/sources/SdkSource.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/sources/SdkSource.java
index 8187480..a0d515a 100755
--- a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/sources/SdkSource.java
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/sources/SdkSource.java
@@ -34,6 +34,8 @@ import com.android.sdklib.internal.repository.packages.SamplePackage;
import com.android.sdklib.internal.repository.packages.SourcePackage;
import com.android.sdklib.internal.repository.packages.SystemImagePackage;
import com.android.sdklib.internal.repository.packages.ToolPackage;
+import com.android.sdklib.io.NonClosingInputStream;
+import com.android.sdklib.io.NonClosingInputStream.CloseBehavior;
import com.android.sdklib.repository.RepoConstants;
import com.android.sdklib.repository.SdkAddonConstants;
import com.android.sdklib.repository.SdkRepoConstants;
@@ -358,10 +360,11 @@ public abstract class SdkSource implements IDescription, Comparable<SdkSource> {
String[] defaultNames = getDefaultXmlFileUrls();
String firstDefaultName = defaultNames.length > 0 ? defaultNames[0] : "";
- InputStream xml = fetchUrl(url, cache, monitor.createSubMonitor(1), exception);
+ InputStream xml = fetchXmlUrl(url, cache, monitor.createSubMonitor(1), exception);
if (xml != null) {
int version = getXmlSchemaVersion(xml);
if (version == 0) {
+ closeStream(xml);
xml = null;
}
}
@@ -385,10 +388,11 @@ public abstract class SdkSource implements IDescription, Comparable<SdkSource> {
if (newUrl.equals(url)) {
continue;
}
- xml = fetchUrl(newUrl, cache, subMonitor.createSubMonitor(1), exception);
+ xml = fetchXmlUrl(newUrl, cache, subMonitor.createSubMonitor(1), exception);
if (xml != null) {
int version = getXmlSchemaVersion(xml);
if (version == 0) {
+ closeStream(xml);
xml = null;
} else {
url = newUrl;
@@ -414,7 +418,7 @@ public abstract class SdkSource implements IDescription, Comparable<SdkSource> {
}
url += firstDefaultName;
- xml = fetchUrl(url, cache, monitor.createSubMonitor(1), exception);
+ xml = fetchXmlUrl(url, cache, monitor.createSubMonitor(1), exception);
usingAlternateUrl = true;
} else {
monitor.incProgress(1);
@@ -487,7 +491,8 @@ public abstract class SdkSource implements IDescription, Comparable<SdkSource> {
}
url += firstDefaultName;
- xml = fetchUrl(url, cache, subMonitor.createSubMonitor(1),
+ closeStream(xml);
+ xml = fetchXmlUrl(url, cache, subMonitor.createSubMonitor(1),
null /* outException */);
subMonitor.incProgress(1);
// Loop to try the alternative document
@@ -589,6 +594,7 @@ public abstract class SdkSource implements IDescription, Comparable<SdkSource> {
// done
monitor.incProgress(1);
+ closeStream(xml);
}
private void setDefaultDescription() {
@@ -610,8 +616,6 @@ public abstract class SdkSource implements IDescription, Comparable<SdkSource> {
/**
* Fetches the document at the given URL and returns it as a string. Returns
* null if anything wrong happens and write errors to the monitor.
- * References: <br/>
- * URL Connection:
*
* @param urlString The URL to load, as a string.
* @param monitor {@link ITaskMonitor} related to this URL.
@@ -619,12 +623,17 @@ public abstract class SdkSource implements IDescription, Comparable<SdkSource> {
* happens during the fetch.
* @see UrlOpener UrlOpener, which handles all URL logic.
*/
- private InputStream fetchUrl(String urlString,
+ private InputStream fetchXmlUrl(String urlString,
DownloadCache cache,
ITaskMonitor monitor,
Exception[] outException) {
try {
- return cache.openCachedUrl(urlString, monitor);
+ InputStream xml = cache.openCachedUrl(urlString, monitor);
+ if (xml != null) {
+ xml.mark(500000);
+ xml = new NonClosingInputStream(xml).setCloseBehavior(CloseBehavior.RESET);
+ }
+ return xml;
} catch (Exception e) {
if (outException != null) {
outException[0] = e;
@@ -635,6 +644,21 @@ public abstract class SdkSource implements IDescription, Comparable<SdkSource> {
}
/**
+ * Closes the stream, ignore any exception from InputStream.close().
+ * If the stream is a NonClosingInputStream, sets it to CloseBehavior.CLOSE first.
+ */
+ private void closeStream(InputStream is) {
+ if (is != null) {
+ if (is instanceof NonClosingInputStream) {
+ ((NonClosingInputStream) is).setCloseBehavior(CloseBehavior.CLOSE);
+ }
+ try {
+ is.close();
+ } catch (IOException ignore) {}
+ }
+ }
+
+ /**
* Validates this XML against one of the requested SDK Repository schemas.
* If the XML was correctly validated, returns the schema that worked.
* If it doesn't validate, returns null and stores the error in outError[0].
@@ -662,6 +686,7 @@ public abstract class SdkSource implements IDescription, Comparable<SdkSource> {
validatorFound[0] = Boolean.TRUE;
// Reset the stream if it supports that operation.
+ assert xml.markSupported();
xml.reset();
// Validation throws a bunch of possible Exceptions on failure.
@@ -702,6 +727,7 @@ public abstract class SdkSource implements IDescription, Comparable<SdkSource> {
// Get an XML document
Document doc = null;
try {
+ assert xml.markSupported();
xml.reset();
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
@@ -946,6 +972,7 @@ public abstract class SdkSource implements IDescription, Comparable<SdkSource> {
factory.setNamespaceAware(true);
DocumentBuilder builder = factory.newDocumentBuilder();
+ assert xml.markSupported();
xml.reset();
Document doc = builder.parse(new InputSource(xml));
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/io/NonClosingInputStream.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/io/NonClosingInputStream.java
new file mode 100755
index 0000000..e21e47a
--- /dev/null
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/io/NonClosingInputStream.java
@@ -0,0 +1,104 @@
+/*
+ * 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.io;
+
+import com.android.annotations.NonNull;
+
+import java.io.FilterInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+
+/**
+ * Wraps an {@link InputStream} to change its closing behavior:
+ * this makes it possible to ignore close operations or have them perform a
+ * {@link InputStream#reset()} instead (if supported by the underlying stream)
+ * or plain ignored.
+ */
+public class NonClosingInputStream extends FilterInputStream {
+
+ private final InputStream mInputStream;
+ private CloseBehavior mCloseBehavior = CloseBehavior.CLOSE;
+
+ public enum CloseBehavior {
+ /**
+ * The behavior of {@link NonClosingInputStream#close()} is to close the
+ * underlying input stream. This is the default.
+ */
+ CLOSE,
+ /**
+ * The behavior of {@link NonClosingInputStream#close()} is to ignore the
+ * close request and do nothing.
+ */
+ IGNORE,
+ /**
+ * The behavior of {@link NonClosingInputStream#close()} is to call
+ * {@link InputStream#reset()} on the underlying stream. This will
+ * only succeed if the underlying stream supports it, e.g. it must
+ * have {@link InputStream#markSupported()} return true <em>and</em>
+ * the caller should have called {@link InputStream#mark(int)} at some
+ * point before.
+ */
+ RESET
+ }
+
+ /**
+ * Wraps an existing stream into this filtering stream.
+ * @param in A non-null input stream.
+ */
+ public NonClosingInputStream(@NonNull InputStream in) {
+ super(in);
+ mInputStream = in;
+ }
+
+ /**
+ * Returns the current {@link CloseBehavior}.
+ * @return the current {@link CloseBehavior}. Never null.
+ */
+ public @NonNull CloseBehavior getCloseBehavior() {
+ return mCloseBehavior;
+ }
+
+ /**
+ * Changes the current {@link CloseBehavior}.
+ *
+ * @param closeBehavior A new non-null {@link CloseBehavior}.
+ * @return Self for chaining.
+ */
+ public NonClosingInputStream setCloseBehavior(@NonNull CloseBehavior closeBehavior) {
+ mCloseBehavior = closeBehavior;
+ return this;
+ }
+
+ /**
+ * Performs the requested {@code close()} operation, depending on the current
+ * {@link CloseBehavior}.
+ */
+ @Override
+ public void close() throws IOException {
+ switch (mCloseBehavior) {
+ case IGNORE:
+ break;
+ case RESET:
+ mInputStream.reset();
+ break;
+ case CLOSE:
+ mInputStream.close();
+ break;
+ }
+ }
+}