#4312: PackageComparator.java

tools/org.projectforge.tools.refactoring/src/org/projectforge/packagecompare/PackageComparator.java

Path: tools/org.projectforge.tools.refactoring/src/org/projectforge/packagecompare/PackageComparator.java

Type: One-off refactoring tool · Lines: 129

Purpose: Validates that no Java class was lost during the monolith-to-multimodule migration — recursively scans old and new codebase structures and reports missing, changed, and new classes.

Source: GitHub

129 lines · 106 code · 0 comments · 23 blank
CommitMessage
9ebb885222016-07-18 Initial commit
Historical artifact. This tool was written during the migration from ProjectForge 6.x (monolith at projectforge-webapp/src/main/java) to 7.x (multi-module: projectforge-business/, projectforge-wicket/, projectforge-rest/, plugins/*/). It verified that all classes were correctly moved to their new module homes. The hardcoded paths reference a developer's machine (/Users/blumenstein/), making it non-portable.

How it works — the comparison algorithm

The tool builds two HashMap<String, String> maps (filename → package path) by recursively walking Java source trees:

  1. Old structure scan: Walks projectforge-webapp/src/main/java (the pre-migration monolith), extracts every .java file, stores className → packagePath
  2. New structure scan: Walks THREE separate trees: projectforge-business/src/main/java, projectforge-wicket/src/main/java, and 9 plugin directories under plugins/<name>/src/main/java. All results go into a single newStructure map.
  3. Comparison: For each old class, checks if it exists in the new map with (a) the same package → success, (b) a different package → reports the change in key=value format, (c) not found → reports as missing. Then checks new classes not in old → reports as additions.

Critical limitation: Using filename (without package) as the map key means two same-named classes in different packages collide. The last one scanned wins. In practice, ProjectForge avoids duplicate class names across packages, but this is a design flaw.

Key code sections

private static String[] plugins = { "org.projectforge.plugins.skillmatrix",
  "org.projectforge.plugins.banking", "org.projectforge.plugins.crm",
  "org.projectforge.plugins.licensemanagement", "org.projectforge.plugins.liquidityplanning",
  "org.projectforge.plugins.marketing", "org.projectforge.plugins.memo",
  "org.projectforge.plugins.poll", "org.projectforge.plugins.todo" };

The 9 plugins that were extracted from the monolith into independent Gradle submodules. This list may be outdated — plugins may have been added/removed since the refactoring.

write() (lines 98-112): Recursive file tree walker. Skips .DS_Store (macOS metadata), dives into directories, and for .java files extracts the class name (stripping .java extension) and package (converting filesystem path /org/projectforge/business/address/org.projectforge.business.address). Uses parent.list() without null check — NPE if directory doesn't exist.

writePropertiesFile() (lines 74-96): Misleading name — it prints to stdout, doesn't write a file. Output format is oldPackage=NewPackage (like .properties format), intended for automated refactoring scripts. Console messages are in German ("Klasse in new structure not found") — internal tool for German-speaking team.

Key takeaways