001package gudusoft.gsqlparser.lineage.v2.validation;
002
003import java.io.File;
004import java.io.IOException;
005import java.nio.file.Files;
006import java.util.Collections;
007import java.util.LinkedHashMap;
008import java.util.Map;
009
010/**
011 * Where the validator finds the text of a {@code NAMED} source unit to check spans against
012 * (specification Sections 7.8 and 12.2). Without the text, spans are checked for order only and a note says so.
013 *
014 * @since 4.2.17
015 */
016public interface SourceTextProvider {
017
018    /** No source text: spans are not checked against text. */
019    SourceTextProvider NONE = new SourceTextProvider() {
020        @Override
021        public byte[] read(String namespace, String logicalName) {
022            return null;
023        }
024    };
025
026    /**
027     * The bytes of a source unit, or null when this provider does not have them.
028     *
029     * @param namespace   the unit's {@code namespace}
030     * @param logicalName the unit's {@code logicalName}
031     */
032    byte[] read(String namespace, String logicalName);
033
034    /**
035     * A provider that maps namespaces to directories; a logical name is a path relative to its directory, and a
036     * name that is absolute, has a {@code ..} segment or ends with {@code /} is never read.
037     */
038    final class Directories implements SourceTextProvider {
039        private final Map<String, File> roots;
040
041        public Directories(Map<String, File> roots) {
042            this.roots = Collections.unmodifiableMap(new LinkedHashMap<String, File>(roots));
043        }
044
045        @Override
046        public byte[] read(String namespace, String logicalName) {
047            File root = roots.get(namespace);
048            // a name that ends with "/" names a directory, never a file, before File drops the separator
049            if (root == null || logicalName == null || logicalName.isEmpty() || logicalName.startsWith("/")
050                    || logicalName.endsWith("/")) {
051                return null;
052            }
053            for (String part : logicalName.split("/", -1)) {
054                if ("..".equals(part)) {
055                    return null;
056                }
057            }
058            File file = new File(root, logicalName.replace('/', File.separatorChar));
059            if (!file.isFile()) {
060                return null;
061            }
062            try {
063                return Files.readAllBytes(file.toPath());
064            } catch (IOException e) {
065                return null;
066            }
067        }
068    }
069}