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

mgoellnitz / dinistiq / 2371356991

08 Mar 2026 03:24PM UTC coverage: 95.274%. Remained the same
2371356991

push

gitlab-ci

Martin Goellnitz
Avoid deprecated URL API

368 of 394 branches covered (93.4%)

Branch coverage included in aggregate %.

640 of 664 relevant lines covered (96.39%)

0.96 hits per line

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

97.54
/src/main/java/dinistiq/SimpleClassResolver.java
1
/**
2
 *
3
 * Copyright 2013-2019 Martin Goellnitz
4
 *
5
 * This program is free software: you can redistribute it and/or modify
6
 * it under the terms of the GNU Lesser General Public License as published by
7
 * the Free Software Foundation, either version 3 of the License, or
8
 * (at your option) any later version.
9
 *
10
 * This program is distributed in the hope that it will be useful,
11
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
 * GNU Lesser General Public License for more details.
14
 *
15
 * You should have received a copy of the GNU Lesser General Public License
16
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17
 *
18
 */
19
package dinistiq;
20

21
import java.io.File;
22
import java.io.IOException;
23
import java.lang.annotation.Annotation;
24
import java.lang.reflect.Modifier;
25
import java.net.URI;
26
import java.net.URL;
27
import java.net.URLDecoder;
28
import java.util.Collection;
29
import java.util.Enumeration;
30
import java.util.HashSet;
31
import java.util.Set;
32
import java.util.SortedSet;
33
import java.util.TreeSet;
34
import java.util.jar.JarEntry;
35
import java.util.jar.JarInputStream;
36
import org.slf4j.Logger;
37
import org.slf4j.LoggerFactory;
38

39

40
/**
41
 *
42
 * Resolve classes or classnames from a given set of packages.
43
 *
44
 * One design goal in finding the classes was to hit the JAR files just once but apart from that this is a
45
 * far too simple implementation.
46
 *
47
 */
48
public class SimpleClassResolver implements ClassResolver {
49

50
    private static final Logger LOG = LoggerFactory.getLogger(SimpleClassResolver.class);
1✔
51

52
    private static final Set<String> CLASSES_TO_IGNORE = new HashSet<>();
1✔
53

54
    private final Set<String> packageNames;
55

56
    private final Set<String> properties;
57

58
    private final Set<String> classNames;
59

60

61
    /*
62
     * These classes must be ignored to be able to use Dinistiq without web integration
63
     * and corresponding useless error entries in the log
64
     */
65
    static {
66
        CLASSES_TO_IGNORE.add("dinistiq.web.DinistiqContextLoaderListener");
1✔
67
        CLASSES_TO_IGNORE.add("dinistiq.web.RegisterableServlet");
1✔
68
    }
1✔
69

70

71
    /**
72
     * Adds all relevant JAR/.class URLs for a given package to an already present set of URLs.
73
     *
74
     * @param urls set of URLs to add the newly resolved ones to.
75
     * @param packageName name of the package
76
     */
77
    private void addUrlsForPackage(Set<URL> urls, String packageName) {
78
        String packagePath = packageName.replace('.', '/');
1✔
79
        try {
80
            Enumeration<URL> urlEnumeration = Thread.currentThread().getContextClassLoader().getResources(packagePath);
1✔
81
            while (urlEnumeration.hasMoreElements()) {
1✔
82
                String url = urlEnumeration.nextElement().toString();
1✔
83
                int idx = url.indexOf('!');
1✔
84
                url = idx>0 ? url.substring(0, idx) : url;
1✔
85
                url = url.startsWith("jar:") ? url.substring(4) : url;
1✔
86
                url = url.startsWith("vfs:/") ? "file"+url.substring(3, url.length()-packagePath.length()-2) : url;
1!
87
                url = url.endsWith(".jar") ? url : url.substring(0, url.length()-packagePath.length());
1✔
88
                LOG.info("addUrlsForPackage() resulting URL {}", url);
1✔
89
                urls.add(URI.create(url).toURL());
1✔
90
            } // while
1✔
91
        } catch (IOException e) {
×
92
            LOG.error("addUrlsForPackage()", e);
×
93
        } // try/catch
1✔
94
    } // addUrlsForPackage()
1✔
95

96

97
    /**
98
     * Checks a file name if it needs to be considered for properties files or derive a class name from.
99
     *
100
     * @param name name of a file to be scanned
101
     */
102
    protected final void checkClassAndAdd(String name) {
103
        LOG.debug("checkClassAndAdd() name={}", name);
1✔
104
        if (name.endsWith(".class")&&(name.indexOf('$')<0)) {
1✔
105
            String n = name.replace(File.separatorChar, '/').replace('/', '.');
1✔
106
            String className = n.substring(0, n.length()-6);
1✔
107
            LOG.debug("checkClassAndAdd() class name {}", className);
1✔
108
            boolean add = false;
1✔
109
            if (!CLASSES_TO_IGNORE.contains(className)) {
1✔
110
                for (String packageName : packageNames) {
1✔
111
                    add = add||className.startsWith(packageName);
1✔
112
                } // if
1✔
113
            }
114
            if (add) {
1✔
115
                LOG.debug("checkClassAndAdd(): {}", className);
1✔
116
                classNames.add(className);
1✔
117
            } // if
118
        } // if
119
        if (name.endsWith(".properties")) {
1✔
120
            LOG.info("checkClassAndAdd() properties {}", name);
1✔
121
            properties.add(name.replace(File.separatorChar, '/'));
1✔
122
        } // if
123
    } // checkClassAndAdd()
1✔
124

125

126
    /**
127
     * Recurses a given directory to scan for properties and class files.
128
     *
129
     * @param dir base directory to scann recursively
130
     * @param basePathLength length of the basePath string
131
     */
132
    protected final void recurseSubDir(File dir, int basePathLength) {
133
        LOG.debug("recurseSubDir() scanning {}", dir.getAbsolutePath());
1✔
134
        for (File f : (dir.isDirectory() ? dir.listFiles() : new File[0])) {
1✔
135
            String fileName = f.getAbsolutePath().substring(basePathLength);
1✔
136
            LOG.debug("recurseSubDir() fileName={}", fileName);
1✔
137
            if (fileName.endsWith(".class")||fileName.endsWith(".properties")) {
1✔
138
                checkClassAndAdd(fileName);
1✔
139
            } else {
140
                recurseSubDir(f, basePathLength);
1✔
141
            } // if
142
        } // for
143
    } // recurseSubDir()
1✔
144

145

146
    /**
147
     * Initialize class resolver with a given set of package names to scan.
148
     *
149
     * @param packageNames Set of string names for pakckges to scan
150
     */
151
    public SimpleClassResolver(Set<String> packageNames) {
1✔
152
        this.packageNames = new HashSet<>(packageNames);
1✔
153
        // to have the properties files in the path which we intend to use for configuration
154
        this.packageNames.add(this.getClass().getPackage().getName());
1✔
155

156
        properties = new HashSet<>();
1✔
157
        classNames = new HashSet<>();
1✔
158
        Set<URL> urls = new HashSet<>();
1✔
159
        for (String packageName : this.packageNames) {
1✔
160
            addUrlsForPackage(urls, packageName);
1✔
161
        } // if
1✔
162
        LOG.debug("() url # {}", urls.size());
1✔
163
        for (URL u : urls) {
1✔
164
            try {
165
                String path = URLDecoder.decode(u.getPath(), "UTF-8");
1✔
166
                LOG.info("(): path {}", path);
1✔
167
                if (path.endsWith(".jar")) {
1✔
168
                    LOG.info("(): scanning jar {}", path);
1✔
169
                    try ( JarInputStream is = new JarInputStream(u.openStream())) {
1✔
170
                        for (JarEntry entry = is.getNextJarEntry(); entry!=null; entry = is.getNextJarEntry()) {
1✔
171
                            LOG.info("(): entry {}", entry.getName());
1✔
172
                            checkClassAndAdd(entry.getName());
1✔
173
                        } // for
174
                    }
175
                } else {
176
                    File dir = new File(path);
1✔
177
                    int basePathLength = dir.getAbsolutePath().length()+1;
1✔
178
                    recurseSubDir(dir, basePathLength);
1✔
179
                } // if
180
            } catch (IOException e) {
×
181
                LOG.error("()", e);
×
182
            } // try/catch
1✔
183
        } // for
1✔
184
    } // SimpleClassResolver()
1✔
185

186

187
    /**
188
     * Helper method to keep areas with suppressed warnings small.
189
     *
190
     * @param <T>
191
     * @param className
192
     * @return simply returns Class.forName(classname)
193
     * @throws ClassNotFoundException
194
     */
195
    @SuppressWarnings("unchecked")
196
    private <T extends Object> Class<T> loadClass(String className) throws ClassNotFoundException {
197
        return (Class<T>) Class.forName(className);
1✔
198
    } // loadClass()
199

200

201
    /**
202
     * Helper method for all API methods which have to scan all detected classes.
203
     *
204
     * @return collection of classes for the collected class names.
205
     */
206
    private <T extends Object> Collection<Class<T>> getClasses() {
207
        Set<Class<T>> result = new HashSet<>();
1✔
208
        for (String className : classNames) {
1✔
209
            try {
210
                LOG.debug("getClasses() className={}", className);
1✔
211
                Class<T> cls = loadClass(className);
1✔
212
                result.add(cls);
1✔
213
            } catch (ClassNotFoundException|Error e) {
1✔
214
                LOG.error("getClasses() file format error for class name "+className, e);
1✔
215
            } // try/catch
1✔
216
        } // for
1✔
217
        return result;
1✔
218
    } // getClasses()
219

220

221
    /**
222
     * Get classes from underlying packages satisfying the given superclass which are no interfaces and not abstract.
223
     *
224
     * @see ClassResolver#getSubclasses(java.lang.Class)
225
     */
226
    @Override
227
    public <T extends Object> Set<Class<T>> getSubclasses(Class<T> c) {
228
        Set<Class<T>> result = new HashSet<>();
1✔
229
        LOG.debug("getSubclasses() checking {} classes", classNames.size());
1✔
230
        Collection<Class<T>> classes = getClasses();
1✔
231
        for (Class<T> cls : classes) {
1✔
232
            LOG.debug("getSubclasses() className={}", cls.getName());
1✔
233
            if ((!cls.isInterface())&&c.isAssignableFrom(cls)&&((cls.getModifiers()&Modifier.ABSTRACT)==0)) {
1✔
234
                result.add(cls);
1✔
235
            } // if
236
        } // for
1✔
237
        return result;
1✔
238
    } // getSubclasses()
239

240

241
    /**
242
     * Get classes from underlying packages satisfying the given annotation which are no interfaces and not abstract.
243
     *
244
     * @see ClassResolver#getAnnotated(java.lang.Class)
245
     */
246
    @Override
247
    public <T extends Object> Set<Class<T>> getAnnotated(Class<? extends Annotation> annotation) {
248
        Set<Class<T>> result = new HashSet<>();
1✔
249
        LOG.debug("getAnnotated() checking {} classes", classNames.size());
1✔
250
        Collection<Class<T>> classes = getClasses();
1✔
251
        for (Class<T> cls : classes) {
1✔
252
            LOG.debug("getAnnotated() className={}", cls.getName());
1✔
253
            if ((!cls.isInterface())&&(cls.getAnnotation(annotation)!=null)&&((cls.getModifiers()&Modifier.ABSTRACT)==0)) {
1✔
254
                result.add(cls);
1✔
255
            } // if
256
        } // if
1✔
257
        return result;
1✔
258
    } // getAnnotated()
259

260

261
    /**
262
     * Get classes from underlying packages satisfying the given annotation.
263
     *
264
     * @see ClassResolver#getAnnotatedItems(java.lang.Class)
265
     */
266
    @Override
267
    public <T extends Object> Set<Class<T>> getAnnotatedItems(Class<? extends Annotation> annotation) {
268
        Set<Class<T>> result = new HashSet<>();
1✔
269
        LOG.debug("getAnnotatedItems() checking {} classes", classNames.size());
1✔
270
        Collection<Class<T>> classes = getClasses();
1✔
271
        for (Class<T> cls : classes) {
1✔
272
            LOG.debug("getAnnotatedItems() className={}", cls.getName());
1✔
273
            if (cls.getAnnotation(annotation)!=null) {
1✔
274
                result.add(cls);
1✔
275
            } // if
276
        } // if
1✔
277
        return result;
1✔
278
    } // getAnnotated()
279

280

281
    /**
282
     * Get classes from underlying packages satisfying the given annotation and superclass which are no interfaces.
283
     *
284
     * @see ClassResolver#getAnnotatedSubclasses(java.lang.Class, java.lang.Class)
285
     */
286
    @Override
287
    public <T extends Object> Set<Class<T>> getAnnotatedSubclasses(Class<T> c, Class<? extends Annotation> annotation) {
288
        Set<Class<T>> result = new HashSet<>();
1✔
289
        LOG.debug("getAnnotatedSubclasses() checking {} classes", classNames.size());
1✔
290
        Collection<Class<T>> classes = getClasses();
1✔
291
        for (Class<T> cls : classes) {
1✔
292
            LOG.debug("getAnnotatedSubclasses() className={}", cls.getName());
1✔
293
            if ((cls.getAnnotation(annotation)!=null)&&c.isAssignableFrom(cls)&&(!cls.isInterface())) {
1✔
294
                result.add(cls);
1✔
295
            } // if
296
        } // if
1✔
297
        return result;
1✔
298
    } // getAnnotatedSubclasses()
299

300

301
    /**
302
     * @see ClassResolver#getProperties(java.lang.String)
303
     */
304
    @Override
305
    public SortedSet<String> getProperties(String path) {
306
        SortedSet<String> result = new TreeSet<>();
1✔
307
        for (String property : properties) {
1✔
308
            LOG.info("getProperties({}) checking {}", path, property);
1✔
309
            if (property.startsWith(path)) {
1✔
310
                result.add(property);
1✔
311
            } // if
312
        } // for
1✔
313
        return result;
1✔
314
    } // getProperties()
315

316
} // SimpleClassResolver
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

© 2026 Coveralls, Inc