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