Skip to content

Modernize Java code: remove legacy APIs and improve type safety#78

Merged
rostam merged 1 commit intomasterfrom
modernize
Mar 16, 2026
Merged

Modernize Java code: remove legacy APIs and improve type safety#78
rostam merged 1 commit intomasterfrom
modernize

Conversation

@rostam
Copy link
Owner

@rostam rostam commented Mar 16, 2026

  • Replace Vector with ArrayList in GraphSaveObject
  • Replace deprecated javax.xml.bind.DatatypeConverter with java.util.Base64
  • Replace raw Enumeration/Class types with typed generics in ExtensionClassLoader
  • Add try-with-resources for FileInputStream and ZipFile in ExtensionClassLoader
  • Replace string concatenation in loops with StringBuilder in AlgorithmUtils
  • Simplify isConnected() to use stream().distinct().count()
  • Fix raw ArrayX types to ArrayX<?> across preferences classes (AbstractPreference, GraphPreferences, UserDefinedEligiblity, ArrowHandler)

- Replace Vector with ArrayList in GraphSaveObject
- Replace deprecated javax.xml.bind.DatatypeConverter with java.util.Base64
- Replace raw Enumeration/Class types with typed generics in ExtensionClassLoader
- Add try-with-resources for FileInputStream and ZipFile in ExtensionClassLoader
- Replace string concatenation in loops with StringBuilder in AlgorithmUtils
- Simplify isConnected() to use stream().distinct().count()
- Fix raw ArrayX types to ArrayX<?> across preferences classes (AbstractPreference,
  GraphPreferences, UserDefinedEligiblity, ArrowHandler)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@qodo-code-review
Copy link

Review Summary by Qodo

Modernize Java code: remove legacy APIs and improve type safety

✨ Enhancement

Grey Divider

Walkthroughs

Description
• Replace deprecated javax.xml.bind.DatatypeConverter with java.util.Base64
• Replace Vector with ArrayList in GraphSaveObject
• Replace raw type generics with typed generics across multiple classes
• Replace string concatenation in loops with StringBuilder in AlgorithmUtils
• Add try-with-resources for FileInputStream and ZipFile in ExtensionClassLoader
• Simplify isConnected() to use stream().distinct().count()
• Improve code formatting and control flow structure
Diagram
flowchart LR
  A["Legacy APIs<br/>DatatypeConverter<br/>Vector"] -->|Replace| B["Modern APIs<br/>Base64<br/>ArrayList"]
  C["Raw Types<br/>Class, ArrayX<br/>Enumeration"] -->|Add Generics| D["Typed Generics<br/>Class&lt;?&gt;<br/>ArrayX&lt;?&gt;"]
  E["String Concat<br/>in Loops"] -->|Refactor| F["StringBuilder<br/>Chaining"]
  G["Manual Resource<br/>Management"] -->|Modernize| H["Try-with-resources<br/>Auto-close"]
Loading

Grey Divider

File Changes

1. src/graphtea/extensions/AlgorithmUtils.java ✨ Enhancement +31/-25

Replace string concatenation with StringBuilder

• Replace string concatenation with StringBuilder in getEigenValues methods
• Simplify isConnected() to use stream().distinct().count() instead of Arrays.stream()
• Improve code formatting with proper spacing and braces
• Rename variable EigenValues to eigenValues for consistency
• Use isEmpty() instead of size() > 0 check

src/graphtea/extensions/AlgorithmUtils.java


2. src/graphtea/extensions/io/GraphSaveObject.java ✨ Enhancement +7/-7

Replace deprecated APIs with modern alternatives

• Replace deprecated javax.xml.bind.DatatypeConverter with java.util.Base64
• Replace Vector with ArrayList for vs and es fields
• Update graph2String() to use Base64.getMimeEncoder().encodeToString()
• Update String2Graph() and string2GraphSaveObject() to use Base64.getMimeDecoder().decode()

src/graphtea/extensions/io/GraphSaveObject.java


3. src/graphtea/graph/old/ArrowHandler.java ✨ Enhancement +2/-2

Add generic type parameters to ArrayX usage

• Add generic type parameter to ArrayX in defineEligibleValuesForSettings method signature
• Change HashMap<Object, ArrayX> to HashMap<Object, ArrayX<?>>
• Specify ArrayX<Integer> for the type variable t

src/graphtea/graph/old/ArrowHandler.java


View more (4)
4. src/graphtea/platform/extension/ExtensionClassLoader.java ✨ Enhancement +38/-47

Add generics and try-with-resources for resource management

• Add generic type parameter to Class in classes map declaration
• Add generic type parameters to all Class references throughout the file
• Replace raw Enumeration with Enumeration<? extends ZipEntry>
• Add try-with-resources for FileInputStream in loadClassFiles method
• Refactor unZip method to use try-with-resources for ZipFile and nested streams
• Improve code formatting with proper braces and spacing

src/graphtea/platform/extension/ExtensionClassLoader.java


5. src/graphtea/platform/preferences/AbstractPreference.java ✨ Enhancement +2/-2

Add generic type parameters to ArrayX

• Add generic type parameter to ArrayX in putAttribute method signature
• Change ArrayX to ArrayX<?> in defineAttributes abstract method signature

src/graphtea/platform/preferences/AbstractPreference.java


6. src/graphtea/platform/preferences/GraphPreferences.java ✨ Enhancement +3/-3

Add generic type parameters to ArrayX usage

• Add generic type parameter to ArrayX in defineAttributes method signature
• Add generic type parameters to ArrayX in defineMultipleAttributes method
• Update HashMap declarations to use ArrayX<?>

src/graphtea/platform/preferences/GraphPreferences.java


7. src/graphtea/platform/preferences/UserDefinedEligiblity.java ✨ Enhancement +1/-1

Add generic type parameters to ArrayX

• Add generic type parameter to ArrayX in defineEligibleValuesForSettings method signature
• Change HashMap<Object, ArrayX> to HashMap<Object, ArrayX<?>>

src/graphtea/platform/preferences/UserDefinedEligiblity.java


Grey Divider

Qodo Logo

@qodo-code-review
Copy link

qodo-code-review bot commented Mar 16, 2026

Code Review by Qodo

🐞 Bugs (3) 📘 Rule violations (0) 📎 Requirement gaps (0)

Grey Divider


Action required

1. Old .tea files unreadable 🐞 Bug ✓ Correctness
Description
GraphSaveObject is serialized as the on-disk GraphTea format (*.tea); changing its serialized field
types from Vector to ArrayList will prevent loading graphs saved by older versions (deserialization
will fail due to incompatible field metadata). This breaks backward compatibility for
SaveGraph/LoadGraph.
Code

src/graphtea/extensions/io/GraphSaveObject.java[R25-26]

+    public ArrayList<VertexSaveObject> vs = new ArrayList<>();
+    ArrayList<EdgeSaveObject> es = new ArrayList<>();
Evidence
GraphSaveObject is the serialized payload for the GraphTea file format and is deserialized back into
a GraphModel; changing the field types changes the serialization schema and will fail when reading
older streams.

src/graphtea/extensions/io/GraphSaveObject.java[16-26]
src/graphtea/extensions/io/SaveGraph.java[27-34]
src/graphtea/extensions/io/LoadGraph.java[35-42]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`GraphSaveObject` is persisted to disk via Java serialization for the GraphTea `*.tea` format. The PR changes the serialized field types (`Vector` -&gt; `ArrayList`) without providing any migration logic, which will make older `*.tea` files fail to deserialize.

### Issue Context
`SaveGraph` writes `new GraphSaveObject(graph)` through `ObjectOutputStream`, and `LoadGraph` reads it back through `ObjectInputStream`.

### Fix Focus Areas
- src/graphtea/extensions/io/GraphSaveObject.java[16-36]
- src/graphtea/extensions/io/SaveGraph.java[27-34]
- src/graphtea/extensions/io/LoadGraph.java[35-42]

### Implementation direction
- Add explicit serialization compatibility in `GraphSaveObject`:
 - Use `serialPersistentFields` and `readObject`/`writeObject` to persist the legacy schema (e.g., store as `Vector` in the stream) while using `ArrayList` in memory, **or** read both possible types (`Vector` or `ArrayList`) and convert.
 - Keep stream field names stable (`vs`, `es`, etc.) and convert during `readObject`.
- Validate by loading a `.tea` file saved with the previous version.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. Zip path traversal extraction 🐞 Bug ⛨ Security
Description
ExtensionClassLoader.unZip writes zip entries to destDir + File.separator + entry.getName()
without normalizing/validating the entry path, allowing a crafted extension zip to write outside
destDir via ../ (Zip Slip). This is particularly risky because loadClassFiles automatically unzips
every *.zip in the extensions directory.
Code

src/graphtea/platform/extension/ExtensionClassLoader.java[R146-149]

+                try (InputStream in = zipFile.getInputStream(entry);
+                     OutputStream out = new BufferedOutputStream(
+                             new FileOutputStream(destDir + File.separator + entry.getName()))) {
+                    copyInputStream(in, out);
Evidence
The extension loader automatically extracts arbitrary zip files found in the directory, and
extraction uses the raw ZipEntry name to form the output path; without path validation this allows
writing outside the intended directory.

src/graphtea/platform/extension/ExtensionClassLoader.java[50-54]
src/graphtea/platform/extension/ExtensionClassLoader.java[137-149]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`ExtensionClassLoader.unZip` uses `entry.getName()` directly when creating output `File`s. A malicious zip entry name (e.g., `../outside.txt`) can cause writes outside the target directory (Zip Slip).

### Issue Context
`loadClassFiles` auto-unzips every `*.zip` under the extensions directory.

### Fix Focus Areas
- src/graphtea/platform/extension/ExtensionClassLoader.java[50-55]
- src/graphtea/platform/extension/ExtensionClassLoader.java[137-155]

### Implementation direction
- Use `java.nio.file.Path`:
 - `Path dest = Paths.get(destDir).toRealPath();`
 - `Path target = dest.resolve(entry.getName()).normalize();`
 - Reject entries where `!target.startsWith(dest)`.
- For file entries:
 - `Files.createDirectories(target.getParent());`
 - Write to `target.toFile()`.
- Apply the same containment check for directory entries before `createDirectories`.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

3. Truncated class byte reads 🐞 Bug ⛯ Reliability
Description
ExtensionClassLoader.loadClassFiles reads .class files with a single fis.read(data) and ignores
the returned byte count; if fewer bytes are read than expected, findClass later calls defineClass
with a potentially truncated buffer. This can cause intermittent ClassFormatError/class loading
failures for extensions.
Code

src/graphtea/platform/extension/ExtensionClassLoader.java[R69-72]

                    byte[] data = new byte[(int) file1.length()];
-                    FileInputStream fis = new FileInputStream(file1);
-                    fis.read(data);
+                    try (FileInputStream fis = new FileInputStream(file1)) {
+                        fis.read(data);
+                    }
Evidence
The loader assumes one read fills the entire array sized to the file length, then later passes the
full array length to defineClass, which relies on complete class bytes.

src/graphtea/platform/extension/ExtensionClassLoader.java[68-73]
src/graphtea/platform/extension/ExtensionClassLoader.java[95-99]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`.class` byte arrays are populated via a single `FileInputStream.read(byte[])` call and the return value is ignored. If the read is partial, the loader will later pass a buffer containing unread bytes to `defineClass`.

### Issue Context
`findClass` uses `defineClass(name, data, 0, data.length)` based on `classesData` produced by `loadClassFiles`.

### Fix Focus Areas
- src/graphtea/platform/extension/ExtensionClassLoader.java[68-74]
- src/graphtea/platform/extension/ExtensionClassLoader.java[90-99]

### Implementation direction
- Replace the manual length allocation + single read with:
 - `byte[] data = java.nio.file.Files.readAllBytes(file1.toPath());`
- Or, if keeping streams, use a loop / `DataInputStream.readFully` to fill the array and handle short reads explicitly.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

ⓘ The new review experience is currently in Beta. Learn more

Grey Divider

Qodo Logo

@rostam rostam merged commit bfe6ded into master Mar 16, 2026
3 of 4 checks passed
Comment on lines +25 to +26
public ArrayList<VertexSaveObject> vs = new ArrayList<>();
ArrayList<EdgeSaveObject> es = new ArrayList<>();

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Action required

1. Old .tea files unreadable 🐞 Bug ✓ Correctness

GraphSaveObject is serialized as the on-disk GraphTea format (*.tea); changing its serialized field
types from Vector to ArrayList will prevent loading graphs saved by older versions (deserialization
will fail due to incompatible field metadata). This breaks backward compatibility for
SaveGraph/LoadGraph.
Agent Prompt
### Issue description
`GraphSaveObject` is persisted to disk via Java serialization for the GraphTea `*.tea` format. The PR changes the serialized field types (`Vector` -> `ArrayList`) without providing any migration logic, which will make older `*.tea` files fail to deserialize.

### Issue Context
`SaveGraph` writes `new GraphSaveObject(graph)` through `ObjectOutputStream`, and `LoadGraph` reads it back through `ObjectInputStream`.

### Fix Focus Areas
- src/graphtea/extensions/io/GraphSaveObject.java[16-36]
- src/graphtea/extensions/io/SaveGraph.java[27-34]
- src/graphtea/extensions/io/LoadGraph.java[35-42]

### Implementation direction
- Add explicit serialization compatibility in `GraphSaveObject`:
  - Use `serialPersistentFields` and `readObject`/`writeObject` to persist the legacy schema (e.g., store as `Vector` in the stream) while using `ArrayList` in memory, **or** read both possible types (`Vector` or `ArrayList`) and convert.
  - Keep stream field names stable (`vs`, `es`, etc.) and convert during `readObject`.
- Validate by loading a `.tea` file saved with the previous version.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment on lines +146 to +149
try (InputStream in = zipFile.getInputStream(entry);
OutputStream out = new BufferedOutputStream(
new FileOutputStream(destDir + File.separator + entry.getName()))) {
copyInputStream(in, out);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Action required

2. Zip path traversal extraction 🐞 Bug ⛨ Security

ExtensionClassLoader.unZip writes zip entries to destDir + File.separator + entry.getName()
without normalizing/validating the entry path, allowing a crafted extension zip to write outside
destDir via ../ (Zip Slip). This is particularly risky because loadClassFiles automatically unzips
every *.zip in the extensions directory.
Agent Prompt
### Issue description
`ExtensionClassLoader.unZip` uses `entry.getName()` directly when creating output `File`s. A malicious zip entry name (e.g., `../outside.txt`) can cause writes outside the target directory (Zip Slip).

### Issue Context
`loadClassFiles` auto-unzips every `*.zip` under the extensions directory.

### Fix Focus Areas
- src/graphtea/platform/extension/ExtensionClassLoader.java[50-55]
- src/graphtea/platform/extension/ExtensionClassLoader.java[137-155]

### Implementation direction
- Use `java.nio.file.Path`:
  - `Path dest = Paths.get(destDir).toRealPath();`
  - `Path target = dest.resolve(entry.getName()).normalize();`
  - Reject entries where `!target.startsWith(dest)`.
- For file entries:
  - `Files.createDirectories(target.getParent());`
  - Write to `target.toFile()`.
- Apply the same containment check for directory entries before `createDirectories`.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant