001/*
002 * SPDX-License-Identifier: Apache-2.0
003 *
004 * Copyright 2023-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.io.ByteSink;
021import com.google.common.io.ByteSource;
022
023import dev.enola.common.io.iri.URIs;
024
025import java.io.FileDescriptor;
026import java.io.FileInputStream;
027import java.io.FileOutputStream;
028import java.io.IOException;
029import java.io.InputStream;
030import java.io.OutputStream;
031import java.net.URI;
032import java.nio.charset.Charset;
033
034/**
035 * Resource for "fd:0" (STDIN), "fd:1" (STDOUT), "fd:2" (STDERR) URIs.
036 *
037 * <p>The {@link #mediaType()} will be {@link com.google.common.net.MediaType#OCTET_STREAM} (NOT
038 * {@link com.google.common.net.MediaType#APPLICATION_BINARY}!), unless there is a ?mediaType= query
039 * parameter in the URI argument.
040 *
041 * <p>The Charset of that Media Type will be the {@link Charset#defaultCharset()}, unless there is
042 * either (check first) a ?charset= query parameter in the URI argument (e.g. "fd:0?charset=ASCII",
043 * or "fd:1?charset=UTF-8", or "fd:2?charset=UTF-16BE") or the ?mediaType= query parameter includes
044 * a charset (e.g. "fd:1?mediaType=application/yaml;charset=utf-16be").
045 */
046public class FileDescriptorResource extends BaseResource implements Resource {
047
048    // NB: If updating ^^^ then also update docs/use/fetch/index.md
049
050    public static final String STDOUT = "fd:1?charset=UTF-8";
051
052    public static final URI STDOUT_URI = URI.create(STDOUT);
053
054    public static class Provider implements ResourceProvider {
055
056        @Override
057        public Resource getResource(URI uri) {
058            if ("fd".equals(uri.getScheme())) return new FileDescriptorResource(uri);
059            else return null;
060        }
061    }
062
063    private final FileDescriptor fileDescriptor;
064
065    public FileDescriptorResource(URI uri) {
066        super(addDefaultCharsetIfNone(uri));
067
068        if (!"fd".equals(uri.getScheme())) {
069            throw new IllegalArgumentException(uri.toString());
070        }
071
072        var fd = URIs.getPath(uri);
073        if (fd.startsWith("0")) {
074            fileDescriptor = FileDescriptor.in;
075        } else if (fd.startsWith("1")) {
076            fileDescriptor = FileDescriptor.out;
077        } else if (fd.startsWith("2")) {
078            fileDescriptor = FileDescriptor.err;
079        } else {
080            throw new IllegalArgumentException(fd);
081        }
082    }
083
084    private static URI addDefaultCharsetIfNone(URI uri) {
085        if (URIs.getCharset(uri) == null) return URIs.addCharset(uri, Charset.defaultCharset());
086        else return uri;
087    }
088
089    @Override
090    public ByteSink byteSink() {
091        return new ByteSink() {
092            @Override
093            public OutputStream openStream() throws IOException {
094                return new FileOutputStream(fileDescriptor) {
095                    @Override
096                    public void close() throws IOException {
097                        // IGNORE! Never close.
098                    }
099                };
100            }
101        };
102    }
103
104    @Override
105    public ByteSource byteSource() {
106        return new ByteSource() {
107            @Override
108            public InputStream openStream() throws IOException {
109                return new FileInputStream(fileDescriptor) {
110                    @Override
111                    public void close() throws IOException {
112                        // IGNORE! Never close.
113                    }
114                };
115            }
116        };
117    }
118}