|
1 | 1 | /*
|
2 |
| - * Copyright (c) 2024 Robert Bosch Manufacturing Solutions GmbH |
| 2 | + * Copyright (c) 2025 Robert Bosch Manufacturing Solutions GmbH |
3 | 3 | *
|
4 | 4 | * See the AUTHORS file(s) distributed with this work for additional
|
5 | 5 | * information regarding authorship.
|
|
13 | 13 |
|
14 | 14 | package org.eclipse.esmf.aspectmodel.edit;
|
15 | 15 |
|
| 16 | +import java.io.File; |
| 17 | +import java.io.IOException; |
| 18 | +import java.net.URI; |
| 19 | +import java.nio.file.Files; |
| 20 | +import java.nio.file.Paths; |
16 | 21 | import java.util.ArrayDeque;
|
| 22 | +import java.util.ArrayList; |
17 | 23 | import java.util.Deque;
|
18 | 24 | import java.util.HashMap;
|
| 25 | +import java.util.List; |
19 | 26 | import java.util.Map;
|
20 | 27 | import java.util.Optional;
|
21 | 28 | import java.util.stream.Stream;
|
22 | 29 |
|
23 | 30 | import org.eclipse.esmf.aspectmodel.AspectModelFile;
|
24 | 31 | import org.eclipse.esmf.aspectmodel.loader.AspectModelLoader;
|
25 | 32 | import org.eclipse.esmf.aspectmodel.resolver.modelfile.RawAspectModelFile;
|
| 33 | +import org.eclipse.esmf.aspectmodel.serializer.AspectSerializer; |
| 34 | +import org.eclipse.esmf.aspectmodel.serializer.SerializationException; |
26 | 35 | import org.eclipse.esmf.metamodel.AspectModel;
|
27 | 36 | import org.eclipse.esmf.metamodel.impl.DefaultAspectModel;
|
28 | 37 |
|
|
38 | 47 | * Note the following points:
|
39 | 48 | * <ul>
|
40 | 49 | * <li>Only one AspectChangeManager must wrap a given AspectModel at any time</li>
|
41 |
| - * <li>All changes are done <i>in-memory</i>. In order to write them to the file system, use the |
42 |
| - * {@link org.eclipse.esmf.aspectmodel.serializer.AspectSerializer}</li> |
| 50 | + * <li>All changes are done <i>in-memory</i>. In order to write them to the file system, use {@link #writeChangesToDisk(WriteConfig)} |
| 51 | + * </li> |
43 | 52 | * <li>After performing an {@link #applyChange(Change)}, {@link #undoChange()} or {@link #redoChange()} operation, and until the
|
44 | 53 | * next call of one of them, the methods {@link #modifiedFiles()}, {@link #createdFiles()} and {@link #removedFiles()} indicate
|
45 | 54 | * corresponding changes in the AspectModel's files.
|
@@ -185,4 +194,107 @@ public void indicateFileHasChanged( final AspectModelFile file ) {
|
185 | 194 | fileState.put( file, FileState.CHANGED );
|
186 | 195 | }
|
187 | 196 | }
|
| 197 | + |
| 198 | + /** |
| 199 | + * Syncs all queued changes to the file system. This is the operation that acutally performs operations such as deleting, creating and |
| 200 | + * writing files. |
| 201 | + */ |
| 202 | + public synchronized WriteResult writeChangesToDisk( final WriteConfig config ) { |
| 203 | + final WriteResult writeResult = checkFileSystemConsistency( config ); |
| 204 | + if ( writeResult instanceof WriteResult.PreconditionsNotMet ) { |
| 205 | + return writeResult; |
| 206 | + } |
| 207 | + |
| 208 | + final WriteResult result = performFileSystemWrite(); |
| 209 | + if ( result instanceof WriteResult.Success ) { |
| 210 | + resetFileStates(); |
| 211 | + } |
| 212 | + return result; |
| 213 | + } |
| 214 | + |
| 215 | + protected WriteResult performFileSystemWrite() { |
| 216 | + final List<String> messages = new ArrayList<>(); |
| 217 | + removedFiles() |
| 218 | + .map( fileToRemove -> Paths.get( fileToRemove.sourceLocation().orElseThrow() ).toFile() ) |
| 219 | + .forEach( file -> { |
| 220 | + try { |
| 221 | + Files.delete( file.toPath() ); |
| 222 | + } catch ( final IOException exception ) { |
| 223 | + messages.add( "Could not delete file: " + file ); |
| 224 | + } |
| 225 | + } ); |
| 226 | + |
| 227 | + createdFiles().forEach( fileToCreate -> { |
| 228 | + final File file = Paths.get( fileToCreate.sourceLocation().orElseThrow() ).toFile(); |
| 229 | + if ( !file.getParentFile().exists() && !file.getParentFile().mkdirs() ) { |
| 230 | + messages.add( "Target path to write file could not be created: " + file ); |
| 231 | + } else { |
| 232 | + try { |
| 233 | + AspectSerializer.INSTANCE.write( fileToCreate ); |
| 234 | + } catch ( final SerializationException exception ) { |
| 235 | + messages.add( exception.getMessage() ); |
| 236 | + } |
| 237 | + } |
| 238 | + } ); |
| 239 | + |
| 240 | + modifiedFiles().forEach( aspectModelFile -> { |
| 241 | + try { |
| 242 | + AspectSerializer.INSTANCE.write( aspectModelFile ); |
| 243 | + } catch ( final SerializationException exception ) { |
| 244 | + messages.add( exception.getMessage() ); |
| 245 | + } |
| 246 | + } ); |
| 247 | + |
| 248 | + return messages.isEmpty() |
| 249 | + ? new WriteResult.Success() |
| 250 | + : new WriteResult.WriteFailure( messages ); |
| 251 | + } |
| 252 | + |
| 253 | + protected WriteResult checkFileSystemConsistency( final WriteConfig config ) { |
| 254 | + final List<String> messages = new ArrayList<>(); |
| 255 | + final boolean[] canBeFixedByOverwriting = new boolean[1]; |
| 256 | + removedFiles().map( AspectSerializer.INSTANCE::aspectModelFileUrl ).forEach( url -> { |
| 257 | + if ( !url.getProtocol().equals( "file" ) ) { |
| 258 | + messages.add( "File should be removed, but it is not identified by a file: URL: " + url ); |
| 259 | + } |
| 260 | + final File file = new File( URI.create( url.toString() ) ); |
| 261 | + if ( !file.exists() ) { |
| 262 | + messages.add( "File should be removed, but it does not exist: " + file ); |
| 263 | + } |
| 264 | + } ); |
| 265 | + |
| 266 | + createdFiles().map( AspectSerializer.INSTANCE::aspectModelFileUrl ).forEach( url -> { |
| 267 | + if ( !url.getProtocol().equals( "file" ) ) { |
| 268 | + messages.add( "New file should be written, but it is not identified by a file: URL: " + url ); |
| 269 | + } |
| 270 | + final File file = new File( URI.create( url.toString() ) ); |
| 271 | + if ( file.exists() && !config.forceOverwrite() ) { |
| 272 | + messages.add( "New file should be written, but it already exists: " + file ); |
| 273 | + canBeFixedByOverwriting[0] = true; |
| 274 | + } |
| 275 | + if ( file.exists() && config.forceOverwrite() && !file.canWrite() ) { |
| 276 | + messages.add( "New file should be written, but it is not writable: " + file ); |
| 277 | + } |
| 278 | + } ); |
| 279 | + |
| 280 | + modifiedFiles().map( AspectSerializer.INSTANCE::aspectModelFileUrl ).forEach( url -> { |
| 281 | + if ( !url.getProtocol().equals( "file" ) ) { |
| 282 | + messages.add( "File should be modified, but it is not identified by a file: URL: " + url ); |
| 283 | + } |
| 284 | + final File file = new File( URI.create( url.toString() ) ); |
| 285 | + if ( !file.exists() ) { |
| 286 | + messages.add( "File should be modified, but it does not exist: " + file ); |
| 287 | + } |
| 288 | + if ( !file.canWrite() ) { |
| 289 | + messages.add( "File should be modified, but it is not writable: " + file ); |
| 290 | + } |
| 291 | + if ( !file.isFile() ) { |
| 292 | + messages.add( "File should be modified, but it is not a regular file: " + file ); |
| 293 | + } |
| 294 | + } ); |
| 295 | + |
| 296 | + return messages.isEmpty() |
| 297 | + ? new WriteResult.Success() |
| 298 | + : new WriteResult.PreconditionsNotMet( messages, canBeFixedByOverwriting[0] ); |
| 299 | + } |
188 | 300 | }
|
0 commit comments