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

valkyrjaio / sindri-java / 30138871992

25 Jul 2026 01:37AM UTC coverage: 97.932%. First build
30138871992

Pull #38

github

web-flow
Merge e9912f47b into 04c800ff7
Pull Request #38: [Grpc] Add gRPC data-class generation

483 of 499 branches covered (96.79%)

Branch coverage included in aggregate %.

278 of 302 new or added lines in 14 files covered. (92.05%)

1411 of 1435 relevant lines covered (98.33%)

4.3 hits per line

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

91.07
/src/main/java/io/sindri/ast/MiddlewareClassifier.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.ast;
11

12
import com.github.javaparser.ast.CompilationUnit;
13
import com.github.javaparser.ast.body.ClassOrInterfaceDeclaration;
14
import com.github.javaparser.ast.type.ClassOrInterfaceType;
15
import io.sindri.ast.abstract_.AstReader;
16
import java.util.ArrayDeque;
17
import java.util.Deque;
18
import java.util.HashSet;
19
import java.util.LinkedHashSet;
20
import java.util.Map;
21
import java.util.Optional;
22
import java.util.Set;
23

24
/**
25
 * Classifies a middleware class by which stage contracts it implements — the syntactic equivalent
26
 * of the runtime collector's {@code isAssignableFrom} cascade, for the route-data cache.
27
 *
28
 * <p>A {@code @Middleware} annotation names only the class, not its stage; the stage is the set of
29
 * stage contracts the class implements, directly or through an abstract base or a sub-contract.
30
 * This walks the class's full {@code implements}/{@code extends} ancestry (resolving names through
31
 * each source's imports) and reports which of the given target contracts it reaches. Targets are
32
 * matched by fully-qualified name and never parsed, so the framework contracts need not be
33
 * resolvable — which keeps the cache generator working even before the target module has been
34
 * published.
35
 */
36
public class MiddlewareClassifier extends AstReader {
3✔
37

38
    /** Resolves a fully-qualified type name to its parsed source, or empty when unresolvable. */
39
    public interface SourceResolver {
40

41
        Optional<CompilationUnit> resolve(String fqn);
42
    }
43

44
    /**
45
     * Return the subset of {@code targetFqns} the middleware reaches through its type hierarchy.
46
     *
47
     * @param middlewareFqn the middleware class
48
     * @param resolver resolves an app/framework class to its source (empty stops that branch)
49
     * @param targetFqns the stage-contract fully-qualified names to match against
50
     * @return the matched target FQNs (a class may match several stages)
51
     */
52
    public Set<String> classify(
53
            String middlewareFqn, SourceResolver resolver, Set<String> targetFqns) {
54
        Set<String> matched = new LinkedHashSet<>();
4✔
55
        Set<String> visited = new HashSet<>();
4✔
56
        Deque<String> pending = new ArrayDeque<>();
4✔
57
        pending.push(middlewareFqn);
3✔
58

59
        while (!pending.isEmpty()) {
3✔
60
            String fqn = pending.pop();
4✔
61
            if (!visited.add(fqn)) {
4!
NEW
62
                continue;
×
63
            }
64
            if (targetFqns.contains(fqn)) {
4✔
65
                // A target is terminal: record it, and don't parse it (it may be unresolvable).
66
                matched.add(fqn);
4✔
67
                continue;
1✔
68
            }
69

70
            Optional<CompilationUnit> cu = resolver.resolve(fqn);
4✔
71
            if (cu.isEmpty()) {
3✔
72
                // Unresolvable ancestor (a non-target framework or JDK type) — stop this branch.
73
                continue;
1✔
74
            }
75
            Optional<ClassOrInterfaceDeclaration> type = findClass(cu.get());
6✔
76
            if (type.isEmpty()) {
3!
NEW
77
                continue;
×
78
            }
79

80
            Map<String, String> importMap = buildImportMap(cu.get());
6✔
81
            String pkg = getPackageName(cu.get());
6✔
82
            for (ClassOrInterfaceType ancestor : type.get().getImplementedTypes()) {
13✔
83
                pending.push(resolveName(ancestor, importMap, pkg));
7✔
84
            }
1✔
85
            for (ClassOrInterfaceType ancestor : type.get().getExtendedTypes()) {
13✔
86
                pending.push(resolveName(ancestor, importMap, pkg));
7✔
87
            }
1✔
88
        }
1✔
89

90
        return matched;
2✔
91
    }
92

93
    /**
94
     * Resolve a written {@code implements}/{@code extends} type to a fully-qualified name: an
95
     * already-qualified reference is taken as-is; a simple name is resolved through the file's
96
     * imports, falling back to the same package.
97
     */
98
    private String resolveName(
99
            ClassOrInterfaceType type, Map<String, String> importMap, String pkg) {
100
        String written =
1✔
101
                type.getScope().map(scope -> scope.asString() + ".").orElse("")
11✔
102
                        + type.getNameAsString();
3✔
103
        if (written.contains(".")) {
4✔
104
            return written;
2✔
105
        }
106
        if (importMap.containsKey(written)) {
4✔
107
            return importMap.get(written);
5✔
108
        }
109
        return pkg.isEmpty() ? written : pkg + "." + written;
7!
110
    }
111
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc