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}