• 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

93.48
today-context/src/main/java/infra/context/properties/bind/DefaultBindConstructorProvider.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.properties.bind;
19

20
import org.jspecify.annotations.Nullable;
21

22
import java.lang.reflect.Constructor;
23
import java.lang.reflect.Modifier;
24
import java.util.ArrayList;
25

26
import infra.beans.factory.annotation.Autowired;
27
import infra.core.annotation.MergedAnnotations;
28
import infra.core.annotation.MergedAnnotations.SearchStrategy;
29
import infra.util.ClassUtils;
30

31
/**
32
 * Default {@link BindConstructorProvider} implementation.
33
 *
34
 * @author Madhura Bhave
35
 * @author Phillip Webb
36
 * @author <a href="https://github.com/TAKETODAY">Harry Yang</a>
37
 * @since 4.0
38
 */
39
class DefaultBindConstructorProvider implements BindConstructorProvider {
3✔
40

41
  @SuppressWarnings("NullAway")
42
  @Nullable
43
  @Override
44
  public Constructor<?> getBindConstructor(Bindable<?> bindable, boolean isNestedConstructorBinding) {
45
    Constructors constructors = Constructors.getConstructors(
3✔
46
            bindable.getType().resolve(), isNestedConstructorBinding);
3✔
47
    if (constructors.bind != null
9✔
48
            && constructors.deducedBindConstructor
49
            && !constructors.immutableType) {
50
      if (bindable.getValue() != null && bindable.getValue().get() != null) {
7✔
51
        return null;
2✔
52
      }
53
    }
54
    return constructors.bind;
3✔
55
  }
56

57
  @Nullable
58
  @Override
59
  public Constructor<?> getBindConstructor(Class<?> type, boolean isNestedConstructorBinding) {
60
    Constructors constructors = Constructors.getConstructors(type, isNestedConstructorBinding);
4✔
61
    return constructors.bind;
3✔
62
  }
63

64
  /**
65
   * Data holder for autowired and bind constructors.
66
   */
67
  static final class Constructors {
68

69
    private static final Constructors NONE = new Constructors(
9✔
70
            false, null, false, false);
71

72
    public final boolean hasAutowired;
73

74
    @Nullable
75
    public final Constructor<?> bind;
76

77
    public final boolean immutableType;
78
    public final boolean deducedBindConstructor;
79

80
    private Constructors(boolean hasAutowired, @Nullable Constructor<?> bind,
81
            boolean deducedBindConstructor, boolean immutableType) {
2✔
82
      this.hasAutowired = hasAutowired;
3✔
83
      this.bind = bind;
3✔
84
      this.deducedBindConstructor = deducedBindConstructor;
3✔
85
      this.immutableType = immutableType;
3✔
86
    }
1✔
87

88
    static Constructors getConstructors(@Nullable Class<?> type, boolean isNestedConstructorBinding) {
89
      if (type == null) {
2!
90
        return NONE;
×
91
      }
92
      boolean hasAutowiredConstructor = isAutowiredPresent(type);
3✔
93
      Constructor<?>[] candidates = getCandidateConstructors(type);
3✔
94
      MergedAnnotations[] candidateAnnotations = getAnnotations(candidates);
3✔
95
      boolean deducedBindConstructor = false;
2✔
96
      boolean immutableType = type.isRecord();
3✔
97
      Constructor<?> bind = getConstructorBindingAnnotated(type, candidates, candidateAnnotations);
5✔
98
      if (bind == null && !hasAutowiredConstructor) {
4✔
99
        bind = deduceBindConstructor(type, candidates);
4✔
100
        deducedBindConstructor = bind != null;
6✔
101
      }
102

103
      if (bind != null || isNestedConstructorBinding) {
4✔
104
        if (hasAutowiredConstructor) {
2✔
105
          throw new IllegalStateException(type.getName() + " declares @ConstructorBinding and @Autowired constructor");
7✔
106
        }
107
      }
108
      return new Constructors(hasAutowiredConstructor, bind, deducedBindConstructor, immutableType);
8✔
109
    }
110

111
    private static boolean isAutowiredPresent(Class<?> type) {
112
      for (Constructor<?> constructor : type.getDeclaredConstructors()) {
17✔
113
        if (MergedAnnotations.from(constructor).isPresent(Autowired.class)) {
5✔
114
          return true;
2✔
115
        }
116
      }
117
      Class<?> userClass = ClassUtils.getUserClass(type);
3✔
118
      return userClass != type && isAutowiredPresent(userClass);
5!
119
    }
120

121
    private static Constructor<?>[] getCandidateConstructors(Class<?> type) {
122
      if (isInnerClass(type)) {
3✔
123
        return new Constructor<?>[0];
3✔
124
      }
125

126
      ArrayList<Constructor<?>> constructors = new ArrayList<>();
4✔
127
      for (Constructor<?> constructor : type.getDeclaredConstructors()) {
17✔
128
        if (!constructor.isSynthetic()) {
3!
129
          constructors.add(constructor);
4✔
130
        }
131
      }
132
      return constructors.toArray(new Constructor[0]);
6✔
133
    }
134

135
    private static boolean isInnerClass(Class<?> type) {
136
      try {
137
        return type.getDeclaredField("this$0").isSynthetic();
5✔
138
      }
139
      catch (NoSuchFieldException ex) {
1✔
140
        return false;
2✔
141
      }
142
    }
143

144
    private static MergedAnnotations[] getAnnotations(Constructor<?>[] candidates) {
145
      MergedAnnotations[] candidateAnnotations = new MergedAnnotations[candidates.length];
4✔
146
      for (int i = 0; i < candidates.length; i++) {
8✔
147
        candidateAnnotations[i] = MergedAnnotations.from(candidates[i], SearchStrategy.SUPERCLASS);
8✔
148
      }
149
      return candidateAnnotations;
2✔
150
    }
151

152
    @Nullable
153
    private static Constructor<?> getConstructorBindingAnnotated(Class<?> type, Constructor<?>[] candidates,
154
            MergedAnnotations[] mergedAnnotations) {
155
      Constructor<?> result = null;
2✔
156
      for (int i = 0; i < candidates.length; i++) {
8✔
157
        if (mergedAnnotations[i].isPresent(ConstructorBinding.class)) {
6✔
158
          if (candidates[i].getParameterCount() <= 0) {
5!
159
            throw new IllegalStateException(
×
160
                    type.getName() + " declares @ConstructorBinding on a no-args constructor");
×
161
          }
162
          if (result != null) {
2✔
163
            throw new IllegalStateException(
3✔
164
                    type.getName() + " has more than one @ConstructorBinding constructor");
4✔
165
          }
166

167
          result = candidates[i];
4✔
168
        }
169
      }
170
      return result;
2✔
171
    }
172

173
    @Nullable
174
    private static Constructor<?> deduceBindConstructor(Class<?> type, Constructor<?>[] candidates) {
175
      if (candidates.length == 1 && candidates[0].getParameterCount() > 0) {
9✔
176
        if (type.isMemberClass() && Modifier.isPrivate(candidates[0].getModifiers())) {
9✔
177
          return null;
2✔
178
        }
179
        return candidates[0];
4✔
180
      }
181
      Constructor<?> result = null;
2✔
182
      for (Constructor<?> candidate : candidates) {
16✔
183
        if (!Modifier.isPrivate(candidate.getModifiers())) {
4✔
184
          if (result != null) {
2✔
185
            return null;
2✔
186
          }
187
          result = candidate;
2✔
188
        }
189
      }
190
      return (result != null && result.getParameterCount() > 0) ? result : null;
9✔
191
    }
192

193
  }
194

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