Skip to content

Commit b14a99d

Browse files
committed
Add a method to detect Java 3D extensions
1 parent 1f087d7 commit b14a99d

File tree

3 files changed

+203
-1
lines changed

3 files changed

+203
-1
lines changed
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
/*
2+
* #%L
3+
* Utility classes for working with Java 3D.
4+
* %%
5+
* Copyright (C) 2015 Board of Regents of the University of
6+
* Wisconsin-Madison.
7+
* %%
8+
* Redistribution and use in source and binary forms, with or without
9+
* modification, are permitted provided that the following conditions are met:
10+
*
11+
* 1. Redistributions of source code must retain the above copyright notice,
12+
* this list of conditions and the following disclaimer.
13+
* 2. Redistributions in binary form must reproduce the above copyright notice,
14+
* this list of conditions and the following disclaimer in the documentation
15+
* and/or other materials provided with the distribution.
16+
*
17+
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
18+
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19+
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20+
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
21+
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
22+
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
23+
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
24+
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
25+
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
26+
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27+
* POSSIBILITY OF SUCH DAMAGE.
28+
* #L%
29+
*/
30+
31+
package org.scijava.java3d;
32+
33+
import java.io.File;
34+
import java.util.ArrayList;
35+
import java.util.List;
36+
37+
import org.scijava.plugin.Plugin;
38+
import org.scijava.service.AbstractService;
39+
import org.scijava.service.Service;
40+
41+
/**
42+
* Default service for working with Java 3D.
43+
*
44+
* @author Curtis Rueden
45+
*/
46+
@Plugin(type = Service.class)
47+
public class DefaultJava3DService extends AbstractService implements
48+
Java3DService
49+
{
50+
51+
@Override
52+
public List<File> getLibExtLocations() {
53+
final ArrayList<File> files = new ArrayList<File>();
54+
final String extDirs = System.getProperty("java.ext.dirs");
55+
if (extDirs == null) return files;
56+
for (final String dir : extDirs.split(":")) {
57+
checkLibExtDirectory(files, dir);
58+
}
59+
return files;
60+
}
61+
62+
// -- Helper methods --
63+
64+
private void checkLibExtDirectory(final ArrayList<File> files,
65+
final String dir)
66+
{
67+
checkFile(files, new File(dir, "j3dcore.jar"));
68+
checkFile(files, new File(dir, "vecmath.jar"));
69+
checkFile(files, new File(dir, "j3dutils.jar"));
70+
}
71+
72+
private void checkFile(ArrayList<File> files, File file) {
73+
if (file.exists()) files.add(file);
74+
}
75+
76+
}

src/main/java/org/scijava/java3d/Java3DService.java

Lines changed: 50 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,13 +30,62 @@
3030

3131
package org.scijava.java3d;
3232

33+
import java.io.File;
34+
import java.util.List;
35+
3336
import org.scijava.service.SciJavaService;
37+
import org.scijava.ui.UIService;
3438

3539
/**
3640
* Interface for services which work with Java 3D.
37-
*
41+
* <p>
42+
* Historically, Java 3D was installed as an extension to the Java Runtime
43+
* Environment, meaning that JAR files and native libraries were placed in the
44+
* {@code lib/ext} directory of the JRE installation, or manually appended to
45+
* the Java extensions via the {@code java.ext.dirs} system property.
46+
* </p>
47+
* <p>
48+
* However, that approach has many downsides:
49+
* </p>
50+
* <ul>
51+
* <li>Users must install Java 3D manually, independent of the application.</li>
52+
* <li>Thus, at runtime, the application cannot manage the version of Java 3D in
53+
* the same was that it manages versions of its regular dependencies.</li>
54+
* <li>Similarly, at build time, the Java 3D dependency must be treated
55+
* specially (e.g., with Maven, using {@code provided} scope in the POM).</li>
56+
* </ul>
57+
* <p>
58+
* Failure to manage Java 3D installations as needed can result in cryptic
59+
* version-skew-related error messages, such as {@link NoSuchMethodError} or
60+
* even native-library-related errors. This situation is especially prevalent on
61+
* OS X, where Java 3D 1.3 was pre-installed in
62+
* {@code /System/Library/Java/Extensions} on older versions of the OS, and left
63+
* in place after OS upgrades (despite Java itself being uninstalled).
64+
* </p>
65+
* <p>
66+
* These days, Java 3D is built on top of JOGL, and <a
67+
* href="https://github.com/hharrison/java3d-core">available on GitHub</a>. But
68+
* old installations of Java 3D still lurk, waiting to disrupt applications at
69+
* runtime: libraries present on the Java extensions path take precedence over
70+
* those on the regular class path.
71+
* </p>
72+
* <p>
73+
* This service mitigates the issue by warning users (via
74+
* {@link UIService#showDialog}) if any Java 3D installations are detected as
75+
* Java extensions, and asking the user to clean them up before proceeding.
76+
* Consequently, applications can now use the modern JOGL version of Java 3D,
77+
* without worrying about obsolete versions of Java 3D interfering at runtime.
78+
* </p>
79+
*
3880
* @author Curtis Rueden
3981
*/
4082
public interface Java3DService extends SciJavaService {
4183

84+
/**
85+
* Gets the locations where Java 3D is installed as an extension.
86+
*
87+
* @return a {@link List} of directories containing Java 3D libraries.
88+
*/
89+
List<File> getLibExtLocations();
90+
4291
}

src/test/java/org/scijava/java3d/Java3DServiceTest.java

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,11 +30,88 @@
3030

3131
package org.scijava.java3d;
3232

33+
import static org.junit.Assert.assertEquals;
34+
35+
import java.io.File;
36+
import java.io.IOException;
37+
import java.util.HashSet;
38+
39+
import org.junit.After;
40+
import org.junit.Before;
41+
import org.junit.Test;
42+
import org.scijava.Context;
43+
import org.scijava.util.FileUtils;
44+
3345
/**
3446
* Tests {@link Java3DService}.
3547
*
3648
* @author Curtis Rueden
3749
*/
3850
public class Java3DServiceTest {
3951

52+
private Context context;
53+
private Java3DService j3d;
54+
private File tmp1;
55+
private File tmp2;
56+
57+
@Before
58+
public void setUp() throws IOException {
59+
context = new Context(Java3DService.class);
60+
j3d = context.service(Java3DService.class);
61+
62+
tmp1 = FileUtils.createTemporaryDirectory("libExt", "1");
63+
tmp2 = FileUtils.createTemporaryDirectory("libExt", "2");
64+
}
65+
66+
@After
67+
public void tearDown() {
68+
FileUtils.deleteRecursively(tmp1);
69+
FileUtils.deleteRecursively(tmp2);
70+
}
71+
72+
@Test
73+
public void testGetLibExtLocations() throws IOException {
74+
final HashSet<File> expected = new HashSet<File>();
75+
76+
System.setProperty("java.ext.dirs", tmp1.getAbsolutePath() + ":" +
77+
tmp2.getAbsolutePath());
78+
79+
final File j3dcore1 = createFile(tmp1, "j3dcore.jar");
80+
expected.add(j3dcore1);
81+
assertEquals(expected, libExtFiles());
82+
83+
final File j3dcore2 = createFile(tmp2, "j3dcore.jar");
84+
expected.add(j3dcore2);
85+
assertEquals(expected, libExtFiles());
86+
87+
final File vecmath = createFile(tmp1, "vecmath.jar");
88+
createFile(tmp2, "red-herring");
89+
expected.add(vecmath);
90+
assertEquals(expected, libExtFiles());
91+
92+
System.setProperty("java.ext.dirs", tmp1.getAbsolutePath());
93+
expected.remove(j3dcore2);
94+
assertEquals(expected, libExtFiles());
95+
96+
System.setProperty("java.ext.dirs", "");
97+
expected.clear();
98+
assertEquals(expected, libExtFiles());
99+
100+
System.clearProperty("java.ext.dirs");
101+
assertEquals(expected, libExtFiles());
102+
}
103+
104+
// -- Helper methods --
105+
106+
private File createFile(final File dir, final String name) throws IOException
107+
{
108+
final File file = new File(dir, name);
109+
file.createNewFile();
110+
return file;
111+
}
112+
113+
private HashSet<File> libExtFiles() {
114+
return new HashSet<File>(j3d.getLibExtLocations());
115+
}
116+
40117
}

0 commit comments

Comments
 (0)