001/*
002 * JDrupes Builder
003 * Copyright (C) 2025 Michael N. Lipp
004 * 
005 * This program is free software: you can redistribute it and/or modify
006 * it under the terms of the GNU Affero General Public License as
007 * published by the Free Software Foundation, either version 3 of the
008 * License, or (at your option) any later version.
009 *
010 * This program is distributed in the hope that it will be useful,
011 * but WITHOUT ANY WARRANTY; without even the implied warranty of
012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
013 * GNU Affero General Public License for more details.
014 *
015 * You should have received a copy of the GNU Affero General Public License
016 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
017 */
018
019package org.jdrupes.builder.core;
020
021import java.io.IOException;
022import java.io.InputStream;
023import java.io.OutputStream;
024import java.lang.reflect.Proxy;
025import java.nio.file.Files;
026import java.nio.file.Path;
027import java.time.Instant;
028import java.util.Objects;
029import org.jdrupes.builder.api.BuildException;
030import org.jdrupes.builder.api.FileResource;
031import org.jdrupes.builder.api.Proxyable;
032import org.jdrupes.builder.api.ResourceType;
033
034/// A resource that represents a file.
035///
036public class DefaultFileResource extends ResourceObject
037        implements FileResource {
038
039    private final Path path;
040
041    /// Instantiates a new file resource.
042    ///
043    /// @param type the type
044    /// @param path the path
045    ///
046    protected DefaultFileResource(ResourceType<? extends FileResource> type,
047            Path path) {
048        super(type);
049        if (!path.isAbsolute()) {
050            throw new BuildException("Path must be absolute, is " + path);
051        }
052        this.path = path;
053    }
054
055    /// Creates a new file resource.
056    ///
057    /// @param <T> the resource type
058    /// @param type the type
059    /// @param path the path
060    /// @return the t
061    ///
062    @SuppressWarnings({ "unchecked" })
063    public static <T extends FileResource> T createFileResource(
064            ResourceType<T> type, Path path) {
065        return (T) Proxy.newProxyInstance(type.rawType().getClassLoader(),
066            new Class<?>[] { type.rawType(), Proxyable.class },
067            new ForwardingHandler(new DefaultFileResource(type, path)));
068    }
069
070    /// Path.
071    ///
072    /// @return the path
073    ///
074    @Override
075    public Path path() {
076        return path;
077    }
078
079    @Override
080    public Instant asOf() {
081        if (!path.toFile().exists()) {
082            return Instant.MIN;
083        }
084        return Instant.ofEpochMilli(path.toFile().lastModified());
085    }
086
087    @Override
088    public InputStream inputStream() throws IOException {
089        return Files.newInputStream(path);
090    }
091
092    @Override
093    public OutputStream outputStream() throws IOException {
094        return Files.newOutputStream(path);
095    }
096
097    @Override
098    public int hashCode() {
099        final int prime = 31;
100        int result = super.hashCode();
101        result = prime * result + Objects.hash(path);
102        return result;
103    }
104
105    @Override
106    public boolean equals(Object obj) {
107        if (this == obj) {
108            return true;
109        }
110        if (!super.equals(obj)) {
111            return false;
112        }
113        return (obj instanceof FileResource other)
114            && Objects.equals(path, other.path());
115    }
116
117    @Override
118    public String toString() {
119        var relPath = Path.of("").toAbsolutePath().relativize(path);
120        return type() + ": " + (relPath.equals(Path.of("")) ? "."
121            : relPath.toString()) + " (" + asOfLocalized() + ")";
122    }
123}