diff options
author | Raphael Moll <ralf@android.com> | 2012-06-15 11:24:51 -0700 |
---|---|---|
committer | Raphael Moll <ralf@android.com> | 2012-06-15 12:54:21 -0700 |
commit | f453507f61785ebb5594b5cdb867286f9e848fdb (patch) | |
tree | c90d3d10fe613830919f80a121a859754dd9cd67 | |
parent | d2e6f894cfe4df9ab8fdfb44114b245115e58645 (diff) | |
download | sdk-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
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;
+ }
+ }
+}
|