aboutsummaryrefslogtreecommitdiffstats
path: root/integration/intellij/src/main/java/ai/vespa/intellij/schema/model/Schema.java
blob: 88cbdd3e07a1eba9f0a11d652051de240ec8eb05 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package ai.vespa.intellij.schema.model;

import ai.vespa.intellij.schema.psi.SdFile;
import ai.vespa.intellij.schema.psi.SdRankProfileDefinition;
import ai.vespa.intellij.schema.psi.SdSchemaDefinition;
import ai.vespa.intellij.schema.utils.AST;
import ai.vespa.intellij.schema.utils.Files;
import ai.vespa.intellij.schema.utils.Path;
import com.intellij.openapi.project.Project;
import com.intellij.psi.PsiFile;
import com.intellij.psi.util.PsiTreeUtil;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;

/**
 * A schema.
 *
 * @author bratseth
 */
public class Schema {

    private final SdFile definition;

    /** The schema this inherits, or empty if none. Resolved lazily. */
    private Optional<Schema> parent = null;

    /** The children of this. Resolved lazily. */
    private Map<String, Schema> children = null;

    /** The profiles of this, either defined inside it or in separate .profile files. Resolved lazily. */
    private Map<String, RankProfile> rankProfiles = null;

    public Schema(SdFile definition) {
        this.definition = definition;
    }

    public String name() { return definition.getName().substring(0, definition.getName().length() - 3); }

    public SdFile definition() { return definition; }

    /** The path of the location of this schema from the project root. */
    public Path path() { return Path.fromString(definition.getContainingDirectory().getVirtualFile().getPath()); }

    public Optional<Schema> parent() {
        if (parent != null) return parent;
        Optional<SdSchemaDefinition> schemaDefinition = PsiTreeUtil.collectElementsOfType(definition, SdSchemaDefinition.class).stream().findFirst();
        if (schemaDefinition.isEmpty()) return Optional.empty(); // No valid schema definition in schema file
        return parent = AST.inherits(schemaDefinition.get())
                           .stream()
                           .findFirst() // Only one schema can be inherited; ignore any following
                           .map(inheritedNode -> fromProjectFile(definition.getProject(), path().append(inheritedNode.getText() + ".sd")));
    }

    public Map<String, Schema> children() {
        if (children != null) return children;
        children = new HashMap<>();
        for (PsiFile file : Files.allFilesIn(path(),"sd", definition().getProject())) {
            Schema schema = new Schema((SdFile)file);
            if ( schema.parent().isPresent() && Objects.equals(schema.parent().get().name(), name()))
                children.put(schema.name(), schema);
        }
        return children;
    }

    /**
     * Returns the rank profiles in this, defined either inside it or in separate .profile files,
     * or inherited from the parent schema.
     */
    public Map<String, RankProfile> rankProfiles() {
        if (rankProfiles != null) return rankProfiles;
        rankProfiles = parent().isPresent() ? parent().get().rankProfiles() : new HashMap<>();

        for (var profileDefinition : PsiTreeUtil.collectElementsOfType(definition, SdRankProfileDefinition.class))
            rankProfiles.put(profileDefinition.getName(), new RankProfile(profileDefinition, this));

        for (var profileFile : Files.allFilesIn(path().append(name()), "profile", definition.getProject())) {
            var profileDefinitions = PsiTreeUtil.collectElementsOfType(profileFile, SdRankProfileDefinition.class);
            if (profileDefinitions.size() != 1) continue; // invalid file
            var profileDefinition = profileDefinitions.stream().findAny().get();
            rankProfiles.put(profileDefinition.getName(), new RankProfile(profileDefinition, this));
        }
        return rankProfiles;
    }

    public Map<String, List<Function>> functions() {
        Map<String, List<Function>> functions = new HashMap<>();
        for (var profile : rankProfiles().values()) {
            for (var entry : profile.definedFunctions().entrySet())
                functions.computeIfAbsent(entry.getKey(), k -> new ArrayList<>()).addAll(entry.getValue());
        }
        return functions;
    }

    @Override
    public String toString() { return "schema " + name(); }

    /**
     * Returns the profile of the given name from the given file.
     *
     * @throws IllegalArgumentException if not found
     */
    public static Schema fromProjectFile(Project project, Path file) {
        Optional<PsiFile> psiFile = Files.open(file, project);
        if (psiFile.isEmpty())
            throw new IllegalArgumentException("Could not find file '" + file + "'");
        if ( ! (psiFile.get() instanceof SdFile))
            throw new IllegalArgumentException(file + " is not a schema file");
        return new Schema((SdFile)psiFile.get());
    }

}