android.mime.types: Allow one-way mime -> ext only override.

Lines in android.mime.types define mappings between MIME types
and extensions; before this CL, MIME -> extension would override
earlier mappings if and only if the first extension was marked
with the suffix '!', as in:

  mime ext1! ext2 ext3

extension -> MIME mappings would always override earlier lines
(in the above example, each of {ext1, ext2, ext3} would be mapped
to mime); there was no opt-out.

After this CL, it's possible to choose between override (put()
semantics) vs. keeping existing mappings (putIfAbsent() semantics) in
both directions. This is now expressed by putting a '?' in front of
the value _from_ which one maps. For example, the line:

  mime ?ext1 ?ext2 ext3

will put a mapping
 ext3 -> mime
that overrides any earlier mapping, but it will putIfAbsent
the mappings:
 ext1 -> mime
 ext2 -> mime
(leaving any mapping untouched that was added earlier).

This CL rewrites android.mime.types to express the same semantics
as before in the new syntax. Therefore, this CL is a pure
refactoring and doesn't change the mapping. Follow-up CLs will make
use of the new ability to express one-way mime -> ext overrides (e.g.
for b/131913690).

Note that each MIME type occurs at most once in the file mime.types,
so the change in semantics (putIfAbsent vs. put) of
  mime ext1 ext2 ext3
does not affect the semantics of that file.

Test: Checked that behavior didn't change by running the test
      logic attached to http://b/122831291#comment14

Change-Id: Ie813ca46b4690f7e0e01bb399416f56654ef08ef
diff --git a/luni/src/main/java/libcore/net/MimeMapImpl.java b/luni/src/main/java/libcore/net/MimeMapImpl.java
index a384da5..225f0e5 100644
--- a/luni/src/main/java/libcore/net/MimeMapImpl.java
+++ b/luni/src/main/java/libcore/net/MimeMapImpl.java
@@ -19,7 +19,9 @@
 import java.io.BufferedReader;
 import java.io.IOException;
 import java.io.InputStreamReader;
+import java.util.ArrayList;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
 import java.util.regex.Pattern;
 
@@ -74,6 +76,35 @@
         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;
+
+        public 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) {
         try (BufferedReader r = new BufferedReader(
@@ -85,46 +116,46 @@
                     line = line.substring(0, commentPos);
                 }
                 line = line.trim();
-                if (line.equals("")) {
+                // 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 = splitPattern.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.
                     continue;
                 }
-
-                final String[] split = splitPattern.split(line);
-                final String mimeType = toLowerCase(split[0]);
-                if (isNullOrEmpty(mimeType)) {
-                    throw new IllegalArgumentException(
-                            "Invalid mimeType " + mimeType + " in: " + line);
+                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));
                 }
-                for (int i = 1; i < split.length; i++) {
-                    String extension = toLowerCase(split[i]);
-                    if (isNullOrEmpty(extension)) {
-                        throw new IllegalArgumentException(
-                                "Invalid extension " + extension + " in: " + line);
-                    }
 
-                    // Normally the first MIME type definition wins, and the
-                    // last extension definition wins. However, a file can
-                    // override a MIME type definition by adding the "!" suffix
-                    // to an extension.
+                // 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);
 
-                    if (extension.endsWith("!")) {
-                        if (i != 1) {
-                            throw new IllegalArgumentException(mimeType + ": override " +
-                                    extension + " must be listed first.");
-                        }
-                        extension = extension.substring(0, extension.length() - 1);
-
-                        // Overriding MIME definition wins
-                        mimeTypeToExtension.put(mimeType, extension);
-                    } else {
-                        // First MIME definition wins
-                        if (!mimeTypeToExtension.containsKey(mimeType)) {
-                            mimeTypeToExtension.put(mimeType, extension);
-                        }
-                    }
-
-                    // Last extension definition wins
-                    extensionToMimeType.put(extension, mimeType);
+                // 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);
                 }
             }
         } catch (IOException e) {
diff --git a/luni/src/main/java/libcore/net/android.mime.types b/luni/src/main/java/libcore/net/android.mime.types
index 856b09e..c192498 100644
--- a/luni/src/main/java/libcore/net/android.mime.types
+++ b/luni/src/main/java/libcore/net/android.mime.types
@@ -1,95 +1,141 @@
 
 ###############################################################################
 #
-#  Android-specific MIME type mappings
+# Android-specific MIME type <-> extension mappings
 #
-#  MIME types that Android has manually added or historically chosen to
-#  override, and which take precidence over any upstream mime.types.
+# Each line below defines an mapping from one MIME type to the first of the
+# listed extensions, and from listed extension back to the MIME type.
+# A mapping overrides any previous mapping _from_ that same MIME type or
+# extension (put() semantics), unless that MIME type / extension is prefixed with '?'
+# (putIfAbsent() semantics).
+#
+#
+###############################################################################
+#
+# EXAMPLES
+#
+# A line of the form:
+#
+#    ?mime ext1 ?ext2 ext3
+#
+# affects the current mappings along the lines of the following pseudo code:
+#
+#    mimeToExt.putIfAbsent("mime", "ext1");
+#    extToMime.put("ext1", "mime");
+#    extToMime.putIfAbsent("ext2", "mime");
+#    extToMime.put("ext3", "mime");
+#
+# The line:
+#
+#     ?text/plain txt
+#
+# leaves any earlier mapping for "text/plain" untouched, or maps that MIME type
+# to the file extension ".txt" if there is no earlier mapping. The line also
+# sets the mapping from file extension ".txt" to be the MIME type "text/plain",
+# regardless of whether a previous mapping existed.
 #
 ###############################################################################
 
-application/epub+zip epub
-application/pkix-cert cer
-application/rss+xml rss
-application/vnd.apple.mpegurl m3u8
-application/vnd.ms-pki.stl stl
-application/vnd.ms-powerpoint pot
-application/vnd.ms-wpl wpl
-application/vnd.stardivision.impress sdp
-application/vnd.stardivision.writer vor
-application/vnd.youtube.yt yt
-application/x-android-drm-fl fl
-application/x-flac flac
-application/x-font pcf
-application/x-mpegurl m3u m3u8
-application/x-pem-file pem
-application/x-pkcs12 p12 pfx
-application/x-webarchive webarchive
-application/x-webarchive-xml webarchivexml
-application/x-x509-server-cert crt
-application/x-x509-user-cert crt
 
-audio/3gpp 3gpp
-audio/aac-adts aac
-audio/imelody imy
-audio/midi ota rtttl xmf
-audio/mobile-xmf mxmf
-audio/mp4 m4a
-audio/mpegurl m3u
-audio/sp-midi smf
-audio/x-matroska mka
-audio/x-pn-realaudio ra
+# File extensions that Android wants to override to point to the given MIME type.
+#
+# After processing a line of the form:
+# ?<mimeType> <extension1> <extension2>
+# If <mimeType> was not already mapped to an extension then it will be
+# mapped to <extension1>.
+# <extension1> and <extension2> are mapped (or remapped) to <mimeType>.
 
-image/bmp bmp
-image/heic heic
-image/heic-sequence heics
-image/heif heif hif
-image/heif-sequence heifs
-image/ico cur
-image/webp webp
-image/x-adobe-dng dng
-image/x-fuji-raf raf
-image/x-icon ico
-image/x-nikon-nrw nrw
-image/x-panasonic-rw2 rw2
-image/x-pentax-pef pef
-image/x-samsung-srw srw
-image/x-sony-arw arw
+?application/epub+zip epub
+?application/pkix-cert cer
+?application/rss+xml rss
+?application/vnd.apple.mpegurl m3u8
+?application/vnd.ms-pki.stl stl
+?application/vnd.ms-powerpoint pot
+?application/vnd.ms-wpl wpl
+?application/vnd.stardivision.impress sdp
+?application/vnd.stardivision.writer vor
+?application/vnd.youtube.yt yt
+?application/x-android-drm-fl fl
+?application/x-flac flac
+?application/x-font pcf
+?application/x-mpegurl m3u m3u8
+?application/x-pem-file pem
+?application/x-pkcs12 p12 pfx
+?application/x-webarchive webarchive
+?application/x-webarchive-xml webarchivexml
+?application/x-x509-server-cert crt
+?application/x-x509-user-cert crt
 
-text/comma-separated-values csv
-text/plain diff po
-text/rtf rtf
-text/text phps
-text/xml xml
-text/x-vcard vcf
+?audio/3gpp 3gpp
+?audio/aac-adts aac
+?audio/imelody imy
+?audio/midi ota rtttl xmf
+?audio/mobile-xmf mxmf
+?audio/mp4 m4a
+?audio/mpegurl m3u
+?audio/sp-midi smf
+?audio/x-matroska mka
+?audio/x-pn-realaudio ra
 
-video/3gpp2 3gpp2 3g2
-video/3gpp 3gpp
-video/avi avi
-video/m4v m4v
-video/mp2p mpeg
-video/mp2t m2ts mts
-video/mp2ts ts
-video/x-webex wrf
+?image/bmp bmp
+?image/heic heic
+?image/heic-sequence heics
+?image/heif heif hif
+?image/heif-sequence heifs
+?image/ico cur
+?image/webp webp
+?image/x-adobe-dng dng
+?image/x-fuji-raf raf
+?image/x-icon ico
+?image/x-nikon-nrw nrw
+?image/x-panasonic-rw2 rw2
+?image/x-pentax-pef pef
+?image/x-samsung-srw srw
+?image/x-sony-arw arw
+
+?text/comma-separated-values csv
+?text/plain diff po
+?text/rtf rtf
+?text/text phps
+?text/xml xml
+?text/x-vcard vcf
+
+?video/3gpp2 3gpp2 3g2
+?video/3gpp 3gpp
+?video/avi avi
+?video/m4v m4v
+?video/mp2p mpeg
+?video/mp2t m2ts mts
+?video/mp2ts ts
+?video/vnd.youtube.yt yt
+?video/x-webex wrf
 
 # Special cases where Android has a strong opinion about mappings, so we
-# define them very last and use "!" to ensure that we force the mapping
-# in both directions.
-application/pgp-signature pgp!
-application/x-x509-ca-cert crt!
-audio/aac aac!
-audio/basic snd!
-audio/flac flac!
-audio/midi rtx!
-audio/mpeg mp3! m4a m4r
-audio/x-mpegurl m3u! m3u8
-image/jpeg jpg!
-image/x-ms-bmp bmp!
-text/plain txt!
-text/x-c++hdr hpp!
-text/x-c++src cpp!
-video/3gpp 3gpp!
-video/mpeg mpeg!
-video/quicktime mov!
-video/vnd.youtube.yt yt
-video/x-matroska mkv!
+# define them very last and make them override in both directions (no "?").
+#
+# Lines here are of the form:
+# <mimeType> <extension1> <extension2> ...
+#
+# After processing each line,
+#   <mimeType> is mapped to <extension1>
+#   <extension1>, <extension2>, ... are all mapped to <mimeType>
+# This overrides any mappings for this <mimeType> / for these extensions
+# that may have been defined earlier.
+
+application/pgp-signature pgp
+application/x-x509-ca-cert crt
+audio/aac aac
+audio/basic snd
+audio/flac flac
+audio/midi rtx
+audio/mpeg mp3 m4a m4r
+audio/x-mpegurl m3u m3u8
+image/jpeg jpg
+image/x-ms-bmp bmp
+text/plain txt
+text/x-c++hdr hpp
+text/x-c++src cpp
+video/3gpp 3gpp
+video/mpeg mpeg
+video/quicktime mov
+video/x-matroska mkv