aboutsummaryrefslogtreecommitdiffstats
path: root/subprojects/store/src/main/java/tools/refinery/store/model/internal/ModelImpl.java
blob: 2b12d5a6b99244a485d03bbc0e4407970c0eaca8 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
/*
 * SPDX-FileCopyrightText: 2021-2023 The Refinery Authors <https://refinery.tools/>
 *
 * SPDX-License-Identifier: EPL-2.0
 */
package tools.refinery.store.model.internal;

import tools.refinery.store.adapter.AdapterUtils;
import tools.refinery.store.adapter.ModelAdapter;
import tools.refinery.store.map.DiffCursor;
import tools.refinery.store.map.Version;
import tools.refinery.store.model.*;
import tools.refinery.store.representation.AnySymbol;
import tools.refinery.store.representation.Symbol;
import tools.refinery.store.tuple.Tuple;

import java.util.*;

public class ModelImpl implements Model {
	private final ModelStore store;
	private Version state;
	private LinkedHashMap<? extends AnySymbol, ? extends VersionedInterpretation<?>> interpretations;
	private final List<ModelAdapter> adapters;
	private final List<ModelListener> listeners = new ArrayList<>();
	private boolean uncommittedChanges;
	private ModelAction pendingAction = ModelAction.NONE;
	private Version restoringToState = null;

	ModelImpl(ModelStore store, Version state, int adapterCount) {
		this.store = store;
		this.state = state;
		adapters = new ArrayList<>(adapterCount);
	}

	void setInterpretations(LinkedHashMap<? extends AnySymbol, ? extends VersionedInterpretation<?>> interpretations) {
		this.interpretations = interpretations;
	}

	@Override
	public ModelStore getStore() {
		return store;
	}

	@Override
	public Version getState() {
		return state;
	}

	@Override
	public <T> Interpretation<T> getInterpretation(Symbol<T> symbol) {
		var interpretation = interpretations.get(symbol);
		if (interpretation == null) {
			throw new IllegalArgumentException("No interpretation for symbol %s in model".formatted(symbol));
		}
		@SuppressWarnings("unchecked")
		var typedInterpretation = (Interpretation<T>) interpretation;
		return typedInterpretation;
	}

	@Override
	public ModelDiffCursor getDiffCursor(Version to) {
		var diffCursors = new HashMap<AnySymbol, DiffCursor<Tuple, ?>>(interpretations.size());
		for (var entry : interpretations.entrySet()) {
			diffCursors.put(entry.getKey(), entry.getValue().getDiffCursor(to));
		}
		return new ModelDiffCursor(diffCursors);
	}

	private void setState(Version state) {
		this.state = state;
		uncommittedChanges = false;
	}

	void markAsChanged() {
		if (!uncommittedChanges) {
			uncommittedChanges = true;
		}
	}

	@Override
	public boolean hasUncommittedChanges() {
		return uncommittedChanges;
	}

	private boolean hasPendingAction() {
		return pendingAction != ModelAction.NONE || restoringToState != null;
	}

	@Override
	public Version commit() {
		if (hasPendingAction()) {
			throw pendingActionError("commit");
		}
		pendingAction = ModelAction.COMMIT;
		try {
			int listenerCount = listeners.size();
			int i = listenerCount;

			// Before commit message to listeners
			while (i > 0) {
				i--;
				listeners.get(i).beforeCommit();
			}

			// Doing the commit on the interpretations
			Version[] interpretationVersions = new Version[interpretations.size()];
			int j = 0;
			for (var interpretationEntry : interpretations.entrySet()) {
				interpretationVersions[j++] = interpretationEntry.getValue().commit();
			}
			ModelVersion modelVersion = new ModelVersion(interpretationVersions);
			setState(modelVersion);

			// After commit message to listeners
			while (i < listenerCount) {
				listeners.get(i).afterCommit();
				i++;
			}

			return modelVersion;
		} finally {
			pendingAction = ModelAction.NONE;
		}
	}

	@Override
	public void restore(Version version) {
		if (hasPendingAction()) {
			throw pendingActionError("restore to %s".formatted(version));
		}

		pendingAction = ModelAction.RESTORE;
		restoringToState = version;
		try {
			int listenerCount = listeners.size();
			int i = listenerCount;
			while (i > 0) {
				i--;
				listeners.get(i).beforeRestore(version);
			}
			int j = 0;
			for (var interpretation : interpretations.values()) {
				interpretation.restore(ModelVersion.getInternalVersion(version, j++));
			}

			setState(version);
			while (i < listenerCount) {
				listeners.get(i).afterRestore();
				i++;
			}
		} finally {
			pendingAction = ModelAction.NONE;
			restoringToState = null;
		}
	}

	public RuntimeException pendingActionError(String currentActionName) {
		var pendingActionName = switch (pendingAction) {
			case NONE -> throw new IllegalArgumentException("Trying to throw pending action error when there is no " +
					"pending action");
			case COMMIT -> "commit";
			case RESTORE -> "restore to %s".formatted(restoringToState);
		};
		return new IllegalStateException("Cannot %s due to pending %s".formatted(currentActionName, pendingActionName));
	}

	@Override
	public <T extends ModelAdapter> Optional<T> tryGetAdapter(Class<? extends T> adapterType) {
		return AdapterUtils.tryGetAdapter(adapters, adapterType);
	}

	@Override
	public <T extends ModelAdapter> T getAdapter(Class<T> adapterType) {
		return AdapterUtils.getAdapter(adapters, adapterType);
	}

	void addAdapter(ModelAdapter adapter) {
		adapters.add(adapter);
	}

	@Override
	public void addListener(ModelListener listener) {
		listeners.add(listener);
	}

	@Override
	public void removeListener(ModelListener listener) {
		listeners.remove(listener);
	}
}