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

aspectran / aspectran / #3986

15 Jan 2025 04:27AM CUT coverage: 35.056% (+0.05%) from 35.004%
#3986

push

github

topframe
Update

7 of 40 new or added lines in 6 files covered. (17.5%)

2 existing lines in 2 files now uncovered.

14194 of 40489 relevant lines covered (35.06%)

0.35 hits per line

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

28.14
/core/src/main/java/com/aspectran/core/context/resource/SiblingClassLoader.java
1
/*
2
 * Copyright (c) 2008-2025 The Aspectran Project
3
 *
4
 * Licensed under the Apache License, Version 2.0 (the "License");
5
 * you may not use this file except in compliance with the License.
6
 * You may obtain a copy of the License at
7
 *
8
 *     http://www.apache.org/licenses/LICENSE-2.0
9
 *
10
 * Unless required by applicable law or agreed to in writing, software
11
 * distributed under the License is distributed on an "AS IS" BASIS,
12
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
 * See the License for the specific language governing permissions and
14
 * limitations under the License.
15
 */
16
package com.aspectran.core.context.resource;
17

18
import com.aspectran.utils.ClassUtils;
19
import com.aspectran.utils.ObjectUtils;
20
import com.aspectran.utils.ToStringBuilder;
21
import com.aspectran.utils.annotation.jsr305.NonNull;
22
import com.aspectran.utils.annotation.jsr305.Nullable;
23

24
import java.io.BufferedInputStream;
25
import java.io.ByteArrayOutputStream;
26
import java.io.IOException;
27
import java.net.URL;
28
import java.net.URLConnection;
29
import java.util.ArrayList;
30
import java.util.Enumeration;
31
import java.util.HashSet;
32
import java.util.Iterator;
33
import java.util.LinkedList;
34
import java.util.List;
35
import java.util.NoSuchElementException;
36
import java.util.Objects;
37
import java.util.Set;
38

39
import static com.aspectran.utils.ClassUtils.PACKAGE_SEPARATOR_CHAR;
40

41
/**
42
 * Specialized class loader for Aspectran.
43
 */
44
public final class SiblingClassLoader extends ClassLoader {
45

46
    private final int id;
47

48
    private final SiblingClassLoader root;
49

50
    private final boolean firstborn;
51

52
    private final String resourceLocation;
53

54
    private final ResourceManager resourceManager;
55

56
    private final List<SiblingClassLoader> children = new LinkedList<>();
1✔
57

58
    private Set<String> excludeClassNames;
59

60
    private Set<String> excludePackageNames;
61

62
    private int reloadedCount;
63

64
    public SiblingClassLoader() throws InvalidResourceException {
65
        this(null, (ClassLoader)null);
×
66
    }
×
67

68
    public SiblingClassLoader(String name) throws InvalidResourceException {
69
        this(name, (ClassLoader)null);
×
70
    }
×
71

72
    public SiblingClassLoader(String name, ClassLoader parent) throws InvalidResourceException {
73
        super(name, parent != null ? parent : ClassUtils.getDefaultClassLoader());
1✔
74

75
        this.id = 1000;
1✔
76
        this.root = this;
1✔
77
        this.firstborn = true;
1✔
78
        this.resourceLocation = null;
1✔
79
        this.resourceManager = new LocalResourceManager(this);
1✔
80
    }
1✔
81

82
    public SiblingClassLoader(String[] resourceLocations) throws InvalidResourceException {
83
        this(null, null, resourceLocations);
×
84
    }
×
85

86
    public SiblingClassLoader(String name, String[] resourceLocations) throws InvalidResourceException {
87
        this(name, null, resourceLocations);
×
88
    }
×
89

90
    public SiblingClassLoader(ClassLoader parent, String[] resourceLocations) throws InvalidResourceException {
91
        this(null, parent, resourceLocations);
×
92
    }
×
93

94
    public SiblingClassLoader(String name, ClassLoader parent, String[] resourceLocations)
95
            throws InvalidResourceException {
96
        this(name, parent != null ? parent : ClassUtils.getDefaultClassLoader());
1✔
97
        if (resourceLocations != null) {
1✔
98
            createChildren(resourceLocations);
×
99
        }
100
    }
1✔
101

102
    private SiblingClassLoader(String name, @NonNull SiblingClassLoader parent, String resourceLocation)
103
            throws InvalidResourceException {
104
        super(name, parent);
×
105

106
        int numOfChildren = parent.addChild(this);
×
107

108
        this.id = (Math.abs(parent.getId() / 1000) + 1) * 1000 + numOfChildren;
×
109
        this.root = parent.getRoot();
×
110
        this.firstborn = (numOfChildren == 1);
×
111
        this.resourceLocation = resourceLocation;
×
112
        this.resourceManager = new LocalResourceManager(this, resourceLocation);
×
113
    }
×
114

115
    void joinSibling(String resourceLocation) throws InvalidResourceException {
116
        SiblingClassLoader parent = (SiblingClassLoader)getParent();
×
117
        parent.createChild(resourceLocation);
×
118
    }
×
119

120
    private void createChildren(@NonNull String[] resourceLocations) throws InvalidResourceException {
121
        SiblingClassLoader scl = this;
×
122
        for (String resourceLocation : resourceLocations) {
×
123
            if (resourceLocation != null && !resourceLocation.isEmpty()) {
×
124
                scl = scl.createChild(resourceLocation);
×
125
            }
126
        }
127
    }
×
128

129
    @NonNull
130
    private SiblingClassLoader createChild(String resourceLocation) throws InvalidResourceException {
131
        if (!firstborn) {
×
132
            throw new IllegalStateException("Only the first among siblings can create a child");
×
133
        }
134
        return new SiblingClassLoader(getName(), this, resourceLocation);
×
135
    }
136

137
    /**
138
     * Adds packages that this ClassLoader should not handle.
139
     * Any class whose fully-qualified name starts with the name registered here will be handled
140
     * by the parent ClassLoader in the usual fashion.
141
     * @param packageNames package names that we be compared against fully qualified package names to exclude
142
     */
143
    public void excludePackage(String... packageNames) {
144
        if (packageNames != null && packageNames.length > 0) {
×
145
            for (String packageName : packageNames) {
×
146
                if (excludePackageNames == null) {
×
147
                    excludePackageNames = new HashSet<>();
×
148
                }
149
                excludePackageNames.add(packageName + PACKAGE_SEPARATOR_CHAR);
×
150
            }
151
        } else {
152
            excludePackageNames = null;
×
153
        }
154
    }
×
155

156
    /**
157
     * Adds classes that this ClassLoader should not handle.
158
     * Any class whose fully-qualified name starts with the name registered here will be handled
159
     * by the parent ClassLoader in the usual fashion.
160
     * @param classNames class names that we be compared against fully qualified class names to exclude
161
     */
162
    public void excludeClass(String... classNames) {
163
        if (classNames != null && classNames.length > 0) {
×
164
            for (String className : classNames) {
×
165
                if (!isExcludedPackage(className)) {
×
166
                    if (excludeClassNames == null) {
×
167
                        excludeClassNames = new HashSet<>();
×
168
                    }
169
                    excludeClassNames.add(className);
×
170
                }
171
            }
172
        } else {
173
            excludeClassNames = null;
×
174
        }
175
    }
×
176

177
    private boolean isExcluded(String className) {
178
        return (isExcludedPackage(className) || isExcludedClass(className));
1✔
179
    }
180

181
    private boolean isExcludedPackage(String className) {
182
        if (excludePackageNames != null) {
1✔
183
            for (String packageName : excludePackageNames) {
×
184
                if (className.startsWith(packageName)) {
×
185
                    return true;
×
186
                }
187
            }
×
188
        }
189
        return false;
1✔
190
    }
191

192
    private boolean isExcludedClass(String className) {
193
        return (excludeClassNames != null && excludeClassNames.contains(className));
1✔
194
    }
195

196
    public int getId() {
197
        return id;
×
198
    }
199

200
    public SiblingClassLoader getRoot() {
201
        return root;
×
202
    }
203

204
    public boolean isRoot() {
205
        return (this == root);
1✔
206
    }
207

208
    public List<SiblingClassLoader> getChildren() {
209
        return children;
1✔
210
    }
211

212
    private int addChild(SiblingClassLoader child) {
213
        synchronized (children) {
×
214
            children.add(child);
×
215
            return children.size();
×
216
        }
217
    }
218

219
    public boolean hasChildren() {
220
        return !children.isEmpty();
×
221
    }
222

223
    public boolean isFirstborn() {
224
        return firstborn;
×
225
    }
226

227
    public ResourceManager getResourceManager() {
228
        return resourceManager;
1✔
229
    }
230

231
    public String getResourceLocation() {
232
        return resourceLocation;
×
233
    }
234

235
    public synchronized void reload() throws InvalidResourceException {
236
        reload(root);
×
237
    }
×
238

239
    private void reload(@NonNull SiblingClassLoader self) throws InvalidResourceException {
240
        self.increaseReloadedCount();
×
241

242
        if (self.getResourceManager() != null) {
×
243
            self.getResourceManager().reset();
×
244
        }
245

246
        SiblingClassLoader firstborn = null;
×
247
        List<SiblingClassLoader> siblings = new ArrayList<>();
×
248
        for (SiblingClassLoader child : self.getChildren()) {
×
249
            if (child.isFirstborn()) {
×
250
                firstborn = child;
×
251
            } else {
252
                siblings.add(child);
×
253
            }
254
        }
×
255
        if (!siblings.isEmpty()) {
×
256
            self.leave(siblings);
×
257
        }
258
        if (firstborn != null) {
×
259
            reload(firstborn);
×
260
        }
261
    }
×
262

263
    private void increaseReloadedCount() {
264
        reloadedCount++;
×
265
    }
×
266

267
    private void leave(@NonNull List<SiblingClassLoader> siblings) {
268
        for (SiblingClassLoader sibling : siblings) {
×
269
            ResourceManager rm = sibling.getResourceManager();
×
270
            if (rm != null) {
×
271
                rm.release();
×
272
            }
273
            children.remove(sibling);
×
274
        }
×
275
    }
×
276

277
    @Override
278
    protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
279
        synchronized (getClassLoadingLock(name)) {
1✔
280
            // First, check if the class has already been loaded
281
            Class<?> c = findLoadedClass(name);
1✔
282
            if (c == null) {
1✔
283
                try {
284
                    // First, search from local repositories
285
                    c = findClass(name);
×
286
                } catch (ClassNotFoundException e) {
1✔
287
                    // ignored
288
                }
×
289
                if (c == null) {
1✔
290
                    // If not found locally, from parent classloader
291
                    ClassLoader parent = root.getParent();
1✔
292
                    if (parent != null) {
1✔
293
                        c = Class.forName(name, false, parent);
1✔
294
                    } else {
295
                        // Try loading the class with the system class loader
296
                        c = findSystemClass(name);
×
297
                    }
298
                }
299
            }
300
            if (resolve) {
1✔
301
                resolveClass(c);
×
302
            }
303
            return c;
1✔
304
        }
305
    }
306

307
    @Override
308
    public Class<?> findClass(String name) throws ClassNotFoundException {
309
        Objects.requireNonNull(name);
1✔
310
        try  {
311
            byte[] classData = loadClassData(name);
1✔
312
            if (classData != null) {
1✔
313
                return defineClass(name, classData, 0, classData.length);
×
314
            } else {
315
                throw new ClassNotFoundException(name);
1✔
316
            }
317
        } catch (InvalidResourceException e) {
×
318
            throw new ClassNotFoundException(name, e);
×
319
        }
320
    }
321

322
    @Nullable
323
    private byte[] loadClassData(String className) throws InvalidResourceException {
324
        if (isExcluded(className)) {
1✔
325
            return null;
×
326
        }
327

328
        String resourceName = ResourceManager.classNameToResourceName(className);
1✔
329
        Enumeration<URL> res = ResourceManager.findResources(resourceName, getAllMembers());
1✔
330
        URL url = null;
1✔
331
        if (res.hasMoreElements()) {
1✔
332
            url = res.nextElement();
×
333
        }
334
        if (url == null) {
1✔
335
            return null;
1✔
336
        }
337

338
        try {
339
            URLConnection connection = url.openConnection();
×
340
            BufferedInputStream input = new BufferedInputStream(connection.getInputStream());
×
341
            ByteArrayOutputStream output = new ByteArrayOutputStream();
×
342
            int i;
343
            while ((i = input.read()) != -1) {
×
344
                output.write(i);
×
345
            }
346
            input.close();
×
347
            byte[] classData = output.toByteArray();
×
348
            output.close();
×
349
            return classData;
×
350
        } catch (IOException e) {
×
351
            throw new InvalidResourceException("Unable to read class file: " + url, e);
×
352
        }
353
    }
354

355
    @Override
356
    public URL getResource(String name) {
357
        // Search from local repositories
358
        URL url = findResource(name);
×
359
        if (url == null) {
×
360
            ClassLoader parent = root.getParent();
×
361
            if (parent != null) {
×
362
                url = parent.getResource(name);
×
363
            } else {
364
                url = getSystemClassLoader().getResource(name);
×
365
            }
366
        }
367
        return url;
×
368
    }
369

370
    @Override
371
    @NonNull
372
    public Enumeration<URL> getResources(String name) throws IOException {
373
        Objects.requireNonNull(name);
1✔
374
        Enumeration<URL> parentResources = null;
1✔
375
        ClassLoader parent = root.getParent();
1✔
376
        if (parent != null) {
1✔
377
            parentResources = parent.getResources(name);
1✔
378
        }
379
        return ResourceManager.findResources(name, getAllMembers(), parentResources);
1✔
380
    }
381

382
    @Override
383
    public URL findResource(String name) {
384
        Objects.requireNonNull(name);
×
385
        URL url = null;
×
NEW
386
        Enumeration<URL> res = ResourceManager.findResources(name, getAllMembers());
×
387
        if (res.hasMoreElements()) {
×
388
            url = res.nextElement();
×
389
        }
390
        return url;
×
391
    }
392

393
    @Override
394
    @NonNull
395
    public Enumeration<URL> findResources(String name) {
396
        Objects.requireNonNull(name);
×
NEW
397
        return ResourceManager.findResources(name, getAllMembers());
×
398
    }
399

400
    @NonNull
401
    public Enumeration<URL> getAllResources() {
NEW
402
        return ResourceManager.findResources(getAllMembers());
×
403
    }
404

405
    @NonNull
406
    public Iterator<SiblingClassLoader> getAllMembers() {
407
        return getMembers(root);
1✔
408
    }
409

410
    @Override
411
    public String toString() {
412
        String thisName = ObjectUtils.simpleIdentityToString(this);
×
413
        ToStringBuilder tsb = new ToStringBuilder(thisName);
×
414
        tsb.append("id", id);
×
415
        tsb.append("name", getName());
×
416
        if (getParent() instanceof SiblingClassLoader parent) {
×
417
            tsb.append("parent", parent.getId());
×
418
        } else if (getParent() != null) {
×
419
            tsb.append("parent", ObjectUtils.simpleIdentityToString(getParent()));
×
420
        }
421
        tsb.append("root", this == root);
×
422
        tsb.append("firstborn", firstborn);
×
423
        tsb.append("resourceLocation", resourceLocation);
×
424
        tsb.append("numberOfResources", resourceManager.getNumberOfResources());
×
425
        tsb.appendSize("numberOfChildren", children);
×
426
        tsb.append("reloadedCount", reloadedCount);
×
427
        return tsb.toString();
×
428
    }
429

430
    @NonNull
431
    public static Iterator<SiblingClassLoader> getMembers(@NonNull final SiblingClassLoader root) {
432
        return new Iterator<>() {
1✔
433
            private SiblingClassLoader next = root;
1✔
434
            private Iterator<SiblingClassLoader> children = root.getChildren().iterator();
1✔
435
            private SiblingClassLoader firstChild;
436

437
            @Override
438
            public boolean hasNext() {
439
                return (next != null);
1✔
440
            }
441

442
            @Override
443
            public SiblingClassLoader next() {
444
                if (next == null) {
1✔
445
                    throw new NoSuchElementException();
×
446
                }
447

448
                SiblingClassLoader current = next;
1✔
449
                if (children.hasNext()) {
1✔
450
                    next = children.next();
×
451
                    if (firstChild == null) {
×
452
                        firstChild = next;
×
453
                    }
454
                } else {
455
                    if (firstChild != null) {
1✔
456
                        children = firstChild.getChildren().iterator();
×
457
                        if (children.hasNext()) {
×
458
                            next = children.next();
×
459
                            firstChild = next;
×
460
                        } else {
461
                            next = null;
×
462
                        }
463
                    } else {
464
                        next = null;
1✔
465
                    }
466
                }
467
                return current;
1✔
468
            }
469
        };
470
    }
471

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

© 2025 Coveralls, Inc