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

TAKETODAY / today-infrastructure / 18154768944

01 Oct 2025 07:26AM UTC coverage: 81.882% (-0.005%) from 81.887%
18154768944

push

github

web-flow
Merge pull request #290 from TAKETODAY/dev/jspecify

jspecify

59788 of 78013 branches covered (76.64%)

Branch coverage included in aggregate %.

141239 of 167496 relevant lines covered (84.32%)

3.6 hits per line

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

90.98
today-context/src/main/java/infra/context/condition/OnClassCondition.java
1
/*
2
 * Copyright 2017 - 2025 the original author or authors.
3
 *
4
 * This program is free software: you can redistribute it and/or modify
5
 * it under the terms of the GNU General Public License as published by
6
 * the Free Software Foundation, either version 3 of the License, or
7
 * (at your option) any later version.
8
 *
9
 * This program is distributed in the hope that it will be useful,
10
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
 * GNU General Public License for more details.
13
 *
14
 * You should have received a copy of the GNU General Public License
15
 * along with this program. If not, see [https://www.gnu.org/licenses/]
16
 */
17

18
package infra.context.condition;
19

20
import org.jspecify.annotations.Nullable;
21

22
import java.lang.annotation.Annotation;
23
import java.util.ArrayList;
24
import java.util.Collections;
25
import java.util.List;
26

27
import infra.context.annotation.Condition;
28
import infra.context.annotation.ConditionContext;
29
import infra.context.annotation.config.AutoConfigurationMetadata;
30
import infra.core.Ordered;
31
import infra.core.type.AnnotatedTypeMetadata;
32
import infra.util.MultiValueMap;
33
import infra.util.ReflectionUtils;
34
import infra.util.StringUtils;
35

36
/**
37
 * {@link Condition} and that checks for the presence or absence of specific classes.
38
 *
39
 * @author Phillip Webb
40
 * @author <a href="https://github.com/TAKETODAY">Harry Yang</a>
41
 * @see ConditionalOnClass
42
 * @see ConditionalOnMissingClass
43
 * @since 4.0 2022/1/16 16:09
44
 */
45
final class OnClassCondition extends FilteringInfraCondition implements Condition, Ordered {
3✔
46

47
  @Override
48
  protected ConditionOutcome[] getOutcomes(String[] configClasses, AutoConfigurationMetadata configMetadata) {
49
    // Split the work and perform half in a background thread if more than one
50
    // processor is available. Using a single additional thread seems to offer the
51
    // best performance. More threads make things worse.
52
    if (configClasses.length > 1 && Runtime.getRuntime().availableProcessors() > 1) {
8!
53
      return resolveOutcomesThreaded(configClasses, configMetadata);
5✔
54
    }
55
    else {
56
      OutcomesResolver outcomesResolver = new StandardOutcomesResolver(configClasses, 0,
8✔
57
              configClasses.length, configMetadata, getBeanClassLoader());
3✔
58
      return outcomesResolver.resolveOutcomes();
3✔
59
    }
60
  }
61

62
  private ConditionOutcome[] resolveOutcomesThreaded(String[] configClasses, AutoConfigurationMetadata configMetadata) {
63
    int split = configClasses.length / 2;
5✔
64
    OutcomesResolver firstHalfResolver = createOutcomesResolver(configClasses, 0, split, configMetadata);
7✔
65
    OutcomesResolver secondHalfResolver = new StandardOutcomesResolver(
8✔
66
            configClasses, split, configClasses.length, configMetadata, getBeanClassLoader());
3✔
67

68
    ConditionOutcome[] secondHalf = secondHalfResolver.resolveOutcomes();
3✔
69
    ConditionOutcome[] firstHalf = firstHalfResolver.resolveOutcomes();
3✔
70
    ConditionOutcome[] outcomes = new ConditionOutcome[configClasses.length];
4✔
71
    System.arraycopy(firstHalf, 0, outcomes, 0, firstHalf.length);
7✔
72
    System.arraycopy(secondHalf, 0, outcomes, split, secondHalf.length);
7✔
73
    return outcomes;
2✔
74
  }
75

76
  private OutcomesResolver createOutcomesResolver(String[] autoConfigurationClasses,
77
          int start, int end, AutoConfigurationMetadata autoConfigurationMetadata) {
78
    var outcomesResolver = new StandardOutcomesResolver(
7✔
79
            autoConfigurationClasses, start, end, autoConfigurationMetadata, getBeanClassLoader());
3✔
80
    return new ThreadedOutcomesResolver(outcomesResolver);
5✔
81
  }
82

83
  @Override
84
  public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
85
    ClassLoader classLoader = context.getClassLoader();
3✔
86
    ConditionMessage matchMessage = ConditionMessage.empty();
2✔
87
    List<String> onClasses = getCandidates(metadata, ConditionalOnClass.class);
5✔
88
    if (onClasses != null) {
2✔
89
      List<String> missing = filter(onClasses, ClassNameFilter.MISSING, classLoader);
6✔
90
      if (!missing.isEmpty()) {
3✔
91
        return ConditionOutcome.noMatch(ConditionMessage.forCondition(ConditionalOnClass.class)
8✔
92
                .didNotFind("required class", "required classes")
3✔
93
                .items(ConditionMessage.Style.QUOTE, missing));
1✔
94
      }
95
      matchMessage = matchMessage.andCondition(ConditionalOnClass.class)
7✔
96
              .found("required class", "required classes")
6✔
97
              .items(ConditionMessage.Style.QUOTE, filter(onClasses, ClassNameFilter.PRESENT, classLoader));
3✔
98
    }
99
    List<String> onMissingClasses = getCandidates(metadata, ConditionalOnMissingClass.class);
5✔
100
    if (onMissingClasses != null) {
2✔
101
      List<String> present = filter(onMissingClasses, ClassNameFilter.PRESENT, classLoader);
6✔
102
      if (!present.isEmpty()) {
3✔
103
        return ConditionOutcome.noMatch(ConditionMessage.forCondition(ConditionalOnMissingClass.class)
8✔
104
                .found("unwanted class", "unwanted classes")
3✔
105
                .items(ConditionMessage.Style.QUOTE, present));
1✔
106
      }
107
      matchMessage = matchMessage.andCondition(ConditionalOnMissingClass.class)
7✔
108
              .didNotFind("unwanted class", "unwanted classes")
6✔
109
              .items(ConditionMessage.Style.QUOTE, filter(onMissingClasses, ClassNameFilter.MISSING, classLoader));
3✔
110
    }
111
    return ConditionOutcome.match(matchMessage);
3✔
112
  }
113

114
  @Nullable
115
  private List<String> getCandidates(AnnotatedTypeMetadata metadata, Class<? extends Annotation> annotationType) {
116
    MultiValueMap<String, Object> attributes = metadata.getAllAnnotationAttributes(annotationType, true);
5✔
117
    if (attributes == null) {
2✔
118
      return null;
2✔
119
    }
120
    List<String> candidates = new ArrayList<>();
4✔
121
    addAll(candidates, attributes.get("value"));
7✔
122
    addAll(candidates, attributes.get("name"));
7✔
123
    return candidates;
2✔
124
  }
125

126
  private void addAll(List<String> list, @Nullable List<Object> itemsToAdd) {
127
    if (itemsToAdd != null) {
2✔
128
      for (Object item : itemsToAdd) {
9✔
129
        Collections.addAll(list, (String[]) item);
5✔
130
      }
1✔
131
    }
132
  }
1✔
133

134
  @Override
135
  public int getOrder() {
136
    return Ordered.HIGHEST_PRECEDENCE;
2✔
137
  }
138

139
  private interface OutcomesResolver {
140

141
    ConditionOutcome[] resolveOutcomes();
142

143
  }
144

145
  private static final class ThreadedOutcomesResolver implements OutcomesResolver {
146

147
    private final Thread thread;
148

149
    private volatile ConditionOutcome @Nullable [] outcomes;
150

151
    @Nullable
152
    private volatile Throwable failure;
153

154
    private ThreadedOutcomesResolver(OutcomesResolver outcomesResolver) {
2✔
155
      this.thread = new Thread(() -> {
8✔
156
        try {
157
          this.outcomes = outcomesResolver.resolveOutcomes();
4✔
158
        }
159
        catch (Throwable ex) {
×
160
          this.failure = ex;
×
161
        }
1✔
162
      });
1✔
163
      this.thread.start();
3✔
164
    }
1✔
165

166
    @Override
167
    public ConditionOutcome[] resolveOutcomes() {
168
      try {
169
        this.thread.join();
3✔
170
      }
171
      catch (InterruptedException ex) {
×
172
        Thread.currentThread().interrupt();
×
173
      }
1✔
174
      Throwable failure = this.failure;
3✔
175
      if (failure != null) {
2!
176
        ReflectionUtils.rethrowRuntimeException(failure);
×
177
      }
178
      ConditionOutcome[] outcomes = this.outcomes;
3✔
179
      return outcomes != null ? outcomes : new ConditionOutcome[0];
5!
180
    }
181

182
  }
183

184
  private record StandardOutcomesResolver(String[] configClasses, int start, int end,
18✔
185
          AutoConfigurationMetadata configMetadata, ClassLoader beanClassLoader) implements OutcomesResolver {
186

187
    @Override
188
    public ConditionOutcome[] resolveOutcomes() {
189
      return getOutcomes(this.configClasses, this.start, this.end, this.configMetadata);
11✔
190
    }
191

192
    @SuppressWarnings("NullAway")
193
    private ConditionOutcome[] getOutcomes(String[] autoConfigurationClasses,
194
            int start, int end, AutoConfigurationMetadata autoConfigurationMetadata) {
195
      ConditionOutcome[] outcomes = new ConditionOutcome[end - start];
5✔
196
      for (int i = start; i < end; i++) {
7✔
197
        String autoConfigurationClass = autoConfigurationClasses[i];
4✔
198
        if (autoConfigurationClass != null) {
2!
199
          String candidates = autoConfigurationMetadata.get(autoConfigurationClass, "ConditionalOnClass");
5✔
200
          if (candidates != null) {
2✔
201
            outcomes[i - start] = getOutcome(candidates);
8✔
202
          }
203
        }
204
      }
205
      return outcomes;
2✔
206
    }
207

208
    @Nullable
209
    private ConditionOutcome getOutcome(String candidates) {
210
      try {
211
        if (!candidates.contains(",")) {
4✔
212
          return getOutcome(candidates, this.beanClassLoader);
6✔
213
        }
214
        for (String candidate : StringUtils.commaDelimitedListToStringArray(candidates)) {
17✔
215
          ConditionOutcome outcome = getOutcome(candidate, this.beanClassLoader);
6✔
216
          if (outcome != null) {
2!
217
            return outcome;
×
218
          }
219
        }
220
      }
221
      catch (Exception ex) {
×
222
        // We'll get another chance later
223
      }
1✔
224
      return null;
2✔
225
    }
226

227
    @Nullable
228
    private ConditionOutcome getOutcome(String className, ClassLoader classLoader) {
229
      if (ClassNameFilter.MISSING.matches(className, classLoader)) {
5✔
230
        return ConditionOutcome.noMatch(ConditionMessage.forCondition(ConditionalOnClass.class)
7✔
231
                .didNotFind("required class")
8✔
232
                .items(ConditionMessage.Style.QUOTE, className));
1✔
233
      }
234
      return null;
2✔
235
    }
236

237
  }
238

239
}
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