• 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

85.0
today-context/src/main/java/infra/instrument/classloading/ShadowingClassLoader.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.instrument.classloading;
19

20
import org.jspecify.annotations.Nullable;
21

22
import java.io.IOException;
23
import java.io.InputStream;
24
import java.lang.instrument.ClassFileTransformer;
25
import java.lang.instrument.IllegalClassFormatException;
26
import java.net.URL;
27
import java.util.ArrayList;
28
import java.util.Enumeration;
29
import java.util.HashMap;
30

31
import infra.core.DecoratingClassLoader;
32
import infra.core.OverridingClassLoader;
33
import infra.lang.Assert;
34
import infra.util.FileCopyUtils;
35
import infra.util.StringUtils;
36

37
/**
38
 * ClassLoader decorator that shadows an enclosing ClassLoader,
39
 * applying registered transformers to all affected classes.
40
 *
41
 * @author Rob Harrop
42
 * @author Juergen Hoeller
43
 * @author Costin Leau
44
 * @author <a href="https://github.com/TAKETODAY">Harry Yang</a>
45
 * @see #addTransformer
46
 * @see OverridingClassLoader
47
 * @since 4.0
48
 */
49
public class ShadowingClassLoader extends DecoratingClassLoader {
50

51
  /** Packages that are excluded by default. */
52
  public static final String[] DEFAULT_EXCLUDED_PACKAGES = {
72✔
53
          "java.", "javax.", "jdk.", "sun.", "oracle.", "com.sun.", "com.ibm.", "COM.ibm.",
54
          "org.w3c.", "org.xml.", "org.dom4j.", "org.eclipse", "org.aspectj.", "net.sf.cglib",
55
          "infra.bytecode", "org.apache.xerces.", "org.apache.commons.logging."
56
  };
57

58
  private final ClassLoader enclosingClassLoader;
59

60
  private final ArrayList<ClassFileTransformer> classFileTransformers = new ArrayList<>(1);
6✔
61

62
  private final HashMap<String, Class<?>> classCache = new HashMap<>();
5✔
63

64
  /**
65
   * Create a new ShadowingClassLoader, decorating the given ClassLoader,
66
   * applying {@link #DEFAULT_EXCLUDED_PACKAGES}.
67
   *
68
   * @param enclosingClassLoader the ClassLoader to decorate
69
   * @see #ShadowingClassLoader(ClassLoader, boolean)
70
   */
71
  public ShadowingClassLoader(ClassLoader enclosingClassLoader) {
72
    this(enclosingClassLoader, true);
4✔
73
  }
1✔
74

75
  /**
76
   * Create a new ShadowingClassLoader, decorating the given ClassLoader.
77
   *
78
   * @param enclosingClassLoader the ClassLoader to decorate
79
   * @param defaultExcludes whether to apply {@link #DEFAULT_EXCLUDED_PACKAGES}
80
   */
81
  public ShadowingClassLoader(ClassLoader enclosingClassLoader, boolean defaultExcludes) {
2✔
82
    Assert.notNull(enclosingClassLoader, "Enclosing ClassLoader is required");
3✔
83
    this.enclosingClassLoader = enclosingClassLoader;
3✔
84
    if (defaultExcludes) {
2!
85
      for (String excludedPackage : DEFAULT_EXCLUDED_PACKAGES) {
16✔
86
        excludePackage(excludedPackage);
3✔
87
      }
88
    }
89
  }
1✔
90

91
  /**
92
   * Add the given ClassFileTransformer to the list of transformers that this
93
   * ClassLoader will apply.
94
   *
95
   * @param transformer the ClassFileTransformer
96
   */
97
  public void addTransformer(ClassFileTransformer transformer) {
98
    Assert.notNull(transformer, "Transformer is required");
3✔
99
    this.classFileTransformers.add(transformer);
5✔
100
  }
1✔
101

102
  /**
103
   * Copy all ClassFileTransformers from the given ClassLoader to the list of
104
   * transformers that this ClassLoader will apply.
105
   *
106
   * @param other the ClassLoader to copy from
107
   */
108
  public void copyTransformers(ShadowingClassLoader other) {
109
    Assert.notNull(other, "Other ClassLoader is required");
3✔
110
    this.classFileTransformers.addAll(other.classFileTransformers);
6✔
111
  }
1✔
112

113
  @Override
114
  public Class<?> loadClass(String name) throws ClassNotFoundException {
115
    if (shouldShadow(name)) {
4✔
116
      Class<?> cls = this.classCache.get(name);
6✔
117
      if (cls != null) {
2✔
118
        return cls;
2✔
119
      }
120
      return doLoadClass(name);
4✔
121
    }
122
    else {
123
      return this.enclosingClassLoader.loadClass(name);
5✔
124
    }
125
  }
126

127
  /**
128
   * Determine whether the given class should be excluded from shadowing.
129
   *
130
   * @param className the name of the class
131
   * @return whether the specified class should be shadowed
132
   */
133
  private boolean shouldShadow(String className) {
134
    return (!className.equals(getClass().getName()) && !className.endsWith("ShadowingClassLoader") &&
13!
135
            isEligibleForShadowing(className));
5✔
136
  }
137

138
  /**
139
   * Determine whether the specified class is eligible for shadowing
140
   * by this class loader.
141
   *
142
   * @param className the class name to check
143
   * @return whether the specified class is eligible
144
   * @see #isExcluded
145
   */
146
  protected boolean isEligibleForShadowing(String className) {
147
    return !isExcluded(className);
8✔
148
  }
149

150
  private Class<?> doLoadClass(String name) throws ClassNotFoundException {
151
    String internalName = StringUtils.replace(name, ".", "/") + ".class";
6✔
152
    InputStream is = this.enclosingClassLoader.getResourceAsStream(internalName);
5✔
153
    if (is == null) {
2✔
154
      throw new ClassNotFoundException(name);
5✔
155
    }
156
    try {
157
      byte[] bytes = FileCopyUtils.copyToByteArray(is);
3✔
158
      bytes = applyTransformers(name, bytes);
5✔
159
      Class<?> cls = defineClass(name, bytes, 0, bytes.length);
8✔
160
      // Additional check for defining the package, if not defined yet.
161
      if (cls.getPackage() == null) {
3!
162
        int packageSeparator = name.lastIndexOf('.');
×
163
        if (packageSeparator != -1) {
×
164
          String packageName = name.substring(0, packageSeparator);
×
165
          definePackage(packageName, null, null, null, null, null, null, null);
×
166
        }
167
      }
168
      this.classCache.put(name, cls);
6✔
169
      return cls;
2✔
170
    }
171
    catch (IOException ex) {
×
172
      throw new ClassNotFoundException("Cannot load resource for class [%s]".formatted(name), ex);
×
173
    }
174
  }
175

176
  private byte[] applyTransformers(String name, byte[] bytes) {
177
    String internalName = StringUtils.replace(name, ".", "/");
5✔
178
    try {
179
      for (ClassFileTransformer transformer : this.classFileTransformers) {
11✔
180
        byte[] transformed = transformer.transform(this, internalName, null, null, bytes);
8✔
181
        bytes = (transformed != null ? transformed : bytes);
4!
182
      }
1✔
183
      return bytes;
2✔
184
    }
185
    catch (IllegalClassFormatException ex) {
1✔
186
      throw new IllegalStateException(ex);
5✔
187
    }
188
  }
189

190
  @Nullable
191
  @Override
192
  public URL getResource(String name) {
193
    return this.enclosingClassLoader.getResource(name);
5✔
194
  }
195

196
  @Override
197
  @Nullable
198
  public InputStream getResourceAsStream(String name) {
199
    return this.enclosingClassLoader.getResourceAsStream(name);
5✔
200
  }
201

202
  @Override
203
  public Enumeration<URL> getResources(String name) throws IOException {
204
    return this.enclosingClassLoader.getResources(name);
5✔
205
  }
206

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