Make MimeMap final and introduce MimeMap.Builder.
This CL topic introduces a new @CorePlatformApi MimeMap.Builder
and uses it to make MimeMap a concrete, final, immutable type.
This has the following advantages:
- Consistency of behavior of MimeMap implementations with regards
to lower-casing and treatment of null is trivial to guarantee
because there is only one implementation.
- The @CorePlatformApi surface now makes more sense. The responsibility
for lower-casing and treatment of null was previously split between
MimeMap in libcore and MimeMapImpl in frameworks/base, which is why
MimeMap.toLowerCase() and MimeMap.isNullOrEmpty() were in the
@CorePlatformApi.
- Most of the logic now lives in libcore / ART module.
frameworks/base now has minimal logic. This makes it easier to write
(in a follow-up CL) a CTS test that asserts all the default mappings,
because that test can now duplicate that small amount of logic in
order to read from a copy of the same data files.
Note: The semantics of the @CorePlatformApi Builder.put(String, List<String>)
are fairly complex, which isn't great. This was done because:
- By following the semantics of the *mime.types file format, it allows
us to minimize the logic in frameworks/base.
- It's simpler than having multiple overloads of put() for
mimeType -> file extension mapping and vice versa,
and for whether or not any existing mapping should be overwritten.
If we had named classes for MimeType and FileExtension with
appropriate case-insensitive equals and hashCode semantics, then
we could instead have API such as
builder.asMimeToExtensionMap().put(...)
but existing API (e.g. Intent.getType(), android.webkit.MimeTypeMap)
has set precedent for treating these as Strings.
Bug: 136256059
Test: atest CtsLibcoreTestCases
Test: atest CtsMimeMapTestCases
Change-Id: I9a185a689745726dd79b15117892001461fa4a0d
diff --git a/mime/java/android/content/type/MimeMapImpl.java b/mime/java/android/content/type/MimeMapImpl.java
index c904ea3..3671603 100644
--- a/mime/java/android/content/type/MimeMapImpl.java
+++ b/mime/java/android/content/type/MimeMapImpl.java
@@ -21,10 +21,8 @@
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
-import java.util.ArrayList;
-import java.util.HashMap;
+import java.util.Arrays;
import java.util.List;
-import java.util.Map;
import java.util.regex.Pattern;
/**
@@ -36,96 +34,27 @@
*
* @hide
*/
-public class MimeMapImpl extends MimeMap {
+public class MimeMapImpl {
/**
* Creates and returns a new {@link MimeMapImpl} instance that implements.
* Android's default mapping between MIME types and extensions.
*/
- public static MimeMapImpl createDefaultInstance() {
+ public static MimeMap createDefaultInstance() {
return parseFromResources("/mime.types", "/android.mime.types");
}
private static final Pattern SPLIT_PATTERN = Pattern.compile("\\s+");
- /**
- * Note: These maps only contain lowercase keys/values, regarded as the
- * {@link #toLowerCase(String) canonical form}.
- *
- * <p>This is the case for both extensions and MIME types. The mime.types
- * data file contains examples of mixed-case MIME types, but some applications
- * use the lowercase version of these same types. RFC 2045 section 2 states
- * that MIME types are case insensitive.
- */
- private final Map<String, String> mMimeTypeToExtension;
- private final Map<String, String> mExtensionToMimeType;
-
- public MimeMapImpl(Map<String, String> mimeTypeToExtension,
- Map<String, String> extensionToMimeType) {
- this.mMimeTypeToExtension = new HashMap<>(mimeTypeToExtension);
- for (Map.Entry<String, String> entry : mimeTypeToExtension.entrySet()) {
- checkValidMimeType(entry.getKey());
- checkValidExtension(entry.getValue());
- }
- this.mExtensionToMimeType = new HashMap<>(extensionToMimeType);
- for (Map.Entry<String, String> entry : extensionToMimeType.entrySet()) {
- checkValidExtension(entry.getKey());
- checkValidMimeType(entry.getValue());
- }
- }
-
- private static void checkValidMimeType(String s) {
- if (MimeMap.isNullOrEmpty(s) || !s.equals(MimeMap.toLowerCase(s))) {
- throw new IllegalArgumentException("Invalid MIME type: " + s);
- }
- }
-
- private static void checkValidExtension(String s) {
- if (MimeMap.isNullOrEmpty(s) || !s.equals(MimeMap.toLowerCase(s))) {
- throw new IllegalArgumentException("Invalid extension: " + s);
- }
- }
-
- static MimeMapImpl parseFromResources(String... resourceNames) {
- Map<String, String> mimeTypeToExtension = new HashMap<>();
- Map<String, String> extensionToMimeType = new HashMap<>();
+ static MimeMap parseFromResources(String... resourceNames) {
+ MimeMap.Builder builder = MimeMap.builder();
for (String resourceName : resourceNames) {
- parseTypes(mimeTypeToExtension, extensionToMimeType, resourceName);
+ parseTypes(builder, resourceName);
}
- return new MimeMapImpl(mimeTypeToExtension, extensionToMimeType);
+ return builder.build();
}
- /**
- * An element of a *mime.types file: A MIME type or an extension, with an optional
- * prefix of "?" (if not overriding an earlier value).
- */
- private static class Element {
- public final boolean keepExisting;
- public final String s;
-
- Element(boolean keepExisting, String value) {
- this.keepExisting = keepExisting;
- this.s = toLowerCase(value);
- if (value.isEmpty()) {
- throw new IllegalArgumentException();
- }
- }
-
- public String toString() {
- return keepExisting ? ("?" + s) : s;
- }
- }
-
- private static String maybePut(Map<String, String> map, Element keyElement, String value) {
- if (keyElement.keepExisting) {
- return map.putIfAbsent(keyElement.s, value);
- } else {
- return map.put(keyElement.s, value);
- }
- }
-
- private static void parseTypes(Map<String, String> mimeTypeToExtension,
- Map<String, String> extensionToMimeType, String resource) {
+ private static void parseTypes(MimeMap.Builder builder, String resource) {
try (BufferedReader r = new BufferedReader(
new InputStreamReader(MimeMapImpl.class.getResourceAsStream(resource)))) {
String line;
@@ -135,60 +64,15 @@
line = line.substring(0, commentPos);
}
line = line.trim();
- // The first time a MIME type is encountered it is mapped to the first extension
- // listed in its line. The first time an extension is encountered it is mapped
- // to the MIME type.
- //
- // When encountering a previously seen MIME type or extension, then by default
- // the later ones override earlier mappings (put() semantics); however if a MIME
- // type or extension is prefixed with '?' then any earlier mapping _from_ that
- // MIME type / extension is kept (putIfAbsent() semantics).
- final String[] split = SPLIT_PATTERN.split(line);
- if (split.length <= 1) {
- // Need mimeType + at least one extension to make a mapping.
- // "mime.types" files may also contain lines with just a mimeType without
- // an extension but we skip them as they provide no mapping info.
+ if (line.isEmpty()) {
continue;
}
- List<Element> lineElements = new ArrayList<>(split.length);
- for (String s : split) {
- boolean keepExisting = s.startsWith("?");
- if (keepExisting) {
- s = s.substring(1);
- }
- if (s.isEmpty()) {
- throw new IllegalArgumentException("Invalid entry in '" + line + "'");
- }
- lineElements.add(new Element(keepExisting, s));
- }
-
- // MIME type -> first extension (one mapping)
- // This will override any earlier mapping from this MIME type to another
- // extension, unless this MIME type was prefixed with '?'.
- Element mimeElement = lineElements.get(0);
- List<Element> extensionElements = lineElements.subList(1, lineElements.size());
- String firstExtension = extensionElements.get(0).s;
- maybePut(mimeTypeToExtension, mimeElement, firstExtension);
-
- // extension -> MIME type (one or more mappings).
- // This will override any earlier mapping from this extension to another
- // MIME type, unless this extension was prefixed with '?'.
- for (Element extensionElement : extensionElements) {
- maybePut(extensionToMimeType, extensionElement, mimeElement.s);
- }
+ List<String> specs = Arrays.asList(SPLIT_PATTERN.split(line));
+ builder.put(specs.get(0), specs.subList(1, specs.size()));
}
} catch (IOException | RuntimeException e) {
throw new RuntimeException("Failed to parse " + resource, e);
}
}
- @Override
- protected String guessExtensionFromLowerCaseMimeType(String mimeType) {
- return mMimeTypeToExtension.get(mimeType);
- }
-
- @Override
- protected String guessMimeTypeFromLowerCaseExtension(String extension) {
- return mExtensionToMimeType.get(extension);
- }
}