1+ /*-
2+ * #%L
3+ * URL scheme handlers for SciJava.
4+ * %%
5+ * Copyright (C) 2023 - 2025 SciJava developers.
6+ * %%
7+ * Redistribution and use in source and binary forms, with or without
8+ * modification, are permitted provided that the following conditions are met:
9+ *
10+ * 1. Redistributions of source code must retain the above copyright notice,
11+ * this list of conditions and the following disclaimer.
12+ * 2. Redistributions in binary form must reproduce the above copyright notice,
13+ * this list of conditions and the following disclaimer in the documentation
14+ * and/or other materials provided with the distribution.
15+ *
16+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
17+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
20+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
21+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
22+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
23+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
24+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
25+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
26+ * POSSIBILITY OF SUCH DAMAGE.
27+ * #L%
28+ */
29+ package org .scijava .links ;
30+
31+ import java .net .URI ;
32+ import java .util .LinkedHashMap ;
33+ import java .util .Map ;
34+ import java .util .Arrays ;
35+ import java .net .URI ;
36+ import java .net .URISyntaxException ;
37+ import java .nio .charset .StandardCharsets ;
38+ import java .util .Objects ;
39+
40+ /**
41+ * Utility class for working with {@link URI} objects.
42+ *
43+ * @author Curtis Rueden, Marwan Zouinkhi
44+ */
45+ public final class FijiURILink {
46+
47+
48+ private final String plugin ; // e.g., "BDV"
49+ private final String subPlugin ; // e.g., "open" (nullable)
50+ private final String query ; // e.g., "a=1&b=2" (nullable)
51+ private final String rawQuery ; // e.g., "a=1&b=2" (nullable)
52+
53+ private FijiURILink (String plugin , String subPlugin , String query , String rawQuery ) {
54+ this .plugin = plugin ;
55+ this .subPlugin = subPlugin ;
56+ this .query = query ;
57+ this .rawQuery = rawQuery ;
58+ }
59+
60+ public static FijiURILink parse (String uriString ) {
61+ Objects .requireNonNull (uriString , "uriString" );
62+ final URI uri ;
63+ try {
64+ uri = new URI (uriString );
65+ } catch (URISyntaxException e ) {
66+ throw new IllegalArgumentException ("Invalid URI: " + uriString , e );
67+ }
68+
69+ if (!"fiji" .equalsIgnoreCase (uri .getScheme ())) {
70+ throw new IllegalArgumentException ("Scheme must be fiji://" );
71+ }
72+ // For opaque vs hierarchical handling: ensure it's hierarchical (has //)
73+ String authority = uri .getAuthority (); // first segment after //
74+ if (authority == null || authority .isEmpty ()) {
75+ throw new IllegalArgumentException ("Missing plugin name after fiji://" );
76+ }
77+ String plugin = authority ;
78+
79+ String path = uri .getPath (); // includes leading '/'
80+ String sub = null ;
81+ if (path != null && !path .isEmpty ()) {
82+ // normalize: "/open" -> "open"; "/" -> null
83+ String trimmed = path .startsWith ("/" ) ? path .substring (1 ) : path ;
84+ sub = trimmed .isEmpty () ? null : trimmed ;
85+ }
86+
87+ // Raw query (no '?'), leave as-is; users can parse if they want.
88+ String q = uri .getQuery ();
89+ // Optional: decode percent-escapes (uncomment if desired)
90+ // q = (q == null) ? null : java.net.URLDecoder.decode(q, StandardCharsets.UTF_8);
91+ String raw = uri .getRawQuery ();
92+ return new FijiURILink (plugin , sub , q , raw );
93+ }
94+
95+ public String getPlugin () { return plugin ; }
96+ public String getSubPlugin () { return subPlugin ; } // may be null
97+ public String getQuery () { return query ; } // may be null
98+ public String getRawQuery () { return rawQuery ; } // may be null
99+
100+ public Map <String , String > getParsedQuery () {
101+ final LinkedHashMap <String , String > map = new LinkedHashMap <>();
102+ final String [] tokens = query == null ? new String [0 ] : query .split ("&" );
103+ for (final String token : tokens ) {
104+ final String [] kv = token .split ("=" , 2 );
105+ final String k = kv [0 ];
106+ final String v = kv .length > 1 ? kv [1 ] : null ;
107+ map .put (k , v );
108+ }
109+ return map ;
110+ }
111+
112+ @ Override public String toString () {
113+ StringBuilder sb = new StringBuilder ("fiji://" ).append (plugin );
114+ if (subPlugin != null ) sb .append ('/' ).append (subPlugin );
115+ if (query != null ) sb .append ('?' ).append (query );
116+ return sb .toString ();
117+ }
118+
119+ // Convenience helper: returns null instead of throwing
120+ public static FijiURILink tryParse (String uriString ) {
121+ try { return parse (uriString ); } catch (RuntimeException e ) { return null ; }
122+ }
123+ }
124+
0 commit comments