Revert remove blender#709
Conversation
There was a problem hiding this comment.
Code Review
This pull request introduces the jme3-blender module to add GLTF support to the jMonkeyEngine SDK, including NetBeans project configurations, a GLTF data object handler, and Python scripts for importing external models via Blender. The code review identified several critical issues: potential resource leaks in Scripts.java and OutputReader.java due to unclosed streams, a potential NameError in the Python scripts if file operations fail, potential NullPointerExceptions in GLTFDataObject.java and GltfExtrasLoader.java, and an inefficient exception-based control flow in GltfExtrasLoader.java that should be replaced with proper JSON type checks.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
| try { | ||
| InputStream in = null; | ||
| OutputStream out = null; | ||
| try { | ||
| URL url = URI.create("nbres:" + root + name).toURL(); | ||
| file = FileUtil.createData(folder, name); | ||
| in = url.openStream(); | ||
| out = file.getOutputStream(); | ||
| FileUtil.copy(in, out); | ||
| } catch (IOException e) { | ||
| Exceptions.printStackTrace(e); | ||
| } finally { | ||
| if (in != null) { | ||
| in.close(); | ||
| } | ||
| if (out != null) { | ||
| out.close(); | ||
| } | ||
| } | ||
| logger.log(Level.INFO, "Extracted script {0}", file.getPath()); | ||
| } catch (IOException ex) { | ||
| Exceptions.printStackTrace(ex); | ||
| } |
There was a problem hiding this comment.
If in.close() throws an IOException in the finally block, out.close() will be skipped, causing a resource leak. Since the project uses Java 21, we should use try-with-resources to automatically and safely manage both streams.
try {
URL url = URI.create("nbres:" + root + name).toURL();
file = FileUtil.createData(folder, name);
try (InputStream in = url.openStream();
OutputStream out = file.getOutputStream()) {
FileUtil.copy(in, out);
}
logger.log(Level.INFO, "Extracted script {0}", file.getPath());
} catch (IOException ex) {
Exceptions.printStackTrace(ex);
}| def save_file(save_path): | ||
| # Check if output file exists already | ||
| try: | ||
| f = open(save_path, 'w') | ||
| f.close() | ||
| ok = True | ||
| except: | ||
| print("Cannot save to path %r" % save_path) | ||
|
|
||
| import traceback | ||
| traceback.print_exc() | ||
|
|
||
| # Save .blend file | ||
| if ok: | ||
| bpy.ops.wm.save_as_mainfile(filepath=save_path) |
There was a problem hiding this comment.
If an exception occurs when opening the file, ok is never defined, which will cause a NameError: name 'ok' is not defined when if ok: is evaluated. We should initialize ok = False beforehand. Also, using a with statement is more idiomatic and avoids bare except: clauses per PEP 8.
| def save_file(save_path): | |
| # Check if output file exists already | |
| try: | |
| f = open(save_path, 'w') | |
| f.close() | |
| ok = True | |
| except: | |
| print("Cannot save to path %r" % save_path) | |
| import traceback | |
| traceback.print_exc() | |
| # Save .blend file | |
| if ok: | |
| bpy.ops.wm.save_as_mainfile(filepath=save_path) | |
| def save_file(save_path): | |
| # Check if output file exists already | |
| ok = False | |
| try: | |
| with open(save_path, 'w') as f: | |
| pass | |
| ok = True | |
| except Exception: | |
| print("Cannot save to path %r" % save_path) | |
| import traceback | |
| traceback.print_exc() | |
| # Save .blend file | |
| if ok: | |
| bpy.ops.wm.save_as_mainfile(filepath=save_path) |
| def save_file(save_path): | ||
| # Check if output file exists already | ||
| try: | ||
| f = open(save_path, 'w') | ||
| f.close() | ||
| ok = True | ||
| except: | ||
| print("Cannot save to path %r" % save_path) | ||
|
|
||
| import traceback | ||
| traceback.print_exc() | ||
|
|
||
| # Save .blend file | ||
| if ok: | ||
| bpy.ops.wm.save_as_mainfile(filepath=save_path) |
There was a problem hiding this comment.
If an exception occurs when opening the file, ok is never defined, which will cause a NameError: name 'ok' is not defined when if ok: is evaluated. We should initialize ok = False beforehand. Also, using a with statement is more idiomatic and avoids bare except: clauses per PEP 8.
| def save_file(save_path): | |
| # Check if output file exists already | |
| try: | |
| f = open(save_path, 'w') | |
| f.close() | |
| ok = True | |
| except: | |
| print("Cannot save to path %r" % save_path) | |
| import traceback | |
| traceback.print_exc() | |
| # Save .blend file | |
| if ok: | |
| bpy.ops.wm.save_as_mainfile(filepath=save_path) | |
| def save_file(save_path): | |
| # Check if output file exists already | |
| ok = False | |
| try: | |
| with open(save_path, 'w') as f: | |
| pass | |
| ok = True | |
| except Exception: | |
| print("Cannot save to path %r" % save_path) | |
| import traceback | |
| traceback.print_exc() | |
| # Save .blend file | |
| if ok: | |
| bpy.ops.wm.save_as_mainfile(filepath=save_path) |
| def save_file(save_path): | ||
| # Check if output file exists already | ||
| try: | ||
| f = open(save_path, 'w') | ||
| f.close() | ||
| ok = True | ||
| except: | ||
| print("Cannot save to path %r" % save_path) | ||
|
|
||
| import traceback | ||
| traceback.print_exc() | ||
|
|
||
| # Save .blend file | ||
| if ok: | ||
| bpy.ops.wm.save_as_mainfile(filepath=save_path) |
There was a problem hiding this comment.
If an exception occurs when opening the file, ok is never defined, which will cause a NameError: name 'ok' is not defined when if ok: is evaluated. We should initialize ok = False beforehand. Also, using a with statement is more idiomatic and avoids bare except: clauses per PEP 8.
| def save_file(save_path): | |
| # Check if output file exists already | |
| try: | |
| f = open(save_path, 'w') | |
| f.close() | |
| ok = True | |
| except: | |
| print("Cannot save to path %r" % save_path) | |
| import traceback | |
| traceback.print_exc() | |
| # Save .blend file | |
| if ok: | |
| bpy.ops.wm.save_as_mainfile(filepath=save_path) | |
| def save_file(save_path): | |
| # Check if output file exists already | |
| ok = False | |
| try: | |
| with open(save_path, 'w') as f: | |
| pass | |
| ok = True | |
| except Exception: | |
| print("Cannot save to path %r" % save_path) | |
| import traceback | |
| traceback.print_exc() | |
| # Save .blend file | |
| if ok: | |
| bpy.ops.wm.save_as_mainfile(filepath=save_path) |
| @Override | ||
| public synchronized ModelKey getAssetKey() { | ||
| GltfModelKey gltf = new GltfModelKey(super.getAssetKey().getName()); | ||
| gltf.setExtrasLoader(GltfExtrasLoader.INSTANCE); | ||
| return gltf; | ||
| } |
There was a problem hiding this comment.
Potential NullPointerException if super.getAssetKey() returns null. It is safer to perform a null check before accessing its properties.
@Override
public synchronized ModelKey getAssetKey() {
ModelKey superKey = super.getAssetKey();
if (superKey == null) {
return null;
}
GltfModelKey gltf = new GltfModelKey(superKey.getName());
gltf.setExtrasLoader(GltfExtrasLoader.INSTANCE);
return gltf;
}| JsonObject jo = null; | ||
| try { | ||
| jo = extras.getAsJsonObject(); | ||
| } | ||
| catch(Exception e) { | ||
| log.warn("Skipping extras:" + extras, e); | ||
| } |
There was a problem hiding this comment.
| protected Object toAttribute( JsonElement el, boolean nested ) { | ||
| try { | ||
| return toAttribute(el.getAsJsonObject(), nested); | ||
| } catch (Exception e) { | ||
| } | ||
|
|
||
| try { | ||
| return toAttribute(el.getAsJsonArray(), nested); | ||
| } catch (Exception e) { | ||
| } | ||
|
|
||
| try { | ||
| return toAttribute(el.getAsJsonPrimitive(), nested); | ||
| } catch (Exception e) { | ||
| } | ||
|
|
||
| log.warn("Unhandled extras element:" + el); | ||
| return null; | ||
| } |
There was a problem hiding this comment.
Using exceptions for control flow (catching Exception to try different types) is an anti-pattern that can be highly inefficient. Instead, use the built-in type check methods like isJsonObject(), isJsonArray(), and isJsonPrimitive() to safely determine the element type.
protected Object toAttribute( JsonElement el, boolean nested ) {
if (el == null || el.isJsonNull()) {
return null;
}
if (el.isJsonObject()) {
return toAttribute(el.getAsJsonObject(), nested);
}
if (el.isJsonArray()) {
return toAttribute(el.getAsJsonArray(), nested);
}
if (el.isJsonPrimitive()) {
return toAttribute(el.getAsJsonPrimitive(), nested);
}
log.warn("Unhandled extras element:" + el);
return null;
}| public void run() { | ||
| try { | ||
| String line; | ||
| while ((line = in.readLine()) != null) { | ||
| if (line.trim().length() > 0) { | ||
| if (progress != null) { | ||
| progress.progress(line); | ||
| } else { | ||
| logger.log(Level.INFO, "{0}", line); | ||
| } | ||
| } | ||
| } | ||
| } catch (Exception e) { | ||
| e.printStackTrace(); | ||
| } | ||
| } |
There was a problem hiding this comment.
The reader in is never closed, which can lead to resource leaks. Additionally, using e.printStackTrace() is discouraged; exceptions should be logged using the logger. We should close the reader in a finally block and log the exception properly.
@Override
public void run() {
try {
String line;
while ((line = in.readLine()) != null) {
if (line.trim().length() > 0) {
if (progress != null) {
progress.progress(line);
} else {
logger.log(Level.INFO, "{0}", line);
}
}
}
} catch (Exception e) {
logger.log(Level.SEVERE, "Error reading output", e);
} finally {
try {
if (in != null) {
in.close();
}
} catch (Exception e) {
logger.log(Level.SEVERE, "Error closing reader", e);
}
}
}
Short term fix