Skip to content
This repository was archived by the owner on Nov 7, 2026. It is now read-only.

Commit d4c33b3

Browse files
Web 0.10.2: Abhängigkeiten «create»/«use», Klasse im Objektinspektor (#180)
Claude-Session: https://claude.ai/code/session_01Ugz28SuLhbcLydch8nbzdQ Co-authored-by: Claude <noreply@anthropic.com>
1 parent af3f383 commit d4c33b3

30 files changed

Lines changed: 697 additions & 337 deletions

‎CHANGELOG.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ The format is loosely inspired by Keep a Changelog.
88

99
### Added
1010

11+
- **Web 0.10.2, VisuObject dependencies and class inspector:** `new X()` in a method draws a dashed arrow «create» to X, a type used only inside a method (parameter, local variable) a dashed «use» arrow; none where an attribute or inheritance already connects the classes. The inspector explains why it is not an association and can turn it into an attribute. Clicking a class shows its name, modifier, abstract, final, superclass and interfaces in the object inspector (abstract is no longer set in the class box). The border between class diagram and object inspector can be dragged. The add line in a class box reads “new attribute …” without a “+”, which looked like public (#180).
1112
- **Web 0.10.1, VisuObject “New” like Eclipse:** a “+ New ▾” menu in the Package Explorer for Java project, source folder, package, class and interface, each with its own window; the class window works like Eclipse's New Java Class (source folder, package, name, modifier public/package, abstract/final, superclass, interfaces, stubs for `main`, superclass constructors and inherited abstract methods, Javadoc with @author). Several source folders per project (e.g. `test`), read from and written to `.classpath` in the workspace and kept in the ZIP. New toolbar tool “Association” as a plain line without navigation next to the directed association. The object inspector has a fixed place below the class diagram and collapses with “–”; it shows end A (where the line starts) and end B (where it ends) with role, multiplicity and navigability. Like in Astah, clicking a role or multiplicity at a selected line (or right-clicking near an end) opens a small editor right there. Renaming a class in the code renames its tab and file (#179).
1213
- **Web 0.10.0, VisuObject like Eclipse and Astah:** an Astah-style toolbar above the class diagram to drag classes and interfaces onto the canvas and draw association, aggregation, composition, inheritance and realization between two classes. Attributes whose type is another class are shown only as a role on the line (with visibility, e.g. `-meineKatzen`), not inside the class box. Object inspector for a relationship: name with reading direction taken from the drag direction (“reverse” button), navigability at each end (navigable = arrowhead, non-navigable = ×, unspecified = open), role and multiplicity at both ends; making the other end navigable adds an attribute named after the role. Inheritance only via the arrow (no “inherits from” choice in the class box). The left area is called “Package Explorer”. Workspace folder like Eclipse (Chrome/Edge, File System Access API): create “VisuObject-Workspace” or open an existing one, one folder per project with `src/package/Class.java`, `visuobject.json` (UML extras and box positions), `.project` and `.classpath` for importing into Eclipse; all projects listed in the Package Explorer, Ctrl+S saves, unsaved tabs show ● instead of × like VS Code, ZIP download stays (#175, #176, #177, #178).
1314
- **Web 0.9.0, VisuObject projects and relationships:** a project with packages like in Eclipse: a package explorer on the left (project › src › packages › files › classes › attributes/methods), every level collapsible, one file per class, `package` line follows the folder, UML package frames in the class diagram, open and save as ZIP (`Name/src/package/Class.java`, fflate, MIT). Layout like WebGoatCode: flat areas with a grey header bar, “–” collapses an area to a strip with its name, splitters between the areas, file tabs above the code. Relationships are drawn by dragging from a handle on the selected class to another class (association, aggregation, composition, inheritance, realization); an object inspector sets kind, name with reading direction, navigability (one way, both ways, no arrow), role and multiplicity at both ends. Java parts go into the code (attribute, list type, imports, `extends`/`implements`), UML-only details into `visuobject.json` in the ZIP. Hollow inheritance triangle; roles at the same distance as multiplicities. The website's VisuStruct links always open VisuStruct (#171, #172, #173).

‎web/package-lock.json‎

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎web/package.json‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "visustruct-web",
33
"private": true,
4-
"version": "0.10.1",
4+
"version": "0.10.2",
55
"type": "module",
66
"scripts": {
77
"dev": "vite",

‎web/src/core/classModel.ts‎

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -560,6 +560,27 @@ export function setAbstract(source: string, cls: UmlClass, abstract: boolean): s
560560
return splice(source, cls.headerFrom, cls.headerTo, changed)
561561
}
562562

563+
/** Modifier im Kopf der Klasse lesen (das Modell kennt nur `abstract`). */
564+
export function classModifiers(source: string, cls: UmlClass): { isPublic: boolean; isFinal: boolean } {
565+
const head = source.slice(cls.headerFrom, cls.nameFrom)
566+
return { isPublic: /\bpublic\b/.test(head), isFinal: /\bfinal\b/.test(head) }
567+
}
568+
569+
/** `public` setzen oder weglassen (dann nur im Package sichtbar). */
570+
export function setPublic(source: string, cls: UmlClass, on: boolean): string {
571+
const head = source.slice(cls.headerFrom, cls.headerTo)
572+
const has = /\bpublic\s+/.test(head.slice(0, cls.nameFrom - cls.headerFrom))
573+
const changed = on ? (has ? head : `public ${head}`) : head.replace(/\bpublic\s+/, '')
574+
return splice(source, cls.headerFrom, cls.headerTo, changed)
575+
}
576+
577+
/** `final` setzen (nimmt `abstract` weg, beides zusammen geht in Java nicht) oder entfernen. */
578+
export function setFinal(source: string, cls: UmlClass, on: boolean): string {
579+
const head = source.slice(cls.headerFrom, cls.headerTo)
580+
const changed = on ? (/\bfinal\b/.test(head) ? head : head.replace(/\babstract\s+/, '').replace(/\bclass\b/, 'final class')) : head.replace(/\bfinal\s+/, '')
581+
return splice(source, cls.headerFrom, cls.headerTo, changed)
582+
}
583+
563584
/** Neues Attribut (hinter dem letzten Attribut) oder neue Methode (vor der schließenden Klammer). */
564585
export function addMember(source: string, cls: UmlClass, text: string): string {
565586
const line = parseUmlLine(text)

‎web/src/core/umlRelations.ts‎

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,50 @@ export interface Generalization {
5858
to: string
5959
}
6060

61+
/** Abhängigkeit: Eine Methode erzeugt (`new X()`) oder benutzt eine Klasse, ohne sie in einem Attribut zu merken. */
62+
export interface Dependency {
63+
from: string
64+
to: string
65+
kind: 'create' | 'use'
66+
/** Methoden bzw. Konstruktoren, in denen das vorkommt. */
67+
methods: string[]
68+
}
69+
70+
/** Kommentare und Texte in Anführungszeichen ausblenden, damit `new Hund` im Kommentar nicht zählt. */
71+
const codeOnly = (text: string) => text.replace(/\/\*[\s\S]*?\*\/|\/\/[^\n]*|"(?:\\.|[^"\\\n])*"|'(?:\\.|[^'\\\n])*'/g, (m) => ' '.repeat(m.length))
72+
73+
/**
74+
* Abhängigkeiten aus den Methoden: gestrichelter Pfeil «create» bei `new X(…)`, sonst «use», wenn der Typ vorkommt.
75+
* Keine, wo schon eine Assoziation (Attribut) oder Vererbung von `from` zu `to` besteht.
76+
*/
77+
export function dependencies(classes: UmlClass[], textOf: (cls: UmlClass) => string | undefined): Dependency[] {
78+
const names = classes.map((c) => c.name)
79+
const out = new Map<string, Dependency>()
80+
for (const cls of classes) {
81+
const text = textOf(cls)
82+
if (!text) continue
83+
const code = codeOnly(text)
84+
const known = new Set([cls.superclass, ...cls.interfaces, ...cls.members.filter((m) => m.kind === 'field' && !m.isStatic).map((m) => associationTarget(m.type).name)])
85+
for (const m of cls.members) {
86+
if (m.kind === 'field') continue
87+
const body = code.slice(m.from, m.to)
88+
for (const other of names) {
89+
if (other === cls.name || known.has(other)) continue
90+
const word = new RegExp(`\\b${other}\\b`)
91+
if (!word.test(body)) continue
92+
const kind = new RegExp(`\\bnew\\s+${other}\\s*[(<\\[{]`).test(body) ? 'create' : 'use'
93+
const key = `${cls.name}>${other}`
94+
const dep = out.get(key) ?? { from: cls.name, to: other, kind, methods: [] }
95+
if (kind === 'create') dep.kind = 'create'
96+
const label = m.kind === 'constructor' ? `${m.name}()` : `${m.name}()`
97+
if (!dep.methods.includes(label)) dep.methods.push(label)
98+
out.set(key, dep)
99+
}
100+
}
101+
}
102+
return [...out.values()]
103+
}
104+
61105
export const multiplicities = ['1', '0..1', '0..*', '1..*', '*']
62106

63107
/** Obergrenze größer als 1: Dann gehört eine Liste in den Code. */

‎web/src/i18n/de.strings‎

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -407,9 +407,8 @@
407407
"web_visuobject_add_interface_tip" = "Neues Interface ins Diagramm und in den Code";
408408
"web_visuobject_layout" = "Neu anordnen";
409409
"web_visuobject_layout_tip" = "Alle Klassen automatisch anordnen: Oberklassen oben";
410-
"web_visuobject_add_field" = "Attribut";
411-
"web_visuobject_add_method" = "Methode";
412-
"web_visuobject_abstract" = "abstrakt";
410+
"web_visuobject_add_field" = "neues Attribut …";
411+
"web_visuobject_add_method" = "neue Methode …";
413412
"web_visuobject_delete_class" = "Klasse löschen";
414413
"web_visuobject_new_class_name" = "NeueKlasse";
415414
"web_visuobject_new_interface_name" = "NeuesInterface";
@@ -424,6 +423,13 @@
424423
"web_visuobject_bad_line" = "„%@“ verstehe ich nicht. Beispiel: - name: String oder + bellen(): void";
425424
"web_visuobject_open_failed" = "Die Datei konnte nicht gelesen werden.";
426425
"web_visuobject_field_ph" = "- name: String";
426+
"web_visuobject_inspector_resize" = "Größe ändern: Grenze zwischen Klassendiagramm und Objektinspektor nach oben oder unten ziehen (Doppelklick = Standard)";
427+
"web_visuobject_class_inspector_hint" = "Änderungen hier schreiben sofort in %@. Attribute und Methoden bearbeitest du mit Doppelklick im Kasten.";
428+
"web_visuobject_dep_title" = "Abhängigkeit «%@»";
429+
"web_visuobject_dep_create_text" = "%1$@ erzeugt in %3$@ ein Objekt der Klasse %2$@ (new).";
430+
"web_visuobject_dep_use_text" = "%1$@ benutzt die Klasse %2$@ in %3$@.";
431+
"web_visuobject_dep_hint" = "Das ist keine Assoziation: %@ merkt sich das %@-Objekt nicht in einem Attribut, es lebt nur in der Methode. Darum gestrichelt.";
432+
"web_visuobject_dep_to_assoc" = "Als Attribut merken (Assoziation zu %@)";
427433
"web_visuobject_end_mult_ph" = "Mult.";
428434
"web_visuobject_end_tip" = "Klick (Linie markiert) oder Rechtsklick: Rolle, Multiplizität und Navigierbarkeit an diesem Ende";
429435
"web_visuobject_end_role_ph" = "z. B. meineKatzen";

‎web/src/i18n/en.strings‎

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -407,9 +407,8 @@
407407
"web_visuobject_add_interface_tip" = "Add a new interface to the diagram and the code";
408408
"web_visuobject_layout" = "Arrange again";
409409
"web_visuobject_layout_tip" = "Arrange all classes automatically: superclasses at the top";
410-
"web_visuobject_add_field" = "Attribute";
411-
"web_visuobject_add_method" = "Method";
412-
"web_visuobject_abstract" = "abstract";
410+
"web_visuobject_add_field" = "new attribute …";
411+
"web_visuobject_add_method" = "new method …";
413412
"web_visuobject_delete_class" = "Delete class";
414413
"web_visuobject_new_class_name" = "NewClass";
415414
"web_visuobject_new_interface_name" = "NewInterface";
@@ -424,6 +423,13 @@
424423
"web_visuobject_bad_line" = "I don’t understand “%@”. Example: - name: String or + bark(): void";
425424
"web_visuobject_open_failed" = "The file could not be read.";
426425
"web_visuobject_field_ph" = "- name: String";
426+
"web_visuobject_inspector_resize" = "Resize: drag the border between class diagram and object inspector up or down (double-click = default)";
427+
"web_visuobject_class_inspector_hint" = "Changes here are written straight into %@. Edit attributes and methods by double-clicking in the box.";
428+
"web_visuobject_dep_title" = "Dependency «%@»";
429+
"web_visuobject_dep_create_text" = "%1$@ creates an object of class %2$@ in %3$@ (new).";
430+
"web_visuobject_dep_use_text" = "%1$@ uses the class %2$@ in %3$@.";
431+
"web_visuobject_dep_hint" = "This is not an association: %@ does not keep the %@ object in an attribute, it only lives inside the method. That is why the line is dashed.";
432+
"web_visuobject_dep_to_assoc" = "Keep as attribute (association to %@)";
427433
"web_visuobject_end_mult_ph" = "mult.";
428434
"web_visuobject_end_tip" = "Click (line selected) or right-click: role, multiplicity and navigability at this end";
429435
"web_visuobject_end_role_ph" = "e.g. myCats";

‎web/src/i18n/pt-PT.strings‎

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -405,9 +405,8 @@
405405
"web_visuobject_add_interface_tip" = "Nova interface no diagrama e no código";
406406
"web_visuobject_layout" = "Reorganizar";
407407
"web_visuobject_layout_tip" = "Organizar todas as classes automaticamente: superclasses em cima";
408-
"web_visuobject_add_field" = "Atributo";
409-
"web_visuobject_add_method" = "Método";
410-
"web_visuobject_abstract" = "abstrata";
408+
"web_visuobject_add_field" = "novo atributo …";
409+
"web_visuobject_add_method" = "novo método …";
411410
"web_visuobject_delete_class" = "Apagar classe";
412411
"web_visuobject_new_class_name" = "NovaClasse";
413412
"web_visuobject_new_interface_name" = "NovaInterface";
@@ -422,6 +421,13 @@
422421
"web_visuobject_bad_line" = "Não percebo «%@». Exemplo: - nome: String ou + ladrar(): void";
423422
"web_visuobject_open_failed" = "Não foi possível ler o ficheiro.";
424423
"web_visuobject_field_ph" = "- nome: String";
424+
"web_visuobject_inspector_resize" = "Redimensionar: arrasta a fronteira entre o diagrama de classes e o inspetor para cima ou para baixo (duplo clique = padrão)";
425+
"web_visuobject_class_inspector_hint" = "As alterações aqui são escritas logo em %@. Edita atributos e métodos com duplo clique na caixa.";
426+
"web_visuobject_dep_title" = "Dependência «%@»";
427+
"web_visuobject_dep_create_text" = "%1$@ cria um objeto da classe %2$@ em %3$@ (new).";
428+
"web_visuobject_dep_use_text" = "%1$@ usa a classe %2$@ em %3$@.";
429+
"web_visuobject_dep_hint" = "Isto não é uma associação: %@ não guarda o objeto %@ num atributo, ele só existe dentro do método. Por isso a linha é tracejada.";
430+
"web_visuobject_dep_to_assoc" = "Guardar como atributo (associação a %@)";
425431
"web_visuobject_end_mult_ph" = "mult.";
426432
"web_visuobject_end_tip" = "Clique (linha selecionada) ou clique direito: papel, multiplicidade e navegabilidade nesta extremidade";
427433
"web_visuobject_end_role_ph" = "p. ex. osMeusGatos";

0 commit comments

Comments
 (0)