1package de.gultsch.common;
2
3import android.net.Uri;
4import androidx.annotation.NonNull;
5import com.google.common.base.MoreObjects;
6import com.google.common.base.Splitter;
7import com.google.common.base.Strings;
8import com.google.common.collect.ImmutableMultimap;
9import java.io.UnsupportedEncodingException;
10import java.net.URLDecoder;
11import java.util.Collection;
12import java.util.Collections;
13import java.util.List;
14import java.util.Locale;
15import java.util.Map;
16
17public class MiniUri {
18
19 private static final String EMPTY_STRING = "";
20
21 private final String raw;
22 private final String scheme;
23 private final String authority;
24 private final String path;
25 private final Map<String, Collection<String>> parameter;
26
27 public MiniUri(final String uri) {
28 this.raw = uri;
29 final var schemeAndRest = Splitter.on(':').limit(2).splitToList(uri);
30 if (schemeAndRest.size() < 2) {
31 this.scheme = uri;
32 this.authority = null;
33 this.path = null;
34 this.parameter = Collections.emptyMap();
35 return;
36 }
37 this.scheme = schemeAndRest.get(0);
38 final var rest = schemeAndRest.get(1);
39 // TODO add fragment parser
40 final var authorityPathAndQuery = Splitter.on('?').limit(2).splitToList(rest);
41 final var authorityPath = authorityPathAndQuery.get(0);
42 System.out.println("authorityPath " + authorityPath);
43 if (authorityPath.length() >= 2 && authorityPath.startsWith("//")) {
44 final var authorityPathParts =
45 Splitter.on('/').limit(2).splitToList(authorityPath.substring(2));
46 this.authority = authorityPathParts.get(0);
47 this.path = authorityPathParts.size() == 2 ? authorityPathParts.get(1) : null;
48 } else {
49 this.authority = null;
50 // TODO path ; style path components from something like geo uri
51 this.path = authorityPath;
52 }
53 if (authorityPathAndQuery.size() == 2) {
54 this.parameter = parseParameters(authorityPathAndQuery.get(1), getDelimiter(scheme));
55 } else {
56 this.parameter = Collections.emptyMap();
57 }
58 }
59
60 private static char getDelimiter(final String scheme) {
61 return switch (scheme) {
62 case "xmpp", "geo" -> ';';
63 default -> '&';
64 };
65 }
66
67 private static Map<String, Collection<String>> parseParameters(
68 final String query, final char separator) {
69 final var builder = new ImmutableMultimap.Builder<String, String>();
70 for (final String pair : Splitter.on(separator).split(query)) {
71 final String[] parts = pair.split("=", 2);
72 if (parts.length == 0) {
73 continue;
74 }
75 final String key = parts[0].toLowerCase(Locale.US);
76 if (parts.length == 2) {
77 try {
78 builder.put(key, URLDecoder.decode(parts[1], "UTF-8"));
79 } catch (final UnsupportedEncodingException e) {
80 builder.put(key, EMPTY_STRING);
81 }
82 } else {
83 builder.put(key, EMPTY_STRING);
84 }
85 }
86 return builder.build().asMap();
87 }
88
89 @NonNull
90 @Override
91 public String toString() {
92 return MoreObjects.toStringHelper(this)
93 .add("scheme", scheme)
94 .add("authority", authority)
95 .add("path", path)
96 .add("parameter", parameter)
97 .toString();
98 }
99
100 public String getScheme() {
101 return this.scheme;
102 }
103
104 public String getAuthority() {
105 return this.authority;
106 }
107
108 public String getPath() {
109 return Strings.isNullOrEmpty(this.path) || this.authority == null
110 ? this.path
111 : '/' + this.path;
112 }
113
114 public List<String> getPathSegments() {
115 return Strings.isNullOrEmpty(this.path)
116 ? Collections.emptyList()
117 : Splitter.on('/').splitToList(this.path);
118 }
119
120 public String getRaw() {
121 return this.raw;
122 }
123
124 public Uri asUri() {
125 return Uri.parse(this.raw);
126 }
127
128 public Map<String, Collection<String>> getParameter() {
129 return this.parameter;
130 }
131}