aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLibravatar Kristóf Marussy <marussy@mit.bme.hu>2024-01-04 18:29:13 +0100
committerLibravatar GitHub <noreply@github.com>2024-01-04 18:29:13 +0100
commit667045429b1d7fdc49d7ecae75b7673d7a2c240e (patch)
tree76daca5944872f0b21d6cef472c5cb0b393dff8b
parentMerge pull request #50 from kris7t/generator-roundtrip (diff)
parentfeat(web): toggle identifier coloring (diff)
downloadrefinery-667045429b1d7fdc49d7ecae75b7673d7a2c240e.tar.gz
refinery-667045429b1d7fdc49d7ecae75b7673d7a2c240e.tar.zst
refinery-667045429b1d7fdc49d7ecae75b7673d7a2c240e.zip
Merge pull request #51 from kris7t/color-identifiers
Color identifiers
-rw-r--r--subprojects/frontend/src/editor/EditorArea.tsx3
-rw-r--r--subprojects/frontend/src/editor/EditorButtons.tsx16
-rw-r--r--subprojects/frontend/src/editor/EditorStore.ts13
-rw-r--r--subprojects/frontend/src/editor/EditorTheme.ts38
-rw-r--r--subprojects/frontend/src/editor/GeneratedModelStore.ts15
-rw-r--r--subprojects/frontend/src/graph/DotGraphVisualizer.tsx4
-rw-r--r--subprojects/frontend/src/graph/GraphStore.ts12
-rw-r--r--subprojects/frontend/src/graph/GraphTheme.tsx28
-rw-r--r--subprojects/frontend/src/graph/dotSource.ts5
-rw-r--r--subprojects/frontend/src/theme/ThemeProvider.tsx40
-rw-r--r--subprojects/frontend/src/xtext/xtextServiceResults.ts9
-rw-r--r--subprojects/language-ide/src/main/java/tools/refinery/language/ide/syntaxcoloring/ProblemSemanticHighlightingCalculator.java40
-rw-r--r--subprojects/language-ide/src/main/java/tools/refinery/language/ide/syntaxcoloring/TypeHashProvider.java93
-rw-r--r--subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/NodeNameProvider.java62
-rw-r--r--subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/SolutionSerializer.java38
-rw-r--r--subprojects/language-web/src/main/java/tools/refinery/language/web/generator/ModelGenerationWorker.java7
-rw-r--r--subprojects/language-web/src/main/java/tools/refinery/language/web/semantics/SemanticsWorker.java5
-rw-r--r--subprojects/language-web/src/main/java/tools/refinery/language/web/semantics/metadata/MetadataCreator.java31
-rw-r--r--subprojects/language-web/src/main/java/tools/refinery/language/web/semantics/metadata/NodeMetadata.java4
-rw-r--r--subprojects/language-web/src/main/java/tools/refinery/language/web/semantics/metadata/NodeMetadataFactory.java89
-rw-r--r--subprojects/language/src/main/java/tools/refinery/language/resource/ProblemResourceDescriptionStrategy.java50
21 files changed, 495 insertions, 107 deletions
diff --git a/subprojects/frontend/src/editor/EditorArea.tsx b/subprojects/frontend/src/editor/EditorArea.tsx
index 905fa2ec..aafaad40 100644
--- a/subprojects/frontend/src/editor/EditorArea.tsx
+++ b/subprojects/frontend/src/editor/EditorArea.tsx
@@ -1,5 +1,5 @@
1/* 1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> 2 * SPDX-FileCopyrightText: 2021-2024 The Refinery Authors <https://refinery.tools/>
3 * 3 *
4 * SPDX-License-Identifier: EPL-2.0 4 * SPDX-License-Identifier: EPL-2.0
5 */ 5 */
@@ -38,6 +38,7 @@ export default observer(function EditorArea({
38 <EditorTheme 38 <EditorTheme
39 showLineNumbers={editorStore.showLineNumbers} 39 showLineNumbers={editorStore.showLineNumbers}
40 showActiveLine={!editorStore.hasSelection} 40 showActiveLine={!editorStore.hasSelection}
41 colorIdentifiers={editorStore.colorIdentifiers}
41 ref={editorParentRef} 42 ref={editorParentRef}
42 /> 43 />
43 </Box> 44 </Box>
diff --git a/subprojects/frontend/src/editor/EditorButtons.tsx b/subprojects/frontend/src/editor/EditorButtons.tsx
index ca51f975..f4513909 100644
--- a/subprojects/frontend/src/editor/EditorButtons.tsx
+++ b/subprojects/frontend/src/editor/EditorButtons.tsx
@@ -1,5 +1,5 @@
1/* 1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> 2 * SPDX-FileCopyrightText: 2021-2024 The Refinery Authors <https://refinery.tools/>
3 * 3 *
4 * SPDX-License-Identifier: EPL-2.0 4 * SPDX-License-Identifier: EPL-2.0
5 */ 5 */
@@ -8,8 +8,9 @@ import type { Diagnostic } from '@codemirror/lint';
8import CancelIcon from '@mui/icons-material/Cancel'; 8import CancelIcon from '@mui/icons-material/Cancel';
9import CheckIcon from '@mui/icons-material/Check'; 9import CheckIcon from '@mui/icons-material/Check';
10import FormatListNumberedIcon from '@mui/icons-material/FormatListNumbered'; 10import FormatListNumberedIcon from '@mui/icons-material/FormatListNumbered';
11import FormatPaint from '@mui/icons-material/FormatPaint'; 11import FormatPaintIcon from '@mui/icons-material/FormatPaint';
12import InfoOutlinedIcon from '@mui/icons-material/InfoOutlined'; 12import InfoOutlinedIcon from '@mui/icons-material/InfoOutlined';
13import LooksIcon from '@mui/icons-material/Looks';
13import RedoIcon from '@mui/icons-material/Redo'; 14import RedoIcon from '@mui/icons-material/Redo';
14import SearchIcon from '@mui/icons-material/Search'; 15import SearchIcon from '@mui/icons-material/Search';
15import UndoIcon from '@mui/icons-material/Undo'; 16import UndoIcon from '@mui/icons-material/Undo';
@@ -72,6 +73,15 @@ export default observer(function EditorButtons({
72 <FormatListNumberedIcon fontSize="small" /> 73 <FormatListNumberedIcon fontSize="small" />
73 </ToggleButton> 74 </ToggleButton>
74 <ToggleButton 75 <ToggleButton
76 selected={editorStore?.colorIdentifiers ?? false}
77 disabled={editorStore === undefined}
78 onClick={() => editorStore?.toggleColorIdentifiers()}
79 aria-label="Color identifiers"
80 value="color-identifiers"
81 >
82 <LooksIcon fontSize="small" />
83 </ToggleButton>
84 <ToggleButton
75 selected={editorStore?.searchPanel?.state ?? false} 85 selected={editorStore?.searchPanel?.state ?? false}
76 disabled={editorStore === undefined} 86 disabled={editorStore === undefined}
77 onClick={() => editorStore?.searchPanel?.toggle()} 87 onClick={() => editorStore?.searchPanel?.toggle()}
@@ -104,7 +114,7 @@ export default observer(function EditorButtons({
104 aria-label="Automatic format" 114 aria-label="Automatic format"
105 color="inherit" 115 color="inherit"
106 > 116 >
107 <FormatPaint fontSize="small" /> 117 <FormatPaintIcon fontSize="small" />
108 </IconButton> 118 </IconButton>
109 <ConnectButton editorStore={editorStore} /> 119 <ConnectButton editorStore={editorStore} />
110 </Stack> 120 </Stack>
diff --git a/subprojects/frontend/src/editor/EditorStore.ts b/subprojects/frontend/src/editor/EditorStore.ts
index 87c4040e..5e7d05e1 100644
--- a/subprojects/frontend/src/editor/EditorStore.ts
+++ b/subprojects/frontend/src/editor/EditorStore.ts
@@ -1,5 +1,5 @@
1/* 1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> 2 * SPDX-FileCopyrightText: 2021-2024 The Refinery Authors <https://refinery.tools/>
3 * 3 *
4 * SPDX-License-Identifier: EPL-2.0 4 * SPDX-License-Identifier: EPL-2.0
5 */ 5 */
@@ -62,6 +62,8 @@ export default class EditorStore {
62 62
63 showLineNumbers = false; 63 showLineNumbers = false;
64 64
65 colorIdentifiers = true;
66
65 disposed = false; 67 disposed = false;
66 68
67 analyzing = false; 69 analyzing = false;
@@ -96,7 +98,7 @@ export default class EditorStore {
96 })().catch((error) => { 98 })().catch((error) => {
97 log.error('Failed to load XtextClient', error); 99 log.error('Failed to load XtextClient', error);
98 }); 100 });
99 this.graph = new GraphStore(); 101 this.graph = new GraphStore(this);
100 makeAutoObservable<EditorStore, 'client'>(this, { 102 makeAutoObservable<EditorStore, 'client'>(this, {
101 id: false, 103 id: false,
102 state: observable.ref, 104 state: observable.ref,
@@ -279,6 +281,11 @@ export default class EditorStore {
279 log.debug('Show line numbers', this.showLineNumbers); 281 log.debug('Show line numbers', this.showLineNumbers);
280 } 282 }
281 283
284 toggleColorIdentifiers(): void {
285 this.colorIdentifiers = !this.colorIdentifiers;
286 log.debug('Color identifiers', this.colorIdentifiers);
287 }
288
282 get hasSelection(): boolean { 289 get hasSelection(): boolean {
283 return this.state.selection.ranges.some(({ from, to }) => from !== to); 290 return this.state.selection.ranges.some(({ from, to }) => from !== to);
284 } 291 }
@@ -324,7 +331,7 @@ export default class EditorStore {
324 } 331 }
325 332
326 addGeneratedModel(uuid: string, randomSeed: number): void { 333 addGeneratedModel(uuid: string, randomSeed: number): void {
327 this.generatedModels.set(uuid, new GeneratedModelStore(randomSeed)); 334 this.generatedModels.set(uuid, new GeneratedModelStore(randomSeed, this));
328 this.selectGeneratedModel(uuid); 335 this.selectGeneratedModel(uuid);
329 } 336 }
330 337
diff --git a/subprojects/frontend/src/editor/EditorTheme.ts b/subprojects/frontend/src/editor/EditorTheme.ts
index 9f560dfb..1cad4a36 100644
--- a/subprojects/frontend/src/editor/EditorTheme.ts
+++ b/subprojects/frontend/src/editor/EditorTheme.ts
@@ -1,5 +1,5 @@
1/* 1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> 2 * SPDX-FileCopyrightText: 2021-2024 The Refinery Authors <https://refinery.tools/>
3 * 3 *
4 * SPDX-License-Identifier: EPL-2.0 4 * SPDX-License-Identifier: EPL-2.0
5 */ 5 */
@@ -8,10 +8,35 @@ import cancelSVG from '@material-icons/svg/svg/cancel/baseline.svg?raw';
8import expandMoreSVG from '@material-icons/svg/svg/expand_more/baseline.svg?raw'; 8import expandMoreSVG from '@material-icons/svg/svg/expand_more/baseline.svg?raw';
9import infoSVG from '@material-icons/svg/svg/info/baseline.svg?raw'; 9import infoSVG from '@material-icons/svg/svg/info/baseline.svg?raw';
10import warningSVG from '@material-icons/svg/svg/warning/baseline.svg?raw'; 10import warningSVG from '@material-icons/svg/svg/warning/baseline.svg?raw';
11import { alpha, styled, type CSSObject } from '@mui/material/styles'; 11import {
12 alpha,
13 styled,
14 type CSSObject,
15 type Theme,
16} from '@mui/material/styles';
17import { range } from 'lodash-es';
12 18
13import svgURL from '../utils/svgURL'; 19import svgURL from '../utils/svgURL';
14 20
21function createTypeHashStyles(
22 theme: Theme,
23 colorIdentifiers: boolean,
24): CSSObject {
25 if (!colorIdentifiers) {
26 return {};
27 }
28 const result: CSSObject = {};
29 range(theme.palette.highlight.typeHash.length).forEach((i) => {
30 result[`.tok-problem-typeHash-${i}`] = {
31 '&, .tok-typeName': {
32 color: theme.palette.highlight.typeHash[i]?.text,
33 fontWeight: theme.typography.fontWeightEditorTypeHash,
34 },
35 };
36 });
37 return result;
38}
39
15export default styled('div', { 40export default styled('div', {
16 name: 'EditorTheme', 41 name: 'EditorTheme',
17 shouldForwardProp: (propName) => 42 shouldForwardProp: (propName) =>
@@ -19,7 +44,8 @@ export default styled('div', {
19})<{ 44})<{
20 showLineNumbers: boolean; 45 showLineNumbers: boolean;
21 showActiveLine: boolean; 46 showActiveLine: boolean;
22}>(({ theme, showLineNumbers, showActiveLine }) => { 47 colorIdentifiers: boolean;
48}>(({ theme, showLineNumbers, showActiveLine, colorIdentifiers }) => {
23 const editorFontStyle: CSSObject = { 49 const editorFontStyle: CSSObject = {
24 ...theme.typography.editor, 50 ...theme.typography.editor,
25 fontWeight: theme.typography.fontWeightEditorNormal, 51 fontWeight: theme.typography.fontWeightEditorNormal,
@@ -114,6 +140,11 @@ export default styled('div', {
114 }, 140 },
115 '.tok-problem-error': { 141 '.tok-problem-error': {
116 '&, & .tok-typeName': { 142 '&, & .tok-typeName': {
143 color: theme.palette.highlight.comment,
144 },
145 },
146 '.tok-invalid': {
147 '&, & .tok-typeName': {
117 color: theme.palette.error.main, 148 color: theme.palette.error.main,
118 }, 149 },
119 }, 150 },
@@ -124,6 +155,7 @@ export default styled('div', {
124 fontStyle: 'normal', 155 fontStyle: 'normal',
125 }, 156 },
126 }, 157 },
158 ...createTypeHashStyles(theme, colorIdentifiers),
127 }; 159 };
128 160
129 const matchingStyle: CSSObject = { 161 const matchingStyle: CSSObject = {
diff --git a/subprojects/frontend/src/editor/GeneratedModelStore.ts b/subprojects/frontend/src/editor/GeneratedModelStore.ts
index 5088d603..f2695d9a 100644
--- a/subprojects/frontend/src/editor/GeneratedModelStore.ts
+++ b/subprojects/frontend/src/editor/GeneratedModelStore.ts
@@ -1,5 +1,5 @@
1/* 1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> 2 * SPDX-FileCopyrightText: 2021-2024 The Refinery Authors <https://refinery.tools/>
3 * 3 *
4 * SPDX-License-Identifier: EPL-2.0 4 * SPDX-License-Identifier: EPL-2.0
5 */ 5 */
@@ -9,6 +9,8 @@ import { makeAutoObservable } from 'mobx';
9import GraphStore from '../graph/GraphStore'; 9import GraphStore from '../graph/GraphStore';
10import type { SemanticsSuccessResult } from '../xtext/xtextServiceResults'; 10import type { SemanticsSuccessResult } from '../xtext/xtextServiceResults';
11 11
12import type EditorStore from './EditorStore';
13
12export default class GeneratedModelStore { 14export default class GeneratedModelStore {
13 title: string; 15 title: string;
14 16
@@ -18,10 +20,15 @@ export default class GeneratedModelStore {
18 20
19 graph: GraphStore | undefined; 21 graph: GraphStore | undefined;
20 22
21 constructor(randomSeed: number) { 23 constructor(
24 randomSeed: number,
25 private readonly editorStore: EditorStore,
26 ) {
22 const time = new Date().toLocaleTimeString(undefined, { hour12: false }); 27 const time = new Date().toLocaleTimeString(undefined, { hour12: false });
23 this.title = `Generated at ${time} (${randomSeed})`; 28 this.title = `Generated at ${time} (${randomSeed})`;
24 makeAutoObservable(this); 29 makeAutoObservable<GeneratedModelStore, 'editorStore'>(this, {
30 editorStore: false,
31 });
25 } 32 }
26 33
27 get running(): boolean { 34 get running(): boolean {
@@ -43,7 +50,7 @@ export default class GeneratedModelStore {
43 50
44 setSemantics(semantics: SemanticsSuccessResult): void { 51 setSemantics(semantics: SemanticsSuccessResult): void {
45 if (this.running) { 52 if (this.running) {
46 this.graph = new GraphStore(); 53 this.graph = new GraphStore(this.editorStore);
47 this.graph.setSemantics(semantics); 54 this.graph.setSemantics(semantics);
48 } 55 }
49 } 56 }
diff --git a/subprojects/frontend/src/graph/DotGraphVisualizer.tsx b/subprojects/frontend/src/graph/DotGraphVisualizer.tsx
index eec72a7d..72ac58fa 100644
--- a/subprojects/frontend/src/graph/DotGraphVisualizer.tsx
+++ b/subprojects/frontend/src/graph/DotGraphVisualizer.tsx
@@ -1,5 +1,5 @@
1/* 1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/> 2 * SPDX-FileCopyrightText: 2023-2024 The Refinery Authors <https://refinery.tools/>
3 * 3 *
4 * SPDX-License-Identifier: EPL-2.0 4 * SPDX-License-Identifier: EPL-2.0
5 */ 5 */
@@ -150,7 +150,7 @@ function DotGraphVisualizer({
150 ], 150 ],
151 ); 151 );
152 152
153 return <GraphTheme ref={setElement} />; 153 return <GraphTheme ref={setElement} colorNodes={graph.colorNodes} />;
154} 154}
155 155
156DotGraphVisualizer.defaultProps = { 156DotGraphVisualizer.defaultProps = {
diff --git a/subprojects/frontend/src/graph/GraphStore.ts b/subprojects/frontend/src/graph/GraphStore.ts
index ecb016b5..58c4422d 100644
--- a/subprojects/frontend/src/graph/GraphStore.ts
+++ b/subprojects/frontend/src/graph/GraphStore.ts
@@ -1,11 +1,12 @@
1/* 1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/> 2 * SPDX-FileCopyrightText: 2023-2024 The Refinery Authors <https://refinery.tools/>
3 * 3 *
4 * SPDX-License-Identifier: EPL-2.0 4 * SPDX-License-Identifier: EPL-2.0
5 */ 5 */
6 6
7import { makeAutoObservable, observable } from 'mobx'; 7import { makeAutoObservable, observable } from 'mobx';
8 8
9import type EditorStore from '../editor/EditorStore';
9import type { 10import type {
10 RelationMetadata, 11 RelationMetadata,
11 SemanticsSuccessResult, 12 SemanticsSuccessResult,
@@ -65,8 +66,9 @@ export default class GraphStore {
65 66
66 selectedSymbol: RelationMetadata | undefined; 67 selectedSymbol: RelationMetadata | undefined;
67 68
68 constructor() { 69 constructor(private readonly editorStore: EditorStore) {
69 makeAutoObservable(this, { 70 makeAutoObservable<GraphStore, 'editorStore'>(this, {
71 editorStore: false,
70 semantics: observable.ref, 72 semantics: observable.ref,
71 }); 73 });
72 } 74 }
@@ -184,4 +186,8 @@ export default class GraphStore {
184 }); 186 });
185 this.setSelectedSymbol(this.selectedSymbol); 187 this.setSelectedSymbol(this.selectedSymbol);
186 } 188 }
189
190 get colorNodes(): boolean {
191 return this.editorStore.colorIdentifiers;
192 }
187} 193}
diff --git a/subprojects/frontend/src/graph/GraphTheme.tsx b/subprojects/frontend/src/graph/GraphTheme.tsx
index 14d58b96..7334f559 100644
--- a/subprojects/frontend/src/graph/GraphTheme.tsx
+++ b/subprojects/frontend/src/graph/GraphTheme.tsx
@@ -1,5 +1,5 @@
1/* 1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/> 2 * SPDX-FileCopyrightText: 2023-2024 The Refinery Authors <https://refinery.tools/>
3 * 3 *
4 * SPDX-License-Identifier: EPL-2.0 4 * SPDX-License-Identifier: EPL-2.0
5 */ 5 */
@@ -7,7 +7,13 @@
7import cancelSVG from '@material-icons/svg/svg/cancel/baseline.svg?raw'; 7import cancelSVG from '@material-icons/svg/svg/cancel/baseline.svg?raw';
8import labelSVG from '@material-icons/svg/svg/label/baseline.svg?raw'; 8import labelSVG from '@material-icons/svg/svg/label/baseline.svg?raw';
9import labelOutlinedSVG from '@material-icons/svg/svg/label/outline.svg?raw'; 9import labelOutlinedSVG from '@material-icons/svg/svg/label/outline.svg?raw';
10import { alpha, styled, type CSSObject } from '@mui/material/styles'; 10import {
11 alpha,
12 styled,
13 type CSSObject,
14 type Theme,
15} from '@mui/material/styles';
16import { range } from 'lodash-es';
11 17
12import svgURL from '../utils/svgURL'; 18import svgURL from '../utils/svgURL';
13 19
@@ -31,9 +37,24 @@ function createEdgeColor(
31 }; 37 };
32} 38}
33 39
40function createTypeHashStyles(theme: Theme, colorNodes: boolean): CSSObject {
41 if (!colorNodes) {
42 return {};
43 }
44 const result: CSSObject = {};
45 range(theme.palette.highlight.typeHash.length).forEach((i) => {
46 result[`.node-typeHash-${i}`] = {
47 '& [fill="green"]': {
48 fill: theme.palette.highlight.typeHash[i]?.box,
49 },
50 };
51 });
52 return result;
53}
54
34export default styled('div', { 55export default styled('div', {
35 name: 'GraphTheme', 56 name: 'GraphTheme',
36})(({ theme }) => ({ 57})<{ colorNodes: boolean }>(({ theme, colorNodes }) => ({
37 '& svg': { 58 '& svg': {
38 userSelect: 'none', 59 userSelect: 'none',
39 '.node': { 60 '.node': {
@@ -68,6 +89,7 @@ export default styled('div', {
68 '.node-exists-UNKNOWN [stroke="black"]': { 89 '.node-exists-UNKNOWN [stroke="black"]': {
69 strokeDasharray: '5 2', 90 strokeDasharray: '5 2',
70 }, 91 },
92 ...createTypeHashStyles(theme, colorNodes),
71 '.edge': { 93 '.edge': {
72 '& text': { 94 '& text': {
73 fontFamily: theme.typography.fontFamily, 95 fontFamily: theme.typography.fontFamily,
diff --git a/subprojects/frontend/src/graph/dotSource.ts b/subprojects/frontend/src/graph/dotSource.ts
index bd358dfa..3ac5eb1c 100644
--- a/subprojects/frontend/src/graph/dotSource.ts
+++ b/subprojects/frontend/src/graph/dotSource.ts
@@ -1,5 +1,5 @@
1/* 1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/> 2 * SPDX-FileCopyrightText: 2023-2024 The Refinery Authors <https://refinery.tools/>
3 * 3 *
4 * SPDX-License-Identifier: EPL-2.0 4 * SPDX-License-Identifier: EPL-2.0
5 */ 5 */
@@ -142,6 +142,9 @@ function createNodes(
142 if (data.unaryPredicates.size === 0) { 142 if (data.unaryPredicates.size === 0) {
143 classList.push('node-empty'); 143 classList.push('node-empty');
144 } 144 }
145 if (node.typeHash !== undefined) {
146 classList.push(`node-typeHash-${node.typeHash}`);
147 }
145 const classes = classList.join(' '); 148 const classes = classList.join(' ');
146 const name = nodeName(graph, node); 149 const name = nodeName(graph, node);
147 const border = node.kind === 'INDIVIDUAL' ? 2 : 1; 150 const border = node.kind === 'INDIVIDUAL' ? 2 : 1;
diff --git a/subprojects/frontend/src/theme/ThemeProvider.tsx b/subprojects/frontend/src/theme/ThemeProvider.tsx
index 18310147..a996cde8 100644
--- a/subprojects/frontend/src/theme/ThemeProvider.tsx
+++ b/subprojects/frontend/src/theme/ThemeProvider.tsx
@@ -1,5 +1,5 @@
1/* 1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> 2 * SPDX-FileCopyrightText: 2021-2024 The Refinery Authors <https://refinery.tools/>
3 * 3 *
4 * SPDX-License-Identifier: EPL-2.0 4 * SPDX-License-Identifier: EPL-2.0
5 */ 5 */
@@ -24,6 +24,11 @@ interface OuterPalette {
24 border: string; 24 border: string;
25} 25}
26 26
27interface TypeHashPalette {
28 text: string;
29 box: string;
30}
31
27interface HighlightPalette { 32interface HighlightPalette {
28 number: string; 33 number: string;
29 parameter: string; 34 parameter: string;
@@ -41,17 +46,20 @@ interface HighlightPalette {
41 selected: string; 46 selected: string;
42 contrastText: string; 47 contrastText: string;
43 }; 48 };
49 typeHash: TypeHashPalette[];
44} 50}
45 51
46declare module '@mui/material/styles' { 52declare module '@mui/material/styles' {
47 interface TypographyVariants { 53 interface TypographyVariants {
48 fontWeightEditorNormal: number; 54 fontWeightEditorNormal: number;
55 fontWeightEditorTypeHash: number;
49 fontWeightEditorBold: number; 56 fontWeightEditorBold: number;
50 editor: TypographyStyle; 57 editor: TypographyStyle;
51 } 58 }
52 59
53 interface TypographyVariantsOptions { 60 interface TypographyVariantsOptions {
54 fontWeightEditorNormal?: number; 61 fontWeightEditorNormal?: number;
62 fontWeightEditorTypeHash?: number;
55 fontWeightEditorBold?: number; 63 fontWeightEditorBold?: number;
56 editor?: TypographyStyle; 64 editor?: TypographyStyle;
57 } 65 }
@@ -78,6 +86,7 @@ function createResponsiveTheme(
78 '"Open Sans Variable", "Open Sans", "Roboto", "Helvetica", "Arial", sans-serif', 86 '"Open Sans Variable", "Open Sans", "Roboto", "Helvetica", "Arial", sans-serif',
79 fontWeightMedium: 500, 87 fontWeightMedium: 500,
80 fontWeightEditorNormal: 400, 88 fontWeightEditorNormal: 400,
89 fontWeightEditorTypeHash: 500,
81 fontWeightEditorBold: 700, 90 fontWeightEditorBold: 700,
82 button: { 91 button: {
83 fontWeight: 600, 92 fontWeight: 600,
@@ -220,7 +229,7 @@ const lightTheme = (() => {
220 palette: { 229 palette: {
221 mode: 'light', 230 mode: 'light',
222 primary: { main: '#038a99' }, 231 primary: { main: '#038a99' },
223 secondary: { main: '#e45649' }, 232 secondary: { main: '#61afef' },
224 error: { main: '#ca1243' }, 233 error: { main: '#ca1243' },
225 warning: { main: '#c18401' }, 234 warning: { main: '#c18401' },
226 success: { main: '#50a14f' }, 235 success: { main: '#50a14f' },
@@ -256,6 +265,18 @@ const lightTheme = (() => {
256 selected: '#d500f9', 265 selected: '#d500f9',
257 contrastText: '#fff', 266 contrastText: '#fff',
258 }, 267 },
268 typeHash: [
269 { text: '#986801', box: '#e5c07b' },
270 { text: '#d6493e', box: '#e06c75' },
271 { text: '#50a14f', box: '#98c379' },
272 { text: '#a626a4', box: '#c678dd' },
273 { text: '#4078f2', box: '#80a7f4' },
274 { text: '#827662', box: '#e3d1b2' },
275 { text: '#904f53', box: '#e78b8f' },
276 { text: '#637855', box: '#abcc94' },
277 { text: '#805f89', box: '#dbb2e8' },
278 { text: '#5987ae', box: '#92c0e9' },
279 ],
259 }, 280 },
260 }, 281 },
261 }); 282 });
@@ -270,6 +291,7 @@ const darkTheme = (() => {
270 { 291 {
271 typography: { 292 typography: {
272 fontWeightEditorNormal: 350, 293 fontWeightEditorNormal: 350,
294 fontWeightEditorTypeHash: 350,
273 fontWeightEditorBold: 650, 295 fontWeightEditorBold: 650,
274 }, 296 },
275 palette: { 297 palette: {
@@ -277,7 +299,7 @@ const darkTheme = (() => {
277 primary: { main: '#56b6c2' }, 299 primary: { main: '#56b6c2' },
278 secondary: { main: '#be5046' }, 300 secondary: { main: '#be5046' },
279 error: { main: '#e06c75' }, 301 error: { main: '#e06c75' },
280 warning: { main: '#e5c07b' }, 302 warning: { main: '#d19a66' },
281 success: { main: '#98c379' }, 303 success: { main: '#98c379' },
282 info: { main: '#61afef' }, 304 info: { main: '#61afef' },
283 background: { 305 background: {
@@ -311,6 +333,18 @@ const darkTheme = (() => {
311 selected: '#dd33fa', 333 selected: '#dd33fa',
312 contrastText: darkBackground, 334 contrastText: darkBackground,
313 }, 335 },
336 typeHash: [
337 { text: '#e5c07b', box: '#ae8003' },
338 { text: '#e06c75', box: '#a23b47' },
339 { text: '#98c379', box: '#428141' },
340 { text: '#c678dd', box: '#854797' },
341 { text: '#61afef', box: '#3982bb' },
342 { text: '#e3d1b2', box: '#827662' },
343 { text: '#e78b8f', box: '#904f53' },
344 { text: '#abcc94', box: '#647e63' },
345 { text: '#dbb2e8', box: '#805f89' },
346 { text: '#92c0e9', box: '#4f7799' },
347 ],
314 }, 348 },
315 }, 349 },
316 }, 350 },
diff --git a/subprojects/frontend/src/xtext/xtextServiceResults.ts b/subprojects/frontend/src/xtext/xtextServiceResults.ts
index e473bd48..792c7de3 100644
--- a/subprojects/frontend/src/xtext/xtextServiceResults.ts
+++ b/subprojects/frontend/src/xtext/xtextServiceResults.ts
@@ -1,5 +1,5 @@
1/* 1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> 2 * SPDX-FileCopyrightText: 2021-2024 The Refinery Authors <https://refinery.tools/>
3 * 3 *
4 * SPDX-License-Identifier: EPL-2.0 4 * SPDX-License-Identifier: EPL-2.0
5 */ 5 */
@@ -137,6 +137,7 @@ export type ModelGenerationStartedResult = z.infer<
137export const NodeMetadata = z.object({ 137export const NodeMetadata = z.object({
138 name: z.string(), 138 name: z.string(),
139 simpleName: z.string(), 139 simpleName: z.string(),
140 typeHash: z.string().optional(),
140 kind: z.enum(['IMPLICIT', 'INDIVIDUAL', 'NEW']), 141 kind: z.enum(['IMPLICIT', 'INDIVIDUAL', 'NEW']),
141}); 142});
142 143
@@ -182,15 +183,15 @@ export type SemanticsResult = z.infer<typeof SemanticsResult>;
182 183
183export const ModelGenerationResult = z.union([ 184export const ModelGenerationResult = z.union([
184 z.object({ 185 z.object({
185 uuid: z.string().nonempty(), 186 uuid: z.string().min(1),
186 status: z.string(), 187 status: z.string(),
187 }), 188 }),
188 z.object({ 189 z.object({
189 uuid: z.string().nonempty(), 190 uuid: z.string().min(1),
190 error: z.string(), 191 error: z.string(),
191 }), 192 }),
192 SemanticsSuccessResult.extend({ 193 SemanticsSuccessResult.extend({
193 uuid: z.string().nonempty(), 194 uuid: z.string().min(1),
194 }), 195 }),
195]); 196]);
196 197
diff --git a/subprojects/language-ide/src/main/java/tools/refinery/language/ide/syntaxcoloring/ProblemSemanticHighlightingCalculator.java b/subprojects/language-ide/src/main/java/tools/refinery/language/ide/syntaxcoloring/ProblemSemanticHighlightingCalculator.java
index ae8c70e0..f64d4066 100644
--- a/subprojects/language-ide/src/main/java/tools/refinery/language/ide/syntaxcoloring/ProblemSemanticHighlightingCalculator.java
+++ b/subprojects/language-ide/src/main/java/tools/refinery/language/ide/syntaxcoloring/ProblemSemanticHighlightingCalculator.java
@@ -1,5 +1,5 @@
1/* 1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> 2 * SPDX-FileCopyrightText: 2021-2024 The Refinery Authors <https://refinery.tools/>
3 * 3 *
4 * SPDX-License-Identifier: EPL-2.0 4 * SPDX-License-Identifier: EPL-2.0
5 */ 5 */
@@ -38,9 +38,12 @@ public class ProblemSemanticHighlightingCalculator extends DefaultSemanticHighli
38 @Inject 38 @Inject
39 private ProblemDesugarer desugarer; 39 private ProblemDesugarer desugarer;
40 40
41 @Inject
42 private TypeHashProvider typeHashProvider;
43
41 @Override 44 @Override
42 protected boolean highlightElement(EObject object, IHighlightedPositionAcceptor acceptor, 45 protected boolean highlightElement(EObject object, IHighlightedPositionAcceptor acceptor,
43 CancelIndicator cancelIndicator) { 46 CancelIndicator cancelIndicator) {
44 highlightName(object, acceptor); 47 highlightName(object, acceptor);
45 highlightCrossReferences(object, acceptor, cancelIndicator); 48 highlightCrossReferences(object, acceptor, cancelIndicator);
46 return false; 49 return false;
@@ -57,7 +60,7 @@ public class ProblemSemanticHighlightingCalculator extends DefaultSemanticHighli
57 } 60 }
58 61
59 protected void highlightCrossReferences(EObject object, IHighlightedPositionAcceptor acceptor, 62 protected void highlightCrossReferences(EObject object, IHighlightedPositionAcceptor acceptor,
60 CancelIndicator cancelIndicator) { 63 CancelIndicator cancelIndicator) {
61 for (EReference reference : object.eClass().getEAllReferences()) { 64 for (EReference reference : object.eClass().getEAllReferences()) {
62 if (reference.isContainment()) { 65 if (reference.isContainment()) {
63 continue; 66 continue;
@@ -98,7 +101,7 @@ public class ProblemSemanticHighlightingCalculator extends DefaultSemanticHighli
98 boolean isError = ProblemUtil.isError(eObject); 101 boolean isError = ProblemUtil.isError(eObject);
99 if (ProblemUtil.isBuiltIn(eObject)) { 102 if (ProblemUtil.isBuiltIn(eObject)) {
100 var className = isError ? ERROR_CLASS : BUILTIN_CLASS; 103 var className = isError ? ERROR_CLASS : BUILTIN_CLASS;
101 return new String[] { className }; 104 return new String[]{className};
102 } 105 }
103 return getUserDefinedElementHighlightClass(eObject, reference, isError); 106 return getUserDefinedElementHighlightClass(eObject, reference, isError);
104 } 107 }
@@ -113,21 +116,32 @@ public class ProblemSemanticHighlightingCalculator extends DefaultSemanticHighli
113 && desugarer.isContainmentReference(referenceDeclaration)) { 116 && desugarer.isContainmentReference(referenceDeclaration)) {
114 classesBuilder.add(CONTAINMENT_CLASS); 117 classesBuilder.add(CONTAINMENT_CLASS);
115 } 118 }
116 if (isError) { 119 if (isError && reference != null) {
120 // References to error patterns should be highlighted as errors, but error pattern definitions shouldn't.
117 classesBuilder.add(ERROR_CLASS); 121 classesBuilder.add(ERROR_CLASS);
118 } 122 }
119 if (eObject instanceof Node node) { 123 if (eObject instanceof Node node) {
120 if (reference == ProblemPackage.Literals.VARIABLE_OR_NODE_EXPR__VARIABLE_OR_NODE) { 124 highlightNode(node, reference, classesBuilder);
121 classesBuilder.add(NODE_CLASS); 125 }
122 } 126 if (eObject instanceof Relation relation) {
123 if (ProblemUtil.isIndividualNode(node)) { 127 var typeHash = typeHashProvider.getTypeHash(relation);
124 classesBuilder.add(INDIVIDUAL_NODE_CLASS); 128 if (typeHash != null) {
125 } 129 classesBuilder.add("typeHash-" + typeHash);
126 if (ProblemUtil.isNewNode(node)) {
127 classesBuilder.add(NEW_NODE_CLASS);
128 } 130 }
129 } 131 }
130 List<String> classes = classesBuilder.build(); 132 List<String> classes = classesBuilder.build();
131 return classes.toArray(new String[0]); 133 return classes.toArray(new String[0]);
132 } 134 }
135
136 private static void highlightNode(Node node, EReference reference, ImmutableList.Builder<String> classesBuilder) {
137 if (reference == ProblemPackage.Literals.VARIABLE_OR_NODE_EXPR__VARIABLE_OR_NODE) {
138 classesBuilder.add(NODE_CLASS);
139 }
140 if (ProblemUtil.isIndividualNode(node)) {
141 classesBuilder.add(INDIVIDUAL_NODE_CLASS);
142 }
143 if (ProblemUtil.isNewNode(node)) {
144 classesBuilder.add(NEW_NODE_CLASS);
145 }
146 }
133} 147}
diff --git a/subprojects/language-ide/src/main/java/tools/refinery/language/ide/syntaxcoloring/TypeHashProvider.java b/subprojects/language-ide/src/main/java/tools/refinery/language/ide/syntaxcoloring/TypeHashProvider.java
new file mode 100644
index 00000000..f75ecdb2
--- /dev/null
+++ b/subprojects/language-ide/src/main/java/tools/refinery/language/ide/syntaxcoloring/TypeHashProvider.java
@@ -0,0 +1,93 @@
1/*
2 * SPDX-FileCopyrightText: 2024 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.language.ide.syntaxcoloring;
7
8import com.google.common.collect.ImmutableMap;
9import com.google.inject.Inject;
10import com.google.inject.Singleton;
11import org.eclipse.xtext.EcoreUtil2;
12import org.eclipse.xtext.naming.IQualifiedNameConverter;
13import org.eclipse.xtext.naming.IQualifiedNameProvider;
14import org.eclipse.xtext.scoping.IScopeProvider;
15import org.eclipse.xtext.util.IResourceScopeCache;
16import tools.refinery.language.model.problem.*;
17import tools.refinery.language.resource.ProblemResourceDescriptionStrategy;
18import tools.refinery.language.utils.ProblemUtil;
19
20import java.util.*;
21
22@Singleton
23public class TypeHashProvider {
24 private static final String CACHE_KEY = "tools.refinery.language.ide.syntaxcoloring.TypeHashProvider";
25 private static final int COLOR_COUNT = 10;
26
27 @Inject
28 private IResourceScopeCache resourceScopeCache;
29
30 @Inject
31 private IScopeProvider scopeProvider;
32
33 @Inject
34 private IQualifiedNameProvider qualifiedNameProvider;
35
36 @Inject
37 private IQualifiedNameConverter qualifiedNameConverter;
38
39 public String getTypeHash(Relation relation) {
40 if (!(relation instanceof ClassDeclaration || relation instanceof EnumDeclaration) ||
41 ProblemUtil.isBuiltIn(relation)) {
42 return null;
43 }
44 var qualifiedName = qualifiedNameProvider.getFullyQualifiedName(relation);
45 if (qualifiedName == null) {
46 return null;
47 }
48 var qualifiedNameString = qualifiedNameConverter.toString(qualifiedName);
49 var problem = EcoreUtil2.getContainerOfType(relation, Problem.class);
50 if (problem == null) {
51 return null;
52 }
53 var cache = resourceScopeCache.get(CACHE_KEY, problem.eResource(), () -> computeHashes(problem));
54 return cache.get(qualifiedNameString);
55 }
56
57 private Map<String, String> computeHashes(Problem problem) {
58 var qualifiedNameStrings = new TreeSet<String>();
59 var scope = scopeProvider.getScope(problem, ProblemPackage.Literals.ASSERTION__RELATION);
60 for (var description : scope.getAllElements()) {
61 if (ProblemResourceDescriptionStrategy.COLOR_RELATION_TRUE.equals(
62 description.getUserData(ProblemResourceDescriptionStrategy.COLOR_RELATION))) {
63 var qualifiedNameString = qualifiedNameConverter.toString(description.getQualifiedName());
64 qualifiedNameStrings.add(qualifiedNameString);
65 }
66 }
67 var stringList = new ArrayList<>(qualifiedNameStrings);
68 int size = stringList.size();
69 if (size == 0) {
70 return Map.of();
71 }
72 // The use of a non-cryptographic random generator is safe here, because we only use it to shuffle the color
73 // IDs in a pseudo-random way. The shuffle depends on the size of the list of identifiers before padding to
74 // make sure that adding a new class randomizes all color IDs.
75 @SuppressWarnings("squid:S2245")
76 var random = new Random(size);
77 int padding = COLOR_COUNT - (size % COLOR_COUNT);
78 for (int i = 0; i < padding; i++) {
79 stringList.add(null);
80 }
81 size += padding;
82 Collections.shuffle(stringList, random);
83 var mapBuilder = ImmutableMap.<String, String>builder();
84 for (int i = 0; i < size; i++) {
85 var key = stringList.get(i);
86 if (key != null) {
87 int colorId = i % COLOR_COUNT;
88 mapBuilder.put(key, Integer.toString(colorId));
89 }
90 }
91 return mapBuilder.build();
92 }
93}
diff --git a/subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/NodeNameProvider.java b/subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/NodeNameProvider.java
new file mode 100644
index 00000000..517b8604
--- /dev/null
+++ b/subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/NodeNameProvider.java
@@ -0,0 +1,62 @@
1/*
2 * SPDX-FileCopyrightText: 2024 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.language.semantics;
7
8import com.google.inject.Inject;
9import org.eclipse.collections.api.factory.primitive.ObjectIntMaps;
10import org.eclipse.collections.api.map.primitive.MutableObjectIntMap;
11import org.eclipse.xtext.naming.IQualifiedNameConverter;
12import org.eclipse.xtext.naming.QualifiedName;
13import org.eclipse.xtext.scoping.IScopeProvider;
14import tools.refinery.language.model.problem.Node;
15import tools.refinery.language.model.problem.Problem;
16import tools.refinery.language.model.problem.ProblemPackage;
17
18import java.util.Locale;
19
20public class NodeNameProvider {
21 @Inject
22 private IQualifiedNameConverter qualifiedNameConverter;
23
24 @Inject
25 private SemanticsUtils semanticsUtils;
26
27 @Inject
28 private IScopeProvider scopeProvider;
29
30 private Problem problem;
31 private final MutableObjectIntMap<String> indexMap = ObjectIntMaps.mutable.empty();
32
33 public void setProblem(Problem problem) {
34 if (this.problem != null) {
35 throw new IllegalStateException("Problem was already set");
36 }
37 this.problem = problem;
38 }
39
40 public String getNextName(String typeName) {
41 if (problem == null) {
42 throw new IllegalStateException("Problem was not set");
43 }
44 String namePrefix;
45 if (typeName == null || typeName.isEmpty()) {
46 namePrefix = "node";
47 } else {
48 namePrefix = typeName.substring(0, 1).toLowerCase(Locale.ROOT) + typeName.substring(1);
49 }
50 int index = indexMap.getIfAbsent(namePrefix, 0);
51 String nodeName;
52 QualifiedName qualifiedName;
53 var scope = scopeProvider.getScope(problem, ProblemPackage.Literals.NODE_ASSERTION_ARGUMENT__NODE);
54 do {
55 index++;
56 nodeName = namePrefix + index;
57 qualifiedName = qualifiedNameConverter.toQualifiedName(nodeName);
58 } while (semanticsUtils.maybeGetElement(problem, scope, qualifiedName, Node.class) != null);
59 indexMap.put(namePrefix, index);
60 return nodeName;
61 }
62}
diff --git a/subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/SolutionSerializer.java b/subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/SolutionSerializer.java
index 57af599e..09ba34fc 100644
--- a/subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/SolutionSerializer.java
+++ b/subprojects/language-semantics/src/main/java/tools/refinery/language/semantics/SolutionSerializer.java
@@ -1,5 +1,5 @@
1/* 1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/> 2 * SPDX-FileCopyrightText: 2023-2024 The Refinery Authors <https://refinery.tools/>
3 * 3 *
4 * SPDX-License-Identifier: EPL-2.0 4 * SPDX-License-Identifier: EPL-2.0
5 */ 5 */
@@ -8,12 +8,9 @@ package tools.refinery.language.semantics;
8import com.google.inject.Inject; 8import com.google.inject.Inject;
9import com.google.inject.Provider; 9import com.google.inject.Provider;
10import org.eclipse.collections.api.factory.primitive.IntObjectMaps; 10import org.eclipse.collections.api.factory.primitive.IntObjectMaps;
11import org.eclipse.collections.api.factory.primitive.ObjectIntMaps;
12import org.eclipse.collections.api.map.primitive.MutableIntObjectMap; 11import org.eclipse.collections.api.map.primitive.MutableIntObjectMap;
13import org.eclipse.collections.api.map.primitive.MutableObjectIntMap;
14import org.eclipse.emf.common.util.URI; 12import org.eclipse.emf.common.util.URI;
15import org.eclipse.emf.ecore.util.EcoreUtil; 13import org.eclipse.emf.ecore.util.EcoreUtil;
16import org.eclipse.xtext.naming.IQualifiedNameConverter;
17import org.eclipse.xtext.naming.IQualifiedNameProvider; 14import org.eclipse.xtext.naming.IQualifiedNameProvider;
18import org.eclipse.xtext.naming.QualifiedName; 15import org.eclipse.xtext.naming.QualifiedName;
19import org.eclipse.xtext.resource.FileExtensionProvider; 16import org.eclipse.xtext.resource.FileExtensionProvider;
@@ -37,7 +34,6 @@ import tools.refinery.store.tuple.Tuple;
37import java.io.ByteArrayInputStream; 34import java.io.ByteArrayInputStream;
38import java.io.ByteArrayOutputStream; 35import java.io.ByteArrayOutputStream;
39import java.io.IOException; 36import java.io.IOException;
40import java.util.Locale;
41import java.util.Map; 37import java.util.Map;
42import java.util.TreeMap; 38import java.util.TreeMap;
43import java.util.TreeSet; 39import java.util.TreeSet;
@@ -57,9 +53,6 @@ public class SolutionSerializer {
57 private IQualifiedNameProvider qualifiedNameProvider; 53 private IQualifiedNameProvider qualifiedNameProvider;
58 54
59 @Inject 55 @Inject
60 private IQualifiedNameConverter qualifiedNameConverter;
61
62 @Inject
63 private SemanticsUtils semanticsUtils; 56 private SemanticsUtils semanticsUtils;
64 57
65 @Inject 58 @Inject
@@ -68,6 +61,9 @@ public class SolutionSerializer {
68 @Inject 61 @Inject
69 private ProblemDesugarer desugarer; 62 private ProblemDesugarer desugarer;
70 63
64 @Inject
65 private NodeNameProvider nameProvider;
66
71 private ProblemTrace trace; 67 private ProblemTrace trace;
72 private Model model; 68 private Model model;
73 private ReasoningAdapter reasoningAdapter; 69 private ReasoningAdapter reasoningAdapter;
@@ -95,6 +91,7 @@ public class SolutionSerializer {
95 problem = copyProblem(originalProblem, uri); 91 problem = copyProblem(originalProblem, uri);
96 problem.getStatements().removeIf(SolutionSerializer::shouldRemoveStatement); 92 problem.getStatements().removeIf(SolutionSerializer::shouldRemoveStatement);
97 problem.getNodes().removeIf(this::shouldRemoveNode); 93 problem.getNodes().removeIf(this::shouldRemoveNode);
94 nameProvider.setProblem(problem);
98 addExistsAssertions(); 95 addExistsAssertions();
99 addClassAssertions(); 96 addClassAssertions();
100 addReferenceAssertions(); 97 addReferenceAssertions();
@@ -172,11 +169,6 @@ public class SolutionSerializer {
172 return findNode(qualifiedName); 169 return findNode(qualifiedName);
173 } 170 }
174 171
175 private Node findNode(String name) {
176 var qualifiedName = qualifiedNameConverter.toQualifiedName(name);
177 return findNode(qualifiedName);
178 }
179
180 private Node findNode(QualifiedName qualifiedName) { 172 private Node findNode(QualifiedName qualifiedName) {
181 var scope = scopeProvider.getScope(problem, ProblemPackage.Literals.NODE_ASSERTION_ARGUMENT__NODE); 173 var scope = scopeProvider.getScope(problem, ProblemPackage.Literals.NODE_ASSERTION_ARGUMENT__NODE);
182 return semanticsUtils.maybeGetElement(problem, scope, qualifiedName, Node.class); 174 return semanticsUtils.maybeGetElement(problem, scope, qualifiedName, Node.class);
@@ -222,19 +214,17 @@ public class SolutionSerializer {
222 private void addClassAssertions() { 214 private void addClassAssertions() {
223 var types = trace.getMetamodel().typeHierarchy().getPreservedTypes().keySet().stream() 215 var types = trace.getMetamodel().typeHierarchy().getPreservedTypes().keySet().stream()
224 .collect(Collectors.toMap(Function.identity(), this::findPartialRelation)); 216 .collect(Collectors.toMap(Function.identity(), this::findPartialRelation));
225 var indexMap = ObjectIntMaps.mutable.empty();
226 var cursor = model.getInterpretation(TypeHierarchyTranslator.TYPE_SYMBOL).getAll(); 217 var cursor = model.getInterpretation(TypeHierarchyTranslator.TYPE_SYMBOL).getAll();
227 while (cursor.move()) { 218 while (cursor.move()) {
228 var key = cursor.getKey(); 219 var key = cursor.getKey();
229 var nodeId = key.get(0); 220 var nodeId = key.get(0);
230 if (isExistingNode(nodeId)) { 221 if (isExistingNode(nodeId)) {
231 createNodeAndAssertType(nodeId, cursor.getValue(), types, indexMap); 222 createNodeAndAssertType(nodeId, cursor.getValue(), types);
232 } 223 }
233 } 224 }
234 } 225 }
235 226
236 private void createNodeAndAssertType(int nodeId, InferredType inferredType, Map<PartialRelation, Relation> types, 227 private void createNodeAndAssertType(int nodeId, InferredType inferredType, Map<PartialRelation, Relation> types) {
237 MutableObjectIntMap<Object> indexMap) {
238 var candidateTypeSymbol = inferredType.candidateType(); 228 var candidateTypeSymbol = inferredType.candidateType();
239 var candidateRelation = types.get(candidateTypeSymbol); 229 var candidateRelation = types.get(candidateTypeSymbol);
240 if (candidateRelation instanceof EnumDeclaration) { 230 if (candidateRelation instanceof EnumDeclaration) {
@@ -243,18 +233,8 @@ public class SolutionSerializer {
243 } 233 }
244 Node node = nodes.get(nodeId); 234 Node node = nodes.get(nodeId);
245 if (node == null) { 235 if (node == null) {
246 String typeName = candidateRelation.getName(); 236 var typeName = candidateRelation.getName();
247 if (typeName == null || typeName.isEmpty()) { 237 var nodeName = nameProvider.getNextName(typeName);
248 typeName = "node";
249 } else {
250 typeName = typeName.substring(0, 1).toLowerCase(Locale.ROOT) + typeName.substring(1);
251 }
252 int index = indexMap.getIfAbsent(typeName, 0);
253 String nodeName;
254 do {
255 index++;
256 nodeName = typeName + index;
257 } while (findNode(nodeName) != null);
258 node = ProblemFactory.eINSTANCE.createNode(); 238 node = ProblemFactory.eINSTANCE.createNode();
259 node.setName(nodeName); 239 node.setName(nodeName);
260 problem.getNodes().add(node); 240 problem.getNodes().add(node);
diff --git a/subprojects/language-web/src/main/java/tools/refinery/language/web/generator/ModelGenerationWorker.java b/subprojects/language-web/src/main/java/tools/refinery/language/web/generator/ModelGenerationWorker.java
index a3b6ca82..7febce7d 100644
--- a/subprojects/language-web/src/main/java/tools/refinery/language/web/generator/ModelGenerationWorker.java
+++ b/subprojects/language-web/src/main/java/tools/refinery/language/web/generator/ModelGenerationWorker.java
@@ -1,5 +1,5 @@
1/* 1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/> 2 * SPDX-FileCopyrightText: 2023-2024 The Refinery Authors <https://refinery.tools/>
3 * 3 *
4 * SPDX-License-Identifier: EPL-2.0 4 * SPDX-License-Identifier: EPL-2.0
5 */ 5 */
@@ -17,6 +17,7 @@ import tools.refinery.generator.ValidationErrorsException;
17import tools.refinery.language.web.semantics.PartialInterpretation2Json; 17import tools.refinery.language.web.semantics.PartialInterpretation2Json;
18import tools.refinery.language.web.xtext.server.ThreadPoolExecutorServiceProvider; 18import tools.refinery.language.web.xtext.server.ThreadPoolExecutorServiceProvider;
19import tools.refinery.language.web.xtext.server.push.PushWebDocument; 19import tools.refinery.language.web.xtext.server.push.PushWebDocument;
20import tools.refinery.store.reasoning.literal.Concreteness;
20import tools.refinery.store.util.CancellationToken; 21import tools.refinery.store.util.CancellationToken;
21 22
22import java.io.IOException; 23import java.io.IOException;
@@ -142,7 +143,7 @@ public class ModelGenerationWorker implements Runnable {
142 } catch (ValidationErrorsException e) { 143 } catch (ValidationErrorsException e) {
143 var errors = e.getErrors(); 144 var errors = e.getErrors();
144 if (errors != null && !errors.isEmpty()) { 145 if (errors != null && !errors.isEmpty()) {
145 return new ModelGenerationErrorResult(uuid, "Validation error: " + errors.get(0).getMessage()); 146 return new ModelGenerationErrorResult(uuid, "Validation error: " + errors.getFirst().getMessage());
146 } 147 }
147 throw e; 148 throw e;
148 } 149 }
@@ -154,7 +155,7 @@ public class ModelGenerationWorker implements Runnable {
154 notifyResult(new ModelGenerationStatusResult(uuid, "Saving generated model")); 155 notifyResult(new ModelGenerationStatusResult(uuid, "Saving generated model"));
155 cancellationToken.checkCancelled(); 156 cancellationToken.checkCancelled();
156 metadataCreator.setProblemTrace(generator.getProblemTrace()); 157 metadataCreator.setProblemTrace(generator.getProblemTrace());
157 var nodesMetadata = metadataCreator.getNodesMetadata(generator.getModel(), false); 158 var nodesMetadata = metadataCreator.getNodesMetadata(generator.getModel(), Concreteness.CANDIDATE);
158 cancellationToken.checkCancelled(); 159 cancellationToken.checkCancelled();
159 var relationsMetadata = metadataCreator.getRelationsMetadata(); 160 var relationsMetadata = metadataCreator.getRelationsMetadata();
160 cancellationToken.checkCancelled(); 161 cancellationToken.checkCancelled();
diff --git a/subprojects/language-web/src/main/java/tools/refinery/language/web/semantics/SemanticsWorker.java b/subprojects/language-web/src/main/java/tools/refinery/language/web/semantics/SemanticsWorker.java
index 44974869..fed3c8a3 100644
--- a/subprojects/language-web/src/main/java/tools/refinery/language/web/semantics/SemanticsWorker.java
+++ b/subprojects/language-web/src/main/java/tools/refinery/language/web/semantics/SemanticsWorker.java
@@ -1,5 +1,5 @@
1/* 1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/> 2 * SPDX-FileCopyrightText: 2023-2024 The Refinery Authors <https://refinery.tools/>
3 * 3 *
4 * SPDX-License-Identifier: EPL-2.0 4 * SPDX-License-Identifier: EPL-2.0
5 */ 5 */
@@ -20,6 +20,7 @@ import tools.refinery.generator.ModelSemanticsFactory;
20import tools.refinery.language.model.problem.Problem; 20import tools.refinery.language.model.problem.Problem;
21import tools.refinery.language.web.semantics.metadata.MetadataCreator; 21import tools.refinery.language.web.semantics.metadata.MetadataCreator;
22import tools.refinery.language.semantics.TracedException; 22import tools.refinery.language.semantics.TracedException;
23import tools.refinery.store.reasoning.literal.Concreteness;
23import tools.refinery.store.reasoning.translator.TranslationException; 24import tools.refinery.store.reasoning.translator.TranslationException;
24import tools.refinery.store.util.CancellationToken; 25import tools.refinery.store.util.CancellationToken;
25 26
@@ -73,7 +74,7 @@ class SemanticsWorker implements Callable<SemanticsResult> {
73 } 74 }
74 cancellationToken.checkCancelled(); 75 cancellationToken.checkCancelled();
75 metadataCreator.setProblemTrace(semantics.getProblemTrace()); 76 metadataCreator.setProblemTrace(semantics.getProblemTrace());
76 var nodesMetadata = metadataCreator.getNodesMetadata(semantics.getModel(), true); 77 var nodesMetadata = metadataCreator.getNodesMetadata(semantics.getModel(), Concreteness.PARTIAL);
77 cancellationToken.checkCancelled(); 78 cancellationToken.checkCancelled();
78 var relationsMetadata = metadataCreator.getRelationsMetadata(); 79 var relationsMetadata = metadataCreator.getRelationsMetadata();
79 cancellationToken.checkCancelled(); 80 cancellationToken.checkCancelled();
diff --git a/subprojects/language-web/src/main/java/tools/refinery/language/web/semantics/metadata/MetadataCreator.java b/subprojects/language-web/src/main/java/tools/refinery/language/web/semantics/metadata/MetadataCreator.java
index 3fbc5d2d..f05abc45 100644
--- a/subprojects/language-web/src/main/java/tools/refinery/language/web/semantics/metadata/MetadataCreator.java
+++ b/subprojects/language-web/src/main/java/tools/refinery/language/web/semantics/metadata/MetadataCreator.java
@@ -1,11 +1,12 @@
1/* 1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/> 2 * SPDX-FileCopyrightText: 2023-2024 The Refinery Authors <https://refinery.tools/>
3 * 3 *
4 * SPDX-License-Identifier: EPL-2.0 4 * SPDX-License-Identifier: EPL-2.0
5 */ 5 */
6package tools.refinery.language.web.semantics.metadata; 6package tools.refinery.language.web.semantics.metadata;
7 7
8import com.google.inject.Inject; 8import com.google.inject.Inject;
9import com.google.inject.Provider;
9import org.eclipse.emf.ecore.EObject; 10import org.eclipse.emf.ecore.EObject;
10import org.eclipse.xtext.naming.IQualifiedNameConverter; 11import org.eclipse.xtext.naming.IQualifiedNameConverter;
11import org.eclipse.xtext.naming.IQualifiedNameProvider; 12import org.eclipse.xtext.naming.IQualifiedNameProvider;
@@ -18,6 +19,7 @@ import tools.refinery.language.semantics.TracedException;
18import tools.refinery.language.utils.ProblemUtil; 19import tools.refinery.language.utils.ProblemUtil;
19import tools.refinery.store.model.Model; 20import tools.refinery.store.model.Model;
20import tools.refinery.store.reasoning.ReasoningAdapter; 21import tools.refinery.store.reasoning.ReasoningAdapter;
22import tools.refinery.store.reasoning.literal.Concreteness;
21import tools.refinery.store.reasoning.representation.PartialRelation; 23import tools.refinery.store.reasoning.representation.PartialRelation;
22 24
23import java.util.ArrayList; 25import java.util.ArrayList;
@@ -35,10 +37,11 @@ public class MetadataCreator {
35 @Inject 37 @Inject
36 private IQualifiedNameConverter qualifiedNameConverter; 38 private IQualifiedNameConverter qualifiedNameConverter;
37 39
38 private ProblemTrace problemTrace; 40 @Inject
41 private Provider<NodeMetadataFactory> nodeMetadataFactoryProvider;
39 42
43 private ProblemTrace problemTrace;
40 private IScope nodeScope; 44 private IScope nodeScope;
41
42 private IScope relationScope; 45 private IScope relationScope;
43 46
44 public void setProblemTrace(ProblemTrace problemTrace) { 47 public void setProblemTrace(ProblemTrace problemTrace) {
@@ -51,37 +54,35 @@ public class MetadataCreator {
51 relationScope = scopeProvider.getScope(problem, ProblemPackage.Literals.ASSERTION__RELATION); 54 relationScope = scopeProvider.getScope(problem, ProblemPackage.Literals.ASSERTION__RELATION);
52 } 55 }
53 56
54 public static String unnamedNode(int nodeId) { 57 public List<NodeMetadata> getNodesMetadata(Model model, Concreteness concreteness) {
55 return "::" + nodeId;
56 }
57
58 public List<NodeMetadata> getNodesMetadata(Model model, boolean preserveNewNodes) {
59 int nodeCount = model.getAdapter(ReasoningAdapter.class).getNodeCount(); 58 int nodeCount = model.getAdapter(ReasoningAdapter.class).getNodeCount();
60 var nodeTrace = problemTrace.getNodeTrace(); 59 var nodeTrace = problemTrace.getNodeTrace();
61 var nodes = new NodeMetadata[Math.max(nodeTrace.size(), nodeCount)]; 60 var nodes = new NodeMetadata[Math.max(nodeTrace.size(), nodeCount)];
61 var nodeMetadataFactory = nodeMetadataFactoryProvider.get();
62 nodeMetadataFactory.initialize(problemTrace, concreteness, model);
63 boolean preserveNewNodes = concreteness == Concreteness.PARTIAL;
62 for (var entry : nodeTrace.keyValuesView()) { 64 for (var entry : nodeTrace.keyValuesView()) {
63 var node = entry.getOne(); 65 var node = entry.getOne();
64 var id = entry.getTwo(); 66 var id = entry.getTwo();
65 nodes[id] = getNodeMetadata(id, node, preserveNewNodes); 67 nodes[id] = getNodeMetadata(id, node, preserveNewNodes, nodeMetadataFactory);
66 } 68 }
67 for (int i = 0; i < nodes.length; i++) { 69 for (int i = 0; i < nodes.length; i++) {
68 if (nodes[i] == null) { 70 if (nodes[i] == null) {
69 var nodeName = unnamedNode(i); 71 nodes[i] = nodeMetadataFactory.createFreshlyNamedMetadata(i);
70 nodes[i] = new NodeMetadata(nodeName, nodeName, NodeKind.IMPLICIT);
71 } 72 }
72 } 73 }
73 return List.of(nodes); 74 return List.of(nodes);
74 } 75 }
75 76
76 private NodeMetadata getNodeMetadata(int nodeId, Node node, boolean preserveNewNodes) { 77 private NodeMetadata getNodeMetadata(int nodeId, Node node, boolean preserveNewNodes,
78 NodeMetadataFactory nodeMetadataFactory) {
77 var kind = getNodeKind(node); 79 var kind = getNodeKind(node);
78 if (!preserveNewNodes && kind == NodeKind.NEW) { 80 if (!preserveNewNodes && kind == NodeKind.NEW) {
79 var nodeName = unnamedNode(nodeId); 81 return nodeMetadataFactory.createFreshlyNamedMetadata(nodeId);
80 return new NodeMetadata(nodeName, nodeName, NodeKind.IMPLICIT);
81 } 82 }
82 var qualifiedName = getQualifiedName(node); 83 var qualifiedName = getQualifiedName(node);
83 var simpleName = getSimpleName(node, qualifiedName, nodeScope); 84 var simpleName = getSimpleName(node, qualifiedName, nodeScope);
84 return new NodeMetadata(qualifiedNameConverter.toString(qualifiedName), 85 return nodeMetadataFactory.doCreateMetadata(nodeId, qualifiedNameConverter.toString(qualifiedName),
85 qualifiedNameConverter.toString(simpleName), getNodeKind(node)); 86 qualifiedNameConverter.toString(simpleName), getNodeKind(node));
86 } 87 }
87 88
diff --git a/subprojects/language-web/src/main/java/tools/refinery/language/web/semantics/metadata/NodeMetadata.java b/subprojects/language-web/src/main/java/tools/refinery/language/web/semantics/metadata/NodeMetadata.java
index 5da28acf..f3347eac 100644
--- a/subprojects/language-web/src/main/java/tools/refinery/language/web/semantics/metadata/NodeMetadata.java
+++ b/subprojects/language-web/src/main/java/tools/refinery/language/web/semantics/metadata/NodeMetadata.java
@@ -1,9 +1,9 @@
1/* 1/*
2 * SPDX-FileCopyrightText: 2023 The Refinery Authors <https://refinery.tools/> 2 * SPDX-FileCopyrightText: 2023-2023 The Refinery Authors <https://refinery.tools/>
3 * 3 *
4 * SPDX-License-Identifier: EPL-2.0 4 * SPDX-License-Identifier: EPL-2.0
5 */ 5 */
6package tools.refinery.language.web.semantics.metadata; 6package tools.refinery.language.web.semantics.metadata;
7 7
8public record NodeMetadata(String name, String simpleName, NodeKind kind) implements Metadata { 8public record NodeMetadata(String name, String simpleName, String typeHash, NodeKind kind) implements Metadata {
9} 9}
diff --git a/subprojects/language-web/src/main/java/tools/refinery/language/web/semantics/metadata/NodeMetadataFactory.java b/subprojects/language-web/src/main/java/tools/refinery/language/web/semantics/metadata/NodeMetadataFactory.java
new file mode 100644
index 00000000..ce0e50c1
--- /dev/null
+++ b/subprojects/language-web/src/main/java/tools/refinery/language/web/semantics/metadata/NodeMetadataFactory.java
@@ -0,0 +1,89 @@
1/*
2 * SPDX-FileCopyrightText: 2024 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6package tools.refinery.language.web.semantics.metadata;
7
8import com.google.inject.Inject;
9import tools.refinery.language.ide.syntaxcoloring.TypeHashProvider;
10import tools.refinery.language.semantics.NodeNameProvider;
11import tools.refinery.language.semantics.ProblemTrace;
12import tools.refinery.store.model.Interpretation;
13import tools.refinery.store.model.Model;
14import tools.refinery.store.reasoning.ReasoningAdapter;
15import tools.refinery.store.reasoning.interpretation.PartialInterpretation;
16import tools.refinery.store.reasoning.literal.Concreteness;
17import tools.refinery.store.reasoning.representation.PartialRelation;
18import tools.refinery.store.reasoning.translator.typehierarchy.InferredType;
19import tools.refinery.store.reasoning.translator.typehierarchy.TypeHierarchyTranslator;
20import tools.refinery.store.representation.TruthValue;
21import tools.refinery.store.tuple.Tuple;
22
23public class NodeMetadataFactory {
24 @Inject
25 private NodeNameProvider nodeNameProvider;
26
27 @Inject
28 private TypeHashProvider typeHashProvider;
29
30 private ProblemTrace problemTrace;
31 private Concreteness concreteness;
32 private Interpretation<InferredType> typeInterpretation;
33 private PartialInterpretation<TruthValue, Boolean> existsInterpretation;
34
35 public void initialize(ProblemTrace problemTrace, Concreteness concreteness, Model model) {
36 this.problemTrace = problemTrace;
37 this.concreteness = concreteness;
38 typeInterpretation = model.getInterpretation(TypeHierarchyTranslator.TYPE_SYMBOL);
39 existsInterpretation = model.getAdapter(ReasoningAdapter.class).getPartialInterpretation(concreteness,
40 ReasoningAdapter.EXISTS_SYMBOL);
41 nodeNameProvider.setProblem(problemTrace.getProblem());
42 }
43
44 public NodeMetadata doCreateMetadata(int nodeId, String name, String simpleName, NodeKind kind) {
45 var type = getType(nodeId);
46 return doCreateMetadata(name, simpleName, type, kind);
47 }
48
49 public NodeMetadata createFreshlyNamedMetadata(int nodeId) {
50 var type = getType(nodeId);
51 var name = getName(type, nodeId);
52 return doCreateMetadata(name, name, type, NodeKind.IMPLICIT);
53 }
54
55 private PartialRelation getType(int nodeId) {
56 var inferredType = typeInterpretation.get(Tuple.of(nodeId));
57 if (inferredType == null) {
58 return null;
59 }
60 return inferredType.candidateType();
61 }
62
63 private String getName(PartialRelation type, int nodeId) {
64 if (concreteness == Concreteness.CANDIDATE && !existsInterpretation.get(Tuple.of(nodeId)).may()) {
65 // Do not increment the node name counter for non-existent nodes in the candidate interpretation.
66 // While non-existent nodes may appear in the partial interpretation, they are never displayed in the
67 // candidate interpretation.
68 return "::" + nodeId;
69 }
70 if (type == null) {
71 return nodeNameProvider.getNextName(null);
72 }
73 var relation = problemTrace.getRelation(type);
74 return nodeNameProvider.getNextName(relation.getName());
75 }
76
77 private NodeMetadata doCreateMetadata(String name, String simpleName, PartialRelation type, NodeKind kind) {
78 var typeHash = getTypeHash(type);
79 return new NodeMetadata(name, simpleName, typeHash, kind);
80 }
81
82 private String getTypeHash(PartialRelation type) {
83 if (type == null) {
84 return null;
85 }
86 var relation = problemTrace.getRelation(type);
87 return typeHashProvider.getTypeHash(relation);
88 }
89}
diff --git a/subprojects/language/src/main/java/tools/refinery/language/resource/ProblemResourceDescriptionStrategy.java b/subprojects/language/src/main/java/tools/refinery/language/resource/ProblemResourceDescriptionStrategy.java
index cac1f265..c04c7d09 100644
--- a/subprojects/language/src/main/java/tools/refinery/language/resource/ProblemResourceDescriptionStrategy.java
+++ b/subprojects/language/src/main/java/tools/refinery/language/resource/ProblemResourceDescriptionStrategy.java
@@ -1,5 +1,5 @@
1/* 1/*
2 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/> 2 * SPDX-FileCopyrightText: 2021-2024 The Refinery Authors <https://refinery.tools/>
3 * 3 *
4 * SPDX-License-Identifier: EPL-2.0 4 * SPDX-License-Identifier: EPL-2.0
5 */ 5 */
@@ -28,6 +28,8 @@ public class ProblemResourceDescriptionStrategy extends DefaultResourceDescripti
28 public static final String ARITY = DATA_PREFIX + "ARITY"; 28 public static final String ARITY = DATA_PREFIX + "ARITY";
29 public static final String ERROR_PREDICATE = DATA_PREFIX + "ERROR_PREDICATE"; 29 public static final String ERROR_PREDICATE = DATA_PREFIX + "ERROR_PREDICATE";
30 public static final String ERROR_PREDICATE_TRUE = "true"; 30 public static final String ERROR_PREDICATE_TRUE = "true";
31 public static final String COLOR_RELATION = DATA_PREFIX + "COLOR_RELATION";
32 public static final String COLOR_RELATION_TRUE = "true";
31 33
32 @Inject 34 @Inject
33 private IQualifiedNameConverter qualifiedNameConverter; 35 private IQualifiedNameConverter qualifiedNameConverter;
@@ -44,12 +46,9 @@ public class ProblemResourceDescriptionStrategy extends DefaultResourceDescripti
44 var problem = EcoreUtil2.getContainerOfType(eObject, Problem.class); 46 var problem = EcoreUtil2.getContainerOfType(eObject, Problem.class);
45 var problemQualifiedName = getNameAsQualifiedName(problem); 47 var problemQualifiedName = getNameAsQualifiedName(problem);
46 var userData = getUserData(eObject); 48 var userData = getUserData(eObject);
47 boolean nameExported; 49 QualifiedName lastQualifiedNameToExport = null;
48 if (shouldExportSimpleName(eObject)) { 50 if (shouldExportSimpleName(eObject)) {
49 acceptEObjectDescription(eObject, problemQualifiedName, qualifiedName, userData, acceptor); 51 lastQualifiedNameToExport = qualifiedName;
50 nameExported = true;
51 } else {
52 nameExported = false;
53 } 52 }
54 var parent = eObject.eContainer(); 53 var parent = eObject.eContainer();
55 while (parent != null && parent != problem) { 54 while (parent != null && parent != problem) {
@@ -60,16 +59,18 @@ public class ProblemResourceDescriptionStrategy extends DefaultResourceDescripti
60 } 59 }
61 qualifiedName = parentQualifiedName.append(qualifiedName); 60 qualifiedName = parentQualifiedName.append(qualifiedName);
62 if (shouldExportSimpleName(parent)) { 61 if (shouldExportSimpleName(parent)) {
63 acceptEObjectDescription(eObject, problemQualifiedName, qualifiedName, userData, acceptor); 62 if (lastQualifiedNameToExport != null) {
64 nameExported = true; 63 acceptEObjectDescription(eObject, problemQualifiedName, lastQualifiedNameToExport, userData,
65 } else { 64 acceptor);
66 nameExported = false; 65 }
66 lastQualifiedNameToExport = qualifiedName;
67 } 67 }
68 parent = parent.eContainer(); 68 parent = parent.eContainer();
69 } 69 }
70 if (!nameExported) { 70 if (lastQualifiedNameToExport == null) {
71 acceptEObjectDescription(eObject, problemQualifiedName, qualifiedName, userData, acceptor); 71 lastQualifiedNameToExport = qualifiedName;
72 } 72 }
73 acceptEObjectDescription(eObject, problemQualifiedName, lastQualifiedNameToExport, userData, true, acceptor);
73 return true; 74 return true;
74 } 75 }
75 76
@@ -120,8 +121,31 @@ public class ProblemResourceDescriptionStrategy extends DefaultResourceDescripti
120 121
121 private void acceptEObjectDescription(EObject eObject, QualifiedName prefix, QualifiedName qualifiedName, 122 private void acceptEObjectDescription(EObject eObject, QualifiedName prefix, QualifiedName qualifiedName,
122 Map<String, String> userData, IAcceptor<IEObjectDescription> acceptor) { 123 Map<String, String> userData, IAcceptor<IEObjectDescription> acceptor) {
124 acceptEObjectDescription(eObject, prefix, qualifiedName, userData, false, acceptor);
125 }
126
127 private void acceptEObjectDescription(EObject eObject, QualifiedName prefix, QualifiedName qualifiedName,
128 Map<String, String> userData, boolean fullyQualified,
129 IAcceptor<IEObjectDescription> acceptor) {
123 var qualifiedNameWithPrefix = prefix == null ? qualifiedName : prefix.append(qualifiedName); 130 var qualifiedNameWithPrefix = prefix == null ? qualifiedName : prefix.append(qualifiedName);
124 var description = EObjectDescription.create(qualifiedNameWithPrefix, eObject, userData); 131 Map<String, String> userDataWithFullyQualified;
132 if (fullyQualified && shouldColorRelation(eObject)) {
133 userDataWithFullyQualified = ImmutableMap.<String, String>builder()
134 .putAll(userData)
135 .put(COLOR_RELATION, COLOR_RELATION_TRUE)
136 .build();
137 } else {
138 userDataWithFullyQualified = userData;
139 }
140 var description = EObjectDescription.create(qualifiedNameWithPrefix, eObject, userDataWithFullyQualified);
125 acceptor.accept(description); 141 acceptor.accept(description);
126 } 142 }
143
144 private boolean shouldColorRelation(EObject eObject) {
145 if (ProblemUtil.isBuiltIn(eObject)) {
146 return false;
147 }
148 return eObject instanceof ClassDeclaration || eObject instanceof EnumDeclaration;
149
150 }
127} 151}