Skip to content

Commit 5b2c5c7

Browse files
committed
Migrate ClassUtils.getLocation to Types.location
1 parent 4151adf commit 5b2c5c7

File tree

3 files changed

+156
-108
lines changed

3 files changed

+156
-108
lines changed

src/main/java/org/scijava/util/ClassUtils.java

Lines changed: 20 additions & 107 deletions
Original file line numberDiff line numberDiff line change
@@ -32,13 +32,11 @@
3232

3333
package org.scijava.util;
3434

35-
import java.io.File;
3635
import java.lang.annotation.Annotation;
3736
import java.lang.reflect.AnnotatedElement;
3837
import java.lang.reflect.Field;
3938
import java.lang.reflect.Method;
4039
import java.lang.reflect.Type;
41-
import java.net.MalformedURLException;
4240
import java.net.URL;
4341
import java.util.ArrayList;
4442
import java.util.Collections;
@@ -89,111 +87,6 @@ private ClassUtils() {
8987

9088
// -- Class loading, querying and reflection --
9189

92-
/**
93-
* Gets the base location of the given class.
94-
* <p>
95-
* If the class is directly on the file system (e.g.,
96-
* "/path/to/my/package/MyClass.class") then it will return the base directory
97-
* (e.g., "/path/to").
98-
* </p>
99-
* <p>
100-
* If the class is within a JAR file (e.g.,
101-
* "/path/to/my-jar.jar!/my/package/MyClass.class") then it will return the
102-
* path to the JAR (e.g., "/path/to/my-jar.jar").
103-
* </p>
104-
*
105-
* @param className The name of the class whose location is desired.
106-
* @see FileUtils#urlToFile(URL) to convert the result to a {@link File}.
107-
*/
108-
public static URL getLocation(final String className) {
109-
return getLocation(className, null);
110-
}
111-
112-
/**
113-
* Gets the base location of the given class.
114-
* <p>
115-
* If the class is directly on the file system (e.g.,
116-
* "/path/to/my/package/MyClass.class") then it will return the base directory
117-
* (e.g., "/path/to").
118-
* </p>
119-
* <p>
120-
* If the class is within a JAR file (e.g.,
121-
* "/path/to/my-jar.jar!/my/package/MyClass.class") then it will return the
122-
* path to the JAR (e.g., "/path/to/my-jar.jar").
123-
* </p>
124-
*
125-
* @param className The name of the class whose location is desired.
126-
* @param classLoader The class loader to use when loading the class.
127-
* @see FileUtils#urlToFile(URL) to convert the result to a {@link File}.
128-
*/
129-
public static URL getLocation(final String className,
130-
final ClassLoader classLoader)
131-
{
132-
final Class<?> c = Types.load(className, classLoader);
133-
return getLocation(c);
134-
}
135-
136-
/**
137-
* Gets the base location of the given class.
138-
* <p>
139-
* If the class is directly on the file system (e.g.,
140-
* "/path/to/my/package/MyClass.class") then it will return the base directory
141-
* (e.g., "file:/path/to").
142-
* </p>
143-
* <p>
144-
* If the class is within a JAR file (e.g.,
145-
* "/path/to/my-jar.jar!/my/package/MyClass.class") then it will return the
146-
* path to the JAR (e.g., "file:/path/to/my-jar.jar").
147-
* </p>
148-
*
149-
* @param c The class whose location is desired.
150-
* @see FileUtils#urlToFile(URL) to convert the result to a {@link File}.
151-
*/
152-
public static URL getLocation(final Class<?> c) {
153-
if (c == null) return null; // could not load the class
154-
155-
// try the easy way first
156-
try {
157-
final URL codeSourceLocation =
158-
c.getProtectionDomain().getCodeSource().getLocation();
159-
if (codeSourceLocation != null) return codeSourceLocation;
160-
}
161-
catch (final SecurityException e) {
162-
// NB: Cannot access protection domain.
163-
}
164-
catch (final NullPointerException e) {
165-
// NB: Protection domain or code source is null.
166-
}
167-
168-
// NB: The easy way failed, so we try the hard way. We ask for the class
169-
// itself as a resource, then strip the class's path from the URL string,
170-
// leaving the base path.
171-
172-
// get the class's raw resource path
173-
final URL classResource = c.getResource(c.getSimpleName() + ".class");
174-
if (classResource == null) return null; // cannot find class resource
175-
176-
final String url = classResource.toString();
177-
final String suffix = c.getCanonicalName().replace('.', '/') + ".class";
178-
if (!url.endsWith(suffix)) return null; // weird URL
179-
180-
// strip the class's path from the URL string
181-
final String base = url.substring(0, url.length() - suffix.length());
182-
183-
String path = base;
184-
185-
// remove the "jar:" prefix and "!/" suffix, if present
186-
if (path.startsWith("jar:")) path = path.substring(4, path.length() - 2);
187-
188-
try {
189-
return new URL(path);
190-
}
191-
catch (final MalformedURLException e) {
192-
e.printStackTrace();
193-
return null;
194-
}
195-
}
196-
19790
/**
19891
* Gets the given class's {@link Method}s marked with the annotation of the
19992
* specified class.
@@ -587,6 +480,26 @@ public static boolean hasClass(final String className,
587480
return Types.load(className, classLoader) != null;
588481
}
589482

483+
/** @deprecated Use {@link Types#location} and {@link Types#load} instead. */
484+
@Deprecated
485+
public static URL getLocation(final String className) {
486+
return Types.location(Types.load(className));
487+
}
488+
489+
/** @deprecated Use {@link Types#location} and {@link Types#load} instead. */
490+
@Deprecated
491+
public static URL getLocation(final String className,
492+
final ClassLoader classLoader)
493+
{
494+
return Types.location(Types.load(className, classLoader));
495+
}
496+
497+
/** @deprecated Use {@link Types#location} and {@link Types#load} instead. */
498+
@Deprecated
499+
public static URL getLocation(final Class<?> c) {
500+
return Types.location(c);
501+
}
502+
590503
/** @deprecated Use {@link Types#isBoolean} instead. */
591504
@Deprecated
592505
public static boolean isBoolean(final Class<?> type) {

src/main/java/org/scijava/util/Types.java

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@
3131

3232
package org.scijava.util;
3333

34+
import java.io.File;
35+
3436
// Portions of this class were adapted from the
3537
// org.apache.commons.lang3.reflect.TypeUtils and
3638
// org.apache.commons.lang3.Validate classes of
@@ -54,6 +56,8 @@
5456
import java.lang.reflect.Type;
5557
import java.lang.reflect.TypeVariable;
5658
import java.lang.reflect.WildcardType;
59+
import java.net.MalformedURLException;
60+
import java.net.URL;
5761
import java.util.ArrayList;
5862
import java.util.Arrays;
5963
import java.util.Collections;
@@ -65,6 +69,8 @@
6569
import java.util.Objects;
6670
import java.util.Set;
6771

72+
import org.scijava.util.FileUtils;
73+
6874
/**
6975
* Utility class for working with generic types, fields and methods.
7076
* <p>
@@ -212,6 +218,66 @@ public static Class<?> load(final String name, final ClassLoader classLoader,
212218
}
213219
}
214220

221+
/**
222+
* Gets the base location of the given class.
223+
* <p>
224+
* If the class is directly on the file system (e.g.,
225+
* "/path/to/my/package/MyClass.class") then it will return the base directory
226+
* (e.g., "file:/path/to").
227+
* </p>
228+
* <p>
229+
* If the class is within a JAR file (e.g.,
230+
* "/path/to/my-jar.jar!/my/package/MyClass.class") then it will return the
231+
* path to the JAR (e.g., "file:/path/to/my-jar.jar").
232+
* </p>
233+
*
234+
* @param c The class whose location is desired.
235+
* @return URL pointing to the class, or null if the location could not be
236+
* determined.
237+
* @see FileUtils#urlToFile(URL) to convert the result to a {@link File}.
238+
*/
239+
public static URL location(final Class<?> c) {
240+
// try the easy way first
241+
try {
242+
final URL codeSourceLocation =
243+
c.getProtectionDomain().getCodeSource().getLocation();
244+
if (codeSourceLocation != null) return codeSourceLocation;
245+
}
246+
catch (final SecurityException exc) {
247+
// NB: Cannot access protection domain.
248+
}
249+
catch (final NullPointerException exc) {
250+
// NB: Protection domain or code source is null.
251+
}
252+
253+
// NB: The easy way failed, so we try the hard way. We ask for the class
254+
// itself as a resource, then strip the class's path from the URL string,
255+
// leaving the base path.
256+
257+
// get the class's raw resource path
258+
final URL classResource = c.getResource(c.getSimpleName() + ".class");
259+
if (classResource == null) return null; // cannot find class resource
260+
261+
final String url = classResource.toString();
262+
final String suffix = c.getCanonicalName().replace('.', '/') + ".class";
263+
if (!url.endsWith(suffix)) return null; // weird URL
264+
265+
// strip the class's path from the URL string
266+
final String base = url.substring(0, url.length() - suffix.length());
267+
268+
String path = base;
269+
270+
// remove the "jar:" prefix and "!/" suffix, if present
271+
if (path.startsWith("jar:")) path = path.substring(4, path.length() - 2);
272+
273+
try {
274+
return new URL(path);
275+
}
276+
catch (final MalformedURLException e) {
277+
return null;
278+
}
279+
}
280+
215281
/**
216282
* Gets a string representation of the given type.
217283
*

src/test/java/org/scijava/util/TypesTest.java

Lines changed: 70 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,13 +38,21 @@
3838
import static org.junit.Assert.assertSame;
3939
import static org.junit.Assert.assertTrue;
4040
import static org.junit.Assert.fail;
41+
import static org.scijava.test.TestUtils.createTemporaryDirectory;
4142

43+
import java.io.File;
44+
import java.io.FileOutputStream;
45+
import java.io.IOException;
46+
import java.io.InputStream;
47+
import java.io.OutputStream;
4248
import java.io.Serializable;
4349
import java.lang.reflect.Field;
4450
import java.lang.reflect.Type;
4551
import java.lang.reflect.TypeVariable;
4652
import java.math.BigDecimal;
4753
import java.math.BigInteger;
54+
import java.net.URL;
55+
import java.net.URLClassLoader;
4856
import java.util.ArrayList;
4957
import java.util.Collection;
5058
import java.util.HashMap;
@@ -53,14 +61,17 @@
5361
import java.util.List;
5462
import java.util.Map;
5563
import java.util.Set;
64+
import java.util.jar.JarOutputStream;
65+
import java.util.zip.ZipEntry;
5666

5767
import org.junit.Test;
58-
68+
import org.scijava.util.FileUtils;
5969
/**
6070
* Tests {@link Types}.
6171
*
6272
* @author Curtis Rueden
6373
* @author Mark Hiner
74+
* @author Johannes Schindelin
6475
*/
6576
public class TypesTest {
6677

@@ -137,6 +148,43 @@ public void testLoadFailureLoud() {
137148
Types.load("a.non.existent.class", false);
138149
}
139150

151+
/** Tests {@link Types#location} with a class on the file system. */
152+
@Test
153+
public void testLocationUnpackedClass() throws IOException {
154+
final File tmpDir = createTemporaryDirectory("class-utils-test-");
155+
final String path = getClass().getName().replace('.', '/') + ".class";
156+
final File classFile = new File(tmpDir, path);
157+
assertTrue(classFile.getParentFile().exists() ||
158+
classFile.getParentFile().mkdirs());
159+
copy(getClass().getResource("/" + path).openStream(),
160+
new FileOutputStream(classFile), true);
161+
162+
final ClassLoader classLoader =
163+
new URLClassLoader(new URL[] { tmpDir.toURI().toURL() }, null);
164+
final Class<?> c = Types.load(getClass().getName(), classLoader);
165+
final URL location = Types.location(c);
166+
assertEquals(tmpDir, FileUtils.urlToFile(location));
167+
FileUtils.deleteRecursively(tmpDir);
168+
}
169+
170+
/** Tests {@link Types#location} with a class in a JAR file. */
171+
@Test
172+
public void testLocationClassInJar() throws IOException {
173+
final File tmpDir = createTemporaryDirectory("class-utils-test-");
174+
final File jar = new File(tmpDir, "test.jar");
175+
final JarOutputStream out = new JarOutputStream(new FileOutputStream(jar));
176+
final String path = getClass().getName().replace('.', '/') + ".class";
177+
out.putNextEntry(new ZipEntry(path));
178+
copy(getClass().getResource("/" + path).openStream(), out, true);
179+
180+
final ClassLoader classLoader =
181+
new URLClassLoader(new URL[] { jar.toURI().toURL() }, null);
182+
final Class<?> c = Types.load(getClass().getName(), classLoader);
183+
final URL location = Types.location(c);
184+
assertEquals(jar, FileUtils.urlToFile(location));
185+
jar.deleteOnExit();
186+
}
187+
140188
/** Tests {@link Types#name}. */
141189
public void testName() {
142190
@SuppressWarnings("unused")
@@ -513,6 +561,27 @@ public static enum Words {
513561

514562
// -- Helper methods --
515563

564+
/**
565+
* Copies bytes from an {@link InputStream} to an {@link OutputStream}.
566+
*
567+
* @param in the source
568+
* @param out the sink
569+
* @param closeOut whether to close the sink after we're done
570+
* @throws IOException
571+
*/
572+
private void copy(final InputStream in, final OutputStream out,
573+
final boolean closeOut) throws IOException
574+
{
575+
final byte[] buffer = new byte[16384];
576+
for (;;) {
577+
final int count = in.read(buffer);
578+
if (count < 0) break;
579+
out.write(buffer, 0, count);
580+
}
581+
in.close();
582+
if (closeOut) out.close();
583+
}
584+
516585
/** Convenience method to get the {@link Type} of a field. */
517586
private Type type(final Class<?> c, final String fieldName) {
518587
return Types.field(c, fieldName).getGenericType();

0 commit comments

Comments
 (0)