diff options
author | Tobias Thierer <tobiast@google.com> | 2019-08-20 15:23:34 +0100 |
---|---|---|
committer | Tobias Thierer <tobiast@google.com> | 2019-09-27 17:02:31 +0100 |
commit | fd9657d0320f291fb96b58d7fd5cc46b59d35128 (patch) | |
tree | f88eaad5100f326fcfc875b6878f268ba140a6d5 /mime | |
parent | 878c77b70478463757b59e7c2719cc62f6604177 (diff) |
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
Diffstat (limited to 'mime')
-rw-r--r-- | mime/java/android/content/type/MimeMapImpl.java | 138 |
1 files changed, 11 insertions, 127 deletions
diff --git a/mime/java/android/content/type/MimeMapImpl.java b/mime/java/android/content/type/MimeMapImpl.java index c904ea3f9b60..367160367cc0 100644 --- a/mime/java/android/content/type/MimeMapImpl.java +++ b/mime/java/android/content/type/MimeMapImpl.java @@ -21,10 +21,8 @@ import libcore.net.MimeMap; 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 @@ import java.util.regex.Pattern; * * @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); - } - return new MimeMapImpl(mimeTypeToExtension, extensionToMimeType); - } - - /** - * 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); + parseTypes(builder, resourceName); } + return builder.build(); } - 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 @@ public class MimeMapImpl extends MimeMap { 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); - } } |