• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

valkyrjaio / sindri-java / 27918874223

21 Jun 2026 10:03PM UTC coverage: 95.037% (-5.0%) from 100.0%
27918874223

Pull #31

github

web-flow
Merge 6e0924991 into e3a362b61
Pull Request #31: [Generation] Fix HTTP routing data and framework-provider resolution

322 of 358 branches covered (89.94%)

Branch coverage included in aggregate %.

130 of 168 new or added lines in 5 files covered. (77.38%)

1095 of 1133 relevant lines covered (96.65%)

4.14 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

98.1
/src/main/java/io/sindri/generate/abstract_/GenerateDataFromAst.java
1
/*
2
 * This file is part of the Sindri package.
3
 *
4
 * (c) Melech Mizrachi <melechmizrachi@gmail.com>
5
 *
6
 * For the full copyright and license information, please view the LICENSE
7
 * file that was distributed with this source code.
8
 */
9

10
package io.sindri.generate.abstract_;
11

12
import io.sindri.ast.CliRouteAttributeReader;
13
import io.sindri.ast.ComponentProviderReader;
14
import io.sindri.ast.ConfigReader;
15
import io.sindri.ast.HttpRouteAttributeReader;
16
import io.sindri.ast.ListenerProviderReader;
17
import io.sindri.ast.RouteProviderReader;
18
import io.sindri.ast.ServiceProviderReader;
19
import io.sindri.ast.data.HttpRouteData;
20
import io.sindri.ast.data.result.CliRouteAttributeResult;
21
import io.sindri.ast.data.result.ComponentProviderResult;
22
import io.sindri.ast.data.result.ConfigResult;
23
import io.sindri.ast.data.result.HttpRouteAttributeResult;
24
import io.sindri.ast.data.result.RouteProviderResult;
25
import io.sindri.generator.cli.contract.CliDataFileGeneratorContract;
26
import io.sindri.generator.container.contract.ContainerDataFileGeneratorContract;
27
import io.sindri.generator.event.contract.EventDataFileGeneratorContract;
28
import io.sindri.generator.http.contract.HttpDataFileGeneratorContract;
29
import java.util.LinkedHashMap;
30
import java.util.Map;
31

32
public abstract class GenerateDataFromAst {
2✔
33

34
    private final ConfigReader configReader = new ConfigReader();
5✔
35
    private final ComponentProviderReader componentProviderReader = new ComponentProviderReader();
5✔
36
    private final ServiceProviderReader serviceProviderReader = new ServiceProviderReader();
5✔
37
    private final RouteProviderReader routeProviderReader = new RouteProviderReader();
5✔
38
    private final ListenerProviderReader listenerProviderReader = new ListenerProviderReader();
5✔
39
    private final CliRouteAttributeReader cliRouteAttributeReader = new CliRouteAttributeReader();
5✔
40
    private final HttpRouteAttributeReader httpRouteAttributeReader =
5✔
41
            new HttpRouteAttributeReader();
42

43
    /** FQN → staged temp source path (or "" when unresolvable) for classpath-resolved sources. */
44
    private final Map<String, String> classpathSourceCache = new java.util.HashMap<>();
6✔
45

46
    protected abstract ContainerDataFileGeneratorContract getContainerDataFileGenerator();
47

48
    protected abstract EventDataFileGeneratorContract getEventDataFileGenerator();
49

50
    protected abstract CliDataFileGeneratorContract getCliDataFileGenerator();
51

52
    protected abstract HttpDataFileGeneratorContract getHttpDataFileGenerator();
53

54
    public void run(String configFilePath) {
55
        ConfigResult config = configReader.readFile(configFilePath);
5✔
56

57
        ComponentProviderResult allProviderData = collectProviderData(config);
4✔
58

59
        Map<String, String[]> publishers =
2✔
60
                collectPublishers(allProviderData.serviceProviders(), config);
4✔
61

62
        Map<String, String> listeners =
2✔
63
                collectListeners(allProviderData.listenerProviders(), config);
4✔
64

65
        Map<String, String> cliRoutes =
2✔
66
                collectCliRoutes(allProviderData.cliRouteProviders(), config);
4✔
67

68
        Map<String, String> httpRoutes = new LinkedHashMap<>();
4✔
69
        Map<String, HttpRouteData> httpRouteData = new LinkedHashMap<>();
4✔
70
        collectHttpRoutes(allProviderData.httpRouteProviders(), config, httpRoutes, httpRouteData);
7✔
71

72
        String dataClassName = "AppContainerData";
2✔
73
        String dataDir = config.dataPath();
3✔
74
        String dataNamespace = config.dataNamespace();
3✔
75

76
        getContainerDataFileGenerator()
6✔
77
                .generateFile(dataDir, dataClassName, dataNamespace, publishers);
2✔
78
        getEventDataFileGenerator().generateFile(dataDir, "AppEventData", dataNamespace, listeners);
8✔
79
        getCliDataFileGenerator()
6✔
80
                .generateFile(dataDir, "AppCliRoutingData", dataNamespace, cliRoutes);
2✔
81
        getHttpDataFileGenerator()
7✔
82
                .generateFile(
2✔
83
                        dataDir, "AppHttpRoutingData", dataNamespace, httpRoutes, httpRouteData);
84
    }
1✔
85

86
    private ComponentProviderResult collectProviderData(ConfigResult config) {
87
        ComponentProviderResult combined = new ComponentProviderResult();
4✔
88

89
        // Walk the full component-provider graph breadth-first. Each provider can nest further
90
        // component providers (e.g. an app provider pulling in framework providers, which pull in
91
        // their own), so recurse to any depth, guarding against cycles with a visited set.
92
        java.util.Deque<String> queue = new java.util.ArrayDeque<>(config.providers());
6✔
93
        java.util.Set<String> visited = new java.util.HashSet<>();
4✔
94

95
        while (!queue.isEmpty()) {
3✔
96
            String providerFqn = queue.poll();
4✔
97
            if (!visited.add(providerFqn)) {
4✔
98
                continue;
1✔
99
            }
100

101
            String filePath = fqnToFilePath(providerFqn, config.namespace(), config.dir());
8✔
102
            if (filePath.isEmpty()) {
3✔
103
                continue;
1✔
104
            }
105

106
            ComponentProviderResult data = componentProviderReader.readFile(filePath);
5✔
107
            combined = combined.merge(data);
4✔
108
            queue.addAll(data.componentProviders());
5✔
109
        }
1✔
110

111
        return combined;
2✔
112
    }
113

114
    private Map<String, String[]> collectPublishers(
115
            java.util.List<String> serviceProviders, ConfigResult config) {
116
        Map<String, String[]> publishers = new LinkedHashMap<>();
4✔
117
        for (String providerFqn : serviceProviders) {
10✔
118
            String filePath = fqnToFilePath(providerFqn, config.namespace(), config.dir());
8✔
119
            if (filePath.isEmpty()) {
3✔
120
                continue;
1✔
121
            }
122
            publishers.putAll(serviceProviderReader.readFile(filePath).publishers());
7✔
123
        }
1✔
124
        return publishers;
2✔
125
    }
126

127
    private Map<String, String> collectListeners(
128
            java.util.List<String> listenerProviders, ConfigResult config) {
129
        Map<String, String> listeners = new LinkedHashMap<>();
4✔
130
        for (String providerFqn : listenerProviders) {
10✔
131
            String filePath = fqnToFilePath(providerFqn, config.namespace(), config.dir());
8✔
132
            if (filePath.isEmpty()) {
3✔
133
                continue;
1✔
134
            }
135
            listenerProviderReader
3✔
136
                    .readFile(filePath)
1✔
137
                    .listeners()
3✔
138
                    .forEach(expr -> listeners.put(expr.toString(), expr.toString()));
9✔
139
        }
1✔
140
        return listeners;
2✔
141
    }
142

143
    private Map<String, String> collectCliRoutes(
144
            java.util.List<String> cliRouteProviders, ConfigResult config) {
145
        Map<String, String> routes = new LinkedHashMap<>();
4✔
146
        for (String providerFqn : cliRouteProviders) {
10✔
147
            String filePath = fqnToFilePath(providerFqn, config.namespace(), config.dir());
8✔
148
            if (filePath.isEmpty()) {
3✔
149
                continue;
1✔
150
            }
151
            RouteProviderResult routeProvider = routeProviderReader.readFile(filePath);
5✔
152
            for (String controllerFqn : routeProvider.controllerClasses()) {
11✔
153
                String controllerPath =
3✔
154
                        fqnToFilePath(controllerFqn, config.namespace(), config.dir());
5✔
155
                if (!controllerPath.isEmpty()) {
3✔
156
                    CliRouteAttributeResult result =
3✔
157
                            cliRouteAttributeReader.readFile(controllerPath);
2✔
158
                    result.routes().forEach((name, expr) -> routes.put(name, expr.toString()));
12✔
159
                }
160
            }
1✔
161
        }
1✔
162
        return routes;
2✔
163
    }
164

165
    private void collectHttpRoutes(
166
            java.util.List<String> httpRouteProviders,
167
            ConfigResult config,
168
            Map<String, String> httpRoutes,
169
            Map<String, HttpRouteData> httpRouteData) {
170
        for (String providerFqn : httpRouteProviders) {
10✔
171
            String filePath = fqnToFilePath(providerFqn, config.namespace(), config.dir());
8✔
172
            if (filePath.isEmpty()) {
3✔
173
                continue;
1✔
174
            }
175
            RouteProviderResult routeProvider = routeProviderReader.readFile(filePath);
5✔
176
            for (String controllerFqn : routeProvider.controllerClasses()) {
11✔
177
                String controllerPath =
3✔
178
                        fqnToFilePath(controllerFqn, config.namespace(), config.dir());
5✔
179
                if (!controllerPath.isEmpty()) {
3✔
180
                    HttpRouteAttributeResult result =
3✔
181
                            httpRouteAttributeReader.readFile(controllerPath);
2✔
182
                    result.routes().forEach((name, expr) -> httpRoutes.put(name, expr.toString()));
12✔
183
                    httpRouteData.putAll(result.routeData());
4✔
184
                }
185
            }
1✔
186
        }
1✔
187
    }
1✔
188

189
    private String fqnToFilePath(String fqn, String namespace, String srcDir) {
190
        String namespacePkg = namespace.toLowerCase(java.util.Locale.ROOT);
4✔
191
        if (fqn.startsWith(namespacePkg + ".")) {
5✔
192
            String relative = fqn.substring(namespacePkg.length() + 1).replace('.', '/');
10✔
193
            return srcDir + "/" + relative + ".java";
4✔
194
        }
195

196
        // Framework/vendor classes live outside the app source tree. Resolve their .java from the
197
        // sources jar on the classpath (the portable equivalent of PHP's
198
        // ReflectionClass::getFileName()) so their publishers()/providers can be scanned too.
199
        return resolveSourceFromClasspath(fqn);
4✔
200
    }
201

202
    /**
203
     * Resolve a class's {@code .java} source from a sources jar on the classpath and stage it as a
204
     * temp file (the AST readers accept only file paths). Returns {@code ""} when not found.
205
     */
206
    protected String resolveSourceFromClasspath(String fqn) {
207
        String cached = classpathSourceCache.get(fqn);
6✔
208
        if (cached != null) {
2✔
209
            return cached;
2✔
210
        }
211

212
        String resource = fqn.replace('.', '/') + ".java";
6✔
213
        try (java.io.InputStream in =
1✔
214
                getClass().getClassLoader().getResourceAsStream(resource)) {
5✔
215
            if (in == null) {
2✔
216
                classpathSourceCache.put(fqn, "");
6✔
217
                return "";
4✔
218
            }
219

220
            java.nio.file.Path temp = java.nio.file.Files.createTempFile("sindri-src-", ".java");
6✔
221
            temp.toFile().deleteOnExit();
3✔
222
            java.nio.file.Files.copy(
10✔
223
                    in, temp, java.nio.file.StandardCopyOption.REPLACE_EXISTING);
224
            String path = temp.toString();
3✔
225
            classpathSourceCache.put(fqn, path);
6✔
226

227
            return path;
4✔
228
        } catch (java.io.IOException e) {
2!
NEW
229
            classpathSourceCache.put(fqn, "");
×
NEW
230
            return "";
×
231
        }
232
    }
233
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc