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}