public record Clob()

in api/applib/src/main/java/org/apache/causeway/applib/value/Clob.java [76:343]


public record Clob(
    String name,
    MimeType mimeType,
    CharSequence chars
    ) implements NamedWithMimeType {
    

    // -- FACTORIES

    /**
     * Returns a new {@link Clob} of given {@code name}, {@code mimeType} and {@code content}.
     * <p>
     * {@code name} may or may not include the desired filename extension, as it
     * is guaranteed, that the resulting {@link Clob} has the appropriate extension
     * as constraint by the given {@code mimeType}.
     * <p>
     * For more fine-grained control use one of the {@link Clob} constructors directly.
     * @param name - may or may not include the desired filename extension
     * @param mimeType
     * @param content - chars
     * @return new {@link Clob}
     */
    public static Clob of(final String name, final CommonMimeType mimeType, final CharSequence content) {
        var fileName = _Strings.asFileNameWithExtension(name, mimeType.getProposedFileExtensions());
        return new Clob(fileName, mimeType.getMimeType(), content);
    }

    /**
     * Returns a new {@link Clob} of given {@code name}, {@code mimeType} and content from {@code dataSource},
     * wrapped with a {@link Try}.
     * <p>
     * {@code name} may or may not include the desired filename extension, as it
     * is guaranteed, that the resulting {@link Clob} has the appropriate extension
     * as constraint by the given {@code mimeType}.
     * <p>
     * For more fine-grained control use one of the {@link Clob} constructors directly.
     * @param name - may or may not include the desired filename extension
     * @param mimeType
     * @param dataSource - the {@link DataSource} to be opened for reading
     * @param charset - {@link Charset} to use for reading from given {@link DataSource}
     * @return new {@link Clob}
     */
    public static Try<Clob> tryRead(final String name, final CommonMimeType mimeType, final DataSource dataSource,
            final @NonNull Charset charset) {
        return dataSource.tryReadAsString(charset)
                .mapSuccess(string->Clob.of(name, mimeType, string.orElse(null)));
    }

    /**
     * Shortcut for {@code tryRead(name, mimeType, DataSource.ofFile(file), charset)}
     * @see #tryRead(String, org.apache.causeway.applib.value.NamedWithMimeType.CommonMimeType, DataSource, Charset)
     */
    public static Try<Clob> tryRead(final String name, final CommonMimeType mimeType, final File file,
            final @NonNull Charset charset) {
        return tryRead(name, mimeType, DataSource.ofFile(file), charset);
    }

    /**
     * Shortcut for {@link #tryRead(String, org.apache.causeway.applib.value.NamedWithMimeType.CommonMimeType, File, Charset)}
     * using {@link StandardCharsets#UTF_8}.
     */
    public static Try<Clob> tryReadUtf8(final String name, final CommonMimeType mimeType, final File file) {
        return tryRead(name, mimeType, file, StandardCharsets.UTF_8);
    }

    // --

    public Clob(final String name, final String primaryType, final String subType, final char[] chars) {
        this(name, primaryType, subType, new String(chars));
    }

    public Clob(final String name, final String mimeTypeBase, final char[] chars) {
        this(name, mimeTypeBase, new String(chars));
    }

    public Clob(final String name, final MimeType mimeType, final char[] chars) {
        this(name, mimeType, new String(chars));
    }

    public Clob(final String name, final String primaryType, final String subType, final CharSequence chars) {
        this(name, CommonMimeType.newMimeType(primaryType, subType), chars);
    }

    public Clob(final String name, final String mimeTypeBase, final CharSequence chars) {
        this(name, CommonMimeType.newMimeType(mimeTypeBase), chars);
    }

    // canonical constructor
    public Clob(final String name, final MimeType mimeType, final CharSequence chars) {
        if(name == null) {
            throw new IllegalArgumentException("Name cannot be null");
        }
        if(mimeType == null) {
            throw new IllegalArgumentException("MimeType cannot be null");
        }
        if(name.contains(":")) {
            throw new IllegalArgumentException("Name cannot contain ':'");
        }
        if(chars == null) {
            throw new IllegalArgumentException("Chars cannot be null");
        }
        this.name = name;
        this.mimeType = mimeType;
        this.chars = chars;
    }

    /**
     * @deprecated use {@link #chars()} instead
     */
    public CharSequence getChars() { return chars(); }

    // -- UTILITIES

    /**
     * Converts to a {@link Blob}, using given {@link Charset}
     * for the underlying String to byte[] conversion.
     */
    public Blob toBlob(final @NonNull Charset charset) {
        return new Blob(name(), mimeType(), _Strings.toBytes(chars().toString(), charset));
    }

    /**
     * Shortcut for {@link #toBlob(Charset)} using {@link StandardCharsets#UTF_8}.
     */
    public Blob toBlobUtf8() {
        return toBlob(StandardCharsets.UTF_8);
    }

    public void writeCharsTo(final Writer wr) throws IOException {
        if(wr!=null && chars!=null){
            wr.append(chars);
        }
    }

    /**
     * Writes this {@link Clob} to the file represented by
     * the specified <code>File</code> object.
     * <p>
     * If the file exists but is a directory rather than a regular file, does
     * not exist but cannot be created, or cannot be opened for any other
     * reason then a <code>FileNotFoundException</code> is thrown.
     *
     * @param      file the file to be opened for writing; if <code>null</code> this method does nothing
     * @param charset - {@link Charset} to use for writing to given file
     * @see        java.io.FileOutputStream
     * @see        java.io.OutputStreamWriter
     */
    @SneakyThrows
    public void writeTo(final @Nullable File file, final @NonNull Charset charset) {
        if(file==null) {
            return; // just ignore
        }
        try(var os = new OutputStreamWriter(new FileOutputStream(file), charset)){
            writeCharsTo(os);
        }
    }

    /**
     * Shortcut for {@link #writeTo(File, Charset)} using {@link StandardCharsets#UTF_8}.
     */
    public void writeToUtf8(final @Nullable File file) {
        writeTo(file, StandardCharsets.UTF_8);
    }

    @SneakyThrows
    public String asString() {
        var sw = new StringWriter();
        writeCharsTo(sw);
        return sw.toString();
    }

    // -- OBJECT CONTRACT

    @Override
    public boolean equals(final Object o) {
        if (this == o)
            return true;
        if (o == null || getClass() != o.getClass())
            return false;
        final Clob clob = (Clob) o;
        return Objects.equals(name, clob.name) &&
                Objects.equals(mimeType.toString(), clob.mimeType.toString()) &&
                Objects.equals(chars, clob.chars);
    }

    @Override public int hashCode() {
        return Objects.hash(name, mimeType.toString(), chars);
    }

    @Override
    public String toString() {
        return name() + " [" + mimeType().getBaseType() + "]: " + getChars().length() + " chars";
    }

    /**
     * (thread-safe)
     * @implNote see also ClobValueSemanticsProvider
     */
    public static final class JaxbToStringAdapter extends XmlAdapter<String, Clob> {

        private final PrimitiveJaxbAdapters.BytesAdapter bytesAdapter = new PrimitiveJaxbAdapters.BytesAdapter(); // thread-safe

        @Override
        public Clob unmarshal(final String data) throws Exception {
            if(data==null) {
                return null;
            }
            final int colonIdx = data.indexOf(':');
            final String name  = data.substring(0, colonIdx);
            final int colon2Idx  = data.indexOf(":", colonIdx+1);
            final String mimeTypeBase = data.substring(colonIdx+1, colon2Idx);
            final String payload = data.substring(colon2Idx+1);
            final byte[] bytes = bytesAdapter.unmarshal(payload);
            try {
                return new Clob(name, new MimeType(mimeTypeBase), new String(bytes, StandardCharsets.UTF_8));
            } catch (MimeTypeParseException e) {
                throw new RuntimeException(e);
            }
        }

        @Override
        public String marshal(final Clob clob) throws Exception {
            if(clob==null) {
                return null;
            }
            return new StringBuilder()
            .append(clob.name())
            .append(':')
            .append(clob.mimeType().getBaseType())
            .append(':')
            .append(bytesAdapter.marshal(clob.chars().toString().getBytes(StandardCharsets.UTF_8)))
            .toString();
        }

    }

    // -- SERIALIZATION PROXY

    private Object writeReplace() {
        return new SerializationProxy(this);
    }

    private void readObject(final ObjectInputStream stream) throws InvalidObjectException {
        throw new InvalidObjectException("Proxy required");
    }

    private static class SerializationProxy implements Serializable {
        /**
         * Generated, based on String, String, CharSequence
         */
        private static final long serialVersionUID = -3598440606899920566L;
        private final String name;
        private final String mimeTypeBase;
        private final CharSequence chars;

        private SerializationProxy(final Clob clob) {
            this.name = clob.name();
            this.mimeTypeBase = clob.mimeType().getBaseType();
            this.chars = clob.chars();
        }

        private Object readResolve() {
            return new Clob(name, mimeTypeBase, chars);
        }

    }

}