001/* 002 * SPDX-License-Identifier: Apache-2.0 003 * 004 * Copyright 2024-2026 The Enola <https://enola.dev> Authors 005 * 006 * Licensed under the Apache License, Version 2.0 (the "License"); 007 * you may not use this file except in compliance with the License. 008 * You may obtain a copy of the License at 009 * 010 * https://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, software 013 * distributed under the License is distributed on an "AS IS" BASIS, 014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 015 * See the License for the specific language governing permissions and 016 * limitations under the License. 017 */ 018package dev.enola.common.io.resource; 019 020import com.google.common.base.Strings; 021import com.google.common.io.ByteSource; 022import com.google.common.net.MediaType; 023 024import dev.enola.common.io.iri.URIs; 025import dev.enola.common.io.mediatype.MediaTypes; 026 027import io.ipfs.multibase.Multibase; 028 029import org.jspecify.annotations.Nullable; 030 031import java.net.URI; 032import java.net.URISyntaxException; 033 034/** 035 * Resource I/O implementation based on a <a 036 * href="https://github.com/multiformats/multibase">multibase: URLs</a> (invented by Enola.dev; not 037 * standardized anywhere, yet). 038 * 039 * <p>@see {@link DataResource} for another similar URL scheme. 040 */ 041public class MultibaseResource extends BaseResource implements ReadableButNotWritableResource { 042 043 // Intentionally different from MultibaseIRI's mb: scheme. 044 // multibase: is for fetching resources, mb: is for Thing IRIs. 045 private static final String SCHEME = "multibase"; 046 047 public static class Provider implements ResourceProvider { 048 @Override 049 public Resource getResource(URI uri) { 050 if (SCHEME.equals(uri.getScheme())) return new MultibaseResource(uri); 051 else return null; 052 } 053 } 054 055 private final ByteSource byteSource; 056 057 public static Resource of(@Nullable String text, @Nullable MediaType mediaType) { 058 var data = MediaTypes.toStringWithoutSpaces(mediaType) + "," + Strings.nullToEmpty(text); 059 try { 060 return new MultibaseResource(new URI(SCHEME, data, null)); 061 } catch (URISyntaxException e) { 062 throw new IllegalArgumentException(data, e); 063 } 064 } 065 066 public static Resource of(@Nullable String text) { 067 return of(text, null); 068 } 069 070 // TODO Rewrite using https://blog.jetbrains.com/idea/2024/02/constructor-makeover-in-java-22/ 071 // once we're on Java >21 (and avoid having to match the RegExp twice, which is inefficient) 072 private static URI checkSchema(URI uri) { 073 if (!SCHEME.equals(uri.getScheme())) throw new IllegalArgumentException(uri.toString()); 074 return uri; 075 } 076 077 public MultibaseResource(URI uri) { 078 super(checkSchema(uri)); 079 String data = URIs.dropQueryAndFragment(uri).getSchemeSpecificPart(); 080 byteSource = ByteSource.wrap(Multibase.decode(data)); 081 } 082 083 @Override 084 public ByteSource byteSource() { 085 return byteSource; 086 } 087}