From cb809afbc7c6f7f4a2da1eaeb48247b23a11a998 Mon Sep 17 00:00:00 2001 From: "Textor Andreas (BCI/ESW17)" Date: Mon, 29 Jul 2024 09:49:45 +0200 Subject: [PATCH 01/33] Add structure for refactoring AspectModels --- .../esmf/aspectmodel/AspectModelFile.java | 2 - .../esmf/aspectmodel/AspectModelBuilder.java | 59 +++++ .../eclipse/esmf/aspectmodel/edit/Change.java | 32 +++ .../aspectmodel/edit/ModelRefactoring.java | 229 ++++++++++++++++++ .../aspectmodel/edit/SimpleChangeReport.java | 63 +++++ .../aspectmodel/loader/AspectModelLoader.java | 28 +-- .../modelfile/DefaultAspectModelFile.java | 6 +- .../metamodel/impl/DefaultAspectModel.java | 12 +- .../edit/ModelRefactoringTest.java | 87 +++++++ 9 files changed, 483 insertions(+), 35 deletions(-) create mode 100644 core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/AspectModelBuilder.java create mode 100644 core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/Change.java create mode 100644 core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/ModelRefactoring.java create mode 100644 core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/SimpleChangeReport.java create mode 100644 core/esmf-aspect-meta-model-java/src/test/java/org/eclipse/esmf/aspectmodel/edit/ModelRefactoringTest.java diff --git a/core/esmf-aspect-meta-model-interface/src/main/java/org/eclipse/esmf/aspectmodel/AspectModelFile.java b/core/esmf-aspect-meta-model-interface/src/main/java/org/eclipse/esmf/aspectmodel/AspectModelFile.java index ddf41147b..169f336b0 100644 --- a/core/esmf-aspect-meta-model-interface/src/main/java/org/eclipse/esmf/aspectmodel/AspectModelFile.java +++ b/core/esmf-aspect-meta-model-interface/src/main/java/org/eclipse/esmf/aspectmodel/AspectModelFile.java @@ -35,6 +35,4 @@ default List headerComment() { default List elements() { throw new UnsupportedOperationException( "Uninitialized Aspect Model" ); } - - // boolean isAutoMigrated(); } \ No newline at end of file diff --git a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/AspectModelBuilder.java b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/AspectModelBuilder.java new file mode 100644 index 000000000..9bef336af --- /dev/null +++ b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/AspectModelBuilder.java @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2024 Robert Bosch Manufacturing Solutions GmbH + * + * See the AUTHORS file(s) distributed with this work for additional + * information regarding authorship. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * SPDX-License-Identifier: MPL-2.0 + */ + +package org.eclipse.esmf.aspectmodel; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Map; + +import org.eclipse.esmf.aspectmodel.loader.ModelElementFactory; +import org.eclipse.esmf.aspectmodel.resolver.modelfile.DefaultAspectModelFile; +import org.eclipse.esmf.aspectmodel.resolver.modelfile.MetaModelFile; +import org.eclipse.esmf.metamodel.AspectModel; +import org.eclipse.esmf.metamodel.ModelElement; +import org.eclipse.esmf.metamodel.impl.DefaultAspectModel; + +import org.apache.jena.rdf.model.Model; +import org.apache.jena.rdf.model.ModelFactory; +import org.apache.jena.rdf.model.RDFNode; +import org.apache.jena.rdf.model.Statement; +import org.apache.jena.vocabulary.RDF; + +public abstract class AspectModelBuilder { + protected AspectModel buildAspectModel( final Collection inputFiles ) { + final Model mergedModel = ModelFactory.createDefaultModel(); + mergedModel.add( MetaModelFile.metaModelDefinitions() ); + for ( final AspectModelFile file : inputFiles ) { + mergedModel.add( file.sourceModel() ); + } + + final List elements = new ArrayList<>(); + for ( final AspectModelFile file : inputFiles ) { + final DefaultAspectModelFile aspectModelFile = new DefaultAspectModelFile( file.sourceModel(), file.headerComment(), + file.sourceLocation() ); + final Model model = file.sourceModel(); + final ModelElementFactory modelElementFactory = new ModelElementFactory( mergedModel, Map.of(), element -> aspectModelFile ); + final List fileElements = model.listStatements( null, RDF.type, (RDFNode) null ).toList().stream() + .map( Statement::getSubject ) + .filter( RDFNode::isURIResource ) + .map( resource -> mergedModel.createResource( resource.getURI() ) ) + .map( resource -> modelElementFactory.create( ModelElement.class, resource ) ) + .toList(); + aspectModelFile.setElements( fileElements ); + elements.addAll( fileElements ); + } + return new DefaultAspectModel( mergedModel, elements ); + } +} diff --git a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/Change.java b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/Change.java new file mode 100644 index 000000000..f1982ca67 --- /dev/null +++ b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/Change.java @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2024 Robert Bosch Manufacturing Solutions GmbH + * + * See the AUTHORS file(s) distributed with this work for additional + * information regarding authorship. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * SPDX-License-Identifier: MPL-2.0 + */ + +package org.eclipse.esmf.aspectmodel.edit; + +import org.eclipse.esmf.metamodel.AspectModel; + +public interface Change { + void applyChange( AspectModel aspectModel ); + + Change reverse(); + + T accept( Visitor visitor ); + + interface Visitor { + T visitChangeGroup( ModelRefactoring.ChangeGroup changeGroup ); + + T visitRenameElement( ModelRefactoring.RenameElement renameElement ); + + T visitRenameUrn( ModelRefactoring.RenameUrn renameUrn ); + } +} diff --git a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/ModelRefactoring.java b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/ModelRefactoring.java new file mode 100644 index 000000000..7a46dbc65 --- /dev/null +++ b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/ModelRefactoring.java @@ -0,0 +1,229 @@ +/* + * Copyright (c) 2024 Robert Bosch Manufacturing Solutions GmbH + * + * See the AUTHORS file(s) distributed with this work for additional + * information regarding authorship. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * SPDX-License-Identifier: MPL-2.0 + */ + +package org.eclipse.esmf.aspectmodel.edit; + +import java.util.AbstractMap; +import java.util.ArrayDeque; +import java.util.Deque; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import org.eclipse.esmf.aspectmodel.AspectModelBuilder; +import org.eclipse.esmf.aspectmodel.AspectModelFile; +import org.eclipse.esmf.aspectmodel.urn.AspectModelUrn; +import org.eclipse.esmf.metamodel.AspectModel; +import org.eclipse.esmf.metamodel.impl.DefaultAspectModel; + +import org.apache.jena.rdf.model.Model; +import org.apache.jena.rdf.model.ModelFactory; +import org.apache.jena.rdf.model.Property; +import org.apache.jena.rdf.model.RDFNode; +import org.apache.jena.rdf.model.Resource; +import org.apache.jena.rdf.model.Statement; +import org.apache.jena.rdf.model.StmtIterator; + +public class ModelRefactoring extends AspectModelBuilder { + private final Deque undoStack = new ArrayDeque<>(); + private final Deque redoStack = new ArrayDeque<>(); + private final DefaultAspectModel aspectModel; + + public ModelRefactoring( final AspectModel aspectModel ) { + if ( !( aspectModel instanceof DefaultAspectModel ) ) { + throw new RuntimeException(); + } + this.aspectModel = (DefaultAspectModel) aspectModel; + } + + public synchronized void applyChange( final Change change ) { + change.applyChange( aspectModel ); + updateAspectModelAfterChange(); + undoStack.offerLast( change.reverse() ); + } + + private void updateAspectModelAfterChange() { + final AspectModel updatedModel = buildAspectModel( aspectModel.files() ); + aspectModel.setMergedModel( updatedModel.mergedModel() ); + aspectModel.setElements( updatedModel.elements() ); + } + + public synchronized void undoChange() { + if ( undoStack.isEmpty() ) { + return; + } + final Change change = undoStack.pollLast(); + change.applyChange( aspectModel ); + updateAspectModelAfterChange(); + redoStack.offerLast( change.reverse() ); + } + + public synchronized void redoChange() { + if ( redoStack.isEmpty() ) { + return; + } + final Change change = redoStack.pollLast(); + change.applyChange( aspectModel ); + updateAspectModelAfterChange(); + undoStack.offerLast( change.reverse() ); + } + + public class ChangeGroup implements Change { + private final List changes; + + public ChangeGroup( final List changes ) { + this.changes = changes; + } + + @Override + public void applyChange( final AspectModel aspectModel ) { + changes().forEach( change -> change.applyChange( aspectModel ) ); + } + + @Override + public Change reverse() { + return new ChangeGroup( changes().stream().map( Change::reverse ).toList() ); + } + + public List changes() { + return changes; + } + + @Override + public T accept( final Visitor visitor ) { + return visitor.visitChangeGroup( this ); + } + } + + public abstract class EditAspectModel implements Change { + protected record ModelChanges( Model add, Model remove ) { + } + + private Map changesPerFile = null; + + synchronized protected Map changesPerFile() { + if ( changesPerFile == null ) { + changesPerFile = aspectModel.files().stream() + .map( file -> new AbstractMap.SimpleEntry<>( file, calculateChangesPerFile( file ) ) ) + .collect( Collectors.toMap( AbstractMap.SimpleEntry::getKey, AbstractMap.SimpleEntry::getValue ) ); + } + return changesPerFile; + } + + abstract protected ModelChanges calculateChangesPerFile( AspectModelFile aspectModelFile ); + + @Override + public void applyChange( final AspectModel aspectModel ) { + changesPerFile().forEach( ( file, modelChanges ) -> { + if ( aspectModel.files().contains( file ) ) { + file.sourceModel().add( modelChanges.add() ); + file.sourceModel().remove( modelChanges.remove() ); + } + } ); + } + } + + public class RenameUrn extends EditAspectModel { + private final AspectModelUrn from; + private final AspectModelUrn to; + + public RenameUrn( final AspectModelUrn from, final AspectModelUrn to ) { + this.from = from; + this.to = to; + } + + public AspectModelUrn from() { + return from; + } + + public AspectModelUrn to() { + return to; + } + + @Override + protected ModelChanges calculateChangesPerFile( final AspectModelFile aspectModelFile ) { + final Model addModel = ModelFactory.createDefaultModel(); + final Model removeModel = ModelFactory.createDefaultModel(); + + for ( final StmtIterator it = aspectModelFile.sourceModel().listStatements(); it.hasNext(); ) { + final Statement statement = it.next(); + boolean updateTriple = false; + final Resource addSubject; + final Resource removeSubject; + final Property predicate; + final RDFNode addObject; + final RDFNode removeObject; + if ( statement.getSubject().isURIResource() ) { + if ( statement.getSubject().getURI().equals( from.toString() ) ) { + addSubject = addModel.createResource( to.toString() ); + removeSubject = removeModel.createResource( from.toString() ); + updateTriple = true; + } else { + addSubject = statement.getSubject(); + removeSubject = statement.getSubject(); + } + } else { + addSubject = addModel.createResource( statement.getSubject().getId() ); + removeSubject = removeModel.createResource( statement.getSubject().getId() ); + } + + if ( statement.getPredicate().getURI().equals( from.toString() ) ) { + predicate = addModel.createProperty( to.toString() ); + updateTriple = true; + } else { + predicate = statement.getPredicate(); + } + if ( statement.getObject().isURIResource() && statement.getObject().asResource().getURI().equals( from.toString() ) ) { + addObject = addModel.createResource( to.toString() ); + removeObject = removeModel.createResource( from.toString() ); + updateTriple = true; + } else { + if ( statement.getObject().isAnon() ) { + addObject = addModel.createResource( statement.getObject().asResource().getId() ); + removeObject = removeModel.createResource( statement.getObject().asResource().getId() ); + } else { + addObject = statement.getObject(); + removeObject = statement.getObject(); + } + } + if ( updateTriple ) { + addModel.add( addSubject, predicate, addObject ); + removeModel.add( removeSubject, predicate, removeObject ); + } + } + + return new ModelChanges( addModel, removeModel ); + } + + @Override + public Change reverse() { + return new RenameUrn( to, from ); + } + + @Override + public T accept( final Visitor visitor ) { + return visitor.visitRenameUrn( this ); + } + } + + public class RenameElement extends RenameUrn { + public RenameElement( final AspectModelUrn urn, final String name ) { + super( urn, AspectModelUrn.fromUrn( urn.getUrnPrefix() + name ) ); + } + + @Override + public T accept( final Visitor visitor ) { + return visitor.visitRenameElement( this ); + } + } +} diff --git a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/SimpleChangeReport.java b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/SimpleChangeReport.java new file mode 100644 index 000000000..3789f6bc0 --- /dev/null +++ b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/SimpleChangeReport.java @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2024 Robert Bosch Manufacturing Solutions GmbH + * + * See the AUTHORS file(s) distributed with this work for additional + * information regarding authorship. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * SPDX-License-Identifier: MPL-2.0 + */ + +package org.eclipse.esmf.aspectmodel.edit; + +import java.io.StringWriter; +import java.util.stream.Collectors; + +import org.eclipse.esmf.aspectmodel.AspectModelFile; + +import org.apache.jena.rdf.model.Model; + +public class SimpleChangeReport implements Change.Visitor { + @Override + public String visitChangeGroup( final ModelRefactoring.ChangeGroup changeGroup ) { + return changeGroup.changes().stream().map( change -> change.accept( this ) ) + .collect( Collectors.joining( "\n", "- ", "" ) ); + } + + @Override + public String visitRenameElement( final ModelRefactoring.RenameElement renameElement ) { + return renameElement.changesPerFile().entrySet().stream().map( entry -> { + final AspectModelFile file = entry.getKey(); + final ModelRefactoring.EditAspectModel.ModelChanges changes = entry.getValue(); + + final String add = indent( modelToString( changes.add() ) ); + final String remove = indent( modelToString( changes.remove() ) ); + return String.format( "In file %s:%nAdd model:%n ---%n%s%n ---%nRemove model:%n ---%n%s%n ---%n", file.toString(), add, remove ); + } ).collect( Collectors.joining() ); + } + + private String indent( final String s ) { + return s.lines().map( line -> " " + line ).collect( Collectors.joining( "\n" ) ); + } + + private String modelToString( final Model model ) { + final StringWriter stringWriter = new StringWriter(); + model.write( stringWriter, "TURTLE" ); + return stringWriter.toString(); + } + + @Override + public String visitRenameUrn( final ModelRefactoring.RenameUrn renameUrn ) { + return renameUrn.changesPerFile().entrySet().stream().map( entry -> { + final AspectModelFile file = entry.getKey(); + final ModelRefactoring.EditAspectModel.ModelChanges changes = entry.getValue(); + + final String add = indent( modelToString( changes.add() ) ); + final String remove = indent( modelToString( changes.remove() ) ); + return String.format( "In file %s:%nAdd model:%n ---%n%s%nRemove model:%n ---%n%s%n", file.toString(), add, remove ); + } ).collect( Collectors.joining() ); + } +} diff --git a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/loader/AspectModelLoader.java b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/loader/AspectModelLoader.java index f48435df5..b184c438f 100644 --- a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/loader/AspectModelLoader.java +++ b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/loader/AspectModelLoader.java @@ -38,6 +38,7 @@ import java.util.zip.ZipEntry; import java.util.zip.ZipInputStream; +import org.eclipse.esmf.aspectmodel.AspectModelBuilder; import org.eclipse.esmf.aspectmodel.AspectModelFile; import org.eclipse.esmf.aspectmodel.resolver.AspectModelFileLoader; import org.eclipse.esmf.aspectmodel.resolver.EitherStrategy; @@ -71,7 +72,7 @@ /** * The core class to load an {@link AspectModel}. */ -public class AspectModelLoader implements ResolutionStrategySupport { +public class AspectModelLoader extends AspectModelBuilder implements ResolutionStrategySupport { private static final Logger LOG = LoggerFactory.getLogger( AspectModelLoader.class ); private static final String ASPECT_MODELS_FOLDER = "aspect-models"; @@ -399,31 +400,6 @@ private void resolve( final List inputFiles, final LoaderContex } } - private AspectModel buildAspectModel( final Collection inputFiles ) { - final Model mergedModel = ModelFactory.createDefaultModel(); - mergedModel.add( MetaModelFile.metaModelDefinitions() ); - for ( final AspectModelFile file : inputFiles ) { - mergedModel.add( file.sourceModel() ); - } - - final List elements = new ArrayList<>(); - for ( final AspectModelFile file : inputFiles ) { - final DefaultAspectModelFile aspectModelFile = new DefaultAspectModelFile( file.sourceModel(), file.headerComment(), - file.sourceLocation() ); - final Model model = file.sourceModel(); - final ModelElementFactory modelElementFactory = new ModelElementFactory( mergedModel, Map.of(), element -> aspectModelFile ); - final List fileElements = model.listStatements( null, RDF.type, (RDFNode) null ).toList().stream() - .map( Statement::getSubject ) - .filter( RDFNode::isURIResource ) - .map( resource -> mergedModel.createResource( resource.getURI() ) ) - .map( resource -> modelElementFactory.create( ModelElement.class, resource ) ) - .toList(); - aspectModelFile.setElements( fileElements ); - elements.addAll( fileElements ); - } - return new DefaultAspectModel( mergedModel, elements ); - } - /** * Checks if a given model contains the definition of a model element. * diff --git a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/resolver/modelfile/DefaultAspectModelFile.java b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/resolver/modelfile/DefaultAspectModelFile.java index 4e4820cd4..6a0f0553b 100644 --- a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/resolver/modelfile/DefaultAspectModelFile.java +++ b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/resolver/modelfile/DefaultAspectModelFile.java @@ -87,10 +87,6 @@ public int hashCode() { @Override public String toString() { - return "DefaultAspectModelFile[" - + "sourceModel=" + sourceModel + ", " - + "headerComment=" + headerComment + ", " - + "sourceLocation=" + sourceLocation + ", " - + "elements=" + elements + ']'; + return sourceLocation().map( URI::toString ).orElse( "(unknown file)" ); } } diff --git a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/metamodel/impl/DefaultAspectModel.java b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/metamodel/impl/DefaultAspectModel.java index 28cd2eddf..5b8ca8204 100644 --- a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/metamodel/impl/DefaultAspectModel.java +++ b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/metamodel/impl/DefaultAspectModel.java @@ -24,8 +24,8 @@ import org.apache.jena.rdf.model.Model; public class DefaultAspectModel implements AspectModel { - private final Model mergedModel; - private final List elements; + private Model mergedModel; + private List elements; public DefaultAspectModel( final Model mergedModel, final List elements ) { this.mergedModel = mergedModel; @@ -52,4 +52,12 @@ public List elements() { public Model mergedModel() { return mergedModel; } + + public void setMergedModel( final Model mergedModel ) { + this.mergedModel = mergedModel; + } + + public void setElements( final List elements ) { + this.elements = elements; + } } diff --git a/core/esmf-aspect-meta-model-java/src/test/java/org/eclipse/esmf/aspectmodel/edit/ModelRefactoringTest.java b/core/esmf-aspect-meta-model-java/src/test/java/org/eclipse/esmf/aspectmodel/edit/ModelRefactoringTest.java new file mode 100644 index 000000000..3505ebe59 --- /dev/null +++ b/core/esmf-aspect-meta-model-java/src/test/java/org/eclipse/esmf/aspectmodel/edit/ModelRefactoringTest.java @@ -0,0 +1,87 @@ +/* + * Copyright (c) 2024 Robert Bosch Manufacturing Solutions GmbH + * + * See the AUTHORS file(s) distributed with this work for additional + * information regarding authorship. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * SPDX-License-Identifier: MPL-2.0 + */ + +package org.eclipse.esmf.aspectmodel.edit; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.List; + +import org.eclipse.esmf.aspectmodel.urn.AspectModelUrn; +import org.eclipse.esmf.metamodel.Aspect; +import org.eclipse.esmf.metamodel.AspectModel; +import org.eclipse.esmf.metamodel.Property; +import org.eclipse.esmf.test.TestAspect; +import org.eclipse.esmf.test.TestResources; + +import org.junit.jupiter.api.Test; + +public class ModelRefactoringTest { + @Test + void testRenameElement() { + final AspectModel aspectModel = TestResources.load( TestAspect.ASPECT ); + final Aspect aspect = aspectModel.aspect(); + + final AspectModelUrn aspectUrn = aspect.urn(); + final String originalName = aspect.getName(); + assertThat( aspectModel.aspect().urn().getName() ).isEqualTo( originalName ); + + final String newName = "RenamedAspect"; + final ModelRefactoring ctx = new ModelRefactoring( aspectModel ); + final Change renameAspect = ctx.new RenameElement( aspectUrn, newName ); + ctx.applyChange( renameAspect ); + assertThat( aspectModel.aspect().urn().getName() ).isEqualTo( newName ); + } + + @Test + void testUndoRedo() { + final AspectModel aspectModel = TestResources.load( TestAspect.ASPECT ); + final Aspect aspect = aspectModel.aspect(); + + final AspectModelUrn aspectUrn = aspect.urn(); + final String originalName = aspect.getName(); + assertThat( aspectModel.aspect().urn().getName() ).isEqualTo( originalName ); + + final String newName = "RenamedAspect"; + final ModelRefactoring ctx = new ModelRefactoring( aspectModel ); + final Change renameAspect = ctx.new RenameElement( aspectUrn, newName ); + ctx.applyChange( renameAspect ); + assertThat( aspectModel.aspect().urn().getName() ).isEqualTo( newName ); + ctx.undoChange(); + assertThat( aspectModel.aspect().urn().getName() ).isEqualTo( originalName ); + ctx.redoChange(); + assertThat( aspectModel.aspect().urn().getName() ).isEqualTo( newName ); + } + + @Test + void testChangeGroups() { + final AspectModel aspectModel = TestResources.load( TestAspect.ASPECT_WITH_PROPERTY ); + final Aspect aspect = aspectModel.aspect(); + + final AspectModelUrn aspectUrn = aspect.urn(); + final String oldAspectName = aspect.getName(); + final Property property = aspect.getProperties().get( 0 ); + final String oldPropertyName = property.getName(); + + final String newAspectName = "RenamedAspect"; + final String newPropertyName = "renamedProperty"; + final ModelRefactoring ctx = new ModelRefactoring( aspectModel ); + final Change renameAspect = ctx.new RenameElement( aspectUrn, newAspectName ); + final Change renameProperty = ctx.new RenameElement( property.urn(), newPropertyName ); + + final Change group = ctx.new ChangeGroup( List.of( renameAspect, renameProperty ) ); + ctx.applyChange( group ); + assertThat( aspectModel.aspect().urn().getName() ).isEqualTo( newAspectName ); + assertThat( aspectModel.aspect().getProperties().get( 0 ).getName() ).isEqualTo( newPropertyName ); + } +} From c56cae4fdd4eaee55b65e4b88e0508ae4b7a0dde Mon Sep 17 00:00:00 2001 From: "Textor Andreas (BCI/ESW17)" Date: Fri, 2 Aug 2024 16:17:16 +0200 Subject: [PATCH 02/33] Add initial version of model refactoring API --- core/esmf-aspect-meta-model-java/pom.xml | 10 + .../esmf/aspectmodel/AspectModelBuilder.java | 12 +- .../aspectmodel/edit/AspectChangeContext.java | 83 +++++ .../edit/AspectChangeContextConfig.java | 29 ++ .../eclipse/esmf/aspectmodel/edit/Change.java | 14 +- .../esmf/aspectmodel/edit/ChangeContext.java | 22 ++ .../esmf/aspectmodel/edit/ChangeGroup.java | 49 +++ .../esmf/aspectmodel/edit/ChangeReport.java | 28 ++ .../edit/ChangeReportFormatter.java | 63 ++++ .../edit/ModelChangeException.java | 25 ++ .../aspectmodel/edit/ModelRefactoring.java | 229 -------------- .../esmf/aspectmodel/edit/RdfUtil.java | 54 ++++ .../aspectmodel/edit/SimpleChangeReport.java | 63 ---- .../edit/change/AddAspectModelFile.java | 70 +++++ .../edit/change/AddElementDefinition.java | 76 +++++ .../edit/change/EditAspectModel.java | 55 ++++ .../edit/change/MoveElementToNewFile.java | 102 ++++++ .../edit/change/MoveElementToOtherFile.java | 86 +++++ .../change/MoveElementToOtherNamespace.java | 45 +++ .../edit/change/RemoveAspectModelFile.java | 45 +++ .../edit/change/RemoveElementDefinition.java | 84 +++++ .../edit/change/RenameElement.java | 42 +++ .../aspectmodel/edit/change/RenameUrn.java | 111 +++++++ .../edit/change/StructuralChange.java | 33 ++ .../aspectmodel/loader/AspectModelLoader.java | 71 ++--- .../resolver/AspectModelFileLoader.java | 6 +- .../modelfile/RawAspectModelFile.java | 19 ++ .../metamodel/impl/DefaultAspectModel.java | 14 +- .../edit/AspectChangeContextTest.java | 293 ++++++++++++++++++ .../edit/ModelRefactoringTest.java | 87 ------ .../esmf/aspectmodel/edit/RdfUtilTest.java | 83 +++++ .../org/eclipse/esmf/test/TestResources.java | 4 +- .../org/eclipse/esmf/test/TestResources.java | 4 +- 33 files changed, 1578 insertions(+), 433 deletions(-) create mode 100644 core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/AspectChangeContext.java create mode 100644 core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/AspectChangeContextConfig.java create mode 100644 core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/ChangeContext.java create mode 100644 core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/ChangeGroup.java create mode 100644 core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/ChangeReport.java create mode 100644 core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/ChangeReportFormatter.java create mode 100644 core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/ModelChangeException.java delete mode 100644 core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/ModelRefactoring.java create mode 100644 core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/RdfUtil.java delete mode 100644 core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/SimpleChangeReport.java create mode 100644 core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/AddAspectModelFile.java create mode 100644 core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/AddElementDefinition.java create mode 100644 core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/EditAspectModel.java create mode 100644 core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/MoveElementToNewFile.java create mode 100644 core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/MoveElementToOtherFile.java create mode 100644 core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/MoveElementToOtherNamespace.java create mode 100644 core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/RemoveAspectModelFile.java create mode 100644 core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/RemoveElementDefinition.java create mode 100644 core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/RenameElement.java create mode 100644 core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/RenameUrn.java create mode 100644 core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/StructuralChange.java create mode 100644 core/esmf-aspect-meta-model-java/src/test/java/org/eclipse/esmf/aspectmodel/edit/AspectChangeContextTest.java delete mode 100644 core/esmf-aspect-meta-model-java/src/test/java/org/eclipse/esmf/aspectmodel/edit/ModelRefactoringTest.java create mode 100644 core/esmf-aspect-meta-model-java/src/test/java/org/eclipse/esmf/aspectmodel/edit/RdfUtilTest.java diff --git a/core/esmf-aspect-meta-model-java/pom.xml b/core/esmf-aspect-meta-model-java/pom.xml index 687c30a88..94deff6a7 100644 --- a/core/esmf-aspect-meta-model-java/pom.xml +++ b/core/esmf-aspect-meta-model-java/pom.xml @@ -58,6 +58,11 @@ com.google.guava guava + + io.soabase.record-builder + record-builder-processor + provided + @@ -153,6 +158,11 @@ lombok ${lombok-version} + + io.soabase.record-builder + record-builder-processor + ${record-builder-version} + diff --git a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/AspectModelBuilder.java b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/AspectModelBuilder.java index 9bef336af..f12877dee 100644 --- a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/AspectModelBuilder.java +++ b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/AspectModelBuilder.java @@ -31,8 +31,12 @@ import org.apache.jena.rdf.model.Statement; import org.apache.jena.vocabulary.RDF; -public abstract class AspectModelBuilder { - protected AspectModel buildAspectModel( final Collection inputFiles ) { +public class AspectModelBuilder { + public static AspectModel buildEmptyModel() { + return new DefaultAspectModel( new ArrayList<>(), ModelFactory.createDefaultModel(), new ArrayList<>() ); + } + + public static AspectModel buildAspectModelFromFiles( final Collection inputFiles ) { final Model mergedModel = ModelFactory.createDefaultModel(); mergedModel.add( MetaModelFile.metaModelDefinitions() ); for ( final AspectModelFile file : inputFiles ) { @@ -40,9 +44,11 @@ protected AspectModel buildAspectModel( final Collection inputF } final List elements = new ArrayList<>(); + final List files = new ArrayList<>(); for ( final AspectModelFile file : inputFiles ) { final DefaultAspectModelFile aspectModelFile = new DefaultAspectModelFile( file.sourceModel(), file.headerComment(), file.sourceLocation() ); + files.add( aspectModelFile ); final Model model = file.sourceModel(); final ModelElementFactory modelElementFactory = new ModelElementFactory( mergedModel, Map.of(), element -> aspectModelFile ); final List fileElements = model.listStatements( null, RDF.type, (RDFNode) null ).toList().stream() @@ -54,6 +60,6 @@ protected AspectModel buildAspectModel( final Collection inputF aspectModelFile.setElements( fileElements ); elements.addAll( fileElements ); } - return new DefaultAspectModel( mergedModel, elements ); + return new DefaultAspectModel( files, mergedModel, elements ); } } diff --git a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/AspectChangeContext.java b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/AspectChangeContext.java new file mode 100644 index 000000000..e236ac942 --- /dev/null +++ b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/AspectChangeContext.java @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2024 Robert Bosch Manufacturing Solutions GmbH + * + * See the AUTHORS file(s) distributed with this work for additional + * information regarding authorship. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * SPDX-License-Identifier: MPL-2.0 + */ + +package org.eclipse.esmf.aspectmodel.edit; + +import java.util.ArrayDeque; +import java.util.Deque; + +import org.eclipse.esmf.aspectmodel.AspectModelBuilder; +import org.eclipse.esmf.metamodel.AspectModel; +import org.eclipse.esmf.metamodel.impl.DefaultAspectModel; + +public class AspectChangeContext implements ChangeContext { + private final Deque undoStack = new ArrayDeque<>(); + private final Deque redoStack = new ArrayDeque<>(); + private final DefaultAspectModel aspectModel; + private final AspectChangeContextConfig config; + + public AspectChangeContext( final AspectChangeContextConfig config, final AspectModel aspectModel ) { + this.config = config; + if ( !( aspectModel instanceof DefaultAspectModel ) ) { + throw new RuntimeException(); + } + this.aspectModel = (DefaultAspectModel) aspectModel; + } + + public AspectChangeContext( final AspectModel aspectModel ) { + this( AspectChangeContextConfigBuilder.builder().build(), aspectModel ); + } + + public synchronized void applyChange( final Change change ) { + change.fire( this ); + updateAspectModelAfterChange(); + undoStack.offerLast( change.reverse() ); + } + + public synchronized void undoChange() { + if ( undoStack.isEmpty() ) { + return; + } + final Change change = undoStack.pollLast(); + change.fire( this ); + updateAspectModelAfterChange(); + redoStack.offerLast( change.reverse() ); + } + + public synchronized void redoChange() { + if ( redoStack.isEmpty() ) { + return; + } + final Change change = redoStack.pollLast(); + change.fire( this ); + updateAspectModelAfterChange(); + undoStack.offerLast( change.reverse() ); + } + + private void updateAspectModelAfterChange() { + final AspectModel updatedModel = AspectModelBuilder.buildAspectModelFromFiles( aspectModel.files() ); + aspectModel.setMergedModel( updatedModel.mergedModel() ); + aspectModel.setElements( updatedModel.elements() ); + aspectModel.setFiles( updatedModel.files() ); + } + + @Override + public AspectModel aspectModel() { + return aspectModel; + } + + @Override + public AspectChangeContextConfig config() { + return config; + } +} diff --git a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/AspectChangeContextConfig.java b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/AspectChangeContextConfig.java new file mode 100644 index 000000000..3d1524821 --- /dev/null +++ b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/AspectChangeContextConfig.java @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2024 Robert Bosch Manufacturing Solutions GmbH + * + * See the AUTHORS file(s) distributed with this work for additional + * information regarding authorship. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * SPDX-License-Identifier: MPL-2.0 + */ + +package org.eclipse.esmf.aspectmodel.edit; + +import java.util.List; + +import io.soabase.recordbuilder.core.RecordBuilder; + +@RecordBuilder +public record AspectChangeContextConfig( + List defaultFileHeader +) { + public AspectChangeContextConfig { + if ( defaultFileHeader == null ) { + defaultFileHeader = List.of(); + } + } +} diff --git a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/Change.java b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/Change.java index f1982ca67..11585f25a 100644 --- a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/Change.java +++ b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/Change.java @@ -13,20 +13,10 @@ package org.eclipse.esmf.aspectmodel.edit; -import org.eclipse.esmf.metamodel.AspectModel; - public interface Change { - void applyChange( AspectModel aspectModel ); + void fire( ChangeContext changeContext ); Change reverse(); - T accept( Visitor visitor ); - - interface Visitor { - T visitChangeGroup( ModelRefactoring.ChangeGroup changeGroup ); - - T visitRenameElement( ModelRefactoring.RenameElement renameElement ); - - T visitRenameUrn( ModelRefactoring.RenameUrn renameUrn ); - } + ChangeReport report( ChangeContext changeContext ); } diff --git a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/ChangeContext.java b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/ChangeContext.java new file mode 100644 index 000000000..a42b93b01 --- /dev/null +++ b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/ChangeContext.java @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2024 Robert Bosch Manufacturing Solutions GmbH + * + * See the AUTHORS file(s) distributed with this work for additional + * information regarding authorship. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * SPDX-License-Identifier: MPL-2.0 + */ + +package org.eclipse.esmf.aspectmodel.edit; + +import org.eclipse.esmf.metamodel.AspectModel; + +public interface ChangeContext { + AspectModel aspectModel(); + + AspectChangeContextConfig config(); +} diff --git a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/ChangeGroup.java b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/ChangeGroup.java new file mode 100644 index 000000000..f4ffc24c4 --- /dev/null +++ b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/ChangeGroup.java @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2024 Robert Bosch Manufacturing Solutions GmbH + * + * See the AUTHORS file(s) distributed with this work for additional + * information regarding authorship. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * SPDX-License-Identifier: MPL-2.0 + */ + +package org.eclipse.esmf.aspectmodel.edit; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +public class ChangeGroup implements Change { + private final List changes; + + public ChangeGroup( final Change... changes ) { + this( Arrays.asList( changes ) ); + } + + public ChangeGroup( final List changes ) { + this.changes = changes; + } + + @Override + public void fire( final ChangeContext changeContext ) { + changes.forEach( change -> change.fire( changeContext ) ); + } + + @Override + public Change reverse() { + final List reversedChanges = new ArrayList<>( changes.size() ); + for ( int i = changes.size() - 1; i >= 0; i-- ) { + reversedChanges.add( changes.get( i ).reverse() ); + } + return new ChangeGroup( reversedChanges ); + } + + @Override + public ChangeReport report( final ChangeContext changeContext ) { + return new ChangeReport.MultipleEntries( changes.stream().map( change -> change.report( changeContext ) ).toList() ); + } +} diff --git a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/ChangeReport.java b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/ChangeReport.java new file mode 100644 index 000000000..dbd0ddaa6 --- /dev/null +++ b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/ChangeReport.java @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2024 Robert Bosch Manufacturing Solutions GmbH + * + * See the AUTHORS file(s) distributed with this work for additional + * information regarding authorship. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * SPDX-License-Identifier: MPL-2.0 + */ + +package org.eclipse.esmf.aspectmodel.edit; + +import java.util.List; +import java.util.Map; + +public sealed interface ChangeReport { + record SimpleEntry( String text ) implements ChangeReport { + } + + record EntryWithDetails( String summary, Map details ) implements ChangeReport { + } + + record MultipleEntries( List entries ) implements ChangeReport { + } +} diff --git a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/ChangeReportFormatter.java b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/ChangeReportFormatter.java new file mode 100644 index 000000000..046f75a93 --- /dev/null +++ b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/ChangeReportFormatter.java @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2024 Robert Bosch Manufacturing Solutions GmbH + * + * See the AUTHORS file(s) distributed with this work for additional + * information regarding authorship. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * SPDX-License-Identifier: MPL-2.0 + */ + +package org.eclipse.esmf.aspectmodel.edit; + +import java.util.Map; +import java.util.function.Function; + +import org.apache.jena.rdf.model.Model; + +public class ChangeReportFormatter implements Function { + public static final ChangeReportFormatter INSTANCE = new ChangeReportFormatter(); + + private ChangeReportFormatter() { + } + + private void append( final StringBuilder builder, final ChangeReport report, final int indentation ) { + final String indent = " ".repeat( indentation ); + if ( report instanceof final ChangeReport.SimpleEntry simpleEntry ) { + builder.append( simpleEntry.text() ); + builder.append( "\n" ); + } else if ( report instanceof final ChangeReport.MultipleEntries multipleEntries ) { + for ( final ChangeReport entry : multipleEntries.entries() ) { + append( builder, entry, indentation + 2 ); + builder.append( "\n" ); + } + } else if ( report instanceof final ChangeReport.EntryWithDetails entryWithDetails ) { + builder.append( indent ); + builder.append( entryWithDetails.summary() ); + builder.append( "\n" ); + for ( final Map.Entry entry : entryWithDetails.details().entrySet() ) { + builder.append( indent ); + builder.append( indent ); + builder.append( entry.getKey() ); + builder.append( ": " ); + if ( entry.getValue() instanceof final Model model ) { + builder.append( "\n" ); + builder.append( RdfUtil.modelToString( model ) ); + } else { + builder.append( entry.getValue().toString() ); + } + builder.append( "\n" ); + } + } + } + + @Override + public String apply( final ChangeReport changeReport ) { + final StringBuilder builder = new StringBuilder(); + append( builder, changeReport, 0 ); + return builder.toString(); + } +} diff --git a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/ModelChangeException.java b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/ModelChangeException.java new file mode 100644 index 000000000..6ef443f43 --- /dev/null +++ b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/ModelChangeException.java @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2024 Robert Bosch Manufacturing Solutions GmbH + * + * See the AUTHORS file(s) distributed with this work for additional + * information regarding authorship. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * SPDX-License-Identifier: MPL-2.0 + */ + +package org.eclipse.esmf.aspectmodel.edit; + +import java.io.Serial; + +public class ModelChangeException extends RuntimeException { + @Serial + private static final long serialVersionUID = 6040601725774787289L; + + public ModelChangeException( final String message ) { + super( message ); + } +} diff --git a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/ModelRefactoring.java b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/ModelRefactoring.java deleted file mode 100644 index 7a46dbc65..000000000 --- a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/ModelRefactoring.java +++ /dev/null @@ -1,229 +0,0 @@ -/* - * Copyright (c) 2024 Robert Bosch Manufacturing Solutions GmbH - * - * See the AUTHORS file(s) distributed with this work for additional - * information regarding authorship. - * - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at https://mozilla.org/MPL/2.0/. - * - * SPDX-License-Identifier: MPL-2.0 - */ - -package org.eclipse.esmf.aspectmodel.edit; - -import java.util.AbstractMap; -import java.util.ArrayDeque; -import java.util.Deque; -import java.util.List; -import java.util.Map; -import java.util.stream.Collectors; - -import org.eclipse.esmf.aspectmodel.AspectModelBuilder; -import org.eclipse.esmf.aspectmodel.AspectModelFile; -import org.eclipse.esmf.aspectmodel.urn.AspectModelUrn; -import org.eclipse.esmf.metamodel.AspectModel; -import org.eclipse.esmf.metamodel.impl.DefaultAspectModel; - -import org.apache.jena.rdf.model.Model; -import org.apache.jena.rdf.model.ModelFactory; -import org.apache.jena.rdf.model.Property; -import org.apache.jena.rdf.model.RDFNode; -import org.apache.jena.rdf.model.Resource; -import org.apache.jena.rdf.model.Statement; -import org.apache.jena.rdf.model.StmtIterator; - -public class ModelRefactoring extends AspectModelBuilder { - private final Deque undoStack = new ArrayDeque<>(); - private final Deque redoStack = new ArrayDeque<>(); - private final DefaultAspectModel aspectModel; - - public ModelRefactoring( final AspectModel aspectModel ) { - if ( !( aspectModel instanceof DefaultAspectModel ) ) { - throw new RuntimeException(); - } - this.aspectModel = (DefaultAspectModel) aspectModel; - } - - public synchronized void applyChange( final Change change ) { - change.applyChange( aspectModel ); - updateAspectModelAfterChange(); - undoStack.offerLast( change.reverse() ); - } - - private void updateAspectModelAfterChange() { - final AspectModel updatedModel = buildAspectModel( aspectModel.files() ); - aspectModel.setMergedModel( updatedModel.mergedModel() ); - aspectModel.setElements( updatedModel.elements() ); - } - - public synchronized void undoChange() { - if ( undoStack.isEmpty() ) { - return; - } - final Change change = undoStack.pollLast(); - change.applyChange( aspectModel ); - updateAspectModelAfterChange(); - redoStack.offerLast( change.reverse() ); - } - - public synchronized void redoChange() { - if ( redoStack.isEmpty() ) { - return; - } - final Change change = redoStack.pollLast(); - change.applyChange( aspectModel ); - updateAspectModelAfterChange(); - undoStack.offerLast( change.reverse() ); - } - - public class ChangeGroup implements Change { - private final List changes; - - public ChangeGroup( final List changes ) { - this.changes = changes; - } - - @Override - public void applyChange( final AspectModel aspectModel ) { - changes().forEach( change -> change.applyChange( aspectModel ) ); - } - - @Override - public Change reverse() { - return new ChangeGroup( changes().stream().map( Change::reverse ).toList() ); - } - - public List changes() { - return changes; - } - - @Override - public T accept( final Visitor visitor ) { - return visitor.visitChangeGroup( this ); - } - } - - public abstract class EditAspectModel implements Change { - protected record ModelChanges( Model add, Model remove ) { - } - - private Map changesPerFile = null; - - synchronized protected Map changesPerFile() { - if ( changesPerFile == null ) { - changesPerFile = aspectModel.files().stream() - .map( file -> new AbstractMap.SimpleEntry<>( file, calculateChangesPerFile( file ) ) ) - .collect( Collectors.toMap( AbstractMap.SimpleEntry::getKey, AbstractMap.SimpleEntry::getValue ) ); - } - return changesPerFile; - } - - abstract protected ModelChanges calculateChangesPerFile( AspectModelFile aspectModelFile ); - - @Override - public void applyChange( final AspectModel aspectModel ) { - changesPerFile().forEach( ( file, modelChanges ) -> { - if ( aspectModel.files().contains( file ) ) { - file.sourceModel().add( modelChanges.add() ); - file.sourceModel().remove( modelChanges.remove() ); - } - } ); - } - } - - public class RenameUrn extends EditAspectModel { - private final AspectModelUrn from; - private final AspectModelUrn to; - - public RenameUrn( final AspectModelUrn from, final AspectModelUrn to ) { - this.from = from; - this.to = to; - } - - public AspectModelUrn from() { - return from; - } - - public AspectModelUrn to() { - return to; - } - - @Override - protected ModelChanges calculateChangesPerFile( final AspectModelFile aspectModelFile ) { - final Model addModel = ModelFactory.createDefaultModel(); - final Model removeModel = ModelFactory.createDefaultModel(); - - for ( final StmtIterator it = aspectModelFile.sourceModel().listStatements(); it.hasNext(); ) { - final Statement statement = it.next(); - boolean updateTriple = false; - final Resource addSubject; - final Resource removeSubject; - final Property predicate; - final RDFNode addObject; - final RDFNode removeObject; - if ( statement.getSubject().isURIResource() ) { - if ( statement.getSubject().getURI().equals( from.toString() ) ) { - addSubject = addModel.createResource( to.toString() ); - removeSubject = removeModel.createResource( from.toString() ); - updateTriple = true; - } else { - addSubject = statement.getSubject(); - removeSubject = statement.getSubject(); - } - } else { - addSubject = addModel.createResource( statement.getSubject().getId() ); - removeSubject = removeModel.createResource( statement.getSubject().getId() ); - } - - if ( statement.getPredicate().getURI().equals( from.toString() ) ) { - predicate = addModel.createProperty( to.toString() ); - updateTriple = true; - } else { - predicate = statement.getPredicate(); - } - if ( statement.getObject().isURIResource() && statement.getObject().asResource().getURI().equals( from.toString() ) ) { - addObject = addModel.createResource( to.toString() ); - removeObject = removeModel.createResource( from.toString() ); - updateTriple = true; - } else { - if ( statement.getObject().isAnon() ) { - addObject = addModel.createResource( statement.getObject().asResource().getId() ); - removeObject = removeModel.createResource( statement.getObject().asResource().getId() ); - } else { - addObject = statement.getObject(); - removeObject = statement.getObject(); - } - } - if ( updateTriple ) { - addModel.add( addSubject, predicate, addObject ); - removeModel.add( removeSubject, predicate, removeObject ); - } - } - - return new ModelChanges( addModel, removeModel ); - } - - @Override - public Change reverse() { - return new RenameUrn( to, from ); - } - - @Override - public T accept( final Visitor visitor ) { - return visitor.visitRenameUrn( this ); - } - } - - public class RenameElement extends RenameUrn { - public RenameElement( final AspectModelUrn urn, final String name ) { - super( urn, AspectModelUrn.fromUrn( urn.getUrnPrefix() + name ) ); - } - - @Override - public T accept( final Visitor visitor ) { - return visitor.visitRenameElement( this ); - } - } -} diff --git a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/RdfUtil.java b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/RdfUtil.java new file mode 100644 index 000000000..808b4e577 --- /dev/null +++ b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/RdfUtil.java @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2024 Robert Bosch Manufacturing Solutions GmbH + * + * See the AUTHORS file(s) distributed with this work for additional + * information regarding authorship. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * SPDX-License-Identifier: MPL-2.0 + */ + +package org.eclipse.esmf.aspectmodel.edit; + +import java.io.StringWriter; + +import org.apache.jena.rdf.model.Model; +import org.apache.jena.rdf.model.ModelFactory; +import org.apache.jena.rdf.model.Property; +import org.apache.jena.rdf.model.RDFNode; +import org.apache.jena.rdf.model.Resource; + +public class RdfUtil { + public static Model getModelElementDefinition( final Resource element ) { + final Model result = ModelFactory.createDefaultModel(); + element.getModel().listStatements( element, null, (RDFNode) null ).toList().forEach( statement -> { + final Resource subject = statement.getSubject(); + final Resource newSubject = subject.isAnon() + ? result.createResource( subject.getId() ) + : result.createResource( subject.getURI() ); + final Property newPredicate = result.createProperty( statement.getPredicate().getURI() ); + final RDFNode newObject; + if ( statement.getObject().isURIResource() ) { + newObject = result.createResource( statement.getObject().asResource().getURI() ); + } else if ( statement.getObject().isLiteral() ) { + newObject = statement.getObject(); + } else if ( statement.getObject().isAnon() ) { + newObject = result.createResource( statement.getObject().asResource().getId() ); + result.add( getModelElementDefinition( statement.getObject().asResource() ) ); + } else { + newObject = statement.getObject(); + } + result.add( newSubject, newPredicate, newObject ); + } ); + return result; + } + + public static String modelToString( final Model model ) { + final StringWriter stringWriter = new StringWriter(); + model.write( stringWriter, "TURTLE" ); + return stringWriter.toString(); + } +} diff --git a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/SimpleChangeReport.java b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/SimpleChangeReport.java deleted file mode 100644 index 3789f6bc0..000000000 --- a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/SimpleChangeReport.java +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright (c) 2024 Robert Bosch Manufacturing Solutions GmbH - * - * See the AUTHORS file(s) distributed with this work for additional - * information regarding authorship. - * - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at https://mozilla.org/MPL/2.0/. - * - * SPDX-License-Identifier: MPL-2.0 - */ - -package org.eclipse.esmf.aspectmodel.edit; - -import java.io.StringWriter; -import java.util.stream.Collectors; - -import org.eclipse.esmf.aspectmodel.AspectModelFile; - -import org.apache.jena.rdf.model.Model; - -public class SimpleChangeReport implements Change.Visitor { - @Override - public String visitChangeGroup( final ModelRefactoring.ChangeGroup changeGroup ) { - return changeGroup.changes().stream().map( change -> change.accept( this ) ) - .collect( Collectors.joining( "\n", "- ", "" ) ); - } - - @Override - public String visitRenameElement( final ModelRefactoring.RenameElement renameElement ) { - return renameElement.changesPerFile().entrySet().stream().map( entry -> { - final AspectModelFile file = entry.getKey(); - final ModelRefactoring.EditAspectModel.ModelChanges changes = entry.getValue(); - - final String add = indent( modelToString( changes.add() ) ); - final String remove = indent( modelToString( changes.remove() ) ); - return String.format( "In file %s:%nAdd model:%n ---%n%s%n ---%nRemove model:%n ---%n%s%n ---%n", file.toString(), add, remove ); - } ).collect( Collectors.joining() ); - } - - private String indent( final String s ) { - return s.lines().map( line -> " " + line ).collect( Collectors.joining( "\n" ) ); - } - - private String modelToString( final Model model ) { - final StringWriter stringWriter = new StringWriter(); - model.write( stringWriter, "TURTLE" ); - return stringWriter.toString(); - } - - @Override - public String visitRenameUrn( final ModelRefactoring.RenameUrn renameUrn ) { - return renameUrn.changesPerFile().entrySet().stream().map( entry -> { - final AspectModelFile file = entry.getKey(); - final ModelRefactoring.EditAspectModel.ModelChanges changes = entry.getValue(); - - final String add = indent( modelToString( changes.add() ) ); - final String remove = indent( modelToString( changes.remove() ) ); - return String.format( "In file %s:%nAdd model:%n ---%n%s%nRemove model:%n ---%n%s%n", file.toString(), add, remove ); - } ).collect( Collectors.joining() ); - } -} diff --git a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/AddAspectModelFile.java b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/AddAspectModelFile.java new file mode 100644 index 000000000..71073c9e9 --- /dev/null +++ b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/AddAspectModelFile.java @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2024 Robert Bosch Manufacturing Solutions GmbH + * + * See the AUTHORS file(s) distributed with this work for additional + * information regarding authorship. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * SPDX-License-Identifier: MPL-2.0 + */ + +package org.eclipse.esmf.aspectmodel.edit.change; + +import java.util.Map; + +import org.eclipse.esmf.aspectmodel.AspectModelFile; +import org.eclipse.esmf.aspectmodel.edit.Change; +import org.eclipse.esmf.aspectmodel.edit.ChangeContext; +import org.eclipse.esmf.aspectmodel.edit.ChangeReport; +import org.eclipse.esmf.aspectmodel.edit.ModelChangeException; + +public class AddAspectModelFile implements Change { + private final AspectModelFile newFile; + + public AddAspectModelFile( final AspectModelFile newFile ) { + this.newFile = newFile; + } + + @Override + public void fire( final ChangeContext changeContext ) { + changeContext.aspectModel().files().add( newFile ); + } + + @Override + public Change reverse() { + return new Change() { + @Override + public void fire( final ChangeContext changeContext ) { + changeContext.aspectModel().files().remove( fileToRemove( changeContext ) ); + } + + @Override + public Change reverse() { + return AddAspectModelFile.this; + } + + private AspectModelFile fileToRemove( final ChangeContext changeContext ) { + return changeContext.aspectModel().files().stream() + .filter( file -> file.sourceLocation().equals( newFile.sourceLocation() ) ) + .findFirst() + .orElseThrow( () -> new ModelChangeException( "Unable to remove Aspect Model File" ) ); + } + + @Override + public ChangeReport report( final ChangeContext changeContext ) { + final AspectModelFile file = fileToRemove( changeContext ); + return new ChangeReport.EntryWithDetails( "Remove file " + file.sourceLocation(), + Map.of( "sourceModel", file.sourceModel() ) ); + } + }; + } + + @Override + public ChangeReport report( final ChangeContext changeContext ) { + return new ChangeReport.EntryWithDetails( "Add file " + newFile.sourceLocation(), + Map.of( "sourceModel", newFile.sourceModel() ) ); + } +} diff --git a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/AddElementDefinition.java b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/AddElementDefinition.java new file mode 100644 index 000000000..d11dcda4d --- /dev/null +++ b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/AddElementDefinition.java @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2024 Robert Bosch Manufacturing Solutions GmbH + * + * See the AUTHORS file(s) distributed with this work for additional + * information regarding authorship. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * SPDX-License-Identifier: MPL-2.0 + */ + +package org.eclipse.esmf.aspectmodel.edit.change; + +import java.util.Map; + +import org.eclipse.esmf.aspectmodel.AspectModelFile; +import org.eclipse.esmf.aspectmodel.edit.Change; +import org.eclipse.esmf.aspectmodel.edit.ChangeContext; +import org.eclipse.esmf.aspectmodel.edit.ChangeReport; +import org.eclipse.esmf.aspectmodel.urn.AspectModelUrn; + +import org.apache.jena.rdf.model.Model; +import org.apache.jena.rdf.model.ModelFactory; + +public class AddElementDefinition extends EditAspectModel { + private final AspectModelUrn elementUrn; + private final Model definition; + private final AspectModelFile targetFile; + + public AddElementDefinition( final AspectModelUrn elementUrn, final Model definition, final AspectModelFile targetFile ) { + this.elementUrn = elementUrn; + this.definition = definition; + this.targetFile = targetFile; + } + + @Override + protected ModelChanges calculateChangesForFile( final AspectModelFile aspectModelFile ) { + if ( !aspectModelFile.equals( targetFile ) ) { + return ModelChanges.NONE; + } + + return new ModelChanges( definition, ModelFactory.createDefaultModel() ); + } + + @Override + public Change reverse() { + return new EditAspectModel() { + @Override + protected ModelChanges calculateChangesForFile( final AspectModelFile aspectModelFile ) { + if ( aspectModelFile.equals( targetFile ) ) { + return new ModelChanges( ModelFactory.createDefaultModel(), definition ); + } + return ModelChanges.NONE; + } + + @Override + public Change reverse() { + return AddElementDefinition.this; + } + + @Override + public ChangeReport report( final ChangeContext changeContext ) { + return new ChangeReport.EntryWithDetails( "Remove definition of " + elementUrn + " in " + targetFile, + Map.of( "removeStatements", definition ) ); + } + }; + } + + @Override + public ChangeReport report( final ChangeContext changeContext ) { + return new ChangeReport.EntryWithDetails( "Add definition of " + elementUrn + " in " + targetFile, + Map.of( "addStatements", definition ) ); + } +} diff --git a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/EditAspectModel.java b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/EditAspectModel.java new file mode 100644 index 000000000..e5d429b52 --- /dev/null +++ b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/EditAspectModel.java @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2024 Robert Bosch Manufacturing Solutions GmbH + * + * See the AUTHORS file(s) distributed with this work for additional + * information regarding authorship. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * SPDX-License-Identifier: MPL-2.0 + */ + +package org.eclipse.esmf.aspectmodel.edit.change; + +import java.util.AbstractMap; +import java.util.Map; +import java.util.stream.Collectors; + +import org.eclipse.esmf.aspectmodel.AspectModelFile; +import org.eclipse.esmf.aspectmodel.edit.Change; +import org.eclipse.esmf.aspectmodel.edit.ChangeContext; + +import org.apache.jena.rdf.model.Model; +import org.apache.jena.rdf.model.ModelFactory; + +public abstract class EditAspectModel implements Change { + public record ModelChanges( Model add, Model remove ) { + public static final ModelChanges NONE = new ModelChanges( null, null ); + } + + private Map changesPerFile = null; + + synchronized protected Map changesPerFile( final ChangeContext changeContext ) { + if ( changesPerFile == null ) { + changesPerFile = changeContext.aspectModel().files().stream() + .map( file -> new AbstractMap.SimpleEntry<>( file, calculateChangesForFile( file ) ) ) + .filter( entry -> entry.getValue() != ModelChanges.NONE ) + .collect( Collectors.toMap( AbstractMap.SimpleEntry::getKey, AbstractMap.SimpleEntry::getValue ) ); + } + return changesPerFile; + } + + abstract protected ModelChanges calculateChangesForFile( AspectModelFile aspectModelFile ); + + @Override + public void fire( final ChangeContext changeContext ) { + changesPerFile( changeContext ).forEach( ( file, modelChanges ) -> { + if ( changeContext.aspectModel().files().contains( file ) ) { + file.sourceModel().add( modelChanges.add() ); + file.sourceModel().remove( modelChanges.remove() ); + } + } ); + } +} diff --git a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/MoveElementToNewFile.java b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/MoveElementToNewFile.java new file mode 100644 index 000000000..0b69ed632 --- /dev/null +++ b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/MoveElementToNewFile.java @@ -0,0 +1,102 @@ +/* + * Copyright (c) 2024 Robert Bosch Manufacturing Solutions GmbH + * + * See the AUTHORS file(s) distributed with this work for additional + * information regarding authorship. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * SPDX-License-Identifier: MPL-2.0 + */ + +package org.eclipse.esmf.aspectmodel.edit.change; + +import java.net.URI; +import java.util.List; +import java.util.Optional; + +import org.eclipse.esmf.aspectmodel.edit.Change; +import org.eclipse.esmf.aspectmodel.edit.ChangeContext; +import org.eclipse.esmf.aspectmodel.edit.ChangeGroup; +import org.eclipse.esmf.aspectmodel.edit.ChangeReport; +import org.eclipse.esmf.aspectmodel.edit.ModelChangeException; +import org.eclipse.esmf.aspectmodel.edit.RdfUtil; +import org.eclipse.esmf.aspectmodel.resolver.modelfile.RawAspectModelFile; +import org.eclipse.esmf.aspectmodel.resolver.modelfile.RawAspectModelFileBuilder; +import org.eclipse.esmf.aspectmodel.urn.AspectModelUrn; +import org.eclipse.esmf.metamodel.ModelElement; +import org.eclipse.esmf.metamodel.vocabulary.SammNs; + +import org.apache.jena.rdf.model.Model; +import org.apache.jena.rdf.model.Resource; +import org.apache.jena.vocabulary.XSD; + +public class MoveElementToNewFile extends StructuralChange { + private final List headerComment; + private final Optional sourceLocation; + private final AspectModelUrn elementUrn; + private ChangeGroup changes = null; + + public MoveElementToNewFile( final ModelElement modelElement, final List headerComment, final Optional sourceLocation ) { + this( modelElement.urn(), headerComment, sourceLocation ); + if ( modelElement.isAnonymous() ) { + throw new ModelChangeException( "Can not move anonymous model element" ); + } + } + + public MoveElementToNewFile( final AspectModelUrn elementUrn, final List headerComment, final Optional sourceLocation ) { + this.headerComment = headerComment; + this.sourceLocation = sourceLocation; + this.elementUrn = elementUrn; + } + + private void prepareChanges( final ChangeContext changeContext ) { + if ( changes != null ) { + return; + } + + // Prepare new file + final RawAspectModelFile targetFile = RawAspectModelFileBuilder.builder() + .headerComment( headerComment ) + .sourceLocation( sourceLocation ) + .build(); + final Model targetModel = targetFile.sourceModel(); + targetModel.setNsPrefix( SammNs.SAMM.getShortForm(), SammNs.SAMM.getNamespace() ); + targetModel.setNsPrefix( SammNs.SAMMC.getShortForm(), SammNs.SAMMC.getNamespace() ); + targetModel.setNsPrefix( SammNs.UNIT.getShortForm(), SammNs.UNIT.getNamespace() ); + targetModel.setNsPrefix( SammNs.SAMME.getShortForm(), SammNs.SAMME.getNamespace() ); + targetModel.setNsPrefix( "xsd", XSD.NS ); + targetModel.setNsPrefix( "", elementUrn.getUrnPrefix() ); + + // Find source file with element definition + final Model sourceModel = sourceFile( changeContext, elementUrn ).sourceModel(); + final Resource elementResource = sourceModel.createResource( elementUrn.toString() ); + final Model definition = RdfUtil.getModelElementDefinition( elementResource ); + + // Perform move of element definition + changes = new ChangeGroup( + new RemoveElementDefinition( elementUrn ), + new AddAspectModelFile( targetFile ), + new AddElementDefinition( elementUrn, definition, targetFile ) + ); + } + + @Override + public void fire( final ChangeContext changeContext ) { + prepareChanges( changeContext ); + changes.fire( changeContext ); + } + + @Override + public Change reverse() { + return changes.reverse(); + } + + @Override + public ChangeReport report( final ChangeContext changeContext ) { + prepareChanges( changeContext ); + return changes.report( changeContext ); + } +} diff --git a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/MoveElementToOtherFile.java b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/MoveElementToOtherFile.java new file mode 100644 index 000000000..0f2ff0503 --- /dev/null +++ b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/MoveElementToOtherFile.java @@ -0,0 +1,86 @@ +/* + * Copyright (c) 2024 Robert Bosch Manufacturing Solutions GmbH + * + * See the AUTHORS file(s) distributed with this work for additional + * information regarding authorship. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * SPDX-License-Identifier: MPL-2.0 + */ + +package org.eclipse.esmf.aspectmodel.edit.change; + +import java.util.List; +import java.util.function.Predicate; + +import org.eclipse.esmf.aspectmodel.AspectModelFile; +import org.eclipse.esmf.aspectmodel.edit.Change; +import org.eclipse.esmf.aspectmodel.edit.ChangeContext; +import org.eclipse.esmf.aspectmodel.edit.ChangeGroup; +import org.eclipse.esmf.aspectmodel.edit.ChangeReport; +import org.eclipse.esmf.aspectmodel.edit.ModelChangeException; +import org.eclipse.esmf.aspectmodel.edit.RdfUtil; +import org.eclipse.esmf.aspectmodel.urn.AspectModelUrn; + +import org.apache.jena.rdf.model.Model; +import org.apache.jena.rdf.model.Resource; + +public class MoveElementToOtherFile extends StructuralChange { + private final AspectModelUrn elementUrn; + private final Predicate targetFileSelector; + private ChangeGroup changes = null; + + public MoveElementToOtherFile( final AspectModelUrn elementUrn, final Predicate targetFileSelector ) { + this.elementUrn = elementUrn; + this.targetFileSelector = targetFileSelector; + } + + private void prepareChanges( final ChangeContext changeContext ) { + if ( changes != null ) { + return; + } + + final List targetFiles = changeContext.aspectModel().files().stream().filter( targetFileSelector ).toList(); + if ( targetFiles.size() > 1 ) { + throw new ModelChangeException( "Can not determine target file to move element" ); + } + if ( targetFiles.isEmpty() ) { + return; + } + final AspectModelFile targetFile = targetFiles.get( 0 ); + + // Find source file with element definition + final AspectModelFile sourceFile = sourceFile( changeContext, elementUrn ); + if ( sourceFile == targetFile ) { + return; + } + final Resource elementResource = sourceFile.sourceModel().createResource( elementUrn.toString() ); + final Model definition = RdfUtil.getModelElementDefinition( elementResource ); + + // Perform move of element definition + changes = new ChangeGroup( + new RemoveElementDefinition( elementUrn ), + new AddElementDefinition( elementUrn, definition, targetFile ) + ); + } + + @Override + public void fire( final ChangeContext changeContext ) { + prepareChanges( changeContext ); + changes.fire( changeContext ); + } + + @Override + public Change reverse() { + return changes.reverse(); + } + + @Override + public ChangeReport report( final ChangeContext changeContext ) { + prepareChanges( changeContext ); + return changes.report( changeContext ); + } +} diff --git a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/MoveElementToOtherNamespace.java b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/MoveElementToOtherNamespace.java new file mode 100644 index 000000000..f2aa41d80 --- /dev/null +++ b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/MoveElementToOtherNamespace.java @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2024 Robert Bosch Manufacturing Solutions GmbH + * + * See the AUTHORS file(s) distributed with this work for additional + * information regarding authorship. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * SPDX-License-Identifier: MPL-2.0 + */ + +package org.eclipse.esmf.aspectmodel.edit.change; + +import org.eclipse.esmf.aspectmodel.edit.Change; +import org.eclipse.esmf.aspectmodel.edit.ChangeContext; +import org.eclipse.esmf.aspectmodel.edit.ChangeReport; +import org.eclipse.esmf.aspectmodel.urn.AspectModelUrn; +import org.eclipse.esmf.metamodel.Namespace; + +public class MoveElementToOtherNamespace implements Change { + private final AspectModelUrn elementUrn; + private final Namespace targetNamespace; + + public MoveElementToOtherNamespace( final AspectModelUrn elementUrn, final Namespace targetNamespace ) { + this.elementUrn = elementUrn; + this.targetNamespace = targetNamespace; + } + + @Override + public void fire( final ChangeContext changeContext ) { + + } + + @Override + public Change reverse() { + return null; + } + + @Override + public ChangeReport report( final ChangeContext changeContext ) { + return null; + } +} diff --git a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/RemoveAspectModelFile.java b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/RemoveAspectModelFile.java new file mode 100644 index 000000000..585c1074f --- /dev/null +++ b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/RemoveAspectModelFile.java @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2024 Robert Bosch Manufacturing Solutions GmbH + * + * See the AUTHORS file(s) distributed with this work for additional + * information regarding authorship. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * SPDX-License-Identifier: MPL-2.0 + */ + +package org.eclipse.esmf.aspectmodel.edit.change; + +import java.util.Map; + +import org.eclipse.esmf.aspectmodel.AspectModelFile; +import org.eclipse.esmf.aspectmodel.edit.Change; +import org.eclipse.esmf.aspectmodel.edit.ChangeContext; +import org.eclipse.esmf.aspectmodel.edit.ChangeReport; + +public class RemoveAspectModelFile implements Change { + private final AspectModelFile fileToRemove; + + public RemoveAspectModelFile( final AspectModelFile fileToRemove ) { + this.fileToRemove = fileToRemove; + } + + @Override + public void fire( final ChangeContext changeContext ) { + changeContext.aspectModel().files().remove( fileToRemove ); + } + + @Override + public Change reverse() { + return new AddAspectModelFile( fileToRemove ); + } + + @Override + public ChangeReport report( final ChangeContext changeContext ) { + return new ChangeReport.EntryWithDetails( "Remove file " + fileToRemove.sourceLocation(), + Map.of( "sourceModel", fileToRemove.sourceModel() ) ); + } +} diff --git a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/RemoveElementDefinition.java b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/RemoveElementDefinition.java new file mode 100644 index 000000000..36774d88c --- /dev/null +++ b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/RemoveElementDefinition.java @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2024 Robert Bosch Manufacturing Solutions GmbH + * + * See the AUTHORS file(s) distributed with this work for additional + * information regarding authorship. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * SPDX-License-Identifier: MPL-2.0 + */ + +package org.eclipse.esmf.aspectmodel.edit.change; + +import java.util.Map; + +import org.eclipse.esmf.aspectmodel.AspectModelFile; +import org.eclipse.esmf.aspectmodel.edit.Change; +import org.eclipse.esmf.aspectmodel.edit.ChangeContext; +import org.eclipse.esmf.aspectmodel.edit.ChangeReport; +import org.eclipse.esmf.aspectmodel.edit.RdfUtil; +import org.eclipse.esmf.aspectmodel.urn.AspectModelUrn; + +import org.apache.jena.rdf.model.Model; +import org.apache.jena.rdf.model.ModelFactory; +import org.apache.jena.rdf.model.RDFNode; +import org.apache.jena.rdf.model.Resource; +import org.apache.jena.vocabulary.RDF; + +public class RemoveElementDefinition extends EditAspectModel { + private final AspectModelUrn elementUrn; + private AspectModelFile fileWithOriginalDefinition; + private Model definition; + + public RemoveElementDefinition( final AspectModelUrn elementUrn ) { + this.elementUrn = elementUrn; + } + + @Override + protected ModelChanges calculateChangesForFile( final AspectModelFile aspectModelFile ) { + final Model model = aspectModelFile.sourceModel(); + final Resource elementResource = model.createResource( elementUrn.toString() ); + if ( !model.contains( elementResource, RDF.type, (RDFNode) null ) ) { + return ModelChanges.NONE; + } + + fileWithOriginalDefinition = aspectModelFile; + final Model add = ModelFactory.createDefaultModel(); + definition = RdfUtil.getModelElementDefinition( elementResource ); + return new ModelChanges( add, definition ); + } + + @Override + public Change reverse() { + return new EditAspectModel() { + @Override + protected ModelChanges calculateChangesForFile( final AspectModelFile aspectModelFile ) { + return aspectModelFile.sourceLocation().equals( fileWithOriginalDefinition.sourceLocation() ) + ? new ModelChanges( definition, ModelFactory.createDefaultModel() ) + : ModelChanges.NONE; + } + + @Override + public Change reverse() { + return RemoveElementDefinition.this; + } + + @Override + public ChangeReport report( final ChangeContext changeContext ) { + return new ChangeReport.SimpleEntry( + "Add back definition of " + elementUrn + " to " + fileWithOriginalDefinition.sourceLocation() ); + } + }; + } + + @Override + public ChangeReport report( final ChangeContext changeContext ) { + changesPerFile( changeContext ); + return new ChangeReport.EntryWithDetails( + "Remove definition of " + elementUrn + " from " + fileWithOriginalDefinition.sourceLocation(), + Map.of( "definition", definition ) ); + } +} diff --git a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/RenameElement.java b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/RenameElement.java new file mode 100644 index 000000000..2733a1b9e --- /dev/null +++ b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/RenameElement.java @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2024 Robert Bosch Manufacturing Solutions GmbH + * + * See the AUTHORS file(s) distributed with this work for additional + * information regarding authorship. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * SPDX-License-Identifier: MPL-2.0 + */ + +package org.eclipse.esmf.aspectmodel.edit.change; + +import org.eclipse.esmf.aspectmodel.edit.ChangeContext; +import org.eclipse.esmf.aspectmodel.edit.ChangeReport; +import org.eclipse.esmf.aspectmodel.edit.ModelChangeException; +import org.eclipse.esmf.aspectmodel.urn.AspectModelUrn; +import org.eclipse.esmf.metamodel.ModelElement; + +public class RenameElement extends RenameUrn { + public RenameElement( final ModelElement modelElement, final String newName ) { + this( modelElement.urn(), newName ); + if ( modelElement.isAnonymous() ) { + throw new ModelChangeException( "Can not rename anonymous model element" ); + } + } + + public RenameElement( final AspectModelUrn urn, final String newName ) { + super( urn, AspectModelUrn.fromUrn( urn.getUrnPrefix() + newName ) ); + } + + public String name() { + return to().getName(); + } + + @Override + public ChangeReport report( final ChangeContext changeContext ) { + return new ChangeReport.SimpleEntry( "Rename " + from() + " to " + to().getName() ); + } +} diff --git a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/RenameUrn.java b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/RenameUrn.java new file mode 100644 index 000000000..e3fcd60b8 --- /dev/null +++ b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/RenameUrn.java @@ -0,0 +1,111 @@ +/* + * Copyright (c) 2024 Robert Bosch Manufacturing Solutions GmbH + * + * See the AUTHORS file(s) distributed with this work for additional + * information regarding authorship. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * SPDX-License-Identifier: MPL-2.0 + */ + +package org.eclipse.esmf.aspectmodel.edit.change; + +import org.eclipse.esmf.aspectmodel.AspectModelFile; +import org.eclipse.esmf.aspectmodel.edit.Change; +import org.eclipse.esmf.aspectmodel.edit.ChangeContext; +import org.eclipse.esmf.aspectmodel.edit.ChangeReport; +import org.eclipse.esmf.aspectmodel.urn.AspectModelUrn; + +import org.apache.jena.rdf.model.Model; +import org.apache.jena.rdf.model.ModelFactory; +import org.apache.jena.rdf.model.Property; +import org.apache.jena.rdf.model.RDFNode; +import org.apache.jena.rdf.model.Resource; +import org.apache.jena.rdf.model.Statement; +import org.apache.jena.rdf.model.StmtIterator; + +public class RenameUrn extends EditAspectModel { + private final AspectModelUrn from; + private final AspectModelUrn to; + + public RenameUrn( final AspectModelUrn from, final AspectModelUrn to ) { + this.from = from; + this.to = to; + } + + public AspectModelUrn from() { + return from; + } + + public AspectModelUrn to() { + return to; + } + + @Override + protected ModelChanges calculateChangesForFile( final AspectModelFile aspectModelFile ) { + final Model addModel = ModelFactory.createDefaultModel(); + final Model removeModel = ModelFactory.createDefaultModel(); + + for ( final StmtIterator it = aspectModelFile.sourceModel().listStatements(); it.hasNext(); ) { + final Statement statement = it.next(); + boolean updateTriple = false; + final Resource addSubject; + final Resource removeSubject; + final Property predicate; + final RDFNode addObject; + final RDFNode removeObject; + if ( statement.getSubject().isURIResource() ) { + if ( statement.getSubject().getURI().equals( from.toString() ) ) { + addSubject = addModel.createResource( to.toString() ); + removeSubject = removeModel.createResource( from.toString() ); + updateTriple = true; + } else { + addSubject = statement.getSubject(); + removeSubject = statement.getSubject(); + } + } else { + addSubject = addModel.createResource( statement.getSubject().getId() ); + removeSubject = removeModel.createResource( statement.getSubject().getId() ); + } + + if ( statement.getPredicate().getURI().equals( from.toString() ) ) { + predicate = addModel.createProperty( to.toString() ); + updateTriple = true; + } else { + predicate = statement.getPredicate(); + } + if ( statement.getObject().isURIResource() && statement.getObject().asResource().getURI().equals( from.toString() ) ) { + addObject = addModel.createResource( to.toString() ); + removeObject = removeModel.createResource( from.toString() ); + updateTriple = true; + } else { + if ( statement.getObject().isAnon() ) { + addObject = addModel.createResource( statement.getObject().asResource().getId() ); + removeObject = removeModel.createResource( statement.getObject().asResource().getId() ); + } else { + addObject = statement.getObject(); + removeObject = statement.getObject(); + } + } + if ( updateTriple ) { + addModel.add( addSubject, predicate, addObject ); + removeModel.add( removeSubject, predicate, removeObject ); + } + } + + return new ModelChanges( addModel, removeModel ); + } + + @Override + public Change reverse() { + return new RenameUrn( to, from ); + } + + @Override + public ChangeReport report( final ChangeContext changeContext ) { + return new ChangeReport.SimpleEntry( "Rename " + from() + " to " + to() ); + } +} diff --git a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/StructuralChange.java b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/StructuralChange.java new file mode 100644 index 000000000..9885f3c43 --- /dev/null +++ b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/StructuralChange.java @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2024 Robert Bosch Manufacturing Solutions GmbH + * + * See the AUTHORS file(s) distributed with this work for additional + * information regarding authorship. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * SPDX-License-Identifier: MPL-2.0 + */ + +package org.eclipse.esmf.aspectmodel.edit.change; + +import org.eclipse.esmf.aspectmodel.AspectModelFile; +import org.eclipse.esmf.aspectmodel.edit.Change; +import org.eclipse.esmf.aspectmodel.edit.ChangeContext; +import org.eclipse.esmf.aspectmodel.edit.ModelChangeException; +import org.eclipse.esmf.aspectmodel.urn.AspectModelUrn; + +import org.apache.jena.rdf.model.RDFNode; +import org.apache.jena.vocabulary.RDF; + +public abstract class StructuralChange implements Change { + protected AspectModelFile sourceFile( final ChangeContext changeContext, final AspectModelUrn elementUrn ) { + return changeContext.aspectModel().files().stream().filter( aspectModelFile -> + aspectModelFile.sourceModel() + .contains( aspectModelFile.sourceModel().createResource( elementUrn.toString() ), RDF.type, (RDFNode) null ) ) + .findFirst() + .orElseThrow( () -> new ModelChangeException( "Could not locate file containing definition of " + elementUrn ) ); + } +} diff --git a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/loader/AspectModelLoader.java b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/loader/AspectModelLoader.java index b184c438f..d87539597 100644 --- a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/loader/AspectModelLoader.java +++ b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/loader/AspectModelLoader.java @@ -22,6 +22,7 @@ import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; +import java.net.URI; import java.nio.file.Path; import java.util.ArrayDeque; import java.util.ArrayList; @@ -29,7 +30,6 @@ import java.util.Deque; import java.util.HashSet; import java.util.List; -import java.util.Map; import java.util.Optional; import java.util.Set; import java.util.function.Function; @@ -47,20 +47,15 @@ import org.eclipse.esmf.aspectmodel.resolver.ResolutionStrategy; import org.eclipse.esmf.aspectmodel.resolver.ResolutionStrategySupport; import org.eclipse.esmf.aspectmodel.resolver.fs.FlatModelsRoot; -import org.eclipse.esmf.aspectmodel.resolver.modelfile.DefaultAspectModelFile; -import org.eclipse.esmf.aspectmodel.resolver.modelfile.MetaModelFile; import org.eclipse.esmf.aspectmodel.urn.AspectModelUrn; import org.eclipse.esmf.aspectmodel.urn.ElementType; import org.eclipse.esmf.aspectmodel.urn.UrnSyntaxException; import org.eclipse.esmf.aspectmodel.versionupdate.MetaModelVersionMigrator; import org.eclipse.esmf.metamodel.AspectModel; -import org.eclipse.esmf.metamodel.ModelElement; -import org.eclipse.esmf.metamodel.impl.DefaultAspectModel; import org.eclipse.esmf.metamodel.vocabulary.SammNs; import com.google.common.collect.Streams; import org.apache.jena.rdf.model.Model; -import org.apache.jena.rdf.model.ModelFactory; import org.apache.jena.rdf.model.RDFNode; import org.apache.jena.rdf.model.Resource; import org.apache.jena.rdf.model.Statement; @@ -72,7 +67,7 @@ /** * The core class to load an {@link AspectModel}. */ -public class AspectModelLoader extends AspectModelBuilder implements ResolutionStrategySupport { +public class AspectModelLoader implements ResolutionStrategySupport { private static final Logger LOG = LoggerFactory.getLogger( AspectModelLoader.class ); private static final String ASPECT_MODELS_FOLDER = "aspect-models"; @@ -140,7 +135,7 @@ public AspectModel load( final Collection files ) { .toList(); final LoaderContext loaderContext = new LoaderContext(); resolve( migratedFiles, loaderContext ); - return buildAspectModel( loaderContext.loadedFiles() ); + return AspectModelBuilder.buildAspectModelFromFiles( loaderContext.loadedFiles() ); } /** @@ -165,30 +160,36 @@ public AspectModel loadUrns( final Collection urns ) { loaderContext.unresolvedUrns().add( inputUrn.toString() ); } resolve( List.of(), loaderContext ); - return buildAspectModel( loaderContext.loadedFiles() ); + return AspectModelBuilder.buildAspectModelFromFiles( loaderContext.loadedFiles() ); } /** - * Load an Aspect Model from an input stream + * Load an Aspect Model from an input stream and optionally set the source location for this input * * @param inputStream the input stream + * @param sourceLocation the source location for the model * @return the Aspect Model */ - public AspectModel load( final InputStream inputStream ) { - final AspectModelFile rawFile = AspectModelFileLoader.load( inputStream ); + public AspectModel load( final InputStream inputStream, final Optional sourceLocation ) { + final AspectModelFile rawFile = AspectModelFileLoader.load( inputStream, sourceLocation ); final AspectModelFile migratedModel = migrate( rawFile ); final LoaderContext loaderContext = new LoaderContext(); resolve( List.of( migratedModel ), loaderContext ); - return buildAspectModel( loaderContext.loadedFiles() ); + return AspectModelBuilder.buildAspectModelFromFiles( loaderContext.loadedFiles() ); } - @FunctionalInterface - public interface InputStreamProvider { - InputStream get() throws IOException; + /** + * Load an Aspect Model from an input stream + * + * @param inputStream the input stream + * @return the Aspect Model + */ + public AspectModel load( final InputStream inputStream ) { + return load( inputStream, Optional.empty() ); } /** - * Load Namespace Package (Archive) an Aspect Model from a File + * Load a Namespace Package (Archive) from a File * * @param namespacePackage the archive file * @return the Aspect Model @@ -198,16 +199,16 @@ public AspectModel loadNamespacePackage( final File namespacePackage ) { throw new RuntimeException( new FileNotFoundException( "The specified file does not exist or is not a file." ) ); } - try ( InputStream inputStream = new FileInputStream( namespacePackage ) ) { + try ( final InputStream inputStream = new FileInputStream( namespacePackage ) ) { return loadNamespacePackage( inputStream ); - } catch ( IOException e ) { + } catch ( final IOException e ) { LOG.error( "Error reading the file: {}", namespacePackage.getAbsolutePath(), e ); throw new RuntimeException( "Error reading the file: " + namespacePackage.getAbsolutePath(), e ); } } /** - * Load Namespace Package (Archive) an Aspect Model from an InputStream + * Load a Namespace Package (Archive) from an InputStream * * @param inputStream the input stream * @return the Aspect Model @@ -216,7 +217,7 @@ public AspectModel loadNamespacePackage( final InputStream inputStream ) { final ByteArrayOutputStream baos = new ByteArrayOutputStream(); try { inputStream.transferTo( baos ); - } catch ( IOException e ) { + } catch ( final IOException e ) { throw new RuntimeException( e ); } final boolean hasAspectModelsFolder = containsFolderInNamespacePackage( new ByteArrayInputStream( baos.toByteArray() ) ); @@ -224,43 +225,43 @@ public AspectModel loadNamespacePackage( final InputStream inputStream ) { } private AspectModel loadNamespacePackageFromStream( final InputStream inputStream, final boolean hasAspectModelsFolder ) { - List aspectModelFiles = new ArrayList<>(); + final List aspectModelFiles = new ArrayList<>(); - try ( ZipInputStream zis = new ZipInputStream( inputStream ) ) { + try ( final ZipInputStream zis = new ZipInputStream( inputStream ) ) { ZipEntry entry; - while ( (entry = zis.getNextEntry()) != null ) { - boolean isRelevantEntry = - (hasAspectModelsFolder && entry.getName().contains( String.format( "%s/", ASPECT_MODELS_FOLDER ) ) && entry.getName() - .endsWith( ".ttl" )) - || (!hasAspectModelsFolder && entry.getName().endsWith( ".ttl" )); + while ( ( entry = zis.getNextEntry() ) != null ) { + final boolean isRelevantEntry = + ( hasAspectModelsFolder && entry.getName().contains( String.format( "%s/", ASPECT_MODELS_FOLDER ) ) && entry.getName() + .endsWith( ".ttl" ) ) + || ( !hasAspectModelsFolder && entry.getName().endsWith( ".ttl" ) ); if ( isRelevantEntry ) { - AspectModelFile aspectModelFile = migrate( AspectModelFileLoader.load( zis ) ); + final AspectModelFile aspectModelFile = migrate( AspectModelFileLoader.load( zis ) ); aspectModelFiles.add( aspectModelFile ); } } zis.closeEntry(); - } catch ( IOException e ) { + } catch ( final IOException e ) { LOG.error( "Error reading the Archive input stream", e ); throw new RuntimeException( "Error reading the Archive input stream", e ); } - LoaderContext loaderContext = new LoaderContext(); + final LoaderContext loaderContext = new LoaderContext(); resolve( aspectModelFiles, loaderContext ); - return buildAspectModel( loaderContext.loadedFiles() ); + return AspectModelBuilder.buildAspectModelFromFiles( loaderContext.loadedFiles() ); } private boolean containsFolderInNamespacePackage( final InputStream inputStream ) { - try ( ZipInputStream zis = new ZipInputStream( inputStream ) ) { + try ( final ZipInputStream zis = new ZipInputStream( inputStream ) ) { ZipEntry entry; - while ( (entry = zis.getNextEntry()) != null ) { + while ( ( entry = zis.getNextEntry() ) != null ) { if ( entry.isDirectory() && entry.getName().contains( String.format( "%s/", ASPECT_MODELS_FOLDER ) ) ) { return true; } } - } catch ( IOException e ) { + } catch ( final IOException e ) { throw new RuntimeException( e ); } return false; diff --git a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/resolver/AspectModelFileLoader.java b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/resolver/AspectModelFileLoader.java index 07df2bfa7..5ca78199e 100644 --- a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/resolver/AspectModelFileLoader.java +++ b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/resolver/AspectModelFileLoader.java @@ -81,8 +81,12 @@ private static List headerComment( final String content ) { } public static RawAspectModelFile load( final InputStream inputStream ) { + return load( inputStream, Optional.empty() ); + } + + public static RawAspectModelFile load( final InputStream inputStream, final Optional sourceLocation ) { final AspectModelFile fromString = load( content( inputStream ) ); - return new RawAspectModelFile( fromString.sourceModel(), fromString.headerComment(), Optional.empty() ); + return new RawAspectModelFile( fromString.sourceModel(), fromString.headerComment(), sourceLocation ); } public static RawAspectModelFile load( final Model model ) { diff --git a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/resolver/modelfile/RawAspectModelFile.java b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/resolver/modelfile/RawAspectModelFile.java index 4fc7d3076..f9c634d47 100644 --- a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/resolver/modelfile/RawAspectModelFile.java +++ b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/resolver/modelfile/RawAspectModelFile.java @@ -19,12 +19,31 @@ import org.eclipse.esmf.aspectmodel.AspectModelFile; +import io.soabase.recordbuilder.core.RecordBuilder; import org.apache.jena.rdf.model.Model; +import org.apache.jena.rdf.model.ModelFactory; @SuppressWarnings( "OptionalUsedAsFieldOrParameterType" ) +@RecordBuilder public record RawAspectModelFile( Model sourceModel, List headerComment, Optional sourceLocation ) implements AspectModelFile { + public RawAspectModelFile { + if ( sourceModel == null ) { + sourceModel = ModelFactory.createDefaultModel(); + } + if ( headerComment == null ) { + headerComment = List.of(); + } + if ( sourceLocation == null ) { + sourceLocation = Optional.empty(); + } + } + + @Override + public String toString() { + return sourceLocation().map( URI::toString ).orElse( "(unknown file)" ); + } } diff --git a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/metamodel/impl/DefaultAspectModel.java b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/metamodel/impl/DefaultAspectModel.java index 5b8ca8204..5ba3b6476 100644 --- a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/metamodel/impl/DefaultAspectModel.java +++ b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/metamodel/impl/DefaultAspectModel.java @@ -17,6 +17,7 @@ import java.util.Optional; import java.util.stream.Collectors; +import org.eclipse.esmf.aspectmodel.AspectModelFile; import org.eclipse.esmf.metamodel.AspectModel; import org.eclipse.esmf.metamodel.ModelElement; import org.eclipse.esmf.metamodel.Namespace; @@ -26,8 +27,10 @@ public class DefaultAspectModel implements AspectModel { private Model mergedModel; private List elements; + private List files; - public DefaultAspectModel( final Model mergedModel, final List elements ) { + public DefaultAspectModel( final List files, final Model mergedModel, final List elements ) { + this.files = files; this.mergedModel = mergedModel; this.elements = elements; } @@ -43,6 +46,11 @@ public List namespaces() { .toList(); } + @Override + public List files() { + return files; + } + @Override public List elements() { return elements; @@ -53,6 +61,10 @@ public Model mergedModel() { return mergedModel; } + public void setFiles( final List files ) { + this.files = files; + } + public void setMergedModel( final Model mergedModel ) { this.mergedModel = mergedModel; } diff --git a/core/esmf-aspect-meta-model-java/src/test/java/org/eclipse/esmf/aspectmodel/edit/AspectChangeContextTest.java b/core/esmf-aspect-meta-model-java/src/test/java/org/eclipse/esmf/aspectmodel/edit/AspectChangeContextTest.java new file mode 100644 index 000000000..105bd442f --- /dev/null +++ b/core/esmf-aspect-meta-model-java/src/test/java/org/eclipse/esmf/aspectmodel/edit/AspectChangeContextTest.java @@ -0,0 +1,293 @@ +/* + * Copyright (c) 2024 Robert Bosch Manufacturing Solutions GmbH + * + * See the AUTHORS file(s) distributed with this work for additional + * information regarding authorship. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * SPDX-License-Identifier: MPL-2.0 + */ + +package org.eclipse.esmf.aspectmodel.edit; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.io.ByteArrayInputStream; +import java.io.InputStream; +import java.net.URI; +import java.nio.charset.StandardCharsets; +import java.util.List; +import java.util.Optional; +import java.util.function.Predicate; + +import org.eclipse.esmf.aspectmodel.AspectModelBuilder; +import org.eclipse.esmf.aspectmodel.AspectModelFile; +import org.eclipse.esmf.aspectmodel.edit.change.AddAspectModelFile; +import org.eclipse.esmf.aspectmodel.edit.change.AddElementDefinition; +import org.eclipse.esmf.aspectmodel.edit.change.MoveElementToNewFile; +import org.eclipse.esmf.aspectmodel.edit.change.MoveElementToOtherFile; +import org.eclipse.esmf.aspectmodel.edit.change.RemoveAspectModelFile; +import org.eclipse.esmf.aspectmodel.edit.change.RenameElement; +import org.eclipse.esmf.aspectmodel.resolver.modelfile.RawAspectModelFileBuilder; +import org.eclipse.esmf.aspectmodel.resolver.parser.ReaderRiotTurtle; +import org.eclipse.esmf.aspectmodel.urn.AspectModelUrn; +import org.eclipse.esmf.metamodel.Aspect; +import org.eclipse.esmf.metamodel.AspectModel; +import org.eclipse.esmf.metamodel.Property; +import org.eclipse.esmf.metamodel.vocabulary.SammNs; +import org.eclipse.esmf.test.TestAspect; +import org.eclipse.esmf.test.TestResources; + +import org.apache.jena.rdf.model.Model; +import org.apache.jena.rdf.model.ModelFactory; +import org.apache.jena.riot.Lang; +import org.apache.jena.riot.RDFLanguages; +import org.apache.jena.riot.RDFParserRegistry; +import org.apache.jena.vocabulary.RDF; +import org.junit.jupiter.api.Test; + +public class AspectChangeContextTest { + @Test + void testRenameElement() { + final AspectModel aspectModel = TestResources.load( TestAspect.ASPECT ); + final Aspect aspect = aspectModel.aspect(); + + final String originalName = aspect.getName(); + assertThat( aspectModel.aspect().urn().getName() ).isEqualTo( originalName ); + + final String newName = "RenamedAspect"; + final AspectChangeContext ctx = new AspectChangeContext( aspectModel ); + final Change renameAspect = new RenameElement( aspect, newName ); + ctx.applyChange( renameAspect ); + assertThat( aspectModel.aspect().getName() ).isEqualTo( newName ); + assertThat( aspectModel.files().get( 0 ).sourceModel().listStatements( null, RDF.type, SammNs.SAMM.Aspect() ) + .nextStatement().getSubject().getURI() ).endsWith( newName ); + + ctx.undoChange(); + assertThat( aspectModel.aspect().getName() ).isEqualTo( originalName ); + ctx.redoChange(); + assertThat( aspectModel.aspect().getName() ).isEqualTo( newName ); + } + + @Test + void testUndoRedo() { + final AspectModel aspectModel = TestResources.load( TestAspect.ASPECT ); + final Aspect aspect = aspectModel.aspect(); + + final AspectModelUrn aspectUrn = aspect.urn(); + final String originalName = aspect.getName(); + assertThat( aspectModel.aspect().urn().getName() ).isEqualTo( originalName ); + + final String newName = "RenamedAspect"; + final AspectChangeContext ctx = new AspectChangeContext( aspectModel ); + final Change renameAspect = new RenameElement( aspectUrn, newName ); + ctx.applyChange( renameAspect ); + assertThat( aspectModel.aspect().getName() ).isEqualTo( newName ); + ctx.undoChange(); + assertThat( aspectModel.aspect().getName() ).isEqualTo( originalName ); + ctx.redoChange(); + assertThat( aspectModel.aspect().getName() ).isEqualTo( newName ); + } + + @Test + void testChangeGroups() { + final AspectModel aspectModel = TestResources.load( TestAspect.ASPECT_WITH_PROPERTY ); + final Aspect aspect = aspectModel.aspect(); + + final AspectModelUrn aspectUrn = aspect.urn(); + final String oldAspectName = aspect.getName(); + final Property property = aspect.getProperties().get( 0 ); + final String oldPropertyName = property.getName(); + + final String newAspectName = "RenamedAspect"; + final String newPropertyName = "renamedProperty"; + final AspectChangeContext ctx = new AspectChangeContext( aspectModel ); + final Change renameAspect = new RenameElement( aspectUrn, newAspectName ); + final Change renameProperty = new RenameElement( property.urn(), newPropertyName ); + + final Change group = new ChangeGroup( renameAspect, renameProperty ); + ctx.applyChange( group ); + assertThat( aspectModel.aspect().urn().getName() ).isEqualTo( newAspectName ); + assertThat( aspectModel.aspect().getProperties().get( 0 ).getName() ).isEqualTo( newPropertyName ); + } + + @Test + void testCreateFile() { + final AspectModel aspectModel = AspectModelBuilder.buildEmptyModel(); + assertThat( aspectModel.files() ).isEmpty(); + assertThat( aspectModel.elements() ).isEmpty(); + final AspectChangeContext ctx = new AspectChangeContext( aspectModel ); + final AspectModelFile aspectModelFile = RawAspectModelFileBuilder.builder() + .sourceLocation( Optional.of( URI.create( "file:///temp/test.ttl" ) ) ) + .build(); + final Change addFile = new AddAspectModelFile( aspectModelFile ); + ctx.applyChange( addFile ); + assertThat( aspectModel.files() ).hasSize( 1 ); + ctx.undoChange(); + assertThat( aspectModel.files() ).isEmpty(); + ctx.redoChange(); + assertThat( aspectModel.files() ).hasSize( 1 ); + } + + @Test + void testRemoveFile() { + final AspectModelFile aspectModelFile = RawAspectModelFileBuilder.builder() + .sourceLocation( Optional.of( URI.create( "file:///temp/test.ttl" ) ) ) + .build(); + final AspectModel aspectModel = AspectModelBuilder.buildAspectModelFromFiles( List.of( aspectModelFile ) ); + assertThat( aspectModel.files() ).hasSize( 1 ); + assertThat( aspectModel.elements() ).isEmpty(); + final AspectChangeContext ctx = new AspectChangeContext( aspectModel ); + ctx.applyChange( new RemoveAspectModelFile( aspectModel.files().get( 0 ) ) ); + assertThat( aspectModel.files() ).isEmpty(); + + ctx.undoChange(); + assertThat( aspectModel.files() ).hasSize( 1 ); + ctx.redoChange(); + assertThat( aspectModel.files() ).isEmpty(); + } + + @Test + void testCreateFileWithElementDefinition() { + final AspectModel aspectModel = AspectModelBuilder.buildEmptyModel(); + assertThat( aspectModel.files() ).isEmpty(); + assertThat( aspectModel.elements() ).isEmpty(); + final AspectChangeContext ctx = new AspectChangeContext( aspectModel ); + final AspectModelFile aspectModelFile = RawAspectModelFileBuilder.builder() + .sourceLocation( Optional.of( URI.create( "file:///temp/test.ttl" ) ) ) + .sourceModel( model( """ + @prefix samm: . + @prefix xsd: . + @prefix : . + + :Aspect a samm:Aspect ; + samm:description "This is a test description"@en ; + samm:properties ( ) ; + samm:operations ( ) . + """ + ) ) + .build(); + final Change addFile = new AddAspectModelFile( aspectModelFile ); + ctx.applyChange( addFile ); + assertThat( aspectModel.files() ).hasSize( 1 ); + assertThat( aspectModel.aspects() ).hasSize( 1 ); + assertThat( aspectModel.aspect().getName() ).isEqualTo( "Aspect" ); + ctx.undoChange(); + assertThat( aspectModel.files() ).isEmpty(); + ctx.redoChange(); + assertThat( aspectModel.files() ).hasSize( 1 ); + } + + @Test + void testCreateFileThenAddElementDefinition() { + final AspectModel aspectModel = AspectModelBuilder.buildEmptyModel(); + assertThat( aspectModel.files() ).isEmpty(); + assertThat( aspectModel.elements() ).isEmpty(); + final AspectChangeContext ctx = new AspectChangeContext( aspectModel ); + final AspectModelFile aspectModelFile = RawAspectModelFileBuilder.builder() + .sourceLocation( Optional.of( URI.create( "file:///temp/test.ttl" ) ) ) + .build(); + + final Change changes = new ChangeGroup( + new AddAspectModelFile( aspectModelFile ), + new AddElementDefinition( AspectModelUrn.fromUrn( "urn:samm:org.eclipse.esmf.test:1.0.0#Aspect" ), + model( """ + @prefix samm: . + @prefix xsd: . + @prefix : . + + :Aspect a samm:Aspect ; + samm:description "This is a test description"@en ; + samm:properties ( ) ; + samm:operations ( ) . + """ ), aspectModelFile ) + ); + + ctx.applyChange( changes ); + assertThat( aspectModel.files() ).hasSize( 1 ); + assertThat( aspectModel.aspects() ).hasSize( 1 ); + assertThat( aspectModel.aspect().getName() ).isEqualTo( "Aspect" ); + ctx.undoChange(); + assertThat( aspectModel.files() ).isEmpty(); + ctx.redoChange(); + assertThat( aspectModel.files() ).hasSize( 1 ); + } + + @Test + void testMoveElementToNewFile() { + final AspectModel aspectModel = TestResources.load( TestAspect.ASPECT ); + assertThat( aspectModel.files() ).hasSize( 1 ); + final URI originalSourceLocation = aspectModel.aspect().getSourceFile().sourceLocation().get(); + final AspectChangeContext ctx = new AspectChangeContext( aspectModel ); + final URI sourceLocation = URI.create( "file:///temp/test.ttl" ); + final Change move = new MoveElementToNewFile( aspectModel.aspect(), List.of(), Optional.of( sourceLocation ) ); + + System.out.println( ChangeReportFormatter.INSTANCE.apply( move.report( ctx ) ) ); + + ctx.applyChange( move ); + assertThat( aspectModel.files() ).hasSize( 2 ); + assertThat( aspectModel.aspect().getSourceFile().sourceLocation() ).contains( sourceLocation ); + assertThat( aspectModel.aspect().getSourceFile().sourceModel().listStatements( null, RDF.type, SammNs.SAMM.Aspect() ).nextStatement() + .getSubject().getURI() ).isEqualTo( aspectModel.aspect().urn().toString() ); + + ctx.undoChange(); + assertThat( aspectModel.files() ).hasSize( 1 ); + assertThat( aspectModel.aspect().getSourceFile().sourceLocation() ).contains( originalSourceLocation ); + assertThat( aspectModel.aspect().getSourceFile().sourceModel().listStatements( null, RDF.type, SammNs.SAMM.Aspect() ).nextStatement() + .getSubject().getURI() ).isEqualTo( aspectModel.aspect().urn().toString() ); + + ctx.redoChange(); + assertThat( aspectModel.files() ).hasSize( 2 ); + assertThat( aspectModel.aspect().getSourceFile().sourceLocation() ).contains( sourceLocation ); + assertThat( aspectModel.aspect().getSourceFile().sourceModel().listStatements( null, RDF.type, SammNs.SAMM.Aspect() ).nextStatement() + .getSubject().getURI() ).isEqualTo( aspectModel.aspect().urn().toString() ); + } + + @Test + void testMoveElementToExistingFile() { + final Optional file1Location = Optional.of( URI.create( "file:///file1.ttl" ) ); + final Optional file2Location = Optional.of( URI.create( "file:///file2.ttl" ) ); + final AspectModel aspectModel = AspectModelBuilder.buildEmptyModel(); + final AspectChangeContext ctx = new AspectChangeContext( aspectModel ); + final AspectModelFile file1 = RawAspectModelFileBuilder.builder() + .sourceLocation( file1Location ) + .sourceModel( model( """ + @prefix samm: . + @prefix xsd: . + @prefix : . + + :Aspect a samm:Aspect ; + samm:description "This is a test description"@en ; + samm:properties ( ) ; + samm:operations ( ) . + """ + ) ) + .build(); + final AspectModelFile file2 = RawAspectModelFileBuilder.builder().sourceLocation( file2Location ).build(); + ctx.applyChange( new ChangeGroup( + new AddAspectModelFile( file1 ), + new AddAspectModelFile( file2 ) + ) ); + + assertThat( aspectModel.aspect().getSourceFile().sourceLocation() ).isEqualTo( file1Location ); + + final AspectModelUrn aspectUrn = aspectModel.aspect().urn(); + final Predicate targetFileLocator = file -> file.sourceLocation().equals( file2Location ); + ctx.applyChange( new MoveElementToOtherFile( aspectUrn, targetFileLocator ) ); + + assertThat( aspectModel.aspect().getSourceFile().sourceLocation() ).isEqualTo( file2Location ); + ctx.undoChange(); + assertThat( aspectModel.aspect().getSourceFile().sourceLocation() ).isEqualTo( file1Location ); + } + + private Model model( final String ttlRepresentation ) { + final Model model = ModelFactory.createDefaultModel(); + final InputStream in = new ByteArrayInputStream( ttlRepresentation.getBytes( StandardCharsets.UTF_8 ) ); + RDFParserRegistry.registerLangTriples( Lang.TURTLE, ReaderRiotTurtle.factory ); + model.read( in, "", RDFLanguages.strLangTurtle ); + return model; + } +} diff --git a/core/esmf-aspect-meta-model-java/src/test/java/org/eclipse/esmf/aspectmodel/edit/ModelRefactoringTest.java b/core/esmf-aspect-meta-model-java/src/test/java/org/eclipse/esmf/aspectmodel/edit/ModelRefactoringTest.java deleted file mode 100644 index 3505ebe59..000000000 --- a/core/esmf-aspect-meta-model-java/src/test/java/org/eclipse/esmf/aspectmodel/edit/ModelRefactoringTest.java +++ /dev/null @@ -1,87 +0,0 @@ -/* - * Copyright (c) 2024 Robert Bosch Manufacturing Solutions GmbH - * - * See the AUTHORS file(s) distributed with this work for additional - * information regarding authorship. - * - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at https://mozilla.org/MPL/2.0/. - * - * SPDX-License-Identifier: MPL-2.0 - */ - -package org.eclipse.esmf.aspectmodel.edit; - -import static org.assertj.core.api.Assertions.assertThat; - -import java.util.List; - -import org.eclipse.esmf.aspectmodel.urn.AspectModelUrn; -import org.eclipse.esmf.metamodel.Aspect; -import org.eclipse.esmf.metamodel.AspectModel; -import org.eclipse.esmf.metamodel.Property; -import org.eclipse.esmf.test.TestAspect; -import org.eclipse.esmf.test.TestResources; - -import org.junit.jupiter.api.Test; - -public class ModelRefactoringTest { - @Test - void testRenameElement() { - final AspectModel aspectModel = TestResources.load( TestAspect.ASPECT ); - final Aspect aspect = aspectModel.aspect(); - - final AspectModelUrn aspectUrn = aspect.urn(); - final String originalName = aspect.getName(); - assertThat( aspectModel.aspect().urn().getName() ).isEqualTo( originalName ); - - final String newName = "RenamedAspect"; - final ModelRefactoring ctx = new ModelRefactoring( aspectModel ); - final Change renameAspect = ctx.new RenameElement( aspectUrn, newName ); - ctx.applyChange( renameAspect ); - assertThat( aspectModel.aspect().urn().getName() ).isEqualTo( newName ); - } - - @Test - void testUndoRedo() { - final AspectModel aspectModel = TestResources.load( TestAspect.ASPECT ); - final Aspect aspect = aspectModel.aspect(); - - final AspectModelUrn aspectUrn = aspect.urn(); - final String originalName = aspect.getName(); - assertThat( aspectModel.aspect().urn().getName() ).isEqualTo( originalName ); - - final String newName = "RenamedAspect"; - final ModelRefactoring ctx = new ModelRefactoring( aspectModel ); - final Change renameAspect = ctx.new RenameElement( aspectUrn, newName ); - ctx.applyChange( renameAspect ); - assertThat( aspectModel.aspect().urn().getName() ).isEqualTo( newName ); - ctx.undoChange(); - assertThat( aspectModel.aspect().urn().getName() ).isEqualTo( originalName ); - ctx.redoChange(); - assertThat( aspectModel.aspect().urn().getName() ).isEqualTo( newName ); - } - - @Test - void testChangeGroups() { - final AspectModel aspectModel = TestResources.load( TestAspect.ASPECT_WITH_PROPERTY ); - final Aspect aspect = aspectModel.aspect(); - - final AspectModelUrn aspectUrn = aspect.urn(); - final String oldAspectName = aspect.getName(); - final Property property = aspect.getProperties().get( 0 ); - final String oldPropertyName = property.getName(); - - final String newAspectName = "RenamedAspect"; - final String newPropertyName = "renamedProperty"; - final ModelRefactoring ctx = new ModelRefactoring( aspectModel ); - final Change renameAspect = ctx.new RenameElement( aspectUrn, newAspectName ); - final Change renameProperty = ctx.new RenameElement( property.urn(), newPropertyName ); - - final Change group = ctx.new ChangeGroup( List.of( renameAspect, renameProperty ) ); - ctx.applyChange( group ); - assertThat( aspectModel.aspect().urn().getName() ).isEqualTo( newAspectName ); - assertThat( aspectModel.aspect().getProperties().get( 0 ).getName() ).isEqualTo( newPropertyName ); - } -} diff --git a/core/esmf-aspect-meta-model-java/src/test/java/org/eclipse/esmf/aspectmodel/edit/RdfUtilTest.java b/core/esmf-aspect-meta-model-java/src/test/java/org/eclipse/esmf/aspectmodel/edit/RdfUtilTest.java new file mode 100644 index 000000000..a34ed2293 --- /dev/null +++ b/core/esmf-aspect-meta-model-java/src/test/java/org/eclipse/esmf/aspectmodel/edit/RdfUtilTest.java @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2024 Robert Bosch Manufacturing Solutions GmbH + * + * See the AUTHORS file(s) distributed with this work for additional + * information regarding authorship. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * SPDX-License-Identifier: MPL-2.0 + */ + +package org.eclipse.esmf.aspectmodel.edit; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.List; + +import org.eclipse.esmf.metamodel.AspectModel; +import org.eclipse.esmf.metamodel.vocabulary.SammNs; +import org.eclipse.esmf.test.TestAspect; +import org.eclipse.esmf.test.TestResources; + +import org.apache.jena.rdf.model.Model; +import org.apache.jena.rdf.model.RDFList; +import org.apache.jena.rdf.model.RDFNode; +import org.apache.jena.rdf.model.Resource; +import org.apache.jena.vocabulary.RDF; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.EnumSource; + +public class RdfUtilTest { + @ParameterizedTest + @EnumSource( value = TestAspect.class ) + void testEveryElementDefinitionContainsATypeAssertion( final TestAspect aspect ) { + final AspectModel aspectModel = TestResources.load( aspect ); + aspectModel.elements().stream() + .filter( element -> !element.isAnonymous() ) + .map( element -> element.getSourceFile().sourceModel().createResource( element.urn().toString() ) ) + .forEach( elementResource -> { + final Model definition = RdfUtil.getModelElementDefinition( elementResource ); + assertThat( definition.listStatements( null, RDF.type, (RDFNode) null ).toList() ).hasSizeGreaterThan( 0 ); + assertThat( definition.listStatements( elementResource, RDF.type, (RDFNode) null ).toList() ).hasSize( 1 ); + } ); + } + + @Test + void testGetElementDefinitionForFlatDefinition() { + final AspectModel aspectModel = TestResources.load( TestAspect.ASPECT ); + final Model sourceModel = aspectModel.aspect().getSourceFile().sourceModel(); + final Resource aspectResource = sourceModel.createResource( aspectModel.aspect().urn().toString() ); + final Model definition = RdfUtil.getModelElementDefinition( aspectResource ); + assertThat( definition.size() ).isEqualTo( sourceModel.size() ); + } + + @Test + void testGetElementDefinitionForNestedDefinition() { + final AspectModel aspectModel = TestResources.load( TestAspect.ASPECT_WITH_PROPERTY ); + final Model sourceModel = aspectModel.aspect().getSourceFile().sourceModel(); + final Resource aspectResource = sourceModel.createResource( aspectModel.aspect().urn().toString() ); + final Model definition = RdfUtil.getModelElementDefinition( aspectResource ); + assertThat( definition.listStatements( null, RDF.type, (RDFNode) null ).toList() ).hasSize( 1 ); + final List propertiesList = definition.listStatements( aspectResource, SammNs.SAMM.properties(), (RDFNode) null ) + .nextStatement() + .getObject().as( RDFList.class ).asJavaList(); + assertThat( propertiesList ).hasSize( 1 ); + } + + @Test + void testGetElementDefinitionForTraitWithConstraints() { + final AspectModel aspectModel = TestResources.load( TestAspect.ASPECT_WITH_CONSTRAINTS ); + final Model sourceModel = aspectModel.aspect().getSourceFile().sourceModel(); + + final Resource traitResource = sourceModel.createResource( TestAspect.TEST_NAMESPACE + "TestRegularExpressionConstraint" ); + final Model definition = RdfUtil.getModelElementDefinition( traitResource ); + final Resource constraint = definition.getResource( traitResource.getURI() ).getProperty( SammNs.SAMMC.constraint() ).getObject() + .asResource(); + assertThat( constraint.getProperty( RDF.type ).getObject().asResource() ).isEqualTo( SammNs.SAMMC.RegularExpressionConstraint() ); + assertThat( constraint.getProperty( SammNs.SAMM.value() ).getObject().asLiteral().getLexicalForm() ).contains( "a-z" ); + } +} diff --git a/core/esmf-aspect-meta-model-java/src/test/java/org/eclipse/esmf/test/TestResources.java b/core/esmf-aspect-meta-model-java/src/test/java/org/eclipse/esmf/test/TestResources.java index a39e7ae16..ffa3acb51 100644 --- a/core/esmf-aspect-meta-model-java/src/test/java/org/eclipse/esmf/test/TestResources.java +++ b/core/esmf-aspect-meta-model-java/src/test/java/org/eclipse/esmf/test/TestResources.java @@ -14,6 +14,8 @@ package org.eclipse.esmf.test; import java.io.InputStream; +import java.net.URI; +import java.util.Optional; import org.eclipse.esmf.aspectmodel.loader.AspectModelLoader; import org.eclipse.esmf.aspectmodel.resolver.ClasspathStrategy; @@ -29,6 +31,6 @@ public static AspectModel load( final TestAspect model ) { final InputStream inputStream = TestResources.class.getClassLoader().getResourceAsStream( path ); final ResolutionStrategy testModelsResolutionStrategy = new ClasspathStrategy( "valid/" + metaModelVersion.toString().toLowerCase() ); - return new AspectModelLoader( testModelsResolutionStrategy ).load( inputStream ); + return new AspectModelLoader( testModelsResolutionStrategy ).load( inputStream, Optional.of( URI.create( "testmodel:" + path ) ) ); } } diff --git a/core/esmf-test-resources/src/main/java/org/eclipse/esmf/test/TestResources.java b/core/esmf-test-resources/src/main/java/org/eclipse/esmf/test/TestResources.java index b1bcec113..1fd096dec 100644 --- a/core/esmf-test-resources/src/main/java/org/eclipse/esmf/test/TestResources.java +++ b/core/esmf-test-resources/src/main/java/org/eclipse/esmf/test/TestResources.java @@ -14,6 +14,8 @@ package org.eclipse.esmf.test; import java.io.InputStream; +import java.net.URI; +import java.util.Optional; import org.eclipse.esmf.aspectmodel.loader.AspectModelLoader; import org.eclipse.esmf.aspectmodel.resolver.ClasspathStrategy; @@ -41,7 +43,7 @@ public static AspectModel load( final TestModel model ) { model.getName() ); final InputStream inputStream = TestResources.class.getClassLoader().getResourceAsStream( path ); final ResolutionStrategy testModelsResolutionStrategy = new ClasspathStrategy( "valid" ); - return new AspectModelLoader( testModelsResolutionStrategy ).load( inputStream ); + return new AspectModelLoader( testModelsResolutionStrategy ).load( inputStream, Optional.of( URI.create( "testmodel:" + path ) ) ); } public static Try loadPayload( final TestModel model ) { From ece6e43908911d1e6867d79884b0e9c21579217c Mon Sep 17 00:00:00 2001 From: "Textor Andreas (BCI/ESW17)" Date: Mon, 5 Aug 2024 08:04:24 +0200 Subject: [PATCH 03/33] Make output of ChangeFormatter more readable --- .../esmf/aspectmodel/{edit => }/RdfUtil.java | 29 ++++++++-- .../edit/ChangeReportFormatter.java | 55 ++++++++++++++++++- .../edit/change/AbstractChange.java | 28 ++++++++++ .../edit/change/AddAspectModelFile.java | 10 ++-- .../edit/change/AddElementDefinition.java | 8 +-- .../edit/change/EditAspectModel.java | 2 +- .../edit/change/MoveElementToNewFile.java | 2 +- .../edit/change/MoveElementToOtherFile.java | 2 +- .../edit/change/RemoveAspectModelFile.java | 6 +- .../edit/change/RemoveElementDefinition.java | 11 ++-- .../edit/change/StructuralChange.java | 2 +- .../aspectmodel/loader/AspectModelLoader.java | 25 +-------- .../edit/AspectChangeContextTest.java | 8 ++- .../esmf/aspectmodel/edit/RdfUtilTest.java | 1 + 14 files changed, 136 insertions(+), 53 deletions(-) rename core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/{edit => }/RdfUtil.java (62%) create mode 100644 core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/AbstractChange.java diff --git a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/RdfUtil.java b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/RdfUtil.java similarity index 62% rename from core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/RdfUtil.java rename to core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/RdfUtil.java index 808b4e577..bc5b867d0 100644 --- a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/RdfUtil.java +++ b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/RdfUtil.java @@ -11,10 +11,17 @@ * SPDX-License-Identifier: MPL-2.0 */ -package org.eclipse.esmf.aspectmodel.edit; +package org.eclipse.esmf.aspectmodel; -import java.io.StringWriter; +import static java.util.stream.Collectors.toSet; +import java.util.Set; +import java.util.function.Function; +import java.util.stream.Stream; + +import org.eclipse.esmf.aspectmodel.urn.AspectModelUrn; + +import com.google.common.collect.Streams; import org.apache.jena.rdf.model.Model; import org.apache.jena.rdf.model.ModelFactory; import org.apache.jena.rdf.model.Property; @@ -46,9 +53,19 @@ public static Model getModelElementDefinition( final Resource element ) { return result; } - public static String modelToString( final Model model ) { - final StringWriter stringWriter = new StringWriter(); - model.write( stringWriter, "TURTLE" ); - return stringWriter.toString(); + public static Set getAllUrnsInModel( final Model model ) { + return Streams.stream( model.listStatements().mapWith( statement -> { + final Stream subjectUri = statement.getSubject().isURIResource() + ? Stream.of( statement.getSubject().getURI() ) + : Stream.empty(); + final Stream propertyUri = Stream.of( statement.getPredicate().getURI() ); + final Stream objectUri = statement.getObject().isURIResource() + ? Stream.of( statement.getObject().asResource().getURI() ) + : Stream.empty(); + + return Stream.of( subjectUri, propertyUri, objectUri ) + .flatMap( Function.identity() ) + .flatMap( urn -> AspectModelUrn.from( urn ).toJavaOptional().stream() ); + } ) ).flatMap( Function.identity() ).collect( toSet() ); } } diff --git a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/ChangeReportFormatter.java b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/ChangeReportFormatter.java index 046f75a93..9ed5a29bb 100644 --- a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/ChangeReportFormatter.java +++ b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/ChangeReportFormatter.java @@ -13,10 +13,23 @@ package org.eclipse.esmf.aspectmodel.edit; +import java.io.StringWriter; import java.util.Map; import java.util.function.Function; +import org.eclipse.esmf.aspectmodel.RdfUtil; +import org.eclipse.esmf.aspectmodel.urn.AspectModelUrn; +import org.eclipse.esmf.metamodel.vocabulary.SammNs; + +import com.google.common.collect.Streams; +import org.apache.jena.rdf.model.Literal; import org.apache.jena.rdf.model.Model; +import org.apache.jena.rdf.model.ModelFactory; +import org.apache.jena.rdf.model.RDFNode; +import org.apache.jena.rdf.model.Resource; +import org.apache.jena.rdf.model.Statement; +import org.apache.jena.vocabulary.RDF; +import org.apache.jena.vocabulary.XSD; public class ChangeReportFormatter implements Function { public static final ChangeReportFormatter INSTANCE = new ChangeReportFormatter(); @@ -45,15 +58,53 @@ private void append( final StringBuilder builder, final ChangeReport report, fin builder.append( ": " ); if ( entry.getValue() instanceof final Model model ) { builder.append( "\n" ); - builder.append( RdfUtil.modelToString( model ) ); + show( model ).lines() + .forEach( line -> { + builder.append( indent ); + builder.append( indent ); + builder.append( indent ); + builder.append( line ); + builder.append( "\n" ); + } ); } else { builder.append( entry.getValue().toString() ); + builder.append( "\n" ); } - builder.append( "\n" ); } } } + protected String show( final Model model ) { + final Model copy = ModelFactory.createDefaultModel(); + copy.add( model ); + RdfUtil.getAllUrnsInModel( model ).forEach( urn -> { + switch ( urn.getElementType() ) { + case META_MODEL -> copy.setNsPrefix( SammNs.SAMM.getShortForm(), SammNs.SAMM.getNamespace() ); + case CHARACTERISTIC -> copy.setNsPrefix( SammNs.SAMMC.getShortForm(), SammNs.SAMMC.getNamespace() ); + case ENTITY -> copy.setNsPrefix( SammNs.SAMME.getShortForm(), SammNs.SAMME.getNamespace() ); + case UNIT -> copy.setNsPrefix( SammNs.UNIT.getShortForm(), SammNs.UNIT.getNamespace() ); + } + } ); + Streams.stream( model.listObjects() ) + .filter( RDFNode::isLiteral ) + .map( RDFNode::asLiteral ) + .map( Literal::getDatatypeURI ) + .filter( type -> type.startsWith( XSD.NS ) ) + .filter( type -> !type.equals( XSD.xstring.getURI() ) ) + .findAny() + .ifPresent( __ -> copy.setNsPrefix( "xsd", XSD.NS ) ); + Streams.stream( model.listStatements( null, RDF.type, (RDFNode) null ) ) + .map( Statement::getSubject ) + .map( Resource::getURI ) + .map( AspectModelUrn::fromUrn ) + .forEach( urn -> copy.setNsPrefix( "", urn.getUrnPrefix() ) ); + final StringWriter stringWriter = new StringWriter(); + stringWriter.append( "--------------------\n" ); + copy.write( stringWriter, "TURTLE" ); + stringWriter.append( "--------------------\n" ); + return stringWriter.toString(); + } + @Override public String apply( final ChangeReport changeReport ) { final StringBuilder builder = new StringBuilder(); diff --git a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/AbstractChange.java b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/AbstractChange.java new file mode 100644 index 000000000..9a74fd3d4 --- /dev/null +++ b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/AbstractChange.java @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2024 Robert Bosch Manufacturing Solutions GmbH + * + * See the AUTHORS file(s) distributed with this work for additional + * information regarding authorship. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * SPDX-License-Identifier: MPL-2.0 + */ + +package org.eclipse.esmf.aspectmodel.edit.change; + +import java.io.StringWriter; +import java.net.URI; + +import org.eclipse.esmf.aspectmodel.AspectModelFile; +import org.eclipse.esmf.aspectmodel.edit.Change; + +import org.apache.jena.rdf.model.Model; + +public abstract class AbstractChange implements Change { + protected String show( final AspectModelFile aspectModelFile ) { + return aspectModelFile.sourceLocation().map( URI::toString ).orElse( "(unknown file)" ); + } +} diff --git a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/AddAspectModelFile.java b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/AddAspectModelFile.java index 71073c9e9..0971c09c8 100644 --- a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/AddAspectModelFile.java +++ b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/AddAspectModelFile.java @@ -21,7 +21,7 @@ import org.eclipse.esmf.aspectmodel.edit.ChangeReport; import org.eclipse.esmf.aspectmodel.edit.ModelChangeException; -public class AddAspectModelFile implements Change { +public class AddAspectModelFile extends AbstractChange { private final AspectModelFile newFile; public AddAspectModelFile( final AspectModelFile newFile ) { @@ -56,15 +56,15 @@ private AspectModelFile fileToRemove( final ChangeContext changeContext ) { @Override public ChangeReport report( final ChangeContext changeContext ) { final AspectModelFile file = fileToRemove( changeContext ); - return new ChangeReport.EntryWithDetails( "Remove file " + file.sourceLocation(), - Map.of( "sourceModel", file.sourceModel() ) ); + return new ChangeReport.EntryWithDetails( "Remove file " + show( file ), + Map.of( "model content to remove", file.sourceModel() ) ); } }; } @Override public ChangeReport report( final ChangeContext changeContext ) { - return new ChangeReport.EntryWithDetails( "Add file " + newFile.sourceLocation(), - Map.of( "sourceModel", newFile.sourceModel() ) ); + return new ChangeReport.EntryWithDetails( "Add file " + show( newFile ), + Map.of( "model content to add", newFile.sourceModel() ) ); } } diff --git a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/AddElementDefinition.java b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/AddElementDefinition.java index d11dcda4d..40e33fd31 100644 --- a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/AddElementDefinition.java +++ b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/AddElementDefinition.java @@ -62,15 +62,15 @@ public Change reverse() { @Override public ChangeReport report( final ChangeContext changeContext ) { - return new ChangeReport.EntryWithDetails( "Remove definition of " + elementUrn + " in " + targetFile, - Map.of( "removeStatements", definition ) ); + return new ChangeReport.EntryWithDetails( "Remove definition of " + elementUrn + " in " + show( targetFile ), + Map.of( "model content to remove", definition ) ); } }; } @Override public ChangeReport report( final ChangeContext changeContext ) { - return new ChangeReport.EntryWithDetails( "Add definition of " + elementUrn + " in " + targetFile, - Map.of( "addStatements", definition ) ); + return new ChangeReport.EntryWithDetails( "Add definition of " + elementUrn + " in " + show( targetFile ), + Map.of( "model content to add", definition ) ); } } diff --git a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/EditAspectModel.java b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/EditAspectModel.java index e5d429b52..db1360d09 100644 --- a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/EditAspectModel.java +++ b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/EditAspectModel.java @@ -24,7 +24,7 @@ import org.apache.jena.rdf.model.Model; import org.apache.jena.rdf.model.ModelFactory; -public abstract class EditAspectModel implements Change { +public abstract class EditAspectModel extends AbstractChange implements Change { public record ModelChanges( Model add, Model remove ) { public static final ModelChanges NONE = new ModelChanges( null, null ); } diff --git a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/MoveElementToNewFile.java b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/MoveElementToNewFile.java index 0b69ed632..ba44d70b9 100644 --- a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/MoveElementToNewFile.java +++ b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/MoveElementToNewFile.java @@ -22,7 +22,7 @@ import org.eclipse.esmf.aspectmodel.edit.ChangeGroup; import org.eclipse.esmf.aspectmodel.edit.ChangeReport; import org.eclipse.esmf.aspectmodel.edit.ModelChangeException; -import org.eclipse.esmf.aspectmodel.edit.RdfUtil; +import org.eclipse.esmf.aspectmodel.RdfUtil; import org.eclipse.esmf.aspectmodel.resolver.modelfile.RawAspectModelFile; import org.eclipse.esmf.aspectmodel.resolver.modelfile.RawAspectModelFileBuilder; import org.eclipse.esmf.aspectmodel.urn.AspectModelUrn; diff --git a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/MoveElementToOtherFile.java b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/MoveElementToOtherFile.java index 0f2ff0503..fcd8a108e 100644 --- a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/MoveElementToOtherFile.java +++ b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/MoveElementToOtherFile.java @@ -22,7 +22,7 @@ import org.eclipse.esmf.aspectmodel.edit.ChangeGroup; import org.eclipse.esmf.aspectmodel.edit.ChangeReport; import org.eclipse.esmf.aspectmodel.edit.ModelChangeException; -import org.eclipse.esmf.aspectmodel.edit.RdfUtil; +import org.eclipse.esmf.aspectmodel.RdfUtil; import org.eclipse.esmf.aspectmodel.urn.AspectModelUrn; import org.apache.jena.rdf.model.Model; diff --git a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/RemoveAspectModelFile.java b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/RemoveAspectModelFile.java index 585c1074f..801bb4178 100644 --- a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/RemoveAspectModelFile.java +++ b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/RemoveAspectModelFile.java @@ -20,7 +20,7 @@ import org.eclipse.esmf.aspectmodel.edit.ChangeContext; import org.eclipse.esmf.aspectmodel.edit.ChangeReport; -public class RemoveAspectModelFile implements Change { +public class RemoveAspectModelFile extends AbstractChange { private final AspectModelFile fileToRemove; public RemoveAspectModelFile( final AspectModelFile fileToRemove ) { @@ -39,7 +39,7 @@ public Change reverse() { @Override public ChangeReport report( final ChangeContext changeContext ) { - return new ChangeReport.EntryWithDetails( "Remove file " + fileToRemove.sourceLocation(), - Map.of( "sourceModel", fileToRemove.sourceModel() ) ); + return new ChangeReport.EntryWithDetails( "Remove file " + show( fileToRemove ), + Map.of( "model content", fileToRemove.sourceModel() ) ); } } diff --git a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/RemoveElementDefinition.java b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/RemoveElementDefinition.java index 36774d88c..b4737fcfa 100644 --- a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/RemoveElementDefinition.java +++ b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/RemoveElementDefinition.java @@ -16,10 +16,10 @@ import java.util.Map; import org.eclipse.esmf.aspectmodel.AspectModelFile; +import org.eclipse.esmf.aspectmodel.RdfUtil; import org.eclipse.esmf.aspectmodel.edit.Change; import org.eclipse.esmf.aspectmodel.edit.ChangeContext; import org.eclipse.esmf.aspectmodel.edit.ChangeReport; -import org.eclipse.esmf.aspectmodel.edit.RdfUtil; import org.eclipse.esmf.aspectmodel.urn.AspectModelUrn; import org.apache.jena.rdf.model.Model; @@ -68,8 +68,9 @@ public Change reverse() { @Override public ChangeReport report( final ChangeContext changeContext ) { - return new ChangeReport.SimpleEntry( - "Add back definition of " + elementUrn + " to " + fileWithOriginalDefinition.sourceLocation() ); + return new ChangeReport.EntryWithDetails( + "Add back definition of " + elementUrn + " to " + show( fileWithOriginalDefinition ), + Map.of( "model content to add", definition ) ); } }; } @@ -78,7 +79,7 @@ public ChangeReport report( final ChangeContext changeContext ) { public ChangeReport report( final ChangeContext changeContext ) { changesPerFile( changeContext ); return new ChangeReport.EntryWithDetails( - "Remove definition of " + elementUrn + " from " + fileWithOriginalDefinition.sourceLocation(), - Map.of( "definition", definition ) ); + "Remove definition of " + elementUrn + " from " + show( fileWithOriginalDefinition ), + Map.of( "model content to remove", definition ) ); } } diff --git a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/StructuralChange.java b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/StructuralChange.java index 9885f3c43..dd5568285 100644 --- a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/StructuralChange.java +++ b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/StructuralChange.java @@ -22,7 +22,7 @@ import org.apache.jena.rdf.model.RDFNode; import org.apache.jena.vocabulary.RDF; -public abstract class StructuralChange implements Change { +public abstract class StructuralChange extends AbstractChange implements Change { protected AspectModelFile sourceFile( final ChangeContext changeContext, final AspectModelUrn elementUrn ) { return changeContext.aspectModel().files().stream().filter( aspectModelFile -> aspectModelFile.sourceModel() diff --git a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/loader/AspectModelLoader.java b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/loader/AspectModelLoader.java index d87539597..d7ec51fc2 100644 --- a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/loader/AspectModelLoader.java +++ b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/loader/AspectModelLoader.java @@ -13,8 +13,6 @@ package org.eclipse.esmf.aspectmodel.loader; -import static java.util.stream.Collectors.toSet; - import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.File; @@ -32,14 +30,13 @@ import java.util.List; import java.util.Optional; import java.util.Set; -import java.util.function.Function; import java.util.function.Supplier; -import java.util.stream.Stream; import java.util.zip.ZipEntry; import java.util.zip.ZipInputStream; import org.eclipse.esmf.aspectmodel.AspectModelBuilder; import org.eclipse.esmf.aspectmodel.AspectModelFile; +import org.eclipse.esmf.aspectmodel.RdfUtil; import org.eclipse.esmf.aspectmodel.resolver.AspectModelFileLoader; import org.eclipse.esmf.aspectmodel.resolver.EitherStrategy; import org.eclipse.esmf.aspectmodel.resolver.FileSystemStrategy; @@ -282,23 +279,6 @@ private LoaderContext() { } } - private Set getAllUrnsInModel( final Model model ) { - return Streams.stream( model.listStatements().mapWith( statement -> { - final Stream subjectUri = statement.getSubject().isURIResource() - ? Stream.of( statement.getSubject().getURI() ) - : Stream.empty(); - final Stream propertyUri = Stream.of( statement.getPredicate().getURI() ); - final Stream objectUri = statement.getObject().isURIResource() - ? Stream.of( statement.getObject().asResource().getURI() ) - : Stream.empty(); - - return Stream.of( subjectUri, propertyUri, objectUri ) - .flatMap( Function.identity() ) - .flatMap( urn -> AspectModelUrn.from( urn ).toJavaOptional().stream() ) - .map( AspectModelUrn::toString ); - } ) ).flatMap( Function.identity() ).collect( toSet() ); - } - /** * Adapter that enables the resolver to handle URNs with the legacy "urn:bamm:" prefix. * @@ -361,7 +341,8 @@ private void urnsFromModelNeedResolution( final AspectModelFile modelFile, final .filter( uri -> uri.startsWith( "urn:samm:" ) ) .forEach( urn -> context.resolvedUrns().add( urn ) ); - getAllUrnsInModel( modelFile.sourceModel() ).stream() + RdfUtil.getAllUrnsInModel( modelFile.sourceModel() ).stream() + .map( AspectModelUrn::toString ) .filter( urn -> !context.resolvedUrns().contains( urn ) ) .filter( urn -> !urn.startsWith( XSD.NS ) ) .filter( urn -> !urn.startsWith( RDF.uri ) ) diff --git a/core/esmf-aspect-meta-model-java/src/test/java/org/eclipse/esmf/aspectmodel/edit/AspectChangeContextTest.java b/core/esmf-aspect-meta-model-java/src/test/java/org/eclipse/esmf/aspectmodel/edit/AspectChangeContextTest.java index 105bd442f..b06ab019e 100644 --- a/core/esmf-aspect-meta-model-java/src/test/java/org/eclipse/esmf/aspectmodel/edit/AspectChangeContextTest.java +++ b/core/esmf-aspect-meta-model-java/src/test/java/org/eclipse/esmf/aspectmodel/edit/AspectChangeContextTest.java @@ -267,16 +267,20 @@ void testMoveElementToExistingFile() { ) ) .build(); final AspectModelFile file2 = RawAspectModelFileBuilder.builder().sourceLocation( file2Location ).build(); + ctx.applyChange( new ChangeGroup( new AddAspectModelFile( file1 ), new AddAspectModelFile( file2 ) ) ); - assertThat( aspectModel.aspect().getSourceFile().sourceLocation() ).isEqualTo( file1Location ); final AspectModelUrn aspectUrn = aspectModel.aspect().urn(); final Predicate targetFileLocator = file -> file.sourceLocation().equals( file2Location ); - ctx.applyChange( new MoveElementToOtherFile( aspectUrn, targetFileLocator ) ); + final Change move = new MoveElementToOtherFile( aspectUrn, targetFileLocator ); + + System.out.println( ChangeReportFormatter.INSTANCE.apply( move.report( ctx ) ) ); + + ctx.applyChange( move ); assertThat( aspectModel.aspect().getSourceFile().sourceLocation() ).isEqualTo( file2Location ); ctx.undoChange(); diff --git a/core/esmf-aspect-meta-model-java/src/test/java/org/eclipse/esmf/aspectmodel/edit/RdfUtilTest.java b/core/esmf-aspect-meta-model-java/src/test/java/org/eclipse/esmf/aspectmodel/edit/RdfUtilTest.java index a34ed2293..1382c3645 100644 --- a/core/esmf-aspect-meta-model-java/src/test/java/org/eclipse/esmf/aspectmodel/edit/RdfUtilTest.java +++ b/core/esmf-aspect-meta-model-java/src/test/java/org/eclipse/esmf/aspectmodel/edit/RdfUtilTest.java @@ -17,6 +17,7 @@ import java.util.List; +import org.eclipse.esmf.aspectmodel.RdfUtil; import org.eclipse.esmf.metamodel.AspectModel; import org.eclipse.esmf.metamodel.vocabulary.SammNs; import org.eclipse.esmf.test.TestAspect; From d64a4bddc1603231c171266da54766e1237274e5 Mon Sep 17 00:00:00 2001 From: "Textor Andreas (BCI/ESW17)" Date: Tue, 6 Aug 2024 15:31:25 +0200 Subject: [PATCH 04/33] Add MoveElementToOtherNamespaceNewFile and fix ChangeReport --- .../aspectmodel/edit/AspectChangeContext.java | 11 ++- .../edit/AspectChangeContextConfig.java | 5 +- .../eclipse/esmf/aspectmodel/edit/Change.java | 4 +- .../esmf/aspectmodel/edit/ChangeContext.java | 6 +- .../esmf/aspectmodel/edit/ChangeGroup.java | 23 +++-- .../esmf/aspectmodel/edit/ChangeReport.java | 5 +- .../edit/ChangeReportFormatter.java | 62 ++++++++++---- .../edit/change/AbstractChange.java | 10 ++- .../edit/change/AddAspectModelFile.java | 33 ++++---- .../edit/change/AddElementDefinition.java | 33 ++------ .../edit/change/EditAspectModel.java | 37 +++++--- .../edit/change/MoveElementToNewFile.java | 30 ++++--- .../edit/change/MoveElementToOtherFile.java | 23 ++--- .../change/MoveElementToOtherNamespace.java | 45 ---------- .../MoveElementToOtherNamespaceNewFile.java | 84 +++++++++++++++++++ .../edit/change/RemoveAspectModelFile.java | 12 +-- .../edit/change/RemoveElementDefinition.java | 24 +----- .../edit/change/RenameElement.java | 6 +- .../aspectmodel/edit/change/RenameUrn.java | 17 ++-- .../edit/change/StructuralChange.java | 13 +-- .../esmf/metamodel/impl/DefaultNamespace.java | 6 ++ .../edit/AspectChangeContextTest.java | 49 +++++++++-- 22 files changed, 314 insertions(+), 224 deletions(-) delete mode 100644 core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/MoveElementToOtherNamespace.java create mode 100644 core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/MoveElementToOtherNamespaceNewFile.java diff --git a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/AspectChangeContext.java b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/AspectChangeContext.java index e236ac942..78ff09ec5 100644 --- a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/AspectChangeContext.java +++ b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/AspectChangeContext.java @@ -15,8 +15,10 @@ import java.util.ArrayDeque; import java.util.Deque; +import java.util.List; import org.eclipse.esmf.aspectmodel.AspectModelBuilder; +import org.eclipse.esmf.aspectmodel.AspectModelFile; import org.eclipse.esmf.metamodel.AspectModel; import org.eclipse.esmf.metamodel.impl.DefaultAspectModel; @@ -38,10 +40,11 @@ public AspectChangeContext( final AspectModel aspectModel ) { this( AspectChangeContextConfigBuilder.builder().build(), aspectModel ); } - public synchronized void applyChange( final Change change ) { - change.fire( this ); + public synchronized ChangeReport applyChange( final Change change ) { + final ChangeReport result = change.fire( this ); updateAspectModelAfterChange(); undoStack.offerLast( change.reverse() ); + return result; } public synchronized void undoChange() { @@ -72,8 +75,8 @@ private void updateAspectModelAfterChange() { } @Override - public AspectModel aspectModel() { - return aspectModel; + public List aspectModelFiles() { + return aspectModel.files(); } @Override diff --git a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/AspectChangeContextConfig.java b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/AspectChangeContextConfig.java index 3d1524821..9519ee739 100644 --- a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/AspectChangeContextConfig.java +++ b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/AspectChangeContextConfig.java @@ -19,8 +19,11 @@ @RecordBuilder public record AspectChangeContextConfig( - List defaultFileHeader + List defaultFileHeader, + boolean detailedChangeReport ) { + public static AspectChangeContextConfig DEFAULT = AspectChangeContextConfigBuilder.builder().build(); + public AspectChangeContextConfig { if ( defaultFileHeader == null ) { defaultFileHeader = List.of(); diff --git a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/Change.java b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/Change.java index 11585f25a..52eeec46c 100644 --- a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/Change.java +++ b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/Change.java @@ -14,9 +14,7 @@ package org.eclipse.esmf.aspectmodel.edit; public interface Change { - void fire( ChangeContext changeContext ); + ChangeReport fire( ChangeContext changeContext ); Change reverse(); - - ChangeReport report( ChangeContext changeContext ); } diff --git a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/ChangeContext.java b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/ChangeContext.java index a42b93b01..c28230b2d 100644 --- a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/ChangeContext.java +++ b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/ChangeContext.java @@ -13,10 +13,12 @@ package org.eclipse.esmf.aspectmodel.edit; -import org.eclipse.esmf.metamodel.AspectModel; +import java.util.List; + +import org.eclipse.esmf.aspectmodel.AspectModelFile; public interface ChangeContext { - AspectModel aspectModel(); + List aspectModelFiles(); AspectChangeContextConfig config(); } diff --git a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/ChangeGroup.java b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/ChangeGroup.java index f4ffc24c4..cbb6f3ba2 100644 --- a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/ChangeGroup.java +++ b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/ChangeGroup.java @@ -18,19 +18,29 @@ import java.util.List; public class ChangeGroup implements Change { + private final String summary; private final List changes; + public ChangeGroup( final String summary, final Change... changes ) { + this( summary, Arrays.asList( changes ) ); + } + public ChangeGroup( final Change... changes ) { - this( Arrays.asList( changes ) ); + this( null, Arrays.asList( changes ) ); } - public ChangeGroup( final List changes ) { + public ChangeGroup( final String summary, final List changes ) { + this.summary = summary; this.changes = changes; } + public ChangeGroup( final List changes ) { + this( null, changes ); + } + @Override - public void fire( final ChangeContext changeContext ) { - changes.forEach( change -> change.fire( changeContext ) ); + public ChangeReport fire( final ChangeContext changeContext ) { + return new ChangeReport.MultipleEntries( summary, changes.stream().map( change -> change.fire( changeContext ) ).toList() ); } @Override @@ -41,9 +51,4 @@ public Change reverse() { } return new ChangeGroup( reversedChanges ); } - - @Override - public ChangeReport report( final ChangeContext changeContext ) { - return new ChangeReport.MultipleEntries( changes.stream().map( change -> change.report( changeContext ) ).toList() ); - } } diff --git a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/ChangeReport.java b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/ChangeReport.java index dbd0ddaa6..48d5841f4 100644 --- a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/ChangeReport.java +++ b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/ChangeReport.java @@ -23,6 +23,9 @@ record SimpleEntry( String text ) implements ChangeReport { record EntryWithDetails( String summary, Map details ) implements ChangeReport { } - record MultipleEntries( List entries ) implements ChangeReport { + record MultipleEntries( String summary, List entries ) implements ChangeReport { + public MultipleEntries( final List entries ) { + this( null, entries ); + } } } diff --git a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/ChangeReportFormatter.java b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/ChangeReportFormatter.java index 9ed5a29bb..935033b10 100644 --- a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/ChangeReportFormatter.java +++ b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/ChangeReportFormatter.java @@ -14,8 +14,9 @@ package org.eclipse.esmf.aspectmodel.edit; import java.io.StringWriter; +import java.util.List; import java.util.Map; -import java.util.function.Function; +import java.util.function.BiFunction; import org.eclipse.esmf.aspectmodel.RdfUtil; import org.eclipse.esmf.aspectmodel.urn.AspectModelUrn; @@ -31,42 +32,73 @@ import org.apache.jena.vocabulary.RDF; import org.apache.jena.vocabulary.XSD; -public class ChangeReportFormatter implements Function { +public class ChangeReportFormatter implements BiFunction { public static final ChangeReportFormatter INSTANCE = new ChangeReportFormatter(); private ChangeReportFormatter() { } - private void append( final StringBuilder builder, final ChangeReport report, final int indentation ) { - final String indent = " ".repeat( indentation ); + private void append( final StringBuilder builder, final ChangeReport report, final AspectChangeContextConfig config, + final int indentationLevel ) { + final String indent = " ".repeat( indentationLevel ); if ( report instanceof final ChangeReport.SimpleEntry simpleEntry ) { + builder.append( indent ); + builder.append( "- " ); builder.append( simpleEntry.text() ); builder.append( "\n" ); } else if ( report instanceof final ChangeReport.MultipleEntries multipleEntries ) { - for ( final ChangeReport entry : multipleEntries.entries() ) { - append( builder, entry, indentation + 2 ); + if ( multipleEntries.summary() != null ) { + builder.append( indent ); + builder.append( "- " ); + builder.append( multipleEntries.summary() ); builder.append( "\n" ); } + final List entries = multipleEntries.entries(); + for ( int i = 0; i < entries.size(); i++ ) { + final ChangeReport entry = entries.get( i ); + final int entryIndentation = multipleEntries.summary() == null + ? indentationLevel + : indentationLevel + 1; + append( builder, entry, config, entryIndentation ); + if ( i < entries.size() - 1 ) { + builder.append( "\n" ); + } + } } else if ( report instanceof final ChangeReport.EntryWithDetails entryWithDetails ) { builder.append( indent ); + builder.append( "- " ); builder.append( entryWithDetails.summary() ); builder.append( "\n" ); for ( final Map.Entry entry : entryWithDetails.details().entrySet() ) { - builder.append( indent ); - builder.append( indent ); - builder.append( entry.getKey() ); - builder.append( ": " ); - if ( entry.getValue() instanceof final Model model ) { + if ( config.detailedChangeReport() && entry.getValue() instanceof final Model model ) { + builder.append( indent ); + builder.append( " - " ); + builder.append( entry.getKey() ); + builder.append( ": " ); builder.append( "\n" ); show( model ).lines() .forEach( line -> { builder.append( indent ); - builder.append( indent ); - builder.append( indent ); + builder.append( " " ); builder.append( line ); builder.append( "\n" ); } ); + } else if ( !config.detailedChangeReport() && entry.getValue() instanceof final Model model ) { + final int numberOfStatements = model.listStatements().toList().size(); + if ( numberOfStatements > 0 ) { + builder.append( indent ); + builder.append( " - " ); + builder.append( entry.getKey() ); + builder.append( ": " ); + builder.append( numberOfStatements ); + builder.append( " RDF statements" ); + builder.append( "\n" ); + } } else { + builder.append( indent ); + builder.append( " - " ); + builder.append( entry.getKey() ); + builder.append( ": " ); builder.append( entry.getValue().toString() ); builder.append( "\n" ); } @@ -106,9 +138,9 @@ protected String show( final Model model ) { } @Override - public String apply( final ChangeReport changeReport ) { + public String apply( final ChangeReport changeReport, final AspectChangeContextConfig config ) { final StringBuilder builder = new StringBuilder(); - append( builder, changeReport, 0 ); + append( builder, changeReport, config, 0 ); return builder.toString(); } } diff --git a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/AbstractChange.java b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/AbstractChange.java index 9a74fd3d4..1d2c76c68 100644 --- a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/AbstractChange.java +++ b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/AbstractChange.java @@ -13,16 +13,18 @@ package org.eclipse.esmf.aspectmodel.edit.change; -import java.io.StringWriter; import java.net.URI; +import java.util.Optional; import org.eclipse.esmf.aspectmodel.AspectModelFile; import org.eclipse.esmf.aspectmodel.edit.Change; -import org.apache.jena.rdf.model.Model; - public abstract class AbstractChange implements Change { protected String show( final AspectModelFile aspectModelFile ) { - return aspectModelFile.sourceLocation().map( URI::toString ).orElse( "(unknown file)" ); + return show( aspectModelFile.sourceLocation() ); + } + + protected String show( final Optional sourceLocation ) { + return sourceLocation.map( URI::toString ).orElse( "(unknown file)" ); } } diff --git a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/AddAspectModelFile.java b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/AddAspectModelFile.java index 0971c09c8..19b74bd59 100644 --- a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/AddAspectModelFile.java +++ b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/AddAspectModelFile.java @@ -21,6 +21,9 @@ import org.eclipse.esmf.aspectmodel.edit.ChangeReport; import org.eclipse.esmf.aspectmodel.edit.ModelChangeException; +import org.apache.jena.rdf.model.Model; +import org.apache.jena.rdf.model.ModelFactory; + public class AddAspectModelFile extends AbstractChange { private final AspectModelFile newFile; @@ -29,16 +32,23 @@ public AddAspectModelFile( final AspectModelFile newFile ) { } @Override - public void fire( final ChangeContext changeContext ) { - changeContext.aspectModel().files().add( newFile ); + public ChangeReport fire( final ChangeContext changeContext ) { + changeContext.aspectModelFiles().add( newFile ); + final Model contentToAdd = ModelFactory.createDefaultModel(); + contentToAdd.add( newFile.sourceModel() ); + return new ChangeReport.EntryWithDetails( "Add file " + show( newFile ), + Map.of( "Model content to add", contentToAdd ) ); } @Override public Change reverse() { return new Change() { @Override - public void fire( final ChangeContext changeContext ) { - changeContext.aspectModel().files().remove( fileToRemove( changeContext ) ); + public ChangeReport fire( final ChangeContext changeContext ) { + final AspectModelFile file = fileToRemove( changeContext ); + changeContext.aspectModelFiles().remove( file ); + return new ChangeReport.EntryWithDetails( "Remove file " + show( file ), + Map.of( "model content to remove", file.sourceModel() ) ); } @Override @@ -47,24 +57,11 @@ public Change reverse() { } private AspectModelFile fileToRemove( final ChangeContext changeContext ) { - return changeContext.aspectModel().files().stream() + return changeContext.aspectModelFiles().stream() .filter( file -> file.sourceLocation().equals( newFile.sourceLocation() ) ) .findFirst() .orElseThrow( () -> new ModelChangeException( "Unable to remove Aspect Model File" ) ); } - - @Override - public ChangeReport report( final ChangeContext changeContext ) { - final AspectModelFile file = fileToRemove( changeContext ); - return new ChangeReport.EntryWithDetails( "Remove file " + show( file ), - Map.of( "model content to remove", file.sourceModel() ) ); - } }; } - - @Override - public ChangeReport report( final ChangeContext changeContext ) { - return new ChangeReport.EntryWithDetails( "Add file " + show( newFile ), - Map.of( "model content to add", newFile.sourceModel() ) ); - } } diff --git a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/AddElementDefinition.java b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/AddElementDefinition.java index 40e33fd31..6b19f42fb 100644 --- a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/AddElementDefinition.java +++ b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/AddElementDefinition.java @@ -13,12 +13,8 @@ package org.eclipse.esmf.aspectmodel.edit.change; -import java.util.Map; - import org.eclipse.esmf.aspectmodel.AspectModelFile; import org.eclipse.esmf.aspectmodel.edit.Change; -import org.eclipse.esmf.aspectmodel.edit.ChangeContext; -import org.eclipse.esmf.aspectmodel.edit.ChangeReport; import org.eclipse.esmf.aspectmodel.urn.AspectModelUrn; import org.apache.jena.rdf.model.Model; @@ -37,11 +33,10 @@ public AddElementDefinition( final AspectModelUrn elementUrn, final Model defini @Override protected ModelChanges calculateChangesForFile( final AspectModelFile aspectModelFile ) { - if ( !aspectModelFile.equals( targetFile ) ) { - return ModelChanges.NONE; - } - - return new ModelChanges( definition, ModelFactory.createDefaultModel() ); + return aspectModelFile.equals( targetFile ) + ? new ModelChanges( definition, ModelFactory.createDefaultModel(), + "Add definition of " + elementUrn ) + : ModelChanges.NONE; } @Override @@ -49,28 +44,16 @@ public Change reverse() { return new EditAspectModel() { @Override protected ModelChanges calculateChangesForFile( final AspectModelFile aspectModelFile ) { - if ( aspectModelFile.equals( targetFile ) ) { - return new ModelChanges( ModelFactory.createDefaultModel(), definition ); - } - return ModelChanges.NONE; + return aspectModelFile.sourceLocation().equals( targetFile.sourceLocation() ) + ? new ModelChanges( ModelFactory.createDefaultModel(), definition, + "Remove definition of " + elementUrn ) + : ModelChanges.NONE; } @Override public Change reverse() { return AddElementDefinition.this; } - - @Override - public ChangeReport report( final ChangeContext changeContext ) { - return new ChangeReport.EntryWithDetails( "Remove definition of " + elementUrn + " in " + show( targetFile ), - Map.of( "model content to remove", definition ) ); - } }; } - - @Override - public ChangeReport report( final ChangeContext changeContext ) { - return new ChangeReport.EntryWithDetails( "Add definition of " + elementUrn + " in " + show( targetFile ), - Map.of( "model content to add", definition ) ); - } } diff --git a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/EditAspectModel.java b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/EditAspectModel.java index db1360d09..f3399b67c 100644 --- a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/EditAspectModel.java +++ b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/EditAspectModel.java @@ -18,38 +18,51 @@ import java.util.stream.Collectors; import org.eclipse.esmf.aspectmodel.AspectModelFile; -import org.eclipse.esmf.aspectmodel.edit.Change; import org.eclipse.esmf.aspectmodel.edit.ChangeContext; +import org.eclipse.esmf.aspectmodel.edit.ChangeReport; import org.apache.jena.rdf.model.Model; -import org.apache.jena.rdf.model.ModelFactory; -public abstract class EditAspectModel extends AbstractChange implements Change { - public record ModelChanges( Model add, Model remove ) { - public static final ModelChanges NONE = new ModelChanges( null, null ); +public abstract class EditAspectModel extends AbstractChange { + public record ModelChanges( Model add, Model remove, String description ) { + public static final ModelChanges NONE = new ModelChanges( null, null, "" ); } - private Map changesPerFile = null; + protected Map changesPerFile = null; - synchronized protected Map changesPerFile( final ChangeContext changeContext ) { + synchronized protected void prepare( final ChangeContext changeContext ) { if ( changesPerFile == null ) { - changesPerFile = changeContext.aspectModel().files().stream() + changesPerFile = changeContext.aspectModelFiles().stream() .map( file -> new AbstractMap.SimpleEntry<>( file, calculateChangesForFile( file ) ) ) .filter( entry -> entry.getValue() != ModelChanges.NONE ) .collect( Collectors.toMap( AbstractMap.SimpleEntry::getKey, AbstractMap.SimpleEntry::getValue ) ); } - return changesPerFile; } abstract protected ModelChanges calculateChangesForFile( AspectModelFile aspectModelFile ); @Override - public void fire( final ChangeContext changeContext ) { - changesPerFile( changeContext ).forEach( ( file, modelChanges ) -> { - if ( changeContext.aspectModel().files().contains( file ) ) { + public ChangeReport fire( final ChangeContext changeContext ) { + prepare( changeContext ); + changesPerFile.forEach( ( file, modelChanges ) -> { + if ( changeContext.aspectModelFiles().contains( file ) ) { file.sourceModel().add( modelChanges.add() ); file.sourceModel().remove( modelChanges.remove() ); } } ); + + return new ChangeReport.MultipleEntries( + changesPerFile.entrySet().stream(). map( entry -> { + final AspectModelFile file = entry.getKey(); + final ModelChanges modelChanges = entry.getValue(); + return new ChangeReport.EntryWithDetails( modelChanges.description(), Map.of( + "Add content in " + show( file ), modelChanges.add(), + "Remove content from " + show( file ), modelChanges.remove() ) + .entrySet().stream() + .filter( descriptionEntry -> !descriptionEntry.getValue().isEmpty() ) + .collect( Collectors.toMap( Map.Entry::getKey, Map.Entry::getValue ) ) ); + } + ).toList() + ); } } diff --git a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/MoveElementToNewFile.java b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/MoveElementToNewFile.java index ba44d70b9..68076f47e 100644 --- a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/MoveElementToNewFile.java +++ b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/MoveElementToNewFile.java @@ -17,12 +17,12 @@ import java.util.List; import java.util.Optional; +import org.eclipse.esmf.aspectmodel.RdfUtil; import org.eclipse.esmf.aspectmodel.edit.Change; import org.eclipse.esmf.aspectmodel.edit.ChangeContext; import org.eclipse.esmf.aspectmodel.edit.ChangeGroup; import org.eclipse.esmf.aspectmodel.edit.ChangeReport; import org.eclipse.esmf.aspectmodel.edit.ModelChangeException; -import org.eclipse.esmf.aspectmodel.RdfUtil; import org.eclipse.esmf.aspectmodel.resolver.modelfile.RawAspectModelFile; import org.eclipse.esmf.aspectmodel.resolver.modelfile.RawAspectModelFileBuilder; import org.eclipse.esmf.aspectmodel.urn.AspectModelUrn; @@ -39,6 +39,10 @@ public class MoveElementToNewFile extends StructuralChange { private final AspectModelUrn elementUrn; private ChangeGroup changes = null; + public MoveElementToNewFile( final ModelElement modelElement, final Optional sourceLocation ) { + this( modelElement, null, sourceLocation ); + } + public MoveElementToNewFile( final ModelElement modelElement, final List headerComment, final Optional sourceLocation ) { this( modelElement.urn(), headerComment, sourceLocation ); if ( modelElement.isAnonymous() ) { @@ -52,14 +56,13 @@ public MoveElementToNewFile( final AspectModelUrn elementUrn, final List this.elementUrn = elementUrn; } - private void prepareChanges( final ChangeContext changeContext ) { - if ( changes != null ) { - return; - } - + protected void prepare( final ChangeContext changeContext ) { // Prepare new file + final List fileHeader = Optional.ofNullable( headerComment ) + .or( () -> Optional.ofNullable( changeContext.config().defaultFileHeader() ) ) + .orElse( List.of() ); final RawAspectModelFile targetFile = RawAspectModelFileBuilder.builder() - .headerComment( headerComment ) + .headerComment( fileHeader ) .sourceLocation( sourceLocation ) .build(); final Model targetModel = targetFile.sourceModel(); @@ -77,6 +80,7 @@ private void prepareChanges( final ChangeContext changeContext ) { // Perform move of element definition changes = new ChangeGroup( + "Move element " + elementUrn + " to new file " + show( targetFile ), new RemoveElementDefinition( elementUrn ), new AddAspectModelFile( targetFile ), new AddElementDefinition( elementUrn, definition, targetFile ) @@ -84,19 +88,13 @@ private void prepareChanges( final ChangeContext changeContext ) { } @Override - public void fire( final ChangeContext changeContext ) { - prepareChanges( changeContext ); - changes.fire( changeContext ); + public ChangeReport fire( final ChangeContext changeContext ) { + prepare( changeContext ); + return changes.fire( changeContext ); } @Override public Change reverse() { return changes.reverse(); } - - @Override - public ChangeReport report( final ChangeContext changeContext ) { - prepareChanges( changeContext ); - return changes.report( changeContext ); - } } diff --git a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/MoveElementToOtherFile.java b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/MoveElementToOtherFile.java index fcd8a108e..274b40aa2 100644 --- a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/MoveElementToOtherFile.java +++ b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/MoveElementToOtherFile.java @@ -17,12 +17,12 @@ import java.util.function.Predicate; import org.eclipse.esmf.aspectmodel.AspectModelFile; +import org.eclipse.esmf.aspectmodel.RdfUtil; import org.eclipse.esmf.aspectmodel.edit.Change; import org.eclipse.esmf.aspectmodel.edit.ChangeContext; import org.eclipse.esmf.aspectmodel.edit.ChangeGroup; import org.eclipse.esmf.aspectmodel.edit.ChangeReport; import org.eclipse.esmf.aspectmodel.edit.ModelChangeException; -import org.eclipse.esmf.aspectmodel.RdfUtil; import org.eclipse.esmf.aspectmodel.urn.AspectModelUrn; import org.apache.jena.rdf.model.Model; @@ -38,12 +38,8 @@ public MoveElementToOtherFile( final AspectModelUrn elementUrn, final Predicate< this.targetFileSelector = targetFileSelector; } - private void prepareChanges( final ChangeContext changeContext ) { - if ( changes != null ) { - return; - } - - final List targetFiles = changeContext.aspectModel().files().stream().filter( targetFileSelector ).toList(); + protected void prepare( final ChangeContext changeContext ) { + final List targetFiles = changeContext.aspectModelFiles().stream().filter( targetFileSelector ).toList(); if ( targetFiles.size() > 1 ) { throw new ModelChangeException( "Can not determine target file to move element" ); } @@ -62,25 +58,20 @@ private void prepareChanges( final ChangeContext changeContext ) { // Perform move of element definition changes = new ChangeGroup( + "Move element " + elementUrn + " to file " + show( targetFile ), new RemoveElementDefinition( elementUrn ), new AddElementDefinition( elementUrn, definition, targetFile ) ); } @Override - public void fire( final ChangeContext changeContext ) { - prepareChanges( changeContext ); - changes.fire( changeContext ); + public ChangeReport fire( final ChangeContext changeContext ) { + prepare( changeContext ); + return changes.fire( changeContext ); } @Override public Change reverse() { return changes.reverse(); } - - @Override - public ChangeReport report( final ChangeContext changeContext ) { - prepareChanges( changeContext ); - return changes.report( changeContext ); - } } diff --git a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/MoveElementToOtherNamespace.java b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/MoveElementToOtherNamespace.java deleted file mode 100644 index f2aa41d80..000000000 --- a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/MoveElementToOtherNamespace.java +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright (c) 2024 Robert Bosch Manufacturing Solutions GmbH - * - * See the AUTHORS file(s) distributed with this work for additional - * information regarding authorship. - * - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at https://mozilla.org/MPL/2.0/. - * - * SPDX-License-Identifier: MPL-2.0 - */ - -package org.eclipse.esmf.aspectmodel.edit.change; - -import org.eclipse.esmf.aspectmodel.edit.Change; -import org.eclipse.esmf.aspectmodel.edit.ChangeContext; -import org.eclipse.esmf.aspectmodel.edit.ChangeReport; -import org.eclipse.esmf.aspectmodel.urn.AspectModelUrn; -import org.eclipse.esmf.metamodel.Namespace; - -public class MoveElementToOtherNamespace implements Change { - private final AspectModelUrn elementUrn; - private final Namespace targetNamespace; - - public MoveElementToOtherNamespace( final AspectModelUrn elementUrn, final Namespace targetNamespace ) { - this.elementUrn = elementUrn; - this.targetNamespace = targetNamespace; - } - - @Override - public void fire( final ChangeContext changeContext ) { - - } - - @Override - public Change reverse() { - return null; - } - - @Override - public ChangeReport report( final ChangeContext changeContext ) { - return null; - } -} diff --git a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/MoveElementToOtherNamespaceNewFile.java b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/MoveElementToOtherNamespaceNewFile.java new file mode 100644 index 000000000..87bb3faff --- /dev/null +++ b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/MoveElementToOtherNamespaceNewFile.java @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2024 Robert Bosch Manufacturing Solutions GmbH + * + * See the AUTHORS file(s) distributed with this work for additional + * information regarding authorship. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * SPDX-License-Identifier: MPL-2.0 + */ + +package org.eclipse.esmf.aspectmodel.edit.change; + +import java.net.URI; +import java.util.List; +import java.util.Optional; + +import org.eclipse.esmf.aspectmodel.edit.Change; +import org.eclipse.esmf.aspectmodel.edit.ChangeContext; +import org.eclipse.esmf.aspectmodel.edit.ChangeGroup; +import org.eclipse.esmf.aspectmodel.edit.ChangeReport; +import org.eclipse.esmf.aspectmodel.edit.ModelChangeException; +import org.eclipse.esmf.aspectmodel.urn.AspectModelUrn; +import org.eclipse.esmf.metamodel.ModelElement; +import org.eclipse.esmf.metamodel.Namespace; + +public class MoveElementToOtherNamespaceNewFile extends StructuralChange { + private final List headerComment; + private final Optional sourceLocation; + private final AspectModelUrn elementUrn; + private final Namespace targetNamespace; + private ChangeGroup changes = null; + + public MoveElementToOtherNamespaceNewFile( final ModelElement modelElement, final Namespace targetNamespace, + final Optional sourceLocation ) { + this( modelElement, targetNamespace, null, sourceLocation ); + } + + public MoveElementToOtherNamespaceNewFile( final ModelElement modelElement, final Namespace targetNamespace, + final List headerComment, final Optional sourceLocation ) { + this( modelElement.urn(), targetNamespace, headerComment, sourceLocation ); + if ( modelElement.isAnonymous() ) { + throw new ModelChangeException( "Can not move anonymous model element" ); + } + } + + public MoveElementToOtherNamespaceNewFile( final AspectModelUrn elementUrn, final Namespace targetNamespace, + final Optional sourceLocation ) { + this( elementUrn, targetNamespace, null, sourceLocation ); + } + + public MoveElementToOtherNamespaceNewFile( final AspectModelUrn elementUrn, final Namespace targetNamespace, + final List headerComment, final Optional sourceLocation ) { + this.elementUrn = elementUrn; + this.targetNamespace = targetNamespace; + this.headerComment = headerComment; + this.sourceLocation = sourceLocation; + } + + protected void prepare( final ChangeContext changeContext ) { + final List fileHeader = Optional.ofNullable( headerComment ) + .or( () -> Optional.ofNullable( changeContext.config().defaultFileHeader() ) ) + .orElse( List.of() ); + changes = new ChangeGroup( + "Move element " + elementUrn + " to new file " + show( sourceLocation ) + + " in namespace " + targetNamespace.elementUrnPrefix(), + new MoveElementToNewFile( elementUrn, fileHeader, sourceLocation ), + new RenameUrn( elementUrn, AspectModelUrn.fromUrn( targetNamespace.elementUrnPrefix() + elementUrn.getName() ) ) + ); + } + + @Override + public ChangeReport fire( final ChangeContext changeContext ) { + prepare( changeContext ); + return changes.fire( changeContext ); + } + + @Override + public Change reverse() { + return changes.reverse(); + } +} diff --git a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/RemoveAspectModelFile.java b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/RemoveAspectModelFile.java index 801bb4178..cb8d551c8 100644 --- a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/RemoveAspectModelFile.java +++ b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/RemoveAspectModelFile.java @@ -28,18 +28,14 @@ public RemoveAspectModelFile( final AspectModelFile fileToRemove ) { } @Override - public void fire( final ChangeContext changeContext ) { - changeContext.aspectModel().files().remove( fileToRemove ); + public ChangeReport fire( final ChangeContext changeContext ) { + changeContext.aspectModelFiles().remove( fileToRemove ); + return new ChangeReport.EntryWithDetails( "Remove file " + show( fileToRemove ), + Map.of( "model content", fileToRemove.sourceModel() ) ); } @Override public Change reverse() { return new AddAspectModelFile( fileToRemove ); } - - @Override - public ChangeReport report( final ChangeContext changeContext ) { - return new ChangeReport.EntryWithDetails( "Remove file " + show( fileToRemove ), - Map.of( "model content", fileToRemove.sourceModel() ) ); - } } diff --git a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/RemoveElementDefinition.java b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/RemoveElementDefinition.java index b4737fcfa..4858abd43 100644 --- a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/RemoveElementDefinition.java +++ b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/RemoveElementDefinition.java @@ -13,13 +13,9 @@ package org.eclipse.esmf.aspectmodel.edit.change; -import java.util.Map; - import org.eclipse.esmf.aspectmodel.AspectModelFile; import org.eclipse.esmf.aspectmodel.RdfUtil; import org.eclipse.esmf.aspectmodel.edit.Change; -import org.eclipse.esmf.aspectmodel.edit.ChangeContext; -import org.eclipse.esmf.aspectmodel.edit.ChangeReport; import org.eclipse.esmf.aspectmodel.urn.AspectModelUrn; import org.apache.jena.rdf.model.Model; @@ -48,7 +44,7 @@ protected ModelChanges calculateChangesForFile( final AspectModelFile aspectMode fileWithOriginalDefinition = aspectModelFile; final Model add = ModelFactory.createDefaultModel(); definition = RdfUtil.getModelElementDefinition( elementResource ); - return new ModelChanges( add, definition ); + return new ModelChanges( add, definition, "Remove definition of " + elementUrn ); } @Override @@ -57,7 +53,8 @@ public Change reverse() { @Override protected ModelChanges calculateChangesForFile( final AspectModelFile aspectModelFile ) { return aspectModelFile.sourceLocation().equals( fileWithOriginalDefinition.sourceLocation() ) - ? new ModelChanges( definition, ModelFactory.createDefaultModel() ) + ? new ModelChanges( definition, ModelFactory.createDefaultModel(), + "Add back definition of " + elementUrn ) : ModelChanges.NONE; } @@ -65,21 +62,6 @@ protected ModelChanges calculateChangesForFile( final AspectModelFile aspectMode public Change reverse() { return RemoveElementDefinition.this; } - - @Override - public ChangeReport report( final ChangeContext changeContext ) { - return new ChangeReport.EntryWithDetails( - "Add back definition of " + elementUrn + " to " + show( fileWithOriginalDefinition ), - Map.of( "model content to add", definition ) ); - } }; } - - @Override - public ChangeReport report( final ChangeContext changeContext ) { - changesPerFile( changeContext ); - return new ChangeReport.EntryWithDetails( - "Remove definition of " + elementUrn + " from " + show( fileWithOriginalDefinition ), - Map.of( "model content to remove", definition ) ); - } } diff --git a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/RenameElement.java b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/RenameElement.java index 2733a1b9e..292f6ce90 100644 --- a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/RenameElement.java +++ b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/RenameElement.java @@ -13,8 +13,6 @@ package org.eclipse.esmf.aspectmodel.edit.change; -import org.eclipse.esmf.aspectmodel.edit.ChangeContext; -import org.eclipse.esmf.aspectmodel.edit.ChangeReport; import org.eclipse.esmf.aspectmodel.edit.ModelChangeException; import org.eclipse.esmf.aspectmodel.urn.AspectModelUrn; import org.eclipse.esmf.metamodel.ModelElement; @@ -36,7 +34,7 @@ public String name() { } @Override - public ChangeReport report( final ChangeContext changeContext ) { - return new ChangeReport.SimpleEntry( "Rename " + from() + " to " + to().getName() ); + protected String changeDescription() { + return "Rename " + from() + " to " + to().getName(); } } diff --git a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/RenameUrn.java b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/RenameUrn.java index e3fcd60b8..007f08521 100644 --- a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/RenameUrn.java +++ b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/RenameUrn.java @@ -15,8 +15,6 @@ import org.eclipse.esmf.aspectmodel.AspectModelFile; import org.eclipse.esmf.aspectmodel.edit.Change; -import org.eclipse.esmf.aspectmodel.edit.ChangeContext; -import org.eclipse.esmf.aspectmodel.edit.ChangeReport; import org.eclipse.esmf.aspectmodel.urn.AspectModelUrn; import org.apache.jena.rdf.model.Model; @@ -49,6 +47,7 @@ protected ModelChanges calculateChangesForFile( final AspectModelFile aspectMode final Model addModel = ModelFactory.createDefaultModel(); final Model removeModel = ModelFactory.createDefaultModel(); + int updatedTriples = 0; for ( final StmtIterator it = aspectModelFile.sourceModel().listStatements(); it.hasNext(); ) { final Statement statement = it.next(); boolean updateTriple = false; @@ -93,19 +92,21 @@ protected ModelChanges calculateChangesForFile( final AspectModelFile aspectMode if ( updateTriple ) { addModel.add( addSubject, predicate, addObject ); removeModel.add( removeSubject, predicate, removeObject ); + updatedTriples++; } } - return new ModelChanges( addModel, removeModel ); + return updatedTriples > 0 + ? new ModelChanges( addModel, removeModel, changeDescription() ) + : ModelChanges.NONE; } - @Override - public Change reverse() { - return new RenameUrn( to, from ); + protected String changeDescription() { + return "Rename " + from() + " to " + to(); } @Override - public ChangeReport report( final ChangeContext changeContext ) { - return new ChangeReport.SimpleEntry( "Rename " + from() + " to " + to() ); + public Change reverse() { + return new RenameUrn( to, from ); } } diff --git a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/StructuralChange.java b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/StructuralChange.java index dd5568285..b8fab5bb8 100644 --- a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/StructuralChange.java +++ b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/StructuralChange.java @@ -14,19 +14,22 @@ package org.eclipse.esmf.aspectmodel.edit.change; import org.eclipse.esmf.aspectmodel.AspectModelFile; -import org.eclipse.esmf.aspectmodel.edit.Change; import org.eclipse.esmf.aspectmodel.edit.ChangeContext; import org.eclipse.esmf.aspectmodel.edit.ModelChangeException; import org.eclipse.esmf.aspectmodel.urn.AspectModelUrn; +import com.google.common.collect.Streams; import org.apache.jena.rdf.model.RDFNode; +import org.apache.jena.rdf.model.Resource; import org.apache.jena.vocabulary.RDF; -public abstract class StructuralChange extends AbstractChange implements Change { +public abstract class StructuralChange extends AbstractChange { protected AspectModelFile sourceFile( final ChangeContext changeContext, final AspectModelUrn elementUrn ) { - return changeContext.aspectModel().files().stream().filter( aspectModelFile -> - aspectModelFile.sourceModel() - .contains( aspectModelFile.sourceModel().createResource( elementUrn.toString() ), RDF.type, (RDFNode) null ) ) + return changeContext.aspectModelFiles().stream().filter( aspectModelFile -> { + final Resource elementResource = aspectModelFile.sourceModel().createResource( elementUrn.toString() ); + return Streams.stream( aspectModelFile.sourceModel().listStatements( elementResource, RDF.type, (RDFNode) null ) ) + .count() == 1; + } ) .findFirst() .orElseThrow( () -> new ModelChangeException( "Could not locate file containing definition of " + elementUrn ) ); } diff --git a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/metamodel/impl/DefaultNamespace.java b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/metamodel/impl/DefaultNamespace.java index 63389bff2..a7b4a3835 100644 --- a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/metamodel/impl/DefaultNamespace.java +++ b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/metamodel/impl/DefaultNamespace.java @@ -20,6 +20,7 @@ import org.eclipse.esmf.aspectmodel.AspectModelFile; import org.eclipse.esmf.aspectmodel.VersionNumber; +import org.eclipse.esmf.aspectmodel.urn.AspectModelUrn; import org.eclipse.esmf.metamodel.ModelElement; import org.eclipse.esmf.metamodel.Namespace; @@ -29,6 +30,11 @@ public class DefaultNamespace implements Namespace { private final List elements; private final Optional source; + public DefaultNamespace( final AspectModelUrn aspectModelUrn, final List elements, + final Optional source ) { + this( aspectModelUrn.getNamespace(), VersionNumber.parse( aspectModelUrn.getVersion() ), elements, source ); + } + public DefaultNamespace( final String packagePart, final VersionNumber versionNumber, final List elements, final Optional source ) { this.packagePart = packagePart; diff --git a/core/esmf-aspect-meta-model-java/src/test/java/org/eclipse/esmf/aspectmodel/edit/AspectChangeContextTest.java b/core/esmf-aspect-meta-model-java/src/test/java/org/eclipse/esmf/aspectmodel/edit/AspectChangeContextTest.java index b06ab019e..82b00eb45 100644 --- a/core/esmf-aspect-meta-model-java/src/test/java/org/eclipse/esmf/aspectmodel/edit/AspectChangeContextTest.java +++ b/core/esmf-aspect-meta-model-java/src/test/java/org/eclipse/esmf/aspectmodel/edit/AspectChangeContextTest.java @@ -29,6 +29,7 @@ import org.eclipse.esmf.aspectmodel.edit.change.AddElementDefinition; import org.eclipse.esmf.aspectmodel.edit.change.MoveElementToNewFile; import org.eclipse.esmf.aspectmodel.edit.change.MoveElementToOtherFile; +import org.eclipse.esmf.aspectmodel.edit.change.MoveElementToOtherNamespaceNewFile; import org.eclipse.esmf.aspectmodel.edit.change.RemoveAspectModelFile; import org.eclipse.esmf.aspectmodel.edit.change.RenameElement; import org.eclipse.esmf.aspectmodel.resolver.modelfile.RawAspectModelFileBuilder; @@ -36,7 +37,9 @@ import org.eclipse.esmf.aspectmodel.urn.AspectModelUrn; import org.eclipse.esmf.metamodel.Aspect; import org.eclipse.esmf.metamodel.AspectModel; +import org.eclipse.esmf.metamodel.Namespace; import org.eclipse.esmf.metamodel.Property; +import org.eclipse.esmf.metamodel.impl.DefaultNamespace; import org.eclipse.esmf.metamodel.vocabulary.SammNs; import org.eclipse.esmf.test.TestAspect; import org.eclipse.esmf.test.TestResources; @@ -223,11 +226,10 @@ void testMoveElementToNewFile() { final URI originalSourceLocation = aspectModel.aspect().getSourceFile().sourceLocation().get(); final AspectChangeContext ctx = new AspectChangeContext( aspectModel ); final URI sourceLocation = URI.create( "file:///temp/test.ttl" ); - final Change move = new MoveElementToNewFile( aspectModel.aspect(), List.of(), Optional.of( sourceLocation ) ); + final Change move = new MoveElementToNewFile( aspectModel.aspect(), Optional.of( sourceLocation ) ); - System.out.println( ChangeReportFormatter.INSTANCE.apply( move.report( ctx ) ) ); - - ctx.applyChange( move ); + final ChangeReport changeReport = ctx.applyChange( move ); + System.out.println( ChangeReportFormatter.INSTANCE.apply( changeReport, AspectChangeContextConfig.DEFAULT ) ); assertThat( aspectModel.files() ).hasSize( 2 ); assertThat( aspectModel.aspect().getSourceFile().sourceLocation() ).contains( sourceLocation ); assertThat( aspectModel.aspect().getSourceFile().sourceModel().listStatements( null, RDF.type, SammNs.SAMM.Aspect() ).nextStatement() @@ -278,15 +280,48 @@ void testMoveElementToExistingFile() { final Predicate targetFileLocator = file -> file.sourceLocation().equals( file2Location ); final Change move = new MoveElementToOtherFile( aspectUrn, targetFileLocator ); - System.out.println( ChangeReportFormatter.INSTANCE.apply( move.report( ctx ) ) ); - - ctx.applyChange( move ); + final ChangeReport changeReport = ctx.applyChange( move ); + System.out.println( ChangeReportFormatter.INSTANCE.apply( changeReport, AspectChangeContextConfig.DEFAULT ) ); assertThat( aspectModel.aspect().getSourceFile().sourceLocation() ).isEqualTo( file2Location ); ctx.undoChange(); assertThat( aspectModel.aspect().getSourceFile().sourceLocation() ).isEqualTo( file1Location ); } + @Test + void testMoveElementToOtherNamespaceNewFile() { + final AspectModel aspectModel = TestResources.load( TestAspect.ASPECT ); + assertThat( aspectModel.files() ).hasSize( 1 ); + final URI originalSourceLocation = aspectModel.aspect().getSourceFile().sourceLocation().get(); + final AspectChangeContext ctx = new AspectChangeContext( aspectModel ); + final URI sourceLocation = URI.create( "file:///temp/test.ttl" ); + + final AspectModelUrn targetUrn = AspectModelUrn.fromUrn( "urn:samm:org.eclipse.esmf.example.new:1.0.0#Aspect" ); + final Namespace targetNamespace = new DefaultNamespace( targetUrn, List.of(), Optional.empty() ); + final Change move = new MoveElementToOtherNamespaceNewFile( aspectModel.aspect(), targetNamespace, Optional.of( sourceLocation ) ); + + final ChangeReport changeReport = ctx.applyChange( move ); + System.out.println( ChangeReportFormatter.INSTANCE.apply( changeReport, AspectChangeContextConfig.DEFAULT ) ); + + assertThat( aspectModel.files() ).hasSize( 2 ); + assertThat( aspectModel.aspect().getSourceFile().sourceLocation() ).contains( sourceLocation ); + assertThat( aspectModel.aspect().getSourceFile().sourceModel().listStatements( null, RDF.type, SammNs.SAMM.Aspect() ).nextStatement() + .getSubject().getURI() ).isEqualTo( aspectModel.aspect().urn().toString() ); + assertThat( aspectModel.aspect().urn() ).isEqualTo( targetUrn ); + + ctx.undoChange(); + assertThat( aspectModel.files() ).hasSize( 1 ); + assertThat( aspectModel.aspect().getSourceFile().sourceLocation() ).contains( originalSourceLocation ); + assertThat( aspectModel.aspect().getSourceFile().sourceModel().listStatements( null, RDF.type, SammNs.SAMM.Aspect() ).nextStatement() + .getSubject().getURI() ).isEqualTo( aspectModel.aspect().urn().toString() ); + + ctx.redoChange(); + assertThat( aspectModel.files() ).hasSize( 2 ); + assertThat( aspectModel.aspect().getSourceFile().sourceLocation() ).contains( sourceLocation ); + assertThat( aspectModel.aspect().getSourceFile().sourceModel().listStatements( null, RDF.type, SammNs.SAMM.Aspect() ).nextStatement() + .getSubject().getURI() ).isEqualTo( aspectModel.aspect().urn().toString() ); + } + private Model model( final String ttlRepresentation ) { final Model model = ModelFactory.createDefaultModel(); final InputStream in = new ByteArrayInputStream( ttlRepresentation.getBytes( StandardCharsets.UTF_8 ) ); From c1f09131d487ab4a2e6fafbcb33411969b2851d5 Mon Sep 17 00:00:00 2001 From: "Textor Andreas (BCI/ESW17)" Date: Wed, 7 Aug 2024 10:09:25 +0200 Subject: [PATCH 05/33] Add MoveElementToOtherNamespaceExistingFile model change --- .../esmf/aspectmodel/edit/ChangeReport.java | 2 + .../edit/change/EditAspectModel.java | 2 +- ...le.java => MoveElementToExistingFile.java} | 41 +++++++------ .../edit/change/MoveElementToNewFile.java | 8 +-- ...veElementToOtherNamespaceExistingFile.java | 61 +++++++++++++++++++ .../MoveElementToOtherNamespaceNewFile.java | 8 +-- .../edit/change/RenameElement.java | 4 -- .../edit/AspectChangeContextTest.java | 54 ++++++++++++++-- 8 files changed, 138 insertions(+), 42 deletions(-) rename core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/{MoveElementToOtherFile.java => MoveElementToExistingFile.java} (66%) create mode 100644 core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/MoveElementToOtherNamespaceExistingFile.java diff --git a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/ChangeReport.java b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/ChangeReport.java index 48d5841f4..464277a91 100644 --- a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/ChangeReport.java +++ b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/ChangeReport.java @@ -17,6 +17,8 @@ import java.util.Map; public sealed interface ChangeReport { + record NoChanges( ) implements ChangeReport {} + record SimpleEntry( String text ) implements ChangeReport { } diff --git a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/EditAspectModel.java b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/EditAspectModel.java index f3399b67c..14a062c90 100644 --- a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/EditAspectModel.java +++ b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/EditAspectModel.java @@ -24,7 +24,7 @@ import org.apache.jena.rdf.model.Model; public abstract class EditAspectModel extends AbstractChange { - public record ModelChanges( Model add, Model remove, String description ) { + protected record ModelChanges( Model add, Model remove, String description ) { public static final ModelChanges NONE = new ModelChanges( null, null, "" ); } diff --git a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/MoveElementToOtherFile.java b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/MoveElementToExistingFile.java similarity index 66% rename from core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/MoveElementToOtherFile.java rename to core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/MoveElementToExistingFile.java index 274b40aa2..669e3a34c 100644 --- a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/MoveElementToOtherFile.java +++ b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/MoveElementToExistingFile.java @@ -13,8 +13,8 @@ package org.eclipse.esmf.aspectmodel.edit.change; -import java.util.List; -import java.util.function.Predicate; +import java.net.URI; +import java.util.Optional; import org.eclipse.esmf.aspectmodel.AspectModelFile; import org.eclipse.esmf.aspectmodel.RdfUtil; @@ -24,34 +24,39 @@ import org.eclipse.esmf.aspectmodel.edit.ChangeReport; import org.eclipse.esmf.aspectmodel.edit.ModelChangeException; import org.eclipse.esmf.aspectmodel.urn.AspectModelUrn; +import org.eclipse.esmf.metamodel.ModelElement; import org.apache.jena.rdf.model.Model; import org.apache.jena.rdf.model.Resource; -public class MoveElementToOtherFile extends StructuralChange { +public class MoveElementToExistingFile extends StructuralChange { private final AspectModelUrn elementUrn; - private final Predicate targetFileSelector; + private final Optional targetFileLocation; private ChangeGroup changes = null; - public MoveElementToOtherFile( final AspectModelUrn elementUrn, final Predicate targetFileSelector ) { + public MoveElementToExistingFile( final ModelElement modelElement, final AspectModelFile targetFile ) { + this( modelElement.urn(), targetFile ); + if ( modelElement.isAnonymous() ) { + throw new ModelChangeException( "Can not move anonymous model element" ); + } + } + + public MoveElementToExistingFile( final AspectModelUrn elementUrn, final AspectModelFile targetFile ) { this.elementUrn = elementUrn; - this.targetFileSelector = targetFileSelector; + targetFileLocation = targetFile.sourceLocation(); } - protected void prepare( final ChangeContext changeContext ) { - final List targetFiles = changeContext.aspectModelFiles().stream().filter( targetFileSelector ).toList(); - if ( targetFiles.size() > 1 ) { - throw new ModelChangeException( "Can not determine target file to move element" ); - } - if ( targetFiles.isEmpty() ) { - return; - } - final AspectModelFile targetFile = targetFiles.get( 0 ); + @Override + public ChangeReport fire( final ChangeContext changeContext ) { + final AspectModelFile targetFile = changeContext.aspectModelFiles().stream() + .filter( file -> file.sourceLocation().equals( targetFileLocation ) ) + .findFirst() + .orElseThrow( () -> new ModelChangeException( "Can not determine target file to move element" ) ); // Find source file with element definition final AspectModelFile sourceFile = sourceFile( changeContext, elementUrn ); if ( sourceFile == targetFile ) { - return; + return new ChangeReport.NoChanges(); } final Resource elementResource = sourceFile.sourceModel().createResource( elementUrn.toString() ); final Model definition = RdfUtil.getModelElementDefinition( elementResource ); @@ -62,11 +67,7 @@ protected void prepare( final ChangeContext changeContext ) { new RemoveElementDefinition( elementUrn ), new AddElementDefinition( elementUrn, definition, targetFile ) ); - } - @Override - public ChangeReport fire( final ChangeContext changeContext ) { - prepare( changeContext ); return changes.fire( changeContext ); } diff --git a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/MoveElementToNewFile.java b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/MoveElementToNewFile.java index 68076f47e..d25db5285 100644 --- a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/MoveElementToNewFile.java +++ b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/MoveElementToNewFile.java @@ -56,7 +56,8 @@ public MoveElementToNewFile( final AspectModelUrn elementUrn, final List this.elementUrn = elementUrn; } - protected void prepare( final ChangeContext changeContext ) { + @Override + public ChangeReport fire( final ChangeContext changeContext ) { // Prepare new file final List fileHeader = Optional.ofNullable( headerComment ) .or( () -> Optional.ofNullable( changeContext.config().defaultFileHeader() ) ) @@ -85,11 +86,6 @@ protected void prepare( final ChangeContext changeContext ) { new AddAspectModelFile( targetFile ), new AddElementDefinition( elementUrn, definition, targetFile ) ); - } - - @Override - public ChangeReport fire( final ChangeContext changeContext ) { - prepare( changeContext ); return changes.fire( changeContext ); } diff --git a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/MoveElementToOtherNamespaceExistingFile.java b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/MoveElementToOtherNamespaceExistingFile.java new file mode 100644 index 000000000..db63f915e --- /dev/null +++ b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/MoveElementToOtherNamespaceExistingFile.java @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2024 Robert Bosch Manufacturing Solutions GmbH + * + * See the AUTHORS file(s) distributed with this work for additional + * information regarding authorship. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * SPDX-License-Identifier: MPL-2.0 + */ + +package org.eclipse.esmf.aspectmodel.edit.change; + +import org.eclipse.esmf.aspectmodel.AspectModelFile; +import org.eclipse.esmf.aspectmodel.edit.Change; +import org.eclipse.esmf.aspectmodel.edit.ChangeContext; +import org.eclipse.esmf.aspectmodel.edit.ChangeGroup; +import org.eclipse.esmf.aspectmodel.edit.ChangeReport; +import org.eclipse.esmf.aspectmodel.edit.ModelChangeException; +import org.eclipse.esmf.aspectmodel.urn.AspectModelUrn; +import org.eclipse.esmf.metamodel.ModelElement; +import org.eclipse.esmf.metamodel.Namespace; + +public class MoveElementToOtherNamespaceExistingFile extends StructuralChange { + private final AspectModelUrn elementUrn; + private final AspectModelFile targetFile; + private final Namespace targetNamespace; + private ChangeGroup changes = null; + + public MoveElementToOtherNamespaceExistingFile( final ModelElement modelElement, final AspectModelFile targetFile, + final Namespace targetNamespace ) { + this( modelElement.urn(), targetFile, targetNamespace ); + if ( modelElement.isAnonymous() ) { + throw new ModelChangeException( "Can not move anonymous model element" ); + } + } + + public MoveElementToOtherNamespaceExistingFile( final AspectModelUrn elementUrn, final AspectModelFile targetFile, + final Namespace targetNamespace ) { + this.elementUrn = elementUrn; + this.targetFile = targetFile; + this.targetNamespace = targetNamespace; + } + + @Override + public ChangeReport fire( final ChangeContext changeContext ) { + changes = new ChangeGroup( + "Move element " + elementUrn + " to file " + show( targetFile ) + " in namespace " + targetNamespace.elementUrnPrefix(), + new MoveElementToExistingFile( elementUrn, targetFile ), + new RenameUrn( elementUrn, AspectModelUrn.fromUrn( targetNamespace.elementUrnPrefix() + elementUrn.getName() ) ) + ); + return changes.fire( changeContext ); + } + + @Override + public Change reverse() { + return changes.reverse(); + } +} diff --git a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/MoveElementToOtherNamespaceNewFile.java b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/MoveElementToOtherNamespaceNewFile.java index 87bb3faff..c8ef55933 100644 --- a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/MoveElementToOtherNamespaceNewFile.java +++ b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/MoveElementToOtherNamespaceNewFile.java @@ -59,7 +59,8 @@ public MoveElementToOtherNamespaceNewFile( final AspectModelUrn elementUrn, fina this.sourceLocation = sourceLocation; } - protected void prepare( final ChangeContext changeContext ) { + @Override + public ChangeReport fire( final ChangeContext changeContext ) { final List fileHeader = Optional.ofNullable( headerComment ) .or( () -> Optional.ofNullable( changeContext.config().defaultFileHeader() ) ) .orElse( List.of() ); @@ -69,11 +70,6 @@ protected void prepare( final ChangeContext changeContext ) { new MoveElementToNewFile( elementUrn, fileHeader, sourceLocation ), new RenameUrn( elementUrn, AspectModelUrn.fromUrn( targetNamespace.elementUrnPrefix() + elementUrn.getName() ) ) ); - } - - @Override - public ChangeReport fire( final ChangeContext changeContext ) { - prepare( changeContext ); return changes.fire( changeContext ); } diff --git a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/RenameElement.java b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/RenameElement.java index 292f6ce90..cce66da3f 100644 --- a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/RenameElement.java +++ b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/RenameElement.java @@ -29,10 +29,6 @@ public RenameElement( final AspectModelUrn urn, final String newName ) { super( urn, AspectModelUrn.fromUrn( urn.getUrnPrefix() + newName ) ); } - public String name() { - return to().getName(); - } - @Override protected String changeDescription() { return "Rename " + from() + " to " + to().getName(); diff --git a/core/esmf-aspect-meta-model-java/src/test/java/org/eclipse/esmf/aspectmodel/edit/AspectChangeContextTest.java b/core/esmf-aspect-meta-model-java/src/test/java/org/eclipse/esmf/aspectmodel/edit/AspectChangeContextTest.java index 82b00eb45..ff2b37539 100644 --- a/core/esmf-aspect-meta-model-java/src/test/java/org/eclipse/esmf/aspectmodel/edit/AspectChangeContextTest.java +++ b/core/esmf-aspect-meta-model-java/src/test/java/org/eclipse/esmf/aspectmodel/edit/AspectChangeContextTest.java @@ -21,14 +21,14 @@ import java.nio.charset.StandardCharsets; import java.util.List; import java.util.Optional; -import java.util.function.Predicate; import org.eclipse.esmf.aspectmodel.AspectModelBuilder; import org.eclipse.esmf.aspectmodel.AspectModelFile; import org.eclipse.esmf.aspectmodel.edit.change.AddAspectModelFile; import org.eclipse.esmf.aspectmodel.edit.change.AddElementDefinition; +import org.eclipse.esmf.aspectmodel.edit.change.MoveElementToExistingFile; import org.eclipse.esmf.aspectmodel.edit.change.MoveElementToNewFile; -import org.eclipse.esmf.aspectmodel.edit.change.MoveElementToOtherFile; +import org.eclipse.esmf.aspectmodel.edit.change.MoveElementToOtherNamespaceExistingFile; import org.eclipse.esmf.aspectmodel.edit.change.MoveElementToOtherNamespaceNewFile; import org.eclipse.esmf.aspectmodel.edit.change.RemoveAspectModelFile; import org.eclipse.esmf.aspectmodel.edit.change.RenameElement; @@ -276,9 +276,7 @@ void testMoveElementToExistingFile() { ) ); assertThat( aspectModel.aspect().getSourceFile().sourceLocation() ).isEqualTo( file1Location ); - final AspectModelUrn aspectUrn = aspectModel.aspect().urn(); - final Predicate targetFileLocator = file -> file.sourceLocation().equals( file2Location ); - final Change move = new MoveElementToOtherFile( aspectUrn, targetFileLocator ); + final Change move = new MoveElementToExistingFile( aspectModel.aspect(), file2 ); final ChangeReport changeReport = ctx.applyChange( move ); System.out.println( ChangeReportFormatter.INSTANCE.apply( changeReport, AspectChangeContextConfig.DEFAULT ) ); @@ -322,6 +320,52 @@ void testMoveElementToOtherNamespaceNewFile() { .getSubject().getURI() ).isEqualTo( aspectModel.aspect().urn().toString() ); } + @Test + void testMoveElementToOtherNamespaceExistingFile() { + final Optional file1Location = Optional.of( URI.create( "file:///file1.ttl" ) ); + final Optional file2Location = Optional.of( URI.create( "file:///file2.ttl" ) ); + final AspectModel aspectModel = AspectModelBuilder.buildEmptyModel(); + final AspectChangeContext ctx = new AspectChangeContext( aspectModel ); + final AspectModelFile file1 = RawAspectModelFileBuilder.builder() + .sourceLocation( file1Location ) + .sourceModel( model( """ + @prefix samm: . + @prefix xsd: . + @prefix : . + + :Aspect a samm:Aspect ; + samm:description "This is a test description"@en ; + samm:properties ( ) ; + samm:operations ( ) . + """ + ) ) + .build(); + final AspectModelFile file2 = RawAspectModelFileBuilder.builder().sourceLocation( file2Location ).build(); + + ctx.applyChange( new ChangeGroup( + new AddAspectModelFile( file1 ), + new AddAspectModelFile( file2 ) + ) ); + assertThat( aspectModel.aspect().getSourceFile().sourceLocation() ).isEqualTo( file1Location ); + + final AspectModelUrn targetUrn = AspectModelUrn.fromUrn( "urn:samm:org.eclipse.esmf.example.new:1.0.0#Aspect" ); + final Namespace targetNamespace = new DefaultNamespace( targetUrn, List.of(), Optional.empty() ); + final Change move = new MoveElementToOtherNamespaceExistingFile( aspectModel.aspect(), file2, targetNamespace ); + + final ChangeReport changeReport = ctx.applyChange( move ); + System.out.println( ChangeReportFormatter.INSTANCE.apply( changeReport, AspectChangeContextConfig.DEFAULT ) ); + + assertThat( aspectModel.aspect().getSourceFile().sourceLocation() ).isEqualTo( file2Location ); + assertThat( aspectModel.aspect().getSourceFile().sourceModel().listStatements( null, RDF.type, SammNs.SAMM.Aspect() ).nextStatement() + .getSubject().getURI() ).isEqualTo( aspectModel.aspect().urn().toString() ); + assertThat( aspectModel.aspect().urn() ).isEqualTo( targetUrn ); + + ctx.undoChange(); + assertThat( aspectModel.aspect().getSourceFile().sourceLocation() ).isEqualTo( file1Location ); + assertThat( aspectModel.aspect().getSourceFile().sourceModel().listStatements( null, RDF.type, SammNs.SAMM.Aspect() ).nextStatement() + .getSubject().getURI() ).isEqualTo( aspectModel.aspect().urn().toString() ); + } + private Model model( final String ttlRepresentation ) { final Model model = ModelFactory.createDefaultModel(); final InputStream in = new ByteArrayInputStream( ttlRepresentation.getBytes( StandardCharsets.UTF_8 ) ); From 8b7493c8e07904ad564f36370d0ae2d03d0d3938 Mon Sep 17 00:00:00 2001 From: "Textor Andreas (BCI/ESW17)" Date: Wed, 7 Aug 2024 11:04:44 +0200 Subject: [PATCH 06/33] Turn ChangeContext into a record --- .../aspectmodel/edit/AspectChangeContext.java | 27 ++++++------------- .../esmf/aspectmodel/edit/ChangeContext.java | 7 +++-- 2 files changed, 11 insertions(+), 23 deletions(-) diff --git a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/AspectChangeContext.java b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/AspectChangeContext.java index 78ff09ec5..a40739f6e 100644 --- a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/AspectChangeContext.java +++ b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/AspectChangeContext.java @@ -15,14 +15,12 @@ import java.util.ArrayDeque; import java.util.Deque; -import java.util.List; import org.eclipse.esmf.aspectmodel.AspectModelBuilder; -import org.eclipse.esmf.aspectmodel.AspectModelFile; import org.eclipse.esmf.metamodel.AspectModel; import org.eclipse.esmf.metamodel.impl.DefaultAspectModel; -public class AspectChangeContext implements ChangeContext { +public class AspectChangeContext { private final Deque undoStack = new ArrayDeque<>(); private final Deque redoStack = new ArrayDeque<>(); private final DefaultAspectModel aspectModel; @@ -30,10 +28,11 @@ public class AspectChangeContext implements ChangeContext { public AspectChangeContext( final AspectChangeContextConfig config, final AspectModel aspectModel ) { this.config = config; - if ( !( aspectModel instanceof DefaultAspectModel ) ) { - throw new RuntimeException(); + if ( aspectModel instanceof final DefaultAspectModel defaultAspectModel ) { + this.aspectModel = defaultAspectModel; + } else { + throw new ModelChangeException( "AspectModel must be an instance of DefaultAspectModel" ); } - this.aspectModel = (DefaultAspectModel) aspectModel; } public AspectChangeContext( final AspectModel aspectModel ) { @@ -41,7 +40,7 @@ public AspectChangeContext( final AspectModel aspectModel ) { } public synchronized ChangeReport applyChange( final Change change ) { - final ChangeReport result = change.fire( this ); + final ChangeReport result = change.fire( new ChangeContext( aspectModel.files(), config ) ); updateAspectModelAfterChange(); undoStack.offerLast( change.reverse() ); return result; @@ -52,7 +51,7 @@ public synchronized void undoChange() { return; } final Change change = undoStack.pollLast(); - change.fire( this ); + change.fire( new ChangeContext( aspectModel.files(), config ) ); updateAspectModelAfterChange(); redoStack.offerLast( change.reverse() ); } @@ -62,7 +61,7 @@ public synchronized void redoChange() { return; } final Change change = redoStack.pollLast(); - change.fire( this ); + change.fire( new ChangeContext( aspectModel.files(), config ) ); updateAspectModelAfterChange(); undoStack.offerLast( change.reverse() ); } @@ -73,14 +72,4 @@ private void updateAspectModelAfterChange() { aspectModel.setElements( updatedModel.elements() ); aspectModel.setFiles( updatedModel.files() ); } - - @Override - public List aspectModelFiles() { - return aspectModel.files(); - } - - @Override - public AspectChangeContextConfig config() { - return config; - } } diff --git a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/ChangeContext.java b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/ChangeContext.java index c28230b2d..edbec19f5 100644 --- a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/ChangeContext.java +++ b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/ChangeContext.java @@ -17,8 +17,7 @@ import org.eclipse.esmf.aspectmodel.AspectModelFile; -public interface ChangeContext { - List aspectModelFiles(); - - AspectChangeContextConfig config(); +public record ChangeContext( + List aspectModelFiles, + AspectChangeContextConfig config ) { } From 8652cac68a113fddffb4a9736875d13d2308e24e Mon Sep 17 00:00:00 2001 From: "Textor Andreas (BCI/ESW17)" Date: Wed, 7 Aug 2024 11:06:26 +0200 Subject: [PATCH 07/33] Add MoveRenameAspectModelFile model change --- .../esmf/aspectmodel/edit/ChangeReport.java | 5 +- .../edit/ChangeReportFormatter.java | 122 ++++++++++-------- .../change/MoveElementToExistingFile.java | 2 +- .../change/MoveRenameAspectModelFile.java | 64 +++++++++ .../edit/AspectChangeContextTest.java | 26 ++++ 5 files changed, 162 insertions(+), 57 deletions(-) create mode 100644 core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/MoveRenameAspectModelFile.java diff --git a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/ChangeReport.java b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/ChangeReport.java index 464277a91..de4bdc9ac 100644 --- a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/ChangeReport.java +++ b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/ChangeReport.java @@ -16,8 +16,9 @@ import java.util.List; import java.util.Map; -public sealed interface ChangeReport { - record NoChanges( ) implements ChangeReport {} +public interface ChangeReport { + ChangeReport NoChanges = new ChangeReport() { + }; record SimpleEntry( String text ) implements ChangeReport { } diff --git a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/ChangeReportFormatter.java b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/ChangeReportFormatter.java index 935033b10..237cea9bb 100644 --- a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/ChangeReportFormatter.java +++ b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/ChangeReportFormatter.java @@ -38,75 +38,89 @@ public class ChangeReportFormatter implements BiFunction entries = multipleEntries.entries(); + for ( int i = 0; i < entries.size(); i++ ) { + final ChangeReport entry = entries.get( i ); + final int entryIndentation = multipleEntries.summary() == null + ? indentationLevel + : indentationLevel + 1; + append( builder, entry, config, entryIndentation ); + if ( i < entries.size() - 1 ) { builder.append( "\n" ); } - final List entries = multipleEntries.entries(); - for ( int i = 0; i < entries.size(); i++ ) { - final ChangeReport entry = entries.get( i ); - final int entryIndentation = multipleEntries.summary() == null - ? indentationLevel - : indentationLevel + 1; - append( builder, entry, config, entryIndentation ); - if ( i < entries.size() - 1 ) { - builder.append( "\n" ); - } - } - } else if ( report instanceof final ChangeReport.EntryWithDetails entryWithDetails ) { - builder.append( indent ); - builder.append( "- " ); - builder.append( entryWithDetails.summary() ); - builder.append( "\n" ); - for ( final Map.Entry entry : entryWithDetails.details().entrySet() ) { - if ( config.detailedChangeReport() && entry.getValue() instanceof final Model model ) { - builder.append( indent ); - builder.append( " - " ); - builder.append( entry.getKey() ); - builder.append( ": " ); - builder.append( "\n" ); - show( model ).lines() - .forEach( line -> { - builder.append( indent ); - builder.append( " " ); - builder.append( line ); - builder.append( "\n" ); - } ); - } else if ( !config.detailedChangeReport() && entry.getValue() instanceof final Model model ) { - final int numberOfStatements = model.listStatements().toList().size(); - if ( numberOfStatements > 0 ) { - builder.append( indent ); - builder.append( " - " ); - builder.append( entry.getKey() ); - builder.append( ": " ); - builder.append( numberOfStatements ); - builder.append( " RDF statements" ); - builder.append( "\n" ); - } - } else { + } + } + + private void handleEntryWithDetails( final StringBuilder builder, final ChangeReport.EntryWithDetails entryWithDetails, + final String indent, final AspectChangeContextConfig config ) { + builder.append( indent ); + builder.append( "- " ); + builder.append( entryWithDetails.summary() ); + builder.append( "\n" ); + for ( final Map.Entry entry : entryWithDetails.details().entrySet() ) { + if ( config.detailedChangeReport() && entry.getValue() instanceof final Model model ) { + builder.append( indent ); + builder.append( " - " ); + builder.append( entry.getKey() ); + builder.append( ": " ); + builder.append( "\n" ); + show( model ).lines() + .forEach( line -> { + builder.append( indent ); + builder.append( " " ); + builder.append( line ); + builder.append( "\n" ); + } ); + } else if ( !config.detailedChangeReport() && entry.getValue() instanceof final Model model ) { + final int numberOfStatements = model.listStatements().toList().size(); + if ( numberOfStatements > 0 ) { builder.append( indent ); builder.append( " - " ); builder.append( entry.getKey() ); builder.append( ": " ); - builder.append( entry.getValue().toString() ); + builder.append( numberOfStatements ); + builder.append( " RDF statements" ); builder.append( "\n" ); } + } else { + builder.append( indent ); + builder.append( " - " ); + builder.append( entry.getKey() ); + builder.append( ": " ); + builder.append( entry.getValue().toString() ); + builder.append( "\n" ); } } } - protected String show( final Model model ) { + private void append( final StringBuilder builder, final ChangeReport report, final AspectChangeContextConfig config, + final int indentationLevel ) { + final String indent = " ".repeat( indentationLevel ); + if ( report instanceof final ChangeReport.SimpleEntry simpleEntry ) { + handleSimpleEntry( builder, simpleEntry, indent ); + } else if ( report instanceof final ChangeReport.MultipleEntries multipleEntries ) { + handleMultipleEntries( builder, multipleEntries, indent, indentationLevel, config ); + } else if ( report instanceof final ChangeReport.EntryWithDetails entryWithDetails ) { + handleEntryWithDetails( builder, entryWithDetails, indent, config ); + } + } + + private String show( final Model model ) { final Model copy = ModelFactory.createDefaultModel(); copy.add( model ); RdfUtil.getAllUrnsInModel( model ).forEach( urn -> { diff --git a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/MoveElementToExistingFile.java b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/MoveElementToExistingFile.java index 669e3a34c..23608c5cd 100644 --- a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/MoveElementToExistingFile.java +++ b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/MoveElementToExistingFile.java @@ -56,7 +56,7 @@ public ChangeReport fire( final ChangeContext changeContext ) { // Find source file with element definition final AspectModelFile sourceFile = sourceFile( changeContext, elementUrn ); if ( sourceFile == targetFile ) { - return new ChangeReport.NoChanges(); + return ChangeReport.NoChanges; } final Resource elementResource = sourceFile.sourceModel().createResource( elementUrn.toString() ); final Model definition = RdfUtil.getModelElementDefinition( elementResource ); diff --git a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/MoveRenameAspectModelFile.java b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/MoveRenameAspectModelFile.java new file mode 100644 index 000000000..ac4b83d62 --- /dev/null +++ b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/MoveRenameAspectModelFile.java @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2024 Robert Bosch Manufacturing Solutions GmbH + * + * See the AUTHORS file(s) distributed with this work for additional + * information regarding authorship. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * SPDX-License-Identifier: MPL-2.0 + */ + +package org.eclipse.esmf.aspectmodel.edit.change; + +import java.net.URI; +import java.util.Optional; + +import org.eclipse.esmf.aspectmodel.AspectModelFile; +import org.eclipse.esmf.aspectmodel.edit.Change; +import org.eclipse.esmf.aspectmodel.edit.ChangeContext; +import org.eclipse.esmf.aspectmodel.edit.ChangeGroup; +import org.eclipse.esmf.aspectmodel.edit.ChangeReport; +import org.eclipse.esmf.aspectmodel.edit.ModelChangeException; +import org.eclipse.esmf.aspectmodel.resolver.modelfile.RawAspectModelFileBuilder; + +public class MoveRenameAspectModelFile extends StructuralChange { + private final AspectModelFile file; + private final Optional newLocation; + private ChangeGroup changes = null; + + public MoveRenameAspectModelFile( final AspectModelFile file, final URI newLocation ) { + this( file, Optional.of( newLocation ) ); + } + + public MoveRenameAspectModelFile( final AspectModelFile file, final Optional newLocation ) { + this.file = file; + this.newLocation = newLocation; + } + + @Override + public ChangeReport fire( final ChangeContext changeContext ) { + final AspectModelFile targetFile = changeContext.aspectModelFiles().stream() + .filter( file -> file.sourceLocation().equals( file.sourceLocation() ) ) + .findFirst() + .orElseThrow( () -> new ModelChangeException( "Can not find file to move/rename" ) ); + + final AspectModelFile replacementFile = RawAspectModelFileBuilder.builder() + .sourceLocation( newLocation ) + .headerComment( targetFile.headerComment() ) + .sourceModel( targetFile.sourceModel() ) + .build(); + changes = new ChangeGroup( + new RemoveAspectModelFile( targetFile ), + new AddAspectModelFile( replacementFile ) + ); + return changes.fire( changeContext ); + } + + @Override + public Change reverse() { + return changes.reverse(); + } +} diff --git a/core/esmf-aspect-meta-model-java/src/test/java/org/eclipse/esmf/aspectmodel/edit/AspectChangeContextTest.java b/core/esmf-aspect-meta-model-java/src/test/java/org/eclipse/esmf/aspectmodel/edit/AspectChangeContextTest.java index ff2b37539..52528d354 100644 --- a/core/esmf-aspect-meta-model-java/src/test/java/org/eclipse/esmf/aspectmodel/edit/AspectChangeContextTest.java +++ b/core/esmf-aspect-meta-model-java/src/test/java/org/eclipse/esmf/aspectmodel/edit/AspectChangeContextTest.java @@ -30,6 +30,7 @@ import org.eclipse.esmf.aspectmodel.edit.change.MoveElementToNewFile; import org.eclipse.esmf.aspectmodel.edit.change.MoveElementToOtherNamespaceExistingFile; import org.eclipse.esmf.aspectmodel.edit.change.MoveElementToOtherNamespaceNewFile; +import org.eclipse.esmf.aspectmodel.edit.change.MoveRenameAspectModelFile; import org.eclipse.esmf.aspectmodel.edit.change.RemoveAspectModelFile; import org.eclipse.esmf.aspectmodel.edit.change.RenameElement; import org.eclipse.esmf.aspectmodel.resolver.modelfile.RawAspectModelFileBuilder; @@ -366,6 +367,31 @@ void testMoveElementToOtherNamespaceExistingFile() { .getSubject().getURI() ).isEqualTo( aspectModel.aspect().urn().toString() ); } + @Test + void testMoveRenameFile() { + final AspectModel aspectModel = TestResources.load( TestAspect.ASPECT ); + final Aspect aspect = aspectModel.aspect(); + + assertThat( aspectModel.files() ).hasSize( 1 ); + + final URI originalLocation = aspect.getSourceFile().sourceLocation().get(); + final AspectChangeContext ctx = new AspectChangeContext( aspectModel ); + final URI newLocation = URI.create( "file:///temp/test.ttl" ); + final Change renameFile = new MoveRenameAspectModelFile( aspect.getSourceFile(), newLocation ); + + ctx.applyChange( renameFile ); + assertThat( aspectModel.files() ).hasSize( 1 ); + assertThat( aspectModel.aspect().getSourceFile().sourceLocation() ).contains( newLocation ); + + ctx.undoChange(); + assertThat( aspectModel.files() ).hasSize( 1 ); + assertThat( aspectModel.aspect().getSourceFile().sourceLocation() ).contains( originalLocation ); + + ctx.redoChange(); + assertThat( aspectModel.files() ).hasSize( 1 ); + assertThat( aspectModel.aspect().getSourceFile().sourceLocation() ).contains( newLocation ); + } + private Model model( final String ttlRepresentation ) { final Model model = ModelFactory.createDefaultModel(); final InputStream in = new ByteArrayInputStream( ttlRepresentation.getBytes( StandardCharsets.UTF_8 ) ); From 9335998118fdff5f91047ce0c9f5269b4b8b7410 Mon Sep 17 00:00:00 2001 From: "Textor Andreas (BCI/ESW17)" Date: Wed, 7 Aug 2024 14:55:27 +0200 Subject: [PATCH 08/33] Implement support for samm:Namespace Fixes #450. Properly supporting samm:Namespace in the element instantiation and the DefaultNamespace implementation is a prerequisite for enabling the PrettyPrinter to write Aspect Model files that don't contain an Aspect model element definition. --- .../esmf/aspectmodel/AspectModelFile.java | 5 ++ .../esmf/metamodel/vocabulary/SAMM.java | 4 + .../esmf/aspectmodel/AspectModelBuilder.java | 46 ++++++++++ .../resolver/ClasspathStrategy.java | 2 +- .../resolver/fs/StructuredModelsRoot.java | 2 +- .../modelfile/DefaultAspectModelFile.java | 11 +++ .../metamodel/impl/DefaultAspectModel.java | 12 +-- .../esmf/metamodel/impl/DefaultNamespace.java | 34 +++++++- .../loader/AspectModelInstantiatorTest.java | 50 +++++++---- .../org/eclipse/esmf/test/TestResources.java | 2 +- .../generator/AspectModelHelper.java | 2 +- .../AspectModelAsyncApiGenerator.java | 4 +- .../AspectModelNamespacePackageCreator.java | 2 +- .../jackson/AspectModelJacksonModuleTest.java | 2 +- .../java/AspectModelJavaGeneratorTest.java | 4 +- .../java/StaticMetaModelGeneratorTest.java | 6 +- .../esmf/aspectmodel/urn/AspectModelUrn.java | 52 +++++++++-- .../aspectmodel/urn/AspectModelUrnTest.java | 87 +++++-------------- .../org/eclipse/esmf/test/TestAspect.java | 1 + .../1.0.0/AspectWithNamespaceDescription.ttl | 36 ++++++++ .../org/eclipse/esmf/test/TestResources.java | 4 +- .../examples/ParseAspectModelUrn.java | 2 +- .../esmf/aspectmodel/CodeGenerationMojo.java | 4 +- .../aspectmodel/GenerateAspectFromAas.java | 2 +- .../esmf/aspect/to/AspectToJavaCommand.java | 2 +- .../java/org/eclipse/esmf/SammCliTest.java | 4 +- 26 files changed, 256 insertions(+), 126 deletions(-) create mode 100644 core/esmf-test-aspect-models/src/main/resources/valid/org.eclipse.esmf.test/1.0.0/AspectWithNamespaceDescription.ttl diff --git a/core/esmf-aspect-meta-model-interface/src/main/java/org/eclipse/esmf/aspectmodel/AspectModelFile.java b/core/esmf-aspect-meta-model-interface/src/main/java/org/eclipse/esmf/aspectmodel/AspectModelFile.java index 169f336b0..ccdab96f9 100644 --- a/core/esmf-aspect-meta-model-interface/src/main/java/org/eclipse/esmf/aspectmodel/AspectModelFile.java +++ b/core/esmf-aspect-meta-model-interface/src/main/java/org/eclipse/esmf/aspectmodel/AspectModelFile.java @@ -19,6 +19,7 @@ import org.eclipse.esmf.metamodel.ModelElement; import org.eclipse.esmf.metamodel.ModelElementGroup; +import org.eclipse.esmf.metamodel.Namespace; import org.apache.jena.rdf.model.Model; @@ -31,6 +32,10 @@ default List headerComment() { Optional sourceLocation(); + default Namespace namespace() { + throw new UnsupportedOperationException( "Uninitialized Aspect Model" ); + } + @Override default List elements() { throw new UnsupportedOperationException( "Uninitialized Aspect Model" ); diff --git a/core/esmf-aspect-meta-model-interface/src/main/java/org/eclipse/esmf/metamodel/vocabulary/SAMM.java b/core/esmf-aspect-meta-model-interface/src/main/java/org/eclipse/esmf/metamodel/vocabulary/SAMM.java index 4f1116a1e..8e452682d 100644 --- a/core/esmf-aspect-meta-model-interface/src/main/java/org/eclipse/esmf/metamodel/vocabulary/SAMM.java +++ b/core/esmf-aspect-meta-model-interface/src/main/java/org/eclipse/esmf/metamodel/vocabulary/SAMM.java @@ -50,6 +50,10 @@ public String getNamespace() { return getUri() + "#"; } + public Resource Namespace() { + return resource( "Namespace" ); + } + public Property listType() { return property( "listType" ); } diff --git a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/AspectModelBuilder.java b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/AspectModelBuilder.java index f12877dee..0d19be572 100644 --- a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/AspectModelBuilder.java +++ b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/AspectModelBuilder.java @@ -15,19 +15,27 @@ import java.util.ArrayList; import java.util.Collection; +import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Optional; +import java.util.stream.Collectors; +import org.eclipse.esmf.aspectmodel.loader.MetaModelBaseAttributes; import org.eclipse.esmf.aspectmodel.loader.ModelElementFactory; import org.eclipse.esmf.aspectmodel.resolver.modelfile.DefaultAspectModelFile; import org.eclipse.esmf.aspectmodel.resolver.modelfile.MetaModelFile; import org.eclipse.esmf.metamodel.AspectModel; import org.eclipse.esmf.metamodel.ModelElement; +import org.eclipse.esmf.metamodel.Namespace; import org.eclipse.esmf.metamodel.impl.DefaultAspectModel; +import org.eclipse.esmf.metamodel.impl.DefaultNamespace; +import org.eclipse.esmf.metamodel.vocabulary.SammNs; import org.apache.jena.rdf.model.Model; import org.apache.jena.rdf.model.ModelFactory; import org.apache.jena.rdf.model.RDFNode; +import org.apache.jena.rdf.model.Resource; import org.apache.jena.rdf.model.Statement; import org.apache.jena.vocabulary.RDF; @@ -45,6 +53,7 @@ public static AspectModel buildAspectModelFromFiles( final Collection elements = new ArrayList<>(); final List files = new ArrayList<>(); + final Map namespaceDefinitions = new HashMap<>(); for ( final AspectModelFile file : inputFiles ) { final DefaultAspectModelFile aspectModelFile = new DefaultAspectModelFile( file.sourceModel(), file.headerComment(), file.sourceLocation() ); @@ -52,6 +61,7 @@ public static AspectModel buildAspectModelFromFiles( final Collection aspectModelFile ); final List fileElements = model.listStatements( null, RDF.type, (RDFNode) null ).toList().stream() + .filter( statement -> !statement.getObject().isURIResource() || !statement.getResource().equals( SammNs.SAMM.Namespace() ) ) .map( Statement::getSubject ) .filter( RDFNode::isURIResource ) .map( resource -> mergedModel.createResource( resource.getURI() ) ) @@ -60,6 +70,42 @@ public static AspectModel buildAspectModelFromFiles( final Collection files, final Collection elements ) { + final Map> elementsGroupedByNamespaceUrn = elements.stream() + .filter( element -> !element.isAnonymous() ) + .collect( Collectors.groupingBy( element -> element.urn().getNamespaceIdentifier() ) ); + for ( final AspectModelFile file : files ) { + final Optional optionalNamespaceUrn = + Optional.ofNullable( file.sourceModel().getNsPrefixURI( "" ) ) + .map( urnPrefix -> urnPrefix.split( "#" )[0] ) + .or( () -> file.elements().stream() + .filter( element -> !element.isAnonymous() ) + .map( element -> element.urn().getNamespaceIdentifier() ) + .findAny() ); + if ( optionalNamespaceUrn.isEmpty() ) { + return; + } + + final String namespaceUrn = optionalNamespaceUrn.get(); + final Optional baseAttributes = elementsGroupedByNamespaceUrn.get( namespaceUrn ).stream() + .map( element -> element.getSourceFile().sourceModel() ) + .filter( model -> model.contains( null, RDF.type, SammNs.SAMM.Namespace() ) ) + .map( model -> { + final ModelElementFactory modelElementFactory = new ModelElementFactory( model, Map.of(), r -> null ); + final Resource namespaceResource = model.listStatements( null, RDF.type, SammNs.SAMM.Namespace() ) + .mapWith( Statement::getSubject ) + .toList().iterator().next(); + return modelElementFactory.createBaseAttributes( namespaceResource ); + } ) + .findFirst(); + final Namespace namespace = new DefaultNamespace( namespaceUrn, elementsGroupedByNamespaceUrn.get( namespaceUrn ), + Optional.of( file ), baseAttributes ); + ( (DefaultAspectModelFile) file ).setNamespace( namespace ); + } + } } diff --git a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/resolver/ClasspathStrategy.java b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/resolver/ClasspathStrategy.java index 2da0781b1..3f3798f8e 100644 --- a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/resolver/ClasspathStrategy.java +++ b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/resolver/ClasspathStrategy.java @@ -152,7 +152,7 @@ private Stream getFilesFromJar( final String directory, final File jarFi public AspectModelFile apply( final AspectModelUrn aspectModelUrn, final ResolutionStrategySupport resolutionStrategySupport ) { final String modelsRootTrailingSlash = modelsRoot.isEmpty() ? "" : "/"; final String directory = String.format( "%s%s%s/%s", modelsRoot, modelsRootTrailingSlash, - aspectModelUrn.getNamespace(), aspectModelUrn.getVersion() ); + aspectModelUrn.getNamespaceMainPart(), aspectModelUrn.getVersion() ); final URL namedResourceFile = resourceUrl( directory, aspectModelUrn.getName() + ".ttl" ); if ( namedResourceFile != null ) { diff --git a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/resolver/fs/StructuredModelsRoot.java b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/resolver/fs/StructuredModelsRoot.java index 1e0ec28f7..b21626308 100644 --- a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/resolver/fs/StructuredModelsRoot.java +++ b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/resolver/fs/StructuredModelsRoot.java @@ -39,7 +39,7 @@ public StructuredModelsRoot( final Path path ) { @Override public Path directoryForNamespace( final AspectModelUrn urn ) { - return rootPath().resolve( urn.getNamespace() ).resolve( urn.getVersion() ); + return rootPath().resolve( urn.getNamespaceMainPart() ).resolve( urn.getVersion() ); } @Override diff --git a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/resolver/modelfile/DefaultAspectModelFile.java b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/resolver/modelfile/DefaultAspectModelFile.java index 6a0f0553b..07471d8d4 100644 --- a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/resolver/modelfile/DefaultAspectModelFile.java +++ b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/resolver/modelfile/DefaultAspectModelFile.java @@ -20,6 +20,7 @@ import org.eclipse.esmf.aspectmodel.AspectModelFile; import org.eclipse.esmf.metamodel.ModelElement; +import org.eclipse.esmf.metamodel.Namespace; import org.apache.jena.rdf.model.Model; @@ -28,6 +29,7 @@ public final class DefaultAspectModelFile implements AspectModelFile { private final List headerComment; private final Optional sourceLocation; private List elements; + private Namespace namespace = null; public DefaultAspectModelFile( final Model sourceModel, final List headerComment, final Optional sourceLocation ) { this.sourceModel = sourceModel; @@ -61,10 +63,19 @@ public List elements() { return elements; } + @Override + public Namespace namespace() { + return namespace; + } + public void setElements( final List elements ) { this.elements = elements; } + public void setNamespace( final Namespace namespace ) { + this.namespace = namespace; + } + @Override public boolean equals( final Object obj ) { if ( obj == this ) { diff --git a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/metamodel/impl/DefaultAspectModel.java b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/metamodel/impl/DefaultAspectModel.java index 5ba3b6476..5e0bfe7aa 100644 --- a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/metamodel/impl/DefaultAspectModel.java +++ b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/metamodel/impl/DefaultAspectModel.java @@ -14,7 +14,6 @@ package org.eclipse.esmf.metamodel.impl; import java.util.List; -import java.util.Optional; import java.util.stream.Collectors; import org.eclipse.esmf.aspectmodel.AspectModelFile; @@ -37,13 +36,10 @@ public DefaultAspectModel( final List files, final Model merged @Override public List namespaces() { - return elements().stream() - .filter( element -> !Namespace.ANONYMOUS.equals( element.urn().getUrnPrefix() ) ) - .collect( Collectors.groupingBy( element -> element.urn().getUrnPrefix() ) ) - .entrySet() - .stream() - . map( entry -> new DefaultNamespace( entry.getKey(), entry.getValue(), Optional.empty() ) ) - .toList(); + return files().stream() + .map( AspectModelFile::namespace ) + .collect( Collectors.toSet() ) + .stream().toList(); } @Override diff --git a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/metamodel/impl/DefaultNamespace.java b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/metamodel/impl/DefaultNamespace.java index a7b4a3835..c16fadff9 100644 --- a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/metamodel/impl/DefaultNamespace.java +++ b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/metamodel/impl/DefaultNamespace.java @@ -17,14 +17,18 @@ import java.util.List; import java.util.Objects; import java.util.Optional; +import java.util.Set; import org.eclipse.esmf.aspectmodel.AspectModelFile; import org.eclipse.esmf.aspectmodel.VersionNumber; +import org.eclipse.esmf.aspectmodel.loader.MetaModelBaseAttributes; import org.eclipse.esmf.aspectmodel.urn.AspectModelUrn; import org.eclipse.esmf.metamodel.ModelElement; import org.eclipse.esmf.metamodel.Namespace; +import org.eclipse.esmf.metamodel.datatype.LangString; public class DefaultNamespace implements Namespace { + private final Optional baseAttributes; private final String packagePart; private final VersionNumber versionNumber; private final List elements; @@ -32,15 +36,21 @@ public class DefaultNamespace implements Namespace { public DefaultNamespace( final AspectModelUrn aspectModelUrn, final List elements, final Optional source ) { - this( aspectModelUrn.getNamespace(), VersionNumber.parse( aspectModelUrn.getVersion() ), elements, source ); + this( aspectModelUrn, elements, source, Optional.empty() ); + } + + public DefaultNamespace( final AspectModelUrn aspectModelUrn, final List elements, + final Optional source, final Optional baseAttributes ) { + this( aspectModelUrn.getNamespaceMainPart(), VersionNumber.parse( aspectModelUrn.getVersion() ), elements, source, baseAttributes ); } public DefaultNamespace( final String packagePart, final VersionNumber versionNumber, final List elements, - final Optional source ) { + final Optional source, final Optional baseAttributes ) { this.packagePart = packagePart; this.versionNumber = versionNumber; this.source = source; this.elements = elements; + this.baseAttributes = baseAttributes; } /** @@ -49,8 +59,9 @@ public DefaultNamespace( final String packagePart, final VersionNumber versionNu * @param uri the namespace uri * @param elements the list of elements in the namspace */ - public DefaultNamespace( final String uri, final List elements, final Optional source ) { - this( uri.split( ":" )[2], VersionNumber.parse( uri.split( ":" )[3].replace( "#", "" ) ), elements, source ); + public DefaultNamespace( final String uri, final List elements, final Optional source, + final Optional baseAttributes ) { + this( uri.split( ":" )[2], VersionNumber.parse( uri.split( ":" )[3].replace( "#", "" ) ), elements, source, baseAttributes ); } // /** @@ -88,6 +99,21 @@ public String getName() { return "urn:samm:%s:%s".formatted( packagePart, versionNumber ); } + @Override + public List getSee() { + return baseAttributes.map( MetaModelBaseAttributes::getSee ).orElseGet( Namespace.super::getSee ); + } + + @Override + public Set getPreferredNames() { + return baseAttributes.map( MetaModelBaseAttributes::getPreferredNames ).orElseGet( Namespace.super::getPreferredNames ); + } + + @Override + public Set getDescriptions() { + return baseAttributes.map( MetaModelBaseAttributes::getDescriptions ).orElseGet( Namespace.super::getDescriptions ); + } + @Override public boolean equals( final Object o ) { if ( this == o ) { diff --git a/core/esmf-aspect-meta-model-java/src/test/java/org/eclipse/esmf/aspectmodel/loader/AspectModelInstantiatorTest.java b/core/esmf-aspect-meta-model-java/src/test/java/org/eclipse/esmf/aspectmodel/loader/AspectModelInstantiatorTest.java index 483e9e558..aba77898a 100644 --- a/core/esmf-aspect-meta-model-java/src/test/java/org/eclipse/esmf/aspectmodel/loader/AspectModelInstantiatorTest.java +++ b/core/esmf-aspect-meta-model-java/src/test/java/org/eclipse/esmf/aspectmodel/loader/AspectModelInstantiatorTest.java @@ -25,6 +25,7 @@ import org.eclipse.esmf.metamodel.Aspect; import org.eclipse.esmf.metamodel.ComplexType; import org.eclipse.esmf.metamodel.Entity; +import org.eclipse.esmf.metamodel.Namespace; import org.eclipse.esmf.metamodel.Property; import org.eclipse.esmf.metamodel.Scalar; import org.eclipse.esmf.metamodel.ScalarValue; @@ -43,12 +44,12 @@ public class AspectModelInstantiatorTest extends AbstractAspectModelInstantiatorTest { @ParameterizedTest @EnumSource( value = TestAspect.class ) - public void testLoadAspectExpectSuccess( final TestAspect aspect ) { + void testLoadAspectExpectSuccess( final TestAspect aspect ) { assertThatCode( () -> loadAspect( aspect ) ).doesNotThrowAnyException(); } @Test - public void testAspectTransformationExpectSuccess() { + void testAspectTransformationExpectSuccess() { final Aspect aspect = loadAspect( TestAspect.ASPECT_WITH_SEE ); final AspectModelUrn expectedAspectModelUrn = TestAspect.ASPECT_WITH_SEE.getUrn(); assertBaseAttributes( aspect, expectedAspectModelUrn, "AspectWithSee", "Test Aspect With See", @@ -56,7 +57,7 @@ public void testAspectTransformationExpectSuccess() { } @Test - public void testPropertyInstantiationExpectSuccess() { + void testPropertyInstantiationExpectSuccess() { final AspectModelUrn expectedAspectModelUrn = AspectModelUrn.fromUrn( TestModel.TEST_NAMESPACE + "testProperty" ); final Aspect aspect = loadAspect( TestAspect.ASPECT_WITH_PROPERTY ); @@ -75,7 +76,7 @@ public void testPropertyInstantiationExpectSuccess() { } @Test - public void testOptionalPropertyInstantiationExpectSuccess() { + void testOptionalPropertyInstantiationExpectSuccess() { final AspectModelUrn expectedAspectModelUrn = AspectModelUrn.fromUrn( TestModel.TEST_NAMESPACE + "testProperty" ); final Aspect aspect = loadAspect( TestAspect.ASPECT_WITH_OPTIONAL_PROPERTY ); @@ -94,7 +95,7 @@ public void testOptionalPropertyInstantiationExpectSuccess() { } @Test - public void testNotInPayloadPropertyInstantiationExpectSuccess() { + void testNotInPayloadPropertyInstantiationExpectSuccess() { final AspectModelUrn expectedAspectModelUrn = AspectModelUrn.fromUrn( TestModel.TEST_NAMESPACE + "description" ); final Aspect aspect = loadAspect( TestAspect.ASPECT_WITH_ENTITY_ENUMERATION_AND_NOT_IN_PAYLOAD_PROPERTIES ); @@ -112,7 +113,7 @@ public void testNotInPayloadPropertyInstantiationExpectSuccess() { } @Test - public void testPropertyWithPayloadNameInstantiationExpectSuccess() { + void testPropertyWithPayloadNameInstantiationExpectSuccess() { final AspectModelUrn expectedAspectModelUrn = AspectModelUrn.fromUrn( TestModel.TEST_NAMESPACE + "testProperty" ); final Aspect aspect = loadAspect( TestAspect.ASPECT_WITH_PROPERTY_WITH_PAYLOAD_NAME ); @@ -131,7 +132,7 @@ public void testPropertyWithPayloadNameInstantiationExpectSuccess() { } @Test - public void testEitherCharacteristicInstantiationExpectSuccess() { + void testEitherCharacteristicInstantiationExpectSuccess() { final AspectModelUrn expectedAspectModelUrn = AspectModelUrn.fromUrn( TestModel.TEST_NAMESPACE + "TestEither" ); final Aspect aspect = loadAspect( TestAspect.ASPECT_WITH_EITHER ); @@ -148,7 +149,7 @@ public void testEitherCharacteristicInstantiationExpectSuccess() { } @Test - public void testSingleEntityCharacteristicInstantiationExpectSuccess() { + void testSingleEntityCharacteristicInstantiationExpectSuccess() { final AspectModelUrn expectedAspectModelUrn = AspectModelUrn.fromUrn( TestModel.TEST_NAMESPACE + "EntityCharacteristic" ); final Aspect aspect = loadAspect( TestAspect.ASPECT_WITH_ENTITY ); @@ -163,7 +164,7 @@ public void testSingleEntityCharacteristicInstantiationExpectSuccess() { } @Test - public void testAbstractEntityInstantiationExpectSuccess() { + void testAbstractEntityInstantiationExpectSuccess() { final AspectModelUrn expectedAspectModelUrn = AspectModelUrn.fromUrn( TestModel.TEST_NAMESPACE + "AbstractTestEntity" ); final Aspect aspect = loadAspect( TestAspect.ASPECT_WITH_ABSTRACT_ENTITY ); @@ -179,7 +180,7 @@ public void testAbstractEntityInstantiationExpectSuccess() { } @Test - public void testCollectionWithAbstractEntityInstantiationExpectSuccess() { + void testCollectionWithAbstractEntityInstantiationExpectSuccess() { final AspectModelUrn expectedAspectModelUrn = AspectModelUrn.fromUrn( TestModel.TEST_NAMESPACE + "AbstractTestEntity" ); final Aspect aspect = loadAspect( TestAspect.ASPECT_WITH_COLLECTION_WITH_ABSTRACT_ENTITY ); @@ -194,7 +195,7 @@ public void testCollectionWithAbstractEntityInstantiationExpectSuccess() { } @Test - public void testCodeCharacteristicInstantiationExpectSuccess() { + void testCodeCharacteristicInstantiationExpectSuccess() { final AspectModelUrn expectedAspectModelUrn = AspectModelUrn.fromUrn( TestModel.TEST_NAMESPACE + "TestCode" ); final Aspect aspect = loadAspect( TestAspect.ASPECT_WITH_CODE ); @@ -210,7 +211,7 @@ public void testCodeCharacteristicInstantiationExpectSuccess() { } @Test - public void testCollectionAspectInstantiationExpectSuccess() { + void testCollectionAspectInstantiationExpectSuccess() { final Aspect aspect = loadAspect( TestAspect.ASPECT_WITH_LIST ); final AspectModelUrn expectedAspectModelUrn = TestAspect.ASPECT_WITH_LIST.getUrn(); @@ -223,7 +224,7 @@ public void testCollectionAspectInstantiationExpectSuccess() { } @Test - public void testAspectWithTwoCollectionsInstantiationExpectSuccess() { + void testAspectWithTwoCollectionsInstantiationExpectSuccess() { final Aspect aspect = loadAspect( TestAspect.ASPECT_WITH_TWO_LISTS ); final AspectModelUrn expectedAspectModelUrn = TestAspect.ASPECT_WITH_TWO_LISTS.getUrn(); @@ -237,7 +238,7 @@ public void testAspectWithTwoCollectionsInstantiationExpectSuccess() { } @Test - public void testAspectWithListAndAdditionalPropertyInstantiationExpectSuccess() { + void testAspectWithListAndAdditionalPropertyInstantiationExpectSuccess() { final Aspect aspect = loadAspect( TestAspect.ASPECT_WITH_LIST_AND_ADDITIONAL_PROPERTY ); final AspectModelUrn expectedAspectModelUrn = TestAspect.ASPECT_WITH_LIST_AND_ADDITIONAL_PROPERTY.getUrn(); @@ -250,13 +251,13 @@ public void testAspectWithListAndAdditionalPropertyInstantiationExpectSuccess() } @Test - public void testAspectWithRecursivePropertyWithOptional() { + void testAspectWithRecursivePropertyWithOptional() { final Aspect aspect = loadAspect( TestAspect.ASPECT_WITH_RECURSIVE_PROPERTY_WITH_OPTIONAL ); assertThat( aspect.getProperties().size() ).isEqualTo( 1 ); final Property firstProperty = aspect.getProperties().get( 0 ); - final Property secondProperty = ((DefaultEntity) firstProperty.getCharacteristic().get().getDataType().get()).getProperties() + final Property secondProperty = ( (DefaultEntity) firstProperty.getCharacteristic().get().getDataType().get() ).getProperties() .get( 0 ); - final Property thirdProperty = ((DefaultEntity) secondProperty.getCharacteristic().get().getDataType().get()).getProperties() + final Property thirdProperty = ( (DefaultEntity) secondProperty.getCharacteristic().get().getDataType().get() ).getProperties() .get( 0 ); assertThat( firstProperty ).isNotEqualTo( secondProperty ); assertThat( secondProperty ).isEqualTo( thirdProperty ); @@ -265,7 +266,7 @@ public void testAspectWithRecursivePropertyWithOptional() { } @Test - public void testMetaModelBaseAttributesFactoryMethod() { + void testMetaModelBaseAttributesFactoryMethod() { final AspectModelUrn urn = AspectModelUrn.fromUrn( "urn:samm:org.eclipse.esmf.samm:1.0.0#TestAspect" ); final MetaModelBaseAttributes baseAttributes = MetaModelBaseAttributes.builder().withUrn( urn ).build(); assertThat( baseAttributes.urn() ).isEqualTo( urn ); @@ -275,7 +276,7 @@ public void testMetaModelBaseAttributesFactoryMethod() { } @Test - public void testMetaModelBaseAttributesBuilder() { + void testMetaModelBaseAttributesBuilder() { final AspectModelUrn urn = AspectModelUrn.fromUrn( "urn:samm:org.eclipse.esmf.samm:1.0.0#TestAspect" ); final MetaModelBaseAttributes baseAttributes = MetaModelBaseAttributes.builder() .withUrn( urn ) @@ -291,4 +292,15 @@ public void testMetaModelBaseAttributesBuilder() { .allMatch( description -> description.getLanguageTag().equals( Locale.ENGLISH ) ); assertThat( baseAttributes.getSee() ).hasSize( 2 ).contains( "see1", "see2" ); } + + @Test + void testLoadAspectWithNamespaceDescription() { + final Aspect aspect = loadAspect( TestAspect.ASPECT_WITH_NAMESPACE_DESCRIPTION ); + final Namespace namespace = aspect.getSourceFile().namespace(); + assertThat( namespace.packagePart() ).isEqualTo( aspect.urn().getNamespaceMainPart() ); + assertThat( namespace.version().toString() ).isEqualTo( aspect.urn().getVersion() ); + assertThat( namespace.getPreferredName( Locale.ENGLISH ) ).isEqualTo( "Test namespace" ); + assertThat( namespace.getDescription( Locale.ENGLISH ) ).isEqualTo( "Test of the namespace pseudo element" ); + assertThat( namespace.getSee() ).hasSize( 1 ).contains( "http://example.com/" ); + } } diff --git a/core/esmf-aspect-meta-model-java/src/test/java/org/eclipse/esmf/test/TestResources.java b/core/esmf-aspect-meta-model-java/src/test/java/org/eclipse/esmf/test/TestResources.java index ffa3acb51..c7a9078bc 100644 --- a/core/esmf-aspect-meta-model-java/src/test/java/org/eclipse/esmf/test/TestResources.java +++ b/core/esmf-aspect-meta-model-java/src/test/java/org/eclipse/esmf/test/TestResources.java @@ -26,7 +26,7 @@ public class TestResources { public static AspectModel load( final TestAspect model ) { final KnownVersion metaModelVersion = KnownVersion.getLatest(); - final String path = String.format( "valid/%s/%s/%s.ttl", model.getUrn().getNamespace(), model.getUrn().getVersion(), + final String path = String.format( "valid/%s/%s/%s.ttl", model.getUrn().getNamespaceMainPart(), model.getUrn().getVersion(), model.getName() ); final InputStream inputStream = TestResources.class.getClassLoader().getResourceAsStream( path ); final ResolutionStrategy testModelsResolutionStrategy = new ClasspathStrategy( diff --git a/core/esmf-aspect-model-document-generators/src/main/java/org/eclipse/esmf/aspectmodel/generator/AspectModelHelper.java b/core/esmf-aspect-model-document-generators/src/main/java/org/eclipse/esmf/aspectmodel/generator/AspectModelHelper.java index 5a07d0b65..d17e1b41c 100644 --- a/core/esmf-aspect-model-document-generators/src/main/java/org/eclipse/esmf/aspectmodel/generator/AspectModelHelper.java +++ b/core/esmf-aspect-model-document-generators/src/main/java/org/eclipse/esmf/aspectmodel/generator/AspectModelHelper.java @@ -105,7 +105,7 @@ public int increment( final int number ) { private String namespaceAnchorPart( final ModelElement modelElement ) { return Optional.ofNullable( modelElement ) .map( ModelElement::urn ) - .map( urn -> urn.getNamespace().replace( ".", "-" ) ).orElse( "" ); + .map( urn -> urn.getNamespaceMainPart().replace( ".", "-" ) ).orElse( "" ); } public String buildAnchor( final ModelElement modelElement, final ModelElement parentElement, final String suffix ) { diff --git a/core/esmf-aspect-model-document-generators/src/main/java/org/eclipse/esmf/aspectmodel/generator/asyncapi/AspectModelAsyncApiGenerator.java b/core/esmf-aspect-model-document-generators/src/main/java/org/eclipse/esmf/aspectmodel/generator/asyncapi/AspectModelAsyncApiGenerator.java index ba84f8230..c8368f362 100644 --- a/core/esmf-aspect-model-document-generators/src/main/java/org/eclipse/esmf/aspectmodel/generator/asyncapi/AspectModelAsyncApiGenerator.java +++ b/core/esmf-aspect-model-document-generators/src/main/java/org/eclipse/esmf/aspectmodel/generator/asyncapi/AspectModelAsyncApiGenerator.java @@ -207,11 +207,11 @@ private void setChannelNodeMeta( final ObjectNode channelNode, final Aspect aspe channelNode.put( "address", StringUtils.isNotBlank( config.channelAddress() ) ? config.channelAddress() : - String.format( "/%s/%s/%s", aspectModelUrn.getNamespace(), aspectModelUrn.getVersion(), aspect.getName() ) ); + String.format( "/%s/%s/%s", aspectModelUrn.getNamespaceMainPart(), aspectModelUrn.getVersion(), aspect.getName() ) ); channelNode.put( DESCRIPTION_FIELD, "This channel for updating " + aspect.getName() + " Aspect." ); final ObjectNode parametersNode = FACTORY.objectNode(); - parametersNode.put( "namespace", aspectModelUrn.getNamespace() ); + parametersNode.put( "namespace", aspectModelUrn.getNamespaceMainPart() ); parametersNode.put( "version", aspectModelUrn.getVersion() ); parametersNode.put( "aspect-name", aspect.getName() ); diff --git a/core/esmf-aspect-model-document-generators/src/main/java/org/eclipse/esmf/aspectmodel/generator/zip/AspectModelNamespacePackageCreator.java b/core/esmf-aspect-model-document-generators/src/main/java/org/eclipse/esmf/aspectmodel/generator/zip/AspectModelNamespacePackageCreator.java index 021cc91ec..65567d135 100644 --- a/core/esmf-aspect-model-document-generators/src/main/java/org/eclipse/esmf/aspectmodel/generator/zip/AspectModelNamespacePackageCreator.java +++ b/core/esmf-aspect-model-document-generators/src/main/java/org/eclipse/esmf/aspectmodel/generator/zip/AspectModelNamespacePackageCreator.java @@ -47,7 +47,7 @@ private static void addFileToArchive( final AspectModelFile file, final ZipOutpu final String aspectString = AspectSerializer.INSTANCE.apply( file.aspect() ); final String fileName = String.format( "%s/%s/%s/%s.ttl", !rootPath.isBlank() ? String.format( "%s/%s", rootPath, BASE_ARCHIVE_FORMAT_PATH ) : BASE_ARCHIVE_FORMAT_PATH, - file.aspect().urn().getNamespace(), + file.aspect().urn().getNamespaceMainPart(), file.aspect().urn().getVersion(), file.aspect().getName() ); final ZipEntry zipEntry = new ZipEntry( fileName ); diff --git a/core/esmf-aspect-model-jackson/src/test/java/org/eclipse/esmf/aspectmodel/jackson/AspectModelJacksonModuleTest.java b/core/esmf-aspect-model-jackson/src/test/java/org/eclipse/esmf/aspectmodel/jackson/AspectModelJacksonModuleTest.java index 57a2931f3..3bd851c97 100644 --- a/core/esmf-aspect-model-jackson/src/test/java/org/eclipse/esmf/aspectmodel/jackson/AspectModelJacksonModuleTest.java +++ b/core/esmf-aspect-model-jackson/src/test/java/org/eclipse/esmf/aspectmodel/jackson/AspectModelJacksonModuleTest.java @@ -250,7 +250,7 @@ private Object generateInstance( final Tuple2 modelNameAndPa private String loadJsonPayload( final TestAspect model, final String payloadName ) throws IOException { final AspectModelUrn modelUrn = model.getUrn(); final URL jsonUrl = getClass().getResource( - String.format( "/%s/%s/%s.json", modelUrn.getNamespace(), modelUrn.getVersion(), payloadName ) ); + String.format( "/%s/%s/%s.json", modelUrn.getNamespaceMainPart(), modelUrn.getVersion(), payloadName ) ); return Resources.toString( jsonUrl, StandardCharsets.UTF_8 ); } diff --git a/core/esmf-aspect-model-java-generator/src/test/java/org/eclipse/esmf/aspectmodel/java/AspectModelJavaGeneratorTest.java b/core/esmf-aspect-model-java-generator/src/test/java/org/eclipse/esmf/aspectmodel/java/AspectModelJavaGeneratorTest.java index b57c8441e..a958d1448 100644 --- a/core/esmf-aspect-model-java-generator/src/test/java/org/eclipse/esmf/aspectmodel/java/AspectModelJavaGeneratorTest.java +++ b/core/esmf-aspect-model-java-generator/src/test/java/org/eclipse/esmf/aspectmodel/java/AspectModelJavaGeneratorTest.java @@ -80,7 +80,7 @@ private Collection getGenerators( final TestAspect testAspect, fi .enableJacksonAnnotations( enableJacksonAnnotations ) .executeLibraryMacros( executeLibraryMacros ) .templateLibFile( templateLibPath ) - .packageName( aspect.urn().getNamespace() ) + .packageName( aspect.urn().getNamespaceMainPart() ) .build(); return List.of( new AspectModelJavaGenerator( aspect, config ) ); } @@ -93,7 +93,7 @@ private Collection getGenerators( final AspectModel aspectModel ) final JavaCodeGenerationConfig config = JavaCodeGenerationConfigBuilder.builder() .enableJacksonAnnotations( true ) .executeLibraryMacros( false ) - .packageName( aspectModel.aspect().urn().getNamespace() ) + .packageName( aspectModel.aspect().urn().getNamespaceMainPart() ) .build(); return List.of( new AspectModelJavaGenerator( aspectModel.aspect(), config ) ); } diff --git a/core/esmf-aspect-model-java-generator/src/test/java/org/eclipse/esmf/aspectmodel/java/StaticMetaModelGeneratorTest.java b/core/esmf-aspect-model-java-generator/src/test/java/org/eclipse/esmf/aspectmodel/java/StaticMetaModelGeneratorTest.java index cc51958ad..3970dd547 100644 --- a/core/esmf-aspect-model-java-generator/src/test/java/org/eclipse/esmf/aspectmodel/java/StaticMetaModelGeneratorTest.java +++ b/core/esmf-aspect-model-java-generator/src/test/java/org/eclipse/esmf/aspectmodel/java/StaticMetaModelGeneratorTest.java @@ -46,7 +46,7 @@ Collection getGenerators( final TestAspect testAspect, final bool .enableJacksonAnnotations( false ) .executeLibraryMacros( executeLibraryMacros ) .templateLibFile( templateLibFile ) - .packageName( aspect.urn().getNamespace() ) + .packageName( aspect.urn().getNamespaceMainPart() ) .build(); final JavaGenerator pojoGenerator = new AspectModelJavaGenerator( aspect, config ); final JavaGenerator staticGenerator = new StaticMetaModelJavaGenerator( aspect, config ); @@ -59,7 +59,7 @@ Collection getGenerators( final TestAspect testAspect ) { final JavaCodeGenerationConfig config = JavaCodeGenerationConfigBuilder.builder() .enableJacksonAnnotations( false ) .executeLibraryMacros( false ) - .packageName( aspect.urn().getNamespace() ) + .packageName( aspect.urn().getNamespaceMainPart() ) .build(); final JavaGenerator pojoGenerator = new AspectModelJavaGenerator( aspect, config ); final JavaGenerator staticGenerator = new StaticMetaModelJavaGenerator( aspect, config ); @@ -72,7 +72,7 @@ Collection getGenerators( final TestSharedAspect testAspect ) { final JavaCodeGenerationConfig config = JavaCodeGenerationConfigBuilder.builder() .enableJacksonAnnotations( false ) .executeLibraryMacros( false ) - .packageName( aspect.urn().getNamespace() ) + .packageName( aspect.urn().getNamespaceMainPart() ) .build(); final JavaGenerator pojoGenerator = new AspectModelJavaGenerator( aspect, config ); final JavaGenerator staticGenerator = new StaticMetaModelJavaGenerator( aspect, config ); diff --git a/core/esmf-aspect-model-urn/src/main/java/org/eclipse/esmf/aspectmodel/urn/AspectModelUrn.java b/core/esmf-aspect-model-urn/src/main/java/org/eclipse/esmf/aspectmodel/urn/AspectModelUrn.java index 6c108bd0e..a0eedea6e 100644 --- a/core/esmf-aspect-model-urn/src/main/java/org/eclipse/esmf/aspectmodel/urn/AspectModelUrn.java +++ b/core/esmf-aspect-model-urn/src/main/java/org/eclipse/esmf/aspectmodel/urn/AspectModelUrn.java @@ -70,18 +70,18 @@ public class AspectModelUrn implements Comparable { private final String name; private final String version; - private final String namespace; + private final String namespaceMainPart; private final ElementType elementType; private final boolean isSammUrn; @JsonValue private final URI urn; - private AspectModelUrn( final URI urn, final String name, final String namespace, final ElementType elementType, + private AspectModelUrn( final URI urn, final String name, final String namespaceMainPart, final ElementType elementType, final String version, final boolean isSammUrn ) { this.urn = urn; this.name = name; - this.namespace = namespace; + this.namespaceMainPart = namespaceMainPart; this.elementType = elementType; this.version = version; this.isSammUrn = isSammUrn; @@ -114,7 +114,19 @@ public static AspectModelUrn fromUrn( final String urn ) { public static AspectModelUrn fromUrn( final URI urn ) { checkNotEmpty( urn ); - final List urnParts = ImmutableList.copyOf( urn.toString().split( "[:|#]" ) ); + final List urnParts; + if ( urn.toString().contains( "#" ) ) { + urnParts = ImmutableList.copyOf( urn.toString().split( "[:|#]" ) ); + } else { + final String[] parts = urn.toString().split( ":" ); + final ImmutableList.Builder builder = ImmutableList.builder(); + for ( final String part : parts ) { + builder.add( part ); + } + builder.add( "" ); + urnParts = builder.build(); + } + final int numberOfUrnParts = urnParts.size(); checkUrn( numberOfUrnParts >= 5, UrnSyntaxException.URN_IS_MISSING_SECTIONS_MESSAGE ); @@ -264,7 +276,7 @@ private static String getName( final boolean isSammUrn, final List urnPa } final String modelElementName = urnParts.get( ASPECT_NAME_INDEX ); - checkElementName( modelElementName, "aspect" ); + checkElementName( modelElementName, "model element" ); return modelElementName; } @@ -284,6 +296,9 @@ private static boolean isSammUrn( final URI urn, final List urnParts, } private static void checkElementName( final String modelElementName, final String elementTypeForErrorMessage ) { + if ( modelElementName.isEmpty() ) { + return; + } checkUrn( modelElementName.matches( MODEL_ELEMENT_NAME_REGEX ), UrnSyntaxException.URN_INVALID_ELEMENT_NAME_MESSAGE, elementTypeForErrorMessage, MODEL_ELEMENT_NAME_REGEX, modelElementName ); @@ -329,19 +344,40 @@ public String getVersion() { * Returns the namespace part of the URN, e.g. com.example.foo * * @return the namespace part of the URN + * @deprecated Use {@link #getNamespaceMainPart()} instead */ + @Deprecated( forRemoval = true ) public String getNamespace() { - return namespace; + return namespaceMainPart; + } + + /** + * Returns the namespace part of the URN, e.g. com.example.foo + * + * @return the namespace part of the URN + */ + public String getNamespaceMainPart() { + return namespaceMainPart; + } + + /** + * Returns the namespace identifier, i.e. the part of the URN before the # symbol + * e.g. urn:samm:com.foo.example:1.0.0 + * + * @return the prefix part of the URN + */ + public String getNamespaceIdentifier() { + return urn.toString().split( "#" )[0]; } /** - * Returns prefix part of the URN, i.e. the part up to and including the # but not including the local name, + * Returns the RDF prefix part of the URN, i.e. the part up to and including the # but not including the local name, * e.g. urn:samm:com.foo.example:1.0.0# * * @return the prefix part of the URN */ public String getUrnPrefix() { - return urn.toString().split( "#" )[0] + "#"; + return getNamespaceIdentifier() + "#"; } public ElementType getElementType() { diff --git a/core/esmf-aspect-model-urn/src/test/java/org/eclipse/esmf/aspectmodel/urn/AspectModelUrnTest.java b/core/esmf-aspect-model-urn/src/test/java/org/eclipse/esmf/aspectmodel/urn/AspectModelUrnTest.java index 5a2649c45..2226dd9e1 100644 --- a/core/esmf-aspect-model-urn/src/test/java/org/eclipse/esmf/aspectmodel/urn/AspectModelUrnTest.java +++ b/core/esmf-aspect-model-urn/src/test/java/org/eclipse/esmf/aspectmodel/urn/AspectModelUrnTest.java @@ -40,32 +40,14 @@ void createFromValidUrn() throws URISyntaxException { assertModelElementUrn( aspectModelUrn, "E2", "org.eclipse.esmf.test" ); } - @Test - void createFromValidLegacyUrn() throws URISyntaxException { - URI validUrn = new URI( baseUri + "aspect-model:Errors:1.0.0" ); - AspectModelUrn aspectModelUrn = AspectModelUrn.fromUrn( validUrn ); - - assertAspectModelUrn( aspectModelUrn, "Errors", "org.eclipse.esmf.test" ); - - validUrn = new URI( baseUri + "aspect-model:E2:1.0.0" ); - aspectModelUrn = AspectModelUrn.fromUrn( validUrn ); - - assertAspectModelUrn( aspectModelUrn, "E2", "org.eclipse.esmf.test" ); - - // Check that URNs using the schema of BAMM (which at the end of 2022 was renamed to SAMM) are still valid - final URI validBammUrn = new URI( "urn:bamm:com.example:1.0.0#MyAspect" ); - final AspectModelUrn bammUrn = AspectModelUrn.fromUrn( validBammUrn ); - assertModelElementUrn( bammUrn, "MyAspect", "com.example" ); - } - @Test void createFromValidUrnMaxLength() throws URISyntaxException { final String namespace = Strings.repeat( "x", 62 ); final URI validUrn = new URI( - "urn:samm:" + namespace + ".test:aspect-model:Errors:1.0.0" ); + "urn:samm:" + namespace + ".test:1.0.0#Errors" ); final AspectModelUrn aspectModelUrn = AspectModelUrn.fromUrn( validUrn ); - assertAspectModelUrn( aspectModelUrn, "Errors", namespace + ".test" ); + assertModelElementUrn( aspectModelUrn, "Errors", namespace + ".test" ); } @Test @@ -122,15 +104,7 @@ void createFromUrnInvalidNamespace() throws URISyntaxException { } @Test - void createFromUrnInvalidAspectName() { - assertThatExceptionOfType( UrnSyntaxException.class ) - .isThrownBy( () -> AspectModelUrn.fromUrn( new URI( baseUri + "aspect-model:123Error:1.0.0" ) ) ) - .withMessage( "The aspect name must match \\p{Alpha}\\p{Alnum}*: 123Error" ); - - assertThatExceptionOfType( UrnSyntaxException.class ) - .isThrownBy( () -> AspectModelUrn.fromUrn( new URI( baseUri + "aspect-model:Error?s:1.0.0" ) ) ) - .withMessage( "The aspect name must match \\p{Alpha}\\p{Alnum}*: Error?s" ); - + void createFromUrnInvalidAspectName() throws URISyntaxException { assertThatExceptionOfType( UrnSyntaxException.class ) .isThrownBy( () -> AspectModelUrn.fromUrn( new URI( sammBaseUri + "meta-model:1.0.0#Aspe?ct" ) ) ) .withMessage( "The meta model element name must match \\p{Alpha}\\p{Alnum}*: Aspe?ct" ); @@ -139,25 +113,13 @@ void createFromUrnInvalidAspectName() { .isThrownBy( () -> AspectModelUrn.fromUrn( new URI( sammBaseUri + "characteristic:1.0.0#Eit?her" ) ) ) .withMessage( "The characteristic name must match \\p{Alpha}\\p{Alnum}*: Eit?her" ); - assertThatExceptionOfType( UrnSyntaxException.class ) - .isThrownBy( () -> AspectModelUrn.fromUrn( new URI( baseUri + "characteristic:Eit?her:1.0.0" ) ) ) - .withMessage( "The characteristic name must match \\p{Alpha}\\p{Alnum}*: Eit?her" ); - assertThatExceptionOfType( UrnSyntaxException.class ) .isThrownBy( () -> AspectModelUrn.fromUrn( new URI( sammBaseUri + "entity:1.0.0#Time?Series" ) ) ) .withMessage( "The entity name must match \\p{Alpha}\\p{Alnum}*: Time?Series" ); - assertThatExceptionOfType( UrnSyntaxException.class ) - .isThrownBy( () -> AspectModelUrn.fromUrn( new URI( baseUri + "entity:Time?Series:1.0.0" ) ) ) - .withMessage( "The entity name must match \\p{Alpha}\\p{Alnum}*: Time?Series" ); - assertThatExceptionOfType( UrnSyntaxException.class ) .isThrownBy( () -> AspectModelUrn.fromUrn( new URI( baseUri + "unit:1.0.0#lit?re" ) ) ) .withMessage( "The unit name must match \\p{Alpha}\\p{Alnum}*: lit?re" ); - - assertThatExceptionOfType( UrnSyntaxException.class ) - .isThrownBy( () -> AspectModelUrn.fromUrn( new URI( baseUri + "unit:lit?re:1.0.0" ) ) ) - .withMessage( "The unit name must match \\p{Alpha}\\p{Alnum}*: lit?re" ); } @Test @@ -245,12 +207,7 @@ private void assertModelElementUrn( final AspectModelUrn aspectModelUrn, final S final String nameSpace ) { assertThat( aspectModelUrn.getName() ).isEqualTo( name ); assertThat( aspectModelUrn.getVersion() ).isEqualTo( "1.0.0" ); - assertThat( aspectModelUrn.getNamespace() ).isEqualTo( nameSpace ); - } - - private void assertAspectModelUrn( final AspectModelUrn aspectModelUrn, final String name, final String nameSpace ) { - assertModelElementUrn( aspectModelUrn, name, nameSpace ); - assertThat( aspectModelUrn.getElementType() ).isEqualTo( ElementType.ASPECT_MODEL ); + assertThat( aspectModelUrn.getNamespaceMainPart() ).isEqualTo( nameSpace ); } @Test @@ -260,7 +217,7 @@ void createUrnForModelElement() throws URISyntaxException { assertThat( elementUrn.getName() ).isEqualTo( "property" ); assertThat( elementUrn.getVersion() ).isEqualTo( "1.0.0" ); - assertThat( elementUrn.getNamespace() ).isEqualTo( "org.eclipse.esmf.test" ); + assertThat( elementUrn.getNamespaceMainPart() ).isEqualTo( "org.eclipse.esmf.test" ); assertThat( elementUrn.getElementType() ).isEqualTo( ElementType.ASPECT_MODEL_ELEMENT ); assertThat( elementUrn.isSammUrn() ).isFalse(); } @@ -272,21 +229,21 @@ void createUrnForSammCharacteristic() throws URISyntaxException { assertThat( metaModelElementUrn.getName() ).isEqualTo( "Either" ); assertThat( metaModelElementUrn.getVersion() ).isEqualTo( "1.0.0" ); - assertThat( metaModelElementUrn.getNamespace() ).isEqualTo( "org.eclipse.esmf.samm" ); + assertThat( metaModelElementUrn.getNamespaceMainPart() ).isEqualTo( "org.eclipse.esmf.samm" ); assertThat( metaModelElementUrn.getElementType() ).isEqualTo( ElementType.CHARACTERISTIC ); assertThat( metaModelElementUrn.isSammUrn() ).isTrue(); } @Test void createUrnForCharacteristic() throws URISyntaxException { - final URI validUrn = new URI( baseUri + "characteristic:TestCharacteristic:1.0.0" ); + final URI validUrn = new URI( baseUri + "characteristic:1.0.0#TestCharacteristic" ); final AspectModelUrn elementUrn = AspectModelUrn.fromUrn( validUrn ); assertThat( elementUrn.getName() ).isEqualTo( "TestCharacteristic" ); assertThat( elementUrn.getVersion() ).isEqualTo( "1.0.0" ); - assertThat( elementUrn.getNamespace() ).isEqualTo( "org.eclipse.esmf.test" ); + assertThat( elementUrn.getNamespaceMainPart() ).isEqualTo( "org.eclipse.esmf.test" ); assertThat( elementUrn.getElementType() ).isEqualTo( ElementType.CHARACTERISTIC ); - assertThat( elementUrn.isSammUrn() ).isFalse(); + assertThat( elementUrn.isSammUrn() ).isTrue(); } @Test @@ -296,7 +253,7 @@ void createUrnForCharacteristicModelElement() throws URISyntaxException { assertThat( elementUrn.getName() ).isEqualTo( "RightCharacteristic" ); assertThat( elementUrn.getVersion() ).isEqualTo( "1.0.0" ); - assertThat( elementUrn.getNamespace() ).isEqualTo( "org.eclipse.esmf.test" ); + assertThat( elementUrn.getNamespaceMainPart() ).isEqualTo( "org.eclipse.esmf.test" ); assertThat( elementUrn.getElementType() ).isEqualTo( ElementType.CHARACTERISTIC_MODEL_ELEMENT ); assertThat( elementUrn.isSammUrn() ).isFalse(); } @@ -308,7 +265,7 @@ void createUrnForSammEntity() throws URISyntaxException { assertThat( metaModelElementUrn.getName() ).isEqualTo( "TimeSeriesEntity" ); assertThat( metaModelElementUrn.getVersion() ).isEqualTo( "1.0.0" ); - assertThat( metaModelElementUrn.getNamespace() ).isEqualTo( "org.eclipse.esmf.samm" ); + assertThat( metaModelElementUrn.getNamespaceMainPart() ).isEqualTo( "org.eclipse.esmf.samm" ); assertThat( metaModelElementUrn.getElementType() ).isEqualTo( ElementType.ENTITY ); assertThat( metaModelElementUrn.isSammUrn() ).isTrue(); } @@ -320,21 +277,21 @@ void createUrnForSammEntityProperty() throws URISyntaxException { assertThat( metaModelElementUrn.getName() ).isEqualTo( "value" ); assertThat( metaModelElementUrn.getVersion() ).isEqualTo( "1.0.0" ); - assertThat( metaModelElementUrn.getNamespace() ).isEqualTo( "org.eclipse.esmf.samm" ); + assertThat( metaModelElementUrn.getNamespaceMainPart() ).isEqualTo( "org.eclipse.esmf.samm" ); assertThat( metaModelElementUrn.getElementType() ).isEqualTo( ElementType.ENTITY ); assertThat( metaModelElementUrn.isSammUrn() ).isTrue(); } @Test void createUrnForEntity() throws URISyntaxException { - final URI validUrn = new URI( baseUri + "entity:TestEntity:1.0.0" ); + final URI validUrn = new URI( baseUri + "entity:1.0.0#TestEntity" ); final AspectModelUrn elementUrn = AspectModelUrn.fromUrn( validUrn ); assertThat( elementUrn.getName() ).isEqualTo( "TestEntity" ); assertThat( elementUrn.getVersion() ).isEqualTo( "1.0.0" ); - assertThat( elementUrn.getNamespace() ).isEqualTo( "org.eclipse.esmf.test" ); + assertThat( elementUrn.getNamespaceMainPart() ).isEqualTo( "org.eclipse.esmf.test" ); assertThat( elementUrn.getElementType() ).isEqualTo( ElementType.ENTITY ); - assertThat( elementUrn.isSammUrn() ).isFalse(); + assertThat( elementUrn.isSammUrn() ).isTrue(); } @Test @@ -344,7 +301,7 @@ void createUrnForEntityProperty() throws URISyntaxException { assertThat( elementUrn.getName() ).isEqualTo( "property" ); assertThat( elementUrn.getVersion() ).isEqualTo( "1.0.0" ); - assertThat( elementUrn.getNamespace() ).isEqualTo( "org.eclipse.esmf.test" ); + assertThat( elementUrn.getNamespaceMainPart() ).isEqualTo( "org.eclipse.esmf.test" ); assertThat( elementUrn.getElementType() ).isEqualTo( ElementType.ENTITY_MODEL_ELEMENT ); assertThat( elementUrn.isSammUrn() ).isFalse(); } @@ -356,7 +313,7 @@ void createUrnForSammUnit() throws URISyntaxException { assertThat( metaModelElementUrn.getName() ).isEqualTo( "litre" ); assertThat( metaModelElementUrn.getVersion() ).isEqualTo( "1.0.0" ); - assertThat( metaModelElementUrn.getNamespace() ).isEqualTo( "org.eclipse.esmf.samm" ); + assertThat( metaModelElementUrn.getNamespaceMainPart() ).isEqualTo( "org.eclipse.esmf.samm" ); assertThat( metaModelElementUrn.getElementType() ).isEqualTo( ElementType.UNIT ); assertThat( metaModelElementUrn.isSammUrn() ).isTrue(); } @@ -368,7 +325,7 @@ void createUrnForUnit() throws URISyntaxException { assertThat( elementUrn.getName() ).isEqualTo( "litre" ); assertThat( elementUrn.getVersion() ).isEqualTo( "1.0.0" ); - assertThat( elementUrn.getNamespace() ).isEqualTo( "org.eclipse.esmf.test" ); + assertThat( elementUrn.getNamespaceMainPart() ).isEqualTo( "org.eclipse.esmf.test" ); assertThat( elementUrn.getElementType() ).isEqualTo( ElementType.UNIT ); assertThat( elementUrn.isSammUrn() ).isFalse(); } @@ -377,12 +334,12 @@ void createUrnForUnit() throws URISyntaxException { void validNamespaceTest() throws URISyntaxException { final URI validNamespaceUrnUnderscore = new URI( "urn:samm:org.eclipse.esmf_test:0.0.1#TestAspect" ); final AspectModelUrn elementUrnWithUnderscore = AspectModelUrn.fromUrn( validNamespaceUrnUnderscore ); - assertThat( elementUrnWithUnderscore.getNamespace() ).isNotEmpty(); - assertThat( elementUrnWithUnderscore.getNamespace() ).isEqualTo( "org.eclipse.esmf_test" ); + assertThat( elementUrnWithUnderscore.getNamespaceMainPart() ).isNotEmpty(); + assertThat( elementUrnWithUnderscore.getNamespaceMainPart() ).isEqualTo( "org.eclipse.esmf_test" ); final URI invalidNamespaceUrnDash = new URI( "urn:samm:org.eclipse.esmf-test:0.0.1#TestAspect" ); final AspectModelUrn elementUrnWithDash = AspectModelUrn.fromUrn( invalidNamespaceUrnDash ); - assertThat( elementUrnWithDash.getNamespace() ).isNotEmpty(); - assertThat( elementUrnWithDash.getNamespace() ).isEqualTo( "org.eclipse.esmf-test" ); + assertThat( elementUrnWithDash.getNamespaceMainPart() ).isNotEmpty(); + assertThat( elementUrnWithDash.getNamespaceMainPart() ).isEqualTo( "org.eclipse.esmf-test" ); } } diff --git a/core/esmf-test-aspect-models/src/main/java/org/eclipse/esmf/test/TestAspect.java b/core/esmf-test-aspect-models/src/main/java/org/eclipse/esmf/test/TestAspect.java index b0a9b1898..2e2fce12b 100644 --- a/core/esmf-test-aspect-models/src/main/java/org/eclipse/esmf/test/TestAspect.java +++ b/core/esmf-test-aspect-models/src/main/java/org/eclipse/esmf/test/TestAspect.java @@ -124,6 +124,7 @@ public enum TestAspect implements TestModel { ASPECT_WITH_MULTIPLE_ENUMERATIONS_ON_MULTIPLE_LEVELS, ASPECT_WITH_MULTIPLE_SEE_ATTRIBUTES, ASPECT_WITH_MULTI_LANGUAGE_TEXT, + ASPECT_WITH_NAMESPACE_DESCRIPTION, ASPECT_WITH_NESTED_ENTITY, ASPECT_WITH_NESTED_ENTITY_LIST, ASPECT_WITH_NESTED_ENTITY_ENUMERATION_WITH_NOT_IN_PAYLOAD, diff --git a/core/esmf-test-aspect-models/src/main/resources/valid/org.eclipse.esmf.test/1.0.0/AspectWithNamespaceDescription.ttl b/core/esmf-test-aspect-models/src/main/resources/valid/org.eclipse.esmf.test/1.0.0/AspectWithNamespaceDescription.ttl new file mode 100644 index 000000000..ece86f4a0 --- /dev/null +++ b/core/esmf-test-aspect-models/src/main/resources/valid/org.eclipse.esmf.test/1.0.0/AspectWithNamespaceDescription.ttl @@ -0,0 +1,36 @@ +# Copyright (c) 2024 Robert Bosch Manufacturing Solutions GmbH +# +# See the AUTHORS file(s) distributed with this work for additional +# information regarding authorship. +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at https://mozilla.org/MPL/2.0/. +# +# SPDX-License-Identifier: MPL-2.0 + +@prefix : . +@prefix samm: . +@prefix samm-c: . +@prefix xsd: . +@prefix unit: . + + a samm:Namespace ; + samm:preferredName "Test namespace"@en ; + samm:description "Test of the namespace pseudo element"@en ; + samm:see . + +:AspectWithProperty a samm:Aspect ; + samm:preferredName "Test Aspect"@en ; + samm:description "This is a test description"@en ; + samm:see ; + samm:properties ( :testProperty ) ; + samm:operations ( ) . + +:testProperty a samm:Property ; + samm:preferredName "Test Property"@en ; + samm:description "This is a test property."@en ; + samm:see ; + samm:see ; + samm:exampleValue "Example Value" ; + samm:characteristic samm-c:Text . diff --git a/core/esmf-test-resources/src/main/java/org/eclipse/esmf/test/TestResources.java b/core/esmf-test-resources/src/main/java/org/eclipse/esmf/test/TestResources.java index 1fd096dec..7f7435570 100644 --- a/core/esmf-test-resources/src/main/java/org/eclipse/esmf/test/TestResources.java +++ b/core/esmf-test-resources/src/main/java/org/eclipse/esmf/test/TestResources.java @@ -30,7 +30,7 @@ public class TestResources { public static AspectModel load( final InvalidTestAspect model ) { - final String path = String.format( "invalid/%s/%s/%s.ttl", model.getUrn().getNamespace(), model.getUrn().getVersion(), + final String path = String.format( "invalid/%s/%s/%s.ttl", model.getUrn().getNamespaceMainPart(), model.getUrn().getVersion(), model.getName() ); final InputStream inputStream = TestResources.class.getClassLoader().getResourceAsStream( path ); final ResolutionStrategy testModelsResolutionStrategy = new ClasspathStrategy( @@ -39,7 +39,7 @@ public static AspectModel load( final InvalidTestAspect model ) { } public static AspectModel load( final TestModel model ) { - final String path = String.format( "valid/%s/%s/%s.ttl", model.getUrn().getNamespace(), model.getUrn().getVersion(), + final String path = String.format( "valid/%s/%s/%s.ttl", model.getUrn().getNamespaceMainPart(), model.getUrn().getVersion(), model.getName() ); final InputStream inputStream = TestResources.class.getClassLoader().getResourceAsStream( path ); final ResolutionStrategy testModelsResolutionStrategy = new ClasspathStrategy( "valid" ); diff --git a/documentation/developer-guide/modules/tooling-guide/examples/ParseAspectModelUrn.java b/documentation/developer-guide/modules/tooling-guide/examples/ParseAspectModelUrn.java index f5aa62922..ec04f6639 100644 --- a/documentation/developer-guide/modules/tooling-guide/examples/ParseAspectModelUrn.java +++ b/documentation/developer-guide/modules/tooling-guide/examples/ParseAspectModelUrn.java @@ -22,7 +22,7 @@ public class ParseAspectModelUrn { public void parseAspectModelUrn() { // tag::parseAspectModelUrn[] final AspectModelUrn urn = AspectModelUrn.fromUrn( "urn:samm:com.example:1.0.0#Example" ); - final String namespace = urn.getNamespace(); // com.example + final String namespace = urn.getNamespaceMainPart(); // com.example final String name = urn.getName(); // Example final String version = urn.getVersion(); // 1.0.0 final String urnPrefix = urn.getUrnPrefix(); // urn:samm:com.example:1.0.0# diff --git a/tools/esmf-aspect-model-maven-plugin/src/main/java/org/eclipse/esmf/aspectmodel/CodeGenerationMojo.java b/tools/esmf-aspect-model-maven-plugin/src/main/java/org/eclipse/esmf/aspectmodel/CodeGenerationMojo.java index 348e1c313..1e3625935 100644 --- a/tools/esmf-aspect-model-maven-plugin/src/main/java/org/eclipse/esmf/aspectmodel/CodeGenerationMojo.java +++ b/tools/esmf-aspect-model-maven-plugin/src/main/java/org/eclipse/esmf/aspectmodel/CodeGenerationMojo.java @@ -55,12 +55,12 @@ protected void validateParameters( final File templateLibFile ) throws MojoExecu protected String determinePackageName( final Aspect aspect ) { if ( packageName == null || packageName.isEmpty() ) { - return aspect.urn().getNamespace(); + return aspect.urn().getNamespaceMainPart(); } final AspectModelUrn urn = aspect.urn(); final VersionNumber versionNumber = VersionNumber.parse( urn.getVersion() ); - final String interpolated = packageName.replace( "{{namespace}}", urn.getNamespace() ) + final String interpolated = packageName.replace( "{{namespace}}", urn.getNamespaceMainPart() ) .replace( "{{majorVersion}}", "" + versionNumber.getMajor() ) .replace( "{{minorVersion}}", "" + versionNumber.getMinor() ) .replace( "{{microVersion}}", "" + versionNumber.getMicro() ); diff --git a/tools/esmf-aspect-model-maven-plugin/src/main/java/org/eclipse/esmf/aspectmodel/GenerateAspectFromAas.java b/tools/esmf-aspect-model-maven-plugin/src/main/java/org/eclipse/esmf/aspectmodel/GenerateAspectFromAas.java index 4ad0ce2a3..e2dccd9b0 100644 --- a/tools/esmf-aspect-model-maven-plugin/src/main/java/org/eclipse/esmf/aspectmodel/GenerateAspectFromAas.java +++ b/tools/esmf-aspect-model-maven-plugin/src/main/java/org/eclipse/esmf/aspectmodel/GenerateAspectFromAas.java @@ -49,7 +49,7 @@ public void executeGeneration() throws MojoExecutionException, MojoFailureExcept private File getOutputFile( final Aspect aspect ) throws MojoExecutionException { final AspectModelUrn urn = aspect.urn(); - final Path outputPath = Path.of( outputDirectory, urn.getNamespace(), urn.getVersion() ); + final Path outputPath = Path.of( outputDirectory, urn.getNamespaceMainPart(), urn.getVersion() ); try { Files.createDirectories( outputPath ); } catch ( final IOException exception ) { diff --git a/tools/samm-cli/src/main/java/org/eclipse/esmf/aspect/to/AspectToJavaCommand.java b/tools/samm-cli/src/main/java/org/eclipse/esmf/aspect/to/AspectToJavaCommand.java index 66053ce63..4c304d6ca 100644 --- a/tools/samm-cli/src/main/java/org/eclipse/esmf/aspect/to/AspectToJavaCommand.java +++ b/tools/samm-cli/src/main/java/org/eclipse/esmf/aspect/to/AspectToJavaCommand.java @@ -87,7 +87,7 @@ private JavaCodeGenerationConfig buildConfig( final Aspect aspect ) { final File templateLibFile = Path.of( templateLib ).toFile(); final String pkgName = Optional.ofNullable( packageName ) .flatMap( pkg -> pkg.isBlank() ? Optional.empty() : Optional.of( pkg ) ) - .orElseGet( () -> aspect.urn().getNamespace() ); + .orElseGet( () -> aspect.urn().getNamespaceMainPart() ); return JavaCodeGenerationConfigBuilder.builder() .executeLibraryMacros( executeLibraryMacros ) .templateLibFile( templateLibFile ) diff --git a/tools/samm-cli/src/test/java/org/eclipse/esmf/SammCliTest.java b/tools/samm-cli/src/test/java/org/eclipse/esmf/SammCliTest.java index 2a9e88fc3..81bde36f5 100644 --- a/tools/samm-cli/src/test/java/org/eclipse/esmf/SammCliTest.java +++ b/tools/samm-cli/src/test/java/org/eclipse/esmf/SammCliTest.java @@ -351,7 +351,7 @@ void testAasToAspectModel() { assertThat( result.stdout() ).isEmpty(); assertThat( result.stderr() ).isEmpty(); - final File directory = outputDirectory.resolve( testModel.getUrn().getNamespace() ) + final File directory = outputDirectory.resolve( testModel.getUrn().getNamespaceMainPart() ) .resolve( testModel.getUrn().getVersion() ) .toFile(); assertThat( directory ).exists(); @@ -391,7 +391,7 @@ void testAasToAspectModelWithSelectedSubmodels() { assertThat( result.stdout() ).isEmpty(); assertThat( result.stderr() ).isEmpty(); - final File directory = outputDirectory.resolve( testModel.getUrn().getNamespace() ) + final File directory = outputDirectory.resolve( testModel.getUrn().getNamespaceMainPart() ) .resolve( testModel.getUrn().getVersion() ) .toFile(); assertThat( directory ).exists(); From 8b2e05d9061a8aae7d28c988bbcae526ab947df7 Mon Sep 17 00:00:00 2001 From: "Textor Andreas (BCI/ESW17)" Date: Wed, 7 Aug 2024 16:27:00 +0200 Subject: [PATCH 09/33] Add correct source locations to Namespace declarations --- .../esmf/aspectmodel/AspectModelBuilder.java | 29 +++++++++++-------- 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/AspectModelBuilder.java b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/AspectModelBuilder.java index 0d19be572..0a1d9b014 100644 --- a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/AspectModelBuilder.java +++ b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/AspectModelBuilder.java @@ -92,19 +92,24 @@ private static void setNamespaces( final Collection files, fina } final String namespaceUrn = optionalNamespaceUrn.get(); - final Optional baseAttributes = elementsGroupedByNamespaceUrn.get( namespaceUrn ).stream() - .map( element -> element.getSourceFile().sourceModel() ) - .filter( model -> model.contains( null, RDF.type, SammNs.SAMM.Namespace() ) ) - .map( model -> { - final ModelElementFactory modelElementFactory = new ModelElementFactory( model, Map.of(), r -> null ); - final Resource namespaceResource = model.listStatements( null, RDF.type, SammNs.SAMM.Namespace() ) - .mapWith( Statement::getSubject ) - .toList().iterator().next(); - return modelElementFactory.createBaseAttributes( namespaceResource ); - } ) - .findFirst(); + MetaModelBaseAttributes namespaceDefinition = null; + AspectModelFile fileContainingNamespaceDefinition = null; + for ( final ModelElement element : elementsGroupedByNamespaceUrn.get( namespaceUrn ) ) { + final AspectModelFile elementFile = element.getSourceFile(); + if ( elementFile.sourceModel().contains( null, RDF.type, SammNs.SAMM.Namespace() ) ) { + final Model model = elementFile.sourceModel(); + final ModelElementFactory modelElementFactory = new ModelElementFactory( model, Map.of(), __ -> null ); + final Resource namespaceResource = model.listStatements( null, RDF.type, SammNs.SAMM.Namespace() ) + .mapWith( Statement::getSubject ) + .toList().iterator().next(); + namespaceDefinition = modelElementFactory.createBaseAttributes( namespaceResource ); + fileContainingNamespaceDefinition = elementFile; + break; + } + } + final Namespace namespace = new DefaultNamespace( namespaceUrn, elementsGroupedByNamespaceUrn.get( namespaceUrn ), - Optional.of( file ), baseAttributes ); + Optional.ofNullable( fileContainingNamespaceDefinition ), Optional.ofNullable( namespaceDefinition ) ); ( (DefaultAspectModelFile) file ).setNamespace( namespace ); } } From ab1ffc8fa6d761d4f6c2d0881cebe0f283d450f7 Mon Sep 17 00:00:00 2001 From: "Textor Andreas (BCI/ESW17)" Date: Wed, 7 Aug 2024 16:27:27 +0200 Subject: [PATCH 10/33] Add capabilities for writing AspectModels to AspectSerializer --- .../serializer/AspectSerializer.java | 121 +++++++++++++++--- .../aspectmodel/serializer/PrettyPrinter.java | 82 ++++++------ .../serializer/SerializationException.java | 29 +++++ .../serializer/PrettyPrinterTest.java | 9 +- 4 files changed, 172 insertions(+), 69 deletions(-) create mode 100644 core/esmf-aspect-model-serializer/src/main/java/org/eclipse/esmf/aspectmodel/serializer/SerializationException.java diff --git a/core/esmf-aspect-model-serializer/src/main/java/org/eclipse/esmf/aspectmodel/serializer/AspectSerializer.java b/core/esmf-aspect-model-serializer/src/main/java/org/eclipse/esmf/aspectmodel/serializer/AspectSerializer.java index a5da06ce0..c2936fb3d 100644 --- a/core/esmf-aspect-model-serializer/src/main/java/org/eclipse/esmf/aspectmodel/serializer/AspectSerializer.java +++ b/core/esmf-aspect-model-serializer/src/main/java/org/eclipse/esmf/aspectmodel/serializer/AspectSerializer.java @@ -13,39 +13,120 @@ package org.eclipse.esmf.aspectmodel.serializer; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; import java.io.PrintWriter; import java.io.StringWriter; +import java.net.MalformedURLException; +import java.net.URI; +import java.net.URL; +import java.nio.charset.StandardCharsets; +import java.util.HashMap; +import java.util.Map; import java.util.function.Function; +import org.eclipse.esmf.aspectmodel.AspectModelFile; import org.eclipse.esmf.metamodel.Aspect; -import org.eclipse.esmf.metamodel.vocabulary.RdfNamespace; - -import org.apache.jena.rdf.model.Model; +import org.eclipse.esmf.metamodel.AspectModel; /** - * Convenience function to serialize an Aspect into a String + * Functions to write Aspect Models and Aspect Model files to Strings or their respective source locations */ -public class AspectSerializer implements Function { +public class AspectSerializer { public static final AspectSerializer INSTANCE = new AspectSerializer(); + private final Map> protocolHandlers = new HashMap<>(); + + protected AspectSerializer() { + registerProtocolHandler( "file", this::createFileOutputStream ); + } + + private OutputStream createFileOutputStream( final URI outputUri ) { + try { + return new FileOutputStream( new File( outputUri ) ); + } catch ( final FileNotFoundException exception ) { + throw new SerializationException( exception ); + } + } - @Override + /** + * Serializes an Aspect and the elements in the same file + * + * @param aspect the Aspect + * @return the String representation in RDF/Turtle + * @deprecated Use {link {@link #aspectToString(Aspect)}} instead + */ + @Deprecated( forRemoval = true ) public String apply( final Aspect aspect ) { - final RdfNamespace aspectNamespace = new RdfNamespace() { - @Override - public String getShortForm() { - return ""; - } - - @Override - public String getUri() { - return aspect.urn().getUrnPrefix(); - } - }; - final RdfModelCreatorVisitor rdfModelCreatorVisitor = new RdfModelCreatorVisitor( aspectNamespace ); + return aspectToString( aspect ); + } + + /** + * Serializes all files of an Aspect Model to their respective source locations + * + * @param aspectModel the Aspect Model + */ + public void write( final AspectModel aspectModel ) { + aspectModel.files().forEach( this::write ); + } + + /** + * Writes the content of an Aspect Model file to its defined source location + * + * @param aspectModelFile the Aspect Model file + */ + public void write( final AspectModelFile aspectModelFile ) { + if ( aspectModelFile.sourceLocation().isEmpty() ) { + throw new SerializationException( "Aspect Model file has no source location" ); + } + + final URI uri = aspectModelFile.sourceLocation().get(); + final URL url; + try { + url = uri.toURL(); + } catch ( final MalformedURLException exception ) { + throw new SerializationException( "Aspect Model file can only be written to locations given by URLs" ); + } + + final Function protocolHandler = protocolHandlers.get( url.getProtocol() ); + if ( protocolHandler == null ) { + throw new SerializationException( "Don't know how to write " + url.getProtocol() + " URLs: " + url ); + } + + final String content = aspectModelFileToString( aspectModelFile ); + try ( final OutputStream out = protocolHandler.apply( uri ) ) { + out.write( content.getBytes( StandardCharsets.UTF_8 ) ); + } catch ( final IOException exception ) { + throw new SerializationException( exception ); + } + } + + public void registerProtocolHandler( final String protocol, final Function protocolHandler ) { + protocolHandlers.put( protocol, protocolHandler ); + } + + /** + * Serializes an Aspect and the elements in the same file + * + * @param aspect the Aspect + * @return the String representation in RDF/Turtle + */ + public String aspectToString( final Aspect aspect ) { + return aspectModelFileToString( aspect.getSourceFile() ); + } + + /** + * Serializes an Aspect Model file + * + * @param aspectModelFile the Aspect Model file + * @return the String representation in RDF/Turtle + */ + public String aspectModelFileToString( final AspectModelFile aspectModelFile ) { final StringWriter stringWriter = new StringWriter(); try ( final PrintWriter printWriter = new PrintWriter( stringWriter ) ) { - final Model model = aspect.accept( rdfModelCreatorVisitor, null ).model(); - final PrettyPrinter prettyPrinter = new PrettyPrinter( model, aspect.urn(), printWriter ); + final PrettyPrinter prettyPrinter = new PrettyPrinter( aspectModelFile, printWriter ); prettyPrinter.print(); } return stringWriter.toString(); diff --git a/core/esmf-aspect-model-serializer/src/main/java/org/eclipse/esmf/aspectmodel/serializer/PrettyPrinter.java b/core/esmf-aspect-model-serializer/src/main/java/org/eclipse/esmf/aspectmodel/serializer/PrettyPrinter.java index a8cec3e9e..ad679b9bc 100644 --- a/core/esmf-aspect-model-serializer/src/main/java/org/eclipse/esmf/aspectmodel/serializer/PrettyPrinter.java +++ b/core/esmf-aspect-model-serializer/src/main/java/org/eclipse/esmf/aspectmodel/serializer/PrettyPrinter.java @@ -30,7 +30,7 @@ import java.util.stream.Collectors; import org.eclipse.esmf.aspectmodel.AspectModelFile; -import org.eclipse.esmf.aspectmodel.urn.AspectModelUrn; +import org.eclipse.esmf.metamodel.ModelElement; import org.eclipse.esmf.metamodel.vocabulary.RdfNamespace; import org.eclipse.esmf.metamodel.vocabulary.SammNs; @@ -75,36 +75,14 @@ public class PrettyPrinter { private final Set processedResources = new HashSet<>(); private final Queue resourceQueue = new ArrayDeque<>(); + private final List elementsToWrite; + private final List headerComment; private final Model model; - private final AspectModelUrn rootElementUrn; private final PrintWriter writer; private final Map prefixMap; - private final List headerComment; private final PrintVisitor printVisitor; - private PrettyPrinter( final Model model, final AspectModelUrn rootElementUrn, final PrintWriter writer, - final List headerComment ) { - this.writer = writer; - this.rootElementUrn = rootElementUrn; - this.model = ModelFactory.createDefaultModel(); - this.model.add( model ); - this.headerComment = List.of(); - - prefixMap = new HashMap<>( RdfNamespace.createPrefixMap() ); - prefixMap.putAll( model.getNsPrefixMap() ); - prefixMap.put( "", rootElementUrn.getUrnPrefix() ); - this.model.setNsPrefixes( prefixMap ); - - propertyOrder = createPredefinedPropertyOrder(); - prefixOrder = createPredefinedPrefixOrder(); - printVisitor = new PrintVisitor( this.model ); - } - - public PrettyPrinter( final Model model, final AspectModelUrn rootElementUrn, final PrintWriter writer ) { - this( model, rootElementUrn, writer, List.of() ); - } - /** * Creates a new Pretty Printer for a given Aspect Model File. * @@ -112,7 +90,20 @@ public PrettyPrinter( final Model model, final AspectModelUrn rootElementUrn, fi * @param writer the writer to write to */ public PrettyPrinter( final AspectModelFile modelFile, final PrintWriter writer ) { - this( modelFile.sourceModel(), modelFile.aspect().urn(), writer, modelFile.headerComment() ); + elementsToWrite = modelFile.elements(); + headerComment = modelFile.headerComment(); + this.writer = writer; + model = ModelFactory.createDefaultModel(); + model.add( modelFile.sourceModel() ); + + prefixMap = new HashMap<>( RdfNamespace.createPrefixMap() ); + prefixMap.putAll( model.getNsPrefixMap() ); + prefixMap.put( "", modelFile.namespace().elementUrnPrefix() ); + model.setNsPrefixes( prefixMap ); + + propertyOrder = createPredefinedPropertyOrder(); + prefixOrder = createPredefinedPrefixOrder(); + printVisitor = new PrintVisitor( model ); } private Comparator createPredefinedPropertyOrder() { @@ -177,21 +168,19 @@ private Comparator> createPredefinedPrefixOrder() { .thenComparing( Map.Entry::getKey ); } - private Properties loadProperties( final String filename ) { + private void showMilestoneBanner() { final Properties properties = new Properties(); - final InputStream propertiesResource = PrettyPrinter.class.getClassLoader().getResourceAsStream( filename ); + final InputStream propertiesResource = PrettyPrinter.class.getClassLoader().getResourceAsStream( "pom.properties" ); + if ( propertiesResource == null ) { + return; + } try { properties.load( propertiesResource ); } catch ( final IOException exception ) { - throw new RuntimeException( "Failed to load Properties: " + filename ); + return; } - return properties; - } - private void showMilestoneBanner() { - // property file generated by properties-maven-plugin at build time - final Properties applicationProperties = loadProperties( "pom.properties" ); - final String version = applicationProperties.get( "aspect-meta-model-version" ).toString(); + final String version = properties.get( "aspect-meta-model-version" ).toString(); if ( version.contains( "-M" ) ) { writer.println( "# This model was created using SAMM version " + version + " and is not intended for productive usage." ); writer.println(); @@ -203,7 +192,7 @@ private void showMilestoneBanner() { */ public void print() { for ( final String line : headerComment ) { - writer.println( line ); + writer.println( "# " + line ); } if ( headerComment.size() > 1 ) { writer.println(); @@ -215,8 +204,11 @@ public void print() { .forEach( entry -> writer.format( "@prefix %s: <%s> .%n", entry.getKey(), entry.getValue() ) ); writer.println(); - final Resource rootElementResource = ResourceFactory.createResource( rootElementUrn.toString() ); - resourceQueue.add( rootElementResource ); + elementsToWrite.stream() + .filter( element -> !element.isAnonymous() ) + .map( ModelElement::urn ) + .map( urn -> ResourceFactory.createResource( urn.toString() ) ) + .forEach( resourceQueue::add ); while ( !resourceQueue.isEmpty() ) { final Resource resource = resourceQueue.poll(); writer.print( processElement( resource, 0 ) ); @@ -300,9 +292,9 @@ private void escapeStringAndAppendToBuilder( final String input, final StringBui int index = 0; while ( index < chars.length ) { final boolean indexAtUnicodeEscapeSequence = chars[index] == '\\' - && (index + 1) < chars.length + && ( index + 1 ) < chars.length && chars[index + 1] == 'u' - && (index + 5) <= (chars.length - 1); + && ( index + 5 ) <= ( chars.length - 1 ); if ( indexAtUnicodeEscapeSequence ) { final long codepoint = Long.parseLong( new String( chars, index + 2, 4 ), 16 ); builder.append( (char) codepoint ); @@ -357,7 +349,7 @@ private String serializeResource( final RDFNode rdfNode, final int indentationLe return print( resource ); } - if ( (resource.isURIResource() && resource.getURI().equals( RDF.nil.getURI() )) + if ( ( resource.isURIResource() && resource.getURI().equals( RDF.nil.getURI() ) ) || statements( resource, RDF.first, null ).iterator().hasNext() ) { return serializeList( resource, indentationLevel ); } @@ -401,9 +393,9 @@ private String processElement( final Resource element, final int indentationLeve .map( statement -> String.format( "%s%s %s", INDENT.repeat( indentationLevel + 1 ), serialize( statement.getPredicate(), indentationLevel ), serialize( statement.getObject(), indentationLevel ) ) ) - .collect( Collectors.joining( String.format( " ;%n" ), "", (element.isAnon() + .collect( Collectors.joining( String.format( " ;%n" ), "", ( element.isAnon() ? String.format( " %n%s]", INDENT.repeat( indentationLevel ) ) - : "") ) ); + : "" ) ) ); if ( body.isEmpty() ) { return String.format( "%s .%n%n", firstLine ); } else { @@ -414,7 +406,7 @@ private String processElement( final Resource element, final int indentationLeve if ( body.endsWith( "]" ) && indentationLevel >= 1 ) { return firstPart; } - return String.format( (indentationLevel >= 1 ? "%s ;%n" : "%s .%n%n"), firstPart ); + return String.format( ( indentationLevel >= 1 ? "%s ;%n" : "%s .%n%n" ), firstPart ); } } @@ -445,7 +437,7 @@ public Object visitLiteral( final Node_Literal it, final LiteralLabel lit ) { lf = lf.replace( singleQuote, "\\'" ); } // RDF 1.1 : Print xsd:string without ^^xsd:string - return singleQuote + lf + singleQuote + (Util.isSimpleString( it ) ? "" : "^^" + it.getLiteralDatatypeURI()); + return singleQuote + lf + singleQuote + ( Util.isSimpleString( it ) ? "" : "^^" + it.getLiteralDatatypeURI() ); } @Override diff --git a/core/esmf-aspect-model-serializer/src/main/java/org/eclipse/esmf/aspectmodel/serializer/SerializationException.java b/core/esmf-aspect-model-serializer/src/main/java/org/eclipse/esmf/aspectmodel/serializer/SerializationException.java new file mode 100644 index 000000000..9d44b32e0 --- /dev/null +++ b/core/esmf-aspect-model-serializer/src/main/java/org/eclipse/esmf/aspectmodel/serializer/SerializationException.java @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2024 Robert Bosch Manufacturing Solutions GmbH + * + * See the AUTHORS file(s) distributed with this work for additional + * information regarding authorship. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * SPDX-License-Identifier: MPL-2.0 + */ + +package org.eclipse.esmf.aspectmodel.serializer; + +import java.io.Serial; + +public class SerializationException extends RuntimeException { + @Serial + private static final long serialVersionUID = 8685799345891779111L; + + public SerializationException( final String message ) { + super( message ); + } + + public SerializationException( final Throwable cause ) { + super( cause ); + } +} diff --git a/core/esmf-aspect-model-serializer/src/test/java/org/eclipse/esmf/aspectmodel/serializer/PrettyPrinterTest.java b/core/esmf-aspect-model-serializer/src/test/java/org/eclipse/esmf/aspectmodel/serializer/PrettyPrinterTest.java index 60ea03e03..3ca7427a7 100644 --- a/core/esmf-aspect-model-serializer/src/test/java/org/eclipse/esmf/aspectmodel/serializer/PrettyPrinterTest.java +++ b/core/esmf-aspect-model-serializer/src/test/java/org/eclipse/esmf/aspectmodel/serializer/PrettyPrinterTest.java @@ -21,6 +21,7 @@ import java.io.PrintWriter; import java.nio.charset.StandardCharsets; +import org.eclipse.esmf.aspectmodel.AspectModelFile; import org.eclipse.esmf.aspectmodel.resolver.services.TurtleLoader; import org.eclipse.esmf.metamodel.AspectModel; import org.eclipse.esmf.test.TestAspect; @@ -36,18 +37,18 @@ public class PrettyPrinterTest { // contains blank nodes which are not referenced from an aspect and therefore not pretty-printed "MODEL_WITH_BLANK_AND_ADDITIONAL_NODES" } ) - public void testPrettyPrinter( final TestAspect testAspect ) { + void testPrettyPrinter( final TestAspect testAspect ) { final AspectModel aspectModel = TestResources.load( testAspect ); - final Model originalModel = aspectModel.files().iterator().next().sourceModel(); + final AspectModelFile originalFile = aspectModel.files().iterator().next(); final ByteArrayOutputStream buffer = new ByteArrayOutputStream(); final PrintWriter writer = new PrintWriter( buffer, false, StandardCharsets.UTF_8 ); - new PrettyPrinter( originalModel, testAspect.getUrn(), writer ).print(); + new PrettyPrinter( originalFile, writer ).print(); writer.flush(); final InputStream bufferInput = new ByteArrayInputStream( buffer.toByteArray() ); final Model prettyPrintedModel = TurtleLoader.loadTurtle( buffer.toString( StandardCharsets.UTF_8 ) ).get(); - assertThat( RdfComparison.hash( originalModel ).equals( RdfComparison.hash( prettyPrintedModel ) ) ).isTrue(); + assertThat( RdfComparison.hash( originalFile.sourceModel() ).equals( RdfComparison.hash( prettyPrintedModel ) ) ).isTrue(); } } From 9241e2850c0f744fbb23caaa239972de261be339 Mon Sep 17 00:00:00 2001 From: "Textor Andreas (BCI/ESW17)" Date: Thu, 8 Aug 2024 07:27:13 +0200 Subject: [PATCH 11/33] Remove usage of deprecated method --- .../generator/zip/AspectModelNamespacePackageCreator.java | 2 +- .../modules/tooling-guide/examples/SerializeAspectModel.java | 2 +- .../org/eclipse/esmf/aspectmodel/GenerateAspectFromAas.java | 2 +- .../src/main/java/org/eclipse/esmf/aspectmodel/PrettyPrint.java | 2 +- .../main/java/org/eclipse/esmf/aas/to/AasToAspectCommand.java | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/core/esmf-aspect-model-document-generators/src/main/java/org/eclipse/esmf/aspectmodel/generator/zip/AspectModelNamespacePackageCreator.java b/core/esmf-aspect-model-document-generators/src/main/java/org/eclipse/esmf/aspectmodel/generator/zip/AspectModelNamespacePackageCreator.java index 65567d135..0cad13fe6 100644 --- a/core/esmf-aspect-model-document-generators/src/main/java/org/eclipse/esmf/aspectmodel/generator/zip/AspectModelNamespacePackageCreator.java +++ b/core/esmf-aspect-model-document-generators/src/main/java/org/eclipse/esmf/aspectmodel/generator/zip/AspectModelNamespacePackageCreator.java @@ -44,7 +44,7 @@ public void accept( final AspectModel aspectModel, final OutputStream outputStre private static void addFileToArchive( final AspectModelFile file, final ZipOutputStream zos, final String rootPath ) throws IOException { - final String aspectString = AspectSerializer.INSTANCE.apply( file.aspect() ); + final String aspectString = AspectSerializer.INSTANCE.aspectToString( file.aspect() ); final String fileName = String.format( "%s/%s/%s/%s.ttl", !rootPath.isBlank() ? String.format( "%s/%s", rootPath, BASE_ARCHIVE_FORMAT_PATH ) : BASE_ARCHIVE_FORMAT_PATH, file.aspect().urn().getNamespaceMainPart(), diff --git a/documentation/developer-guide/modules/tooling-guide/examples/SerializeAspectModel.java b/documentation/developer-guide/modules/tooling-guide/examples/SerializeAspectModel.java index 41c46e530..6bf0d7534 100644 --- a/documentation/developer-guide/modules/tooling-guide/examples/SerializeAspectModel.java +++ b/documentation/developer-guide/modules/tooling-guide/examples/SerializeAspectModel.java @@ -38,7 +38,7 @@ public void serializeAspectModel() { // A String that contains the pretty printed Aspect Model final String aspectString = - AspectSerializer.INSTANCE.apply( aspectModel.aspect() ); + AspectSerializer.INSTANCE.aspectToString( aspectModel.aspect() ); // end::serialize[] assertThat( aspectString ).contains( ":Movement a samm:Aspect" ); assertThat( aspectString ).contains( ":isMoving a samm:Property" ); diff --git a/tools/esmf-aspect-model-maven-plugin/src/main/java/org/eclipse/esmf/aspectmodel/GenerateAspectFromAas.java b/tools/esmf-aspect-model-maven-plugin/src/main/java/org/eclipse/esmf/aspectmodel/GenerateAspectFromAas.java index e2dccd9b0..0cce3f64d 100644 --- a/tools/esmf-aspect-model-maven-plugin/src/main/java/org/eclipse/esmf/aspectmodel/GenerateAspectFromAas.java +++ b/tools/esmf-aspect-model-maven-plugin/src/main/java/org/eclipse/esmf/aspectmodel/GenerateAspectFromAas.java @@ -39,7 +39,7 @@ public void executeGeneration() throws MojoExecutionException, MojoFailureExcept for ( final Aspect aspect : generator.generateAspects() ) { try ( final FileOutputStream outputStreamForFile = new FileOutputStream( getOutputFile( aspect ) ) ) { - outputStreamForFile.write( AspectSerializer.INSTANCE.apply( aspect ).getBytes() ); + outputStreamForFile.write( AspectSerializer.INSTANCE.aspectToString( aspect ).getBytes() ); } catch ( final IOException exception ) { throw new MojoExecutionException( "Could not write file", exception ); } diff --git a/tools/esmf-aspect-model-maven-plugin/src/main/java/org/eclipse/esmf/aspectmodel/PrettyPrint.java b/tools/esmf-aspect-model-maven-plugin/src/main/java/org/eclipse/esmf/aspectmodel/PrettyPrint.java index 624bb3d56..a800ed7c9 100644 --- a/tools/esmf-aspect-model-maven-plugin/src/main/java/org/eclipse/esmf/aspectmodel/PrettyPrint.java +++ b/tools/esmf-aspect-model-maven-plugin/src/main/java/org/eclipse/esmf/aspectmodel/PrettyPrint.java @@ -38,7 +38,7 @@ public void executeGeneration() throws MojoExecutionException, MojoFailureExcept for ( final AspectModel aspectModel : loadModels() ) { final Aspect aspect = aspectModel.aspect(); - final String formatted = AspectSerializer.INSTANCE.apply( aspect ); + final String formatted = AspectSerializer.INSTANCE.aspectToString( aspect ); final String aspectModelFileName = String.format( "%s.ttl", aspect.getName() ); try ( final FileOutputStream streamForFile = getOutputStreamForFile( aspectModelFileName, outputDirectory ); final PrintWriter writer = new PrintWriter( streamForFile ) ) { diff --git a/tools/samm-cli/src/main/java/org/eclipse/esmf/aas/to/AasToAspectCommand.java b/tools/samm-cli/src/main/java/org/eclipse/esmf/aas/to/AasToAspectCommand.java index ba6a2070a..43872e08d 100644 --- a/tools/samm-cli/src/main/java/org/eclipse/esmf/aas/to/AasToAspectCommand.java +++ b/tools/samm-cli/src/main/java/org/eclipse/esmf/aas/to/AasToAspectCommand.java @@ -69,7 +69,7 @@ private void generateAspects( final AasToAspectModelGenerator generator ) { .toList(); for ( final Aspect aspect : filteredAspects ) { - final String aspectString = AspectSerializer.INSTANCE.apply( aspect ); + final String aspectString = AspectSerializer.INSTANCE.aspectToString( aspect ); final File targetFile = modelsRoot.determineAspectModelFile( aspect.urn() ); LOG.info( "Writing {}", targetFile.getAbsolutePath() ); final File directory = targetFile.getParentFile(); From 0b3d3d97127e0cf7e9ff3136991de20ebcf07ba7 Mon Sep 17 00:00:00 2001 From: "Textor Andreas (BCI/ESW17)" Date: Thu, 8 Aug 2024 09:50:27 +0200 Subject: [PATCH 12/33] Fix formatting in PrettyPrinter for prefixes, lists and file header --- .../org/eclipse/esmf/aspectmodel/RdfUtil.java | 58 +++++++++++++++++++ .../edit/ChangeReportFormatter.java | 31 +--------- .../resolver/AspectModelFileLoader.java | 5 +- .../aspectmodel/serializer/PrettyPrinter.java | 47 +++++++++++---- .../serializer/PrettyPrinterTest.java | 28 ++++++--- 5 files changed, 118 insertions(+), 51 deletions(-) diff --git a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/RdfUtil.java b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/RdfUtil.java index bc5b867d0..1142def18 100644 --- a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/RdfUtil.java +++ b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/RdfUtil.java @@ -15,18 +15,26 @@ import static java.util.stream.Collectors.toSet; +import java.util.HashMap; +import java.util.Map; import java.util.Set; import java.util.function.Function; import java.util.stream.Stream; import org.eclipse.esmf.aspectmodel.urn.AspectModelUrn; +import org.eclipse.esmf.aspectmodel.urn.ElementType; +import org.eclipse.esmf.metamodel.vocabulary.SammNs; import com.google.common.collect.Streams; +import org.apache.jena.rdf.model.Literal; import org.apache.jena.rdf.model.Model; import org.apache.jena.rdf.model.ModelFactory; import org.apache.jena.rdf.model.Property; import org.apache.jena.rdf.model.RDFNode; import org.apache.jena.rdf.model.Resource; +import org.apache.jena.rdf.model.Statement; +import org.apache.jena.vocabulary.RDF; +import org.apache.jena.vocabulary.XSD; public class RdfUtil { public static Model getModelElementDefinition( final Resource element ) { @@ -68,4 +76,54 @@ public static Set getAllUrnsInModel( final Model model ) { .flatMap( urn -> AspectModelUrn.from( urn ).toJavaOptional().stream() ); } ) ).flatMap( Function.identity() ).collect( toSet() ); } + + public static void cleanPrefixes( final Model model ) { + final Map originalPrefixMap = new HashMap<>( model.getNsPrefixMap() ); + model.clearNsPrefixMap(); + // SAMM prefixes + getAllUrnsInModel( model ).forEach( urn -> { + switch ( urn.getElementType() ) { + case META_MODEL -> model.setNsPrefix( SammNs.SAMM.getShortForm(), SammNs.SAMM.getNamespace() ); + case CHARACTERISTIC -> model.setNsPrefix( SammNs.SAMMC.getShortForm(), SammNs.SAMMC.getNamespace() ); + case ENTITY -> model.setNsPrefix( SammNs.SAMME.getShortForm(), SammNs.SAMME.getNamespace() ); + case UNIT -> model.setNsPrefix( SammNs.UNIT.getShortForm(), SammNs.UNIT.getNamespace() ); + } + } ); + // XSD + Stream.concat( + Streams.stream( model.listObjects() ) + .filter( RDFNode::isLiteral ) + .map( RDFNode::asLiteral ) + .map( Literal::getDatatypeURI ) + .filter( type -> type.startsWith( XSD.NS ) ) + .filter( type -> !type.equals( XSD.xstring.getURI() ) ), + Streams.stream( model.listObjects() ) + .filter( RDFNode::isURIResource ) + .map( RDFNode::asResource ) + .map( Resource::getURI ) + .filter( type -> type.startsWith( XSD.NS ) ) ) + .findAny() + .ifPresent( __ -> model.setNsPrefix( "xsd", XSD.NS ) ); + // Empty (namespace) prefix + Streams.stream( model.listStatements( null, RDF.type, (RDFNode) null ) ) + .map( Statement::getSubject ) + .filter( Resource::isURIResource ) + .map( Resource::getURI ) + .map( AspectModelUrn::fromUrn ) + .findAny() + .ifPresent( urn -> model.setNsPrefix( "", urn.getUrnPrefix() ) ); + // Add back custom prefixes not already covered: + // - if the prefix or URI is not set already + // - it's not XSD (no need to add it here if it's not added above) + // - if it's a SAMM URN, it's a regular namespace (not a meta model namespace) + originalPrefixMap.forEach( ( prefix, uri ) -> { + if ( !model.getNsPrefixMap().containsKey( prefix ) + && !model.getNsPrefixMap().containsValue( uri ) + && !uri.equals( XSD.NS ) + && ( !uri.startsWith( "urn:samm:" ) || AspectModelUrn.fromUrn( uri + "x" ).getElementType() == ElementType.NONE ) + ) { + model.setNsPrefix( prefix, uri ); + } + } ); + } } diff --git a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/ChangeReportFormatter.java b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/ChangeReportFormatter.java index 237cea9bb..1467ab116 100644 --- a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/ChangeReportFormatter.java +++ b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/ChangeReportFormatter.java @@ -19,18 +19,9 @@ import java.util.function.BiFunction; import org.eclipse.esmf.aspectmodel.RdfUtil; -import org.eclipse.esmf.aspectmodel.urn.AspectModelUrn; -import org.eclipse.esmf.metamodel.vocabulary.SammNs; -import com.google.common.collect.Streams; -import org.apache.jena.rdf.model.Literal; import org.apache.jena.rdf.model.Model; import org.apache.jena.rdf.model.ModelFactory; -import org.apache.jena.rdf.model.RDFNode; -import org.apache.jena.rdf.model.Resource; -import org.apache.jena.rdf.model.Statement; -import org.apache.jena.vocabulary.RDF; -import org.apache.jena.vocabulary.XSD; public class ChangeReportFormatter implements BiFunction { public static final ChangeReportFormatter INSTANCE = new ChangeReportFormatter(); @@ -123,27 +114,7 @@ private void append( final StringBuilder builder, final ChangeReport report, fin private String show( final Model model ) { final Model copy = ModelFactory.createDefaultModel(); copy.add( model ); - RdfUtil.getAllUrnsInModel( model ).forEach( urn -> { - switch ( urn.getElementType() ) { - case META_MODEL -> copy.setNsPrefix( SammNs.SAMM.getShortForm(), SammNs.SAMM.getNamespace() ); - case CHARACTERISTIC -> copy.setNsPrefix( SammNs.SAMMC.getShortForm(), SammNs.SAMMC.getNamespace() ); - case ENTITY -> copy.setNsPrefix( SammNs.SAMME.getShortForm(), SammNs.SAMME.getNamespace() ); - case UNIT -> copy.setNsPrefix( SammNs.UNIT.getShortForm(), SammNs.UNIT.getNamespace() ); - } - } ); - Streams.stream( model.listObjects() ) - .filter( RDFNode::isLiteral ) - .map( RDFNode::asLiteral ) - .map( Literal::getDatatypeURI ) - .filter( type -> type.startsWith( XSD.NS ) ) - .filter( type -> !type.equals( XSD.xstring.getURI() ) ) - .findAny() - .ifPresent( __ -> copy.setNsPrefix( "xsd", XSD.NS ) ); - Streams.stream( model.listStatements( null, RDF.type, (RDFNode) null ) ) - .map( Statement::getSubject ) - .map( Resource::getURI ) - .map( AspectModelUrn::fromUrn ) - .forEach( urn -> copy.setNsPrefix( "", urn.getUrnPrefix() ) ); + RdfUtil.cleanPrefixes( copy ); final StringWriter stringWriter = new StringWriter(); stringWriter.append( "--------------------\n" ); copy.write( stringWriter, "TURTLE" ); diff --git a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/resolver/AspectModelFileLoader.java b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/resolver/AspectModelFileLoader.java index 5ca78199e..6accf3f75 100644 --- a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/resolver/AspectModelFileLoader.java +++ b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/resolver/AspectModelFileLoader.java @@ -73,11 +73,14 @@ private static String content( final InputStream inputStream ) { } private static List headerComment( final String content ) { - return content.lines() + final List list = content.lines() .dropWhile( String::isBlank ) .takeWhile( line -> line.startsWith( "#" ) || isBlank( line ) ) .map( line -> line.startsWith( "#" ) ? line.substring( 1 ).trim() : line ) .toList(); + return !list.isEmpty() && list.get( list.size() - 1 ).isEmpty() + ? list.subList( 0, list.size() - 1 ) + : list; } public static RawAspectModelFile load( final InputStream inputStream ) { diff --git a/core/esmf-aspect-model-serializer/src/main/java/org/eclipse/esmf/aspectmodel/serializer/PrettyPrinter.java b/core/esmf-aspect-model-serializer/src/main/java/org/eclipse/esmf/aspectmodel/serializer/PrettyPrinter.java index ad679b9bc..7cd88db2f 100644 --- a/core/esmf-aspect-model-serializer/src/main/java/org/eclipse/esmf/aspectmodel/serializer/PrettyPrinter.java +++ b/core/esmf-aspect-model-serializer/src/main/java/org/eclipse/esmf/aspectmodel/serializer/PrettyPrinter.java @@ -19,7 +19,6 @@ import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Comparator; -import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; @@ -30,8 +29,9 @@ import java.util.stream.Collectors; import org.eclipse.esmf.aspectmodel.AspectModelFile; +import org.eclipse.esmf.aspectmodel.RdfUtil; +import org.eclipse.esmf.metamodel.Aspect; import org.eclipse.esmf.metamodel.ModelElement; -import org.eclipse.esmf.metamodel.vocabulary.RdfNamespace; import org.eclipse.esmf.metamodel.vocabulary.SammNs; import com.google.common.collect.ImmutableList; @@ -79,7 +79,6 @@ public class PrettyPrinter { private final List headerComment; private final Model model; private final PrintWriter writer; - private final Map prefixMap; private final PrintVisitor printVisitor; @@ -95,11 +94,7 @@ public PrettyPrinter( final AspectModelFile modelFile, final PrintWriter writer this.writer = writer; model = ModelFactory.createDefaultModel(); model.add( modelFile.sourceModel() ); - - prefixMap = new HashMap<>( RdfNamespace.createPrefixMap() ); - prefixMap.putAll( model.getNsPrefixMap() ); - prefixMap.put( "", modelFile.namespace().elementUrnPrefix() ); - model.setNsPrefixes( prefixMap ); + RdfUtil.cleanPrefixes( model ); propertyOrder = createPredefinedPropertyOrder(); prefixOrder = createPredefinedPrefixOrder(); @@ -200,15 +195,24 @@ public void print() { showMilestoneBanner(); - prefixMap.entrySet().stream().sorted( prefixOrder ) + model.getNsPrefixMap().entrySet().stream().sorted( prefixOrder ) .forEach( entry -> writer.format( "@prefix %s: <%s> .%n", entry.getKey(), entry.getValue() ) ); writer.println(); + // Write Aspects first, all other elements afterwards + elementsToWrite.stream() + .filter( element -> element.is( Aspect.class ) ) + .filter( element -> !element.isAnonymous() ) + .map( ModelElement::urn ) + .map( urn -> ResourceFactory.createResource( urn.toString() ) ) + .forEach( resourceQueue::add ); elementsToWrite.stream() .filter( element -> !element.isAnonymous() ) + .filter( element -> !element.is( Aspect.class ) ) .map( ModelElement::urn ) .map( urn -> ResourceFactory.createResource( urn.toString() ) ) .forEach( resourceQueue::add ); + while ( !resourceQueue.isEmpty() ) { final Resource resource = resourceQueue.poll(); writer.print( processElement( resource, 0 ) ); @@ -238,8 +242,28 @@ private String serializeList( final Resource list, final int indentationLevel ) return "( )"; } - return list.as( RDFList.class ).asJavaList().stream().map( listNode -> serialize( listNode, indentationLevel ) ) - .collect( Collectors.joining( " ", "( ", " )" ) ); + final List listContent = list.as( RDFList.class ).asJavaList(); + final long anonymousNodesInList = listContent.stream().filter( RDFNode::isAnon ).count(); + if ( listContent.size() <= 3 && anonymousNodesInList <= 1 ) { + return listContent.stream() + .map( listNode -> serialize( listNode, indentationLevel ) ) + .collect( Collectors.joining( " ", "( ", " )" ) ); + } + final StringBuilder builder = new StringBuilder(); + builder.append( "(\n" ); + int i = 0; + for ( final RDFNode listNode : listContent ) { + builder.append( INDENT.repeat( indentationLevel + 2 ) ); + builder.append( serialize( listNode, indentationLevel + 2 ) ); + i++; + if ( i < listContent.size() ) { + builder.append( "\n" ); + } + } + builder.append( "\n" ); + builder.append( INDENT.repeat( indentationLevel + 1 ) ); + builder.append( ")" ); + return builder.toString(); } private String serialize( final RDFNode rdfNode, final int indentationLevel ) { @@ -418,7 +442,6 @@ private String print( final RDFNode node ) { } record PrintVisitor( Model model ) implements NodeVisitor { - @Override public Object visitAny( final Node_ANY it ) { return "*"; diff --git a/core/esmf-aspect-model-serializer/src/test/java/org/eclipse/esmf/aspectmodel/serializer/PrettyPrinterTest.java b/core/esmf-aspect-model-serializer/src/test/java/org/eclipse/esmf/aspectmodel/serializer/PrettyPrinterTest.java index 3ca7427a7..d23b3b06f 100644 --- a/core/esmf-aspect-model-serializer/src/test/java/org/eclipse/esmf/aspectmodel/serializer/PrettyPrinterTest.java +++ b/core/esmf-aspect-model-serializer/src/test/java/org/eclipse/esmf/aspectmodel/serializer/PrettyPrinterTest.java @@ -20,25 +20,27 @@ import java.io.InputStream; import java.io.PrintWriter; import java.nio.charset.StandardCharsets; +import java.util.Arrays; +import java.util.stream.Stream; import org.eclipse.esmf.aspectmodel.AspectModelFile; import org.eclipse.esmf.aspectmodel.resolver.services.TurtleLoader; import org.eclipse.esmf.metamodel.AspectModel; import org.eclipse.esmf.test.TestAspect; +import org.eclipse.esmf.test.TestModel; +import org.eclipse.esmf.test.TestProperty; import org.eclipse.esmf.test.TestResources; import org.apache.jena.rdf.model.Model; import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.EnumSource; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; public class PrettyPrinterTest { @ParameterizedTest - @EnumSource( value = TestAspect.class, mode = EnumSource.Mode.EXCLUDE, names = { - // contains blank nodes which are not referenced from an aspect and therefore not pretty-printed - "MODEL_WITH_BLANK_AND_ADDITIONAL_NODES" - } ) - void testPrettyPrinter( final TestAspect testAspect ) { - final AspectModel aspectModel = TestResources.load( testAspect ); + @MethodSource( value = "testModels" ) + void testPrettyPrinter( final TestModel testModel ) { + final AspectModel aspectModel = TestResources.load( testModel ); final AspectModelFile originalFile = aspectModel.files().iterator().next(); final ByteArrayOutputStream buffer = new ByteArrayOutputStream(); @@ -47,8 +49,18 @@ void testPrettyPrinter( final TestAspect testAspect ) { writer.flush(); final InputStream bufferInput = new ByteArrayInputStream( buffer.toByteArray() ); - final Model prettyPrintedModel = TurtleLoader.loadTurtle( buffer.toString( StandardCharsets.UTF_8 ) ).get(); + final String formattedModel = buffer.toString( StandardCharsets.UTF_8 ); + System.out.println( formattedModel ); + final Model prettyPrintedModel = TurtleLoader.loadTurtle( formattedModel ).get(); assertThat( RdfComparison.hash( originalFile.sourceModel() ).equals( RdfComparison.hash( prettyPrintedModel ) ) ).isTrue(); } + + static Stream testModels() { + return Stream.concat( Arrays.stream( TestAspect.values() ), Arrays.stream( TestProperty.values() ) + ).filter( testModel -> + // contains blank nodes which are not referenced from an aspect and therefore not pretty-printed + testModel != TestAspect.MODEL_WITH_BLANK_AND_ADDITIONAL_NODES ) + .map( Arguments::arguments ); + } } From 888325172510f9914f2adbe648f9c5e820263f5a Mon Sep 17 00:00:00 2001 From: "Textor Andreas (BCI/ESW17)" Date: Thu, 8 Aug 2024 10:28:12 +0200 Subject: [PATCH 13/33] Keep original element order in PrettyPrinter --- .../aspectmodel/serializer/PrettyPrinter.java | 36 ++++++++++++++----- 1 file changed, 27 insertions(+), 9 deletions(-) diff --git a/core/esmf-aspect-model-serializer/src/main/java/org/eclipse/esmf/aspectmodel/serializer/PrettyPrinter.java b/core/esmf-aspect-model-serializer/src/main/java/org/eclipse/esmf/aspectmodel/serializer/PrettyPrinter.java index 7cd88db2f..bcb2a7fcd 100644 --- a/core/esmf-aspect-model-serializer/src/main/java/org/eclipse/esmf/aspectmodel/serializer/PrettyPrinter.java +++ b/core/esmf-aspect-model-serializer/src/main/java/org/eclipse/esmf/aspectmodel/serializer/PrettyPrinter.java @@ -30,7 +30,6 @@ import org.eclipse.esmf.aspectmodel.AspectModelFile; import org.eclipse.esmf.aspectmodel.RdfUtil; -import org.eclipse.esmf.metamodel.Aspect; import org.eclipse.esmf.metamodel.ModelElement; import org.eclipse.esmf.metamodel.vocabulary.SammNs; @@ -48,6 +47,7 @@ import org.apache.jena.graph.Node_URI; import org.apache.jena.graph.Node_Variable; import org.apache.jena.graph.Triple; +import org.apache.jena.graph.UriNode; import org.apache.jena.graph.impl.LiteralLabel; import org.apache.jena.rdf.model.Literal; import org.apache.jena.rdf.model.Model; @@ -58,6 +58,7 @@ import org.apache.jena.rdf.model.Resource; import org.apache.jena.rdf.model.ResourceFactory; import org.apache.jena.rdf.model.Statement; +import org.apache.jena.rdf.model.StmtIterator; import org.apache.jena.rdf.model.impl.Util; import org.apache.jena.vocabulary.RDF; import org.apache.jena.vocabulary.XSD; @@ -70,6 +71,7 @@ public class PrettyPrinter { private static final String INDENT = " "; private static final String TRIPLE_QUOTE = "\"\"\""; private static final String LINE_BREAK = "\n"; + private final Comparator elementDefinitionOrder; private final Comparator propertyOrder; private final Comparator> prefixOrder; private final Set processedResources = new HashSet<>(); @@ -96,11 +98,34 @@ public PrettyPrinter( final AspectModelFile modelFile, final PrintWriter writer model.add( modelFile.sourceModel() ); RdfUtil.cleanPrefixes( model ); + elementDefinitionOrder = createElementDefinitionOrder(); propertyOrder = createPredefinedPropertyOrder(); prefixOrder = createPredefinedPrefixOrder(); printVisitor = new PrintVisitor( model ); } + private Comparator createElementDefinitionOrder() { + return Comparator.comparingInt( element -> { + final Resource resource = element.getSourceFile().sourceModel().createResource( element.urn().toString() ); + final StmtIterator iterator = resource.getModel().listStatements( resource, RDF.type, (RDFNode) null ); + if ( iterator.hasNext() ) { + final Statement statement = iterator.next(); + if ( statement.getSubject().asNode() instanceof final UriNode uriNode ) { + return uriNode.getToken().line(); + } else { + // This happens when the model was not loaded using the esmf-sdk customized RDF parser, e.g. + // for programmatically created models. + // Fall back to inherent RDF order (i.e. no order). At least try to keep samm:Aspect on the top. + if ( statement.getObject().isURIResource() && statement.getResource().equals( SammNs.SAMM.Aspect() ) ) { + return 0; + } + return 1; + } + } + return Integer.MAX_VALUE; + } ); + } + private Comparator createPredefinedPropertyOrder() { final List predefinedPropertyOrder = new ArrayList<>(); predefinedPropertyOrder.add( SammNs.SAMM._extends() ); @@ -199,16 +224,9 @@ public void print() { .forEach( entry -> writer.format( "@prefix %s: <%s> .%n", entry.getKey(), entry.getValue() ) ); writer.println(); - // Write Aspects first, all other elements afterwards - elementsToWrite.stream() - .filter( element -> element.is( Aspect.class ) ) - .filter( element -> !element.isAnonymous() ) - .map( ModelElement::urn ) - .map( urn -> ResourceFactory.createResource( urn.toString() ) ) - .forEach( resourceQueue::add ); elementsToWrite.stream() .filter( element -> !element.isAnonymous() ) - .filter( element -> !element.is( Aspect.class ) ) + .sorted( elementDefinitionOrder ) .map( ModelElement::urn ) .map( urn -> ResourceFactory.createResource( urn.toString() ) ) .forEach( resourceQueue::add ); From 81bbfbe2f4534009ec52e09b318d6df8bcfe9276 Mon Sep 17 00:00:00 2001 From: "Textor Andreas (BCI/ESW17)" Date: Thu, 8 Aug 2024 11:05:47 +0200 Subject: [PATCH 14/33] Refactor String->Model creation method to RdfUtils --- .../org/eclipse/esmf/aspectmodel/RdfUtil.java | 11 + .../resolver/services/TurtleLoader.java | 2 +- .../edit/AspectChangeContextTest.java | 26 +-- .../shacl/RustLikeFormatterTest.java | 45 ++-- .../aspectmodel/shacl/ShaclValidatorTest.java | 203 ++++++++---------- 5 files changed, 125 insertions(+), 162 deletions(-) diff --git a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/RdfUtil.java b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/RdfUtil.java index 1142def18..5544990b3 100644 --- a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/RdfUtil.java +++ b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/RdfUtil.java @@ -21,6 +21,7 @@ import java.util.function.Function; import java.util.stream.Stream; +import org.eclipse.esmf.aspectmodel.resolver.services.TurtleLoader; import org.eclipse.esmf.aspectmodel.urn.AspectModelUrn; import org.eclipse.esmf.aspectmodel.urn.ElementType; import org.eclipse.esmf.metamodel.vocabulary.SammNs; @@ -126,4 +127,14 @@ public static void cleanPrefixes( final Model model ) { } } ); } + + /** + * Convenience method to load an RDF/Turtle model from its String representation + * + * @param ttlRepresentation the RDF/Turtle representation of the model + * @return the parsed model + */ + public static Model createModel( final String ttlRepresentation ) { + return TurtleLoader.loadTurtle( ttlRepresentation ).get(); + } } diff --git a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/resolver/services/TurtleLoader.java b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/resolver/services/TurtleLoader.java index 42091b4df..1367aa631 100644 --- a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/resolver/services/TurtleLoader.java +++ b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/resolver/services/TurtleLoader.java @@ -78,7 +78,7 @@ public static Try loadTurtle( final URL url ) { } /** - * Loads a Turtle model from an input stream + * Loads a Turtle model from a String containing RDF/Turtle * * @param modelContent The model content * @return The model on success, a corresponding exception otherwise diff --git a/core/esmf-aspect-meta-model-java/src/test/java/org/eclipse/esmf/aspectmodel/edit/AspectChangeContextTest.java b/core/esmf-aspect-meta-model-java/src/test/java/org/eclipse/esmf/aspectmodel/edit/AspectChangeContextTest.java index 52528d354..609719f88 100644 --- a/core/esmf-aspect-meta-model-java/src/test/java/org/eclipse/esmf/aspectmodel/edit/AspectChangeContextTest.java +++ b/core/esmf-aspect-meta-model-java/src/test/java/org/eclipse/esmf/aspectmodel/edit/AspectChangeContextTest.java @@ -14,11 +14,9 @@ package org.eclipse.esmf.aspectmodel.edit; import static org.assertj.core.api.Assertions.assertThat; +import static org.eclipse.esmf.aspectmodel.RdfUtil.createModel; -import java.io.ByteArrayInputStream; -import java.io.InputStream; import java.net.URI; -import java.nio.charset.StandardCharsets; import java.util.List; import java.util.Optional; @@ -34,7 +32,6 @@ import org.eclipse.esmf.aspectmodel.edit.change.RemoveAspectModelFile; import org.eclipse.esmf.aspectmodel.edit.change.RenameElement; import org.eclipse.esmf.aspectmodel.resolver.modelfile.RawAspectModelFileBuilder; -import org.eclipse.esmf.aspectmodel.resolver.parser.ReaderRiotTurtle; import org.eclipse.esmf.aspectmodel.urn.AspectModelUrn; import org.eclipse.esmf.metamodel.Aspect; import org.eclipse.esmf.metamodel.AspectModel; @@ -45,11 +42,6 @@ import org.eclipse.esmf.test.TestAspect; import org.eclipse.esmf.test.TestResources; -import org.apache.jena.rdf.model.Model; -import org.apache.jena.rdf.model.ModelFactory; -import org.apache.jena.riot.Lang; -import org.apache.jena.riot.RDFLanguages; -import org.apache.jena.riot.RDFParserRegistry; import org.apache.jena.vocabulary.RDF; import org.junit.jupiter.api.Test; @@ -162,7 +154,7 @@ void testCreateFileWithElementDefinition() { final AspectChangeContext ctx = new AspectChangeContext( aspectModel ); final AspectModelFile aspectModelFile = RawAspectModelFileBuilder.builder() .sourceLocation( Optional.of( URI.create( "file:///temp/test.ttl" ) ) ) - .sourceModel( model( """ + .sourceModel( createModel( """ @prefix samm: . @prefix xsd: . @prefix : . @@ -198,7 +190,7 @@ void testCreateFileThenAddElementDefinition() { final Change changes = new ChangeGroup( new AddAspectModelFile( aspectModelFile ), new AddElementDefinition( AspectModelUrn.fromUrn( "urn:samm:org.eclipse.esmf.test:1.0.0#Aspect" ), - model( """ + createModel( """ @prefix samm: . @prefix xsd: . @prefix : . @@ -257,7 +249,7 @@ void testMoveElementToExistingFile() { final AspectChangeContext ctx = new AspectChangeContext( aspectModel ); final AspectModelFile file1 = RawAspectModelFileBuilder.builder() .sourceLocation( file1Location ) - .sourceModel( model( """ + .sourceModel( createModel( """ @prefix samm: . @prefix xsd: . @prefix : . @@ -329,7 +321,7 @@ void testMoveElementToOtherNamespaceExistingFile() { final AspectChangeContext ctx = new AspectChangeContext( aspectModel ); final AspectModelFile file1 = RawAspectModelFileBuilder.builder() .sourceLocation( file1Location ) - .sourceModel( model( """ + .sourceModel( createModel( """ @prefix samm: . @prefix xsd: . @prefix : . @@ -391,12 +383,4 @@ void testMoveRenameFile() { assertThat( aspectModel.files() ).hasSize( 1 ); assertThat( aspectModel.aspect().getSourceFile().sourceLocation() ).contains( newLocation ); } - - private Model model( final String ttlRepresentation ) { - final Model model = ModelFactory.createDefaultModel(); - final InputStream in = new ByteArrayInputStream( ttlRepresentation.getBytes( StandardCharsets.UTF_8 ) ); - RDFParserRegistry.registerLangTriples( Lang.TURTLE, ReaderRiotTurtle.factory ); - model.read( in, "", RDFLanguages.strLangTurtle ); - return model; - } } diff --git a/core/esmf-aspect-model-validator/src/test/java/org/eclipse/esmf/aspectmodel/shacl/RustLikeFormatterTest.java b/core/esmf-aspect-model-validator/src/test/java/org/eclipse/esmf/aspectmodel/shacl/RustLikeFormatterTest.java index d045429c2..4bc3dd9db 100644 --- a/core/esmf-aspect-model-validator/src/test/java/org/eclipse/esmf/aspectmodel/shacl/RustLikeFormatterTest.java +++ b/core/esmf-aspect-model-validator/src/test/java/org/eclipse/esmf/aspectmodel/shacl/RustLikeFormatterTest.java @@ -14,20 +14,11 @@ package org.eclipse.esmf.aspectmodel.shacl; import static org.assertj.core.api.Assertions.assertThat; - -import java.io.ByteArrayInputStream; -import java.io.InputStream; -import java.nio.charset.StandardCharsets; - -import org.eclipse.esmf.aspectmodel.resolver.parser.ReaderRiotTurtle; +import static org.eclipse.esmf.aspectmodel.RdfUtil.createModel; import org.apache.jena.rdf.model.Model; -import org.apache.jena.rdf.model.ModelFactory; import org.apache.jena.rdf.model.RDFNode; import org.apache.jena.rdf.model.ResourceFactory; -import org.apache.jena.riot.Lang; -import org.apache.jena.riot.RDFLanguages; -import org.apache.jena.riot.RDFParserRegistry; import org.junit.jupiter.api.Test; public class RustLikeFormatterTest { @@ -38,7 +29,7 @@ public class RustLikeFormatterTest { @Test void testMiddleStatement() { - final Model dataModel = model( """ + final Model dataModel = createModel( """ @prefix : . :Foo a :TestClass ; @@ -54,7 +45,7 @@ void testMiddleStatement() { @Test void testLastStatement() { - final Model dataModel = model( """ + final Model dataModel = createModel( """ @prefix : . :Foo a :TestClass ; @@ -70,7 +61,7 @@ void testLastStatement() { @Test void testMultipleStatementsSameLine() { - final Model dataModel = model( """ + final Model dataModel = createModel( """ @prefix : . :Foo a :TestClass ; @@ -85,7 +76,7 @@ void testMultipleStatementsSameLine() { @Test void testMultiSubjectSameLine() { - final Model dataModel = model( """ + final Model dataModel = createModel( """ @prefix : . :Foo a :TestClass ; :property 1 . :Bar a :TestClass ; :property 2 . @@ -99,7 +90,7 @@ void testMultiSubjectSameLine() { @Test void testAnonymousNodes() { - final Model dataModel = model( """ + final Model dataModel = createModel( """ @prefix : . :Foo a :TestClass ; @@ -114,7 +105,7 @@ void testAnonymousNodes() { @Test void testMultilineAnonymousNode() { - final Model dataModel = model( """ + final Model dataModel = createModel( """ @prefix : . :Foo a :TestClass ; @@ -131,7 +122,7 @@ void testMultilineAnonymousNode() { @Test void testMultilineAnonymousNodeMiddlePart() { - final Model dataModel = model( """ + final Model dataModel = createModel( """ @prefix : . :Foo a :TestClass ; @@ -148,7 +139,7 @@ void testMultilineAnonymousNodeMiddlePart() { @Test void testEmptyList() { - final Model dataModel = model( """ + final Model dataModel = createModel( """ @prefix : . :Foo a :TestClass ; @@ -163,7 +154,7 @@ void testEmptyList() { @Test void testList() { - final Model dataModel = model( """ + final Model dataModel = createModel( """ @prefix : . :Foo a :TestClass ; @@ -178,7 +169,7 @@ void testList() { @Test void testMultilineListStarted() { - final Model dataModel = model( """ + final Model dataModel = createModel( """ @prefix : . :Foo a :TestClass ; @@ -194,7 +185,7 @@ void testMultilineListStarted() { @Test void testMultilineListFinished() { - final Model dataModel = model( """ + final Model dataModel = createModel( """ @prefix : . :Foo a :TestClass ; @@ -210,7 +201,7 @@ void testMultilineListFinished() { @Test void testListWithAnonymousNodes() { - final Model dataModel = model( """ + final Model dataModel = createModel( """ @prefix : . :Foo a :TestClass ; @@ -225,7 +216,7 @@ void testListWithAnonymousNodes() { @Test void testDenseFormatting() { - final Model dataModel = model( """ + final Model dataModel = createModel( """ @prefix : . :Foo a :TestClass;:property 1.:Bar a :TestClass;:property 2. @@ -242,12 +233,4 @@ private void assertCorrectFormatting( final String messageText, final String exp final String reconstructedLine = lineWithSourceText.substring( lineWithSourceText.indexOf( '|' ) + 1 ); assertThat( expectedLine ).isEqualTo( reconstructedLine.trim() ); } - - private Model model( final String ttlRepresentation ) { - final Model model = ModelFactory.createDefaultModel(); - final InputStream in = new ByteArrayInputStream( ttlRepresentation.getBytes( StandardCharsets.UTF_8 ) ); - RDFParserRegistry.registerLangTriples( Lang.TURTLE, ReaderRiotTurtle.factory ); - model.read( in, "", RDFLanguages.strLangTurtle ); - return model; - } } diff --git a/core/esmf-aspect-model-validator/src/test/java/org/eclipse/esmf/aspectmodel/shacl/ShaclValidatorTest.java b/core/esmf-aspect-model-validator/src/test/java/org/eclipse/esmf/aspectmodel/shacl/ShaclValidatorTest.java index 005f844cc..838a8e51b 100644 --- a/core/esmf-aspect-model-validator/src/test/java/org/eclipse/esmf/aspectmodel/shacl/ShaclValidatorTest.java +++ b/core/esmf-aspect-model-validator/src/test/java/org/eclipse/esmf/aspectmodel/shacl/ShaclValidatorTest.java @@ -14,14 +14,11 @@ package org.eclipse.esmf.aspectmodel.shacl; import static org.assertj.core.api.Assertions.assertThat; +import static org.eclipse.esmf.aspectmodel.RdfUtil.createModel; -import java.io.ByteArrayInputStream; -import java.io.InputStream; import java.math.BigInteger; -import java.nio.charset.StandardCharsets; import java.util.List; -import org.eclipse.esmf.aspectmodel.resolver.parser.ReaderRiotTurtle; import org.eclipse.esmf.aspectmodel.shacl.constraint.DatatypeConstraint; import org.eclipse.esmf.aspectmodel.shacl.constraint.MinCountConstraint; import org.eclipse.esmf.aspectmodel.shacl.constraint.NodeKindConstraint; @@ -60,13 +57,9 @@ import org.apache.jena.graph.Node_URI; import org.apache.jena.rdf.model.Literal; import org.apache.jena.rdf.model.Model; -import org.apache.jena.rdf.model.ModelFactory; import org.apache.jena.rdf.model.Property; import org.apache.jena.rdf.model.Resource; import org.apache.jena.rdf.model.ResourceFactory; -import org.apache.jena.riot.Lang; -import org.apache.jena.riot.RDFLanguages; -import org.apache.jena.riot.RDFParserRegistry; import org.apache.jena.vocabulary.XSD; import org.junit.jupiter.api.Test; @@ -80,7 +73,7 @@ public class ShaclValidatorTest { @Test public void testLoadingCustomShape() { - final Model shapesModel = model( """ + final Model shapesModel = createModel( """ @prefix rdf: . @prefix sh: . @prefix xsd: . @@ -122,7 +115,7 @@ public void testLoadingCustomShape() { @Test public void testClassConstraintEvaluation() { - final Model shapesModel = model( """ + final Model shapesModel = createModel( """ @prefix sh: . @prefix xsd: . @prefix : . @@ -138,7 +131,7 @@ public void testClassConstraintEvaluation() { ] . """ ); - final Model dataModel = model( """ + final Model dataModel = createModel( """ @prefix : . @prefix xsd: . @prefix rdfs: . @@ -179,7 +172,7 @@ public void testClassConstraintEvaluation() { @Test public void testDatatypeConstraintEvaluation() { - final Model shapesModel = model( """ + final Model shapesModel = createModel( """ @prefix sh: . @prefix xsd: . @prefix : . @@ -195,7 +188,7 @@ public void testDatatypeConstraintEvaluation() { ] . """ ); - final Model dataModel = model( """ + final Model dataModel = createModel( """ @prefix : . @prefix xsd: . :Foo a :TestClass ; @@ -228,7 +221,7 @@ public void testDatatypeConstraintEvaluation() { @Test public void testNodeKindConstraintEvaluation() { - final Model shapesModel = model( """ + final Model shapesModel = createModel( """ @prefix sh: . @prefix xsd: . @prefix : . @@ -244,7 +237,7 @@ public void testNodeKindConstraintEvaluation() { ] . """ ); - final Model dataModel = model( """ + final Model dataModel = createModel( """ @prefix : . @prefix xsd: . :Foo a :TestClass ; @@ -276,7 +269,7 @@ public void testNodeKindConstraintEvaluation() { @Test public void testMinCountConstraintEvaluation() { - final Model shapesModel = model( """ + final Model shapesModel = createModel( """ @prefix sh: . @prefix : . @@ -291,7 +284,7 @@ public void testMinCountConstraintEvaluation() { ] . """ ); - final Model dataModel = model( """ + final Model dataModel = createModel( """ @prefix : . :Foo a :TestClass . """ ); @@ -319,7 +312,7 @@ public void testMinCountConstraintEvaluation() { @Test public void testMaxCountEvaluation() { - final Model shapesModel = model( """ + final Model shapesModel = createModel( """ @prefix sh: . @prefix : . @@ -334,7 +327,7 @@ public void testMaxCountEvaluation() { ] . """ ); - final Model dataModel = model( """ + final Model dataModel = createModel( """ @prefix : . :Foo a :TestClass ; :testProperty "foo" ; @@ -364,7 +357,7 @@ public void testMaxCountEvaluation() { @Test public void testMinExclusiveConstraint() { - final Model shapesModel = model( """ + final Model shapesModel = createModel( """ @prefix sh: . @prefix : . @@ -379,7 +372,7 @@ public void testMinExclusiveConstraint() { ] . """ ); - final Model dataModel = model( """ + final Model dataModel = createModel( """ @prefix : . :Foo a :TestClass ; :testProperty 42 . @@ -410,7 +403,7 @@ public void testMinExclusiveConstraint() { @Test public void testMinInclusiveConstraint() { - final Model shapesModel = model( """ + final Model shapesModel = createModel( """ @prefix sh: . @prefix : . @@ -425,7 +418,7 @@ public void testMinInclusiveConstraint() { ] . """ ); - final Model dataModel = model( """ + final Model dataModel = createModel( """ @prefix : . :Foo a :TestClass ; :testProperty 41 . @@ -457,7 +450,7 @@ public void testMinInclusiveConstraint() { @Test public void testMaxExclusiveConstraint() { - final Model shapesModel = model( """ + final Model shapesModel = createModel( """ @prefix sh: . @prefix : . @@ -472,7 +465,7 @@ public void testMaxExclusiveConstraint() { ] . """ ); - final Model dataModel = model( """ + final Model dataModel = createModel( """ @prefix : . :Foo a :TestClass ; :testProperty 42 . @@ -503,7 +496,7 @@ public void testMaxExclusiveConstraint() { @Test public void testMaxInclusiveConstraint() { - final Model shapesModel = model( """ + final Model shapesModel = createModel( """ @prefix sh: . @prefix : . @@ -518,7 +511,7 @@ public void testMaxInclusiveConstraint() { ] . """ ); - final Model dataModel = model( """ + final Model dataModel = createModel( """ @prefix : . :Foo a :TestClass ; :testProperty 43 . @@ -550,7 +543,7 @@ public void testMaxInclusiveConstraint() { @Test public void testMinLengthConstraint() { - final Model shapesModel = model( """ + final Model shapesModel = createModel( """ @prefix sh: . @prefix : . @@ -565,7 +558,7 @@ public void testMinLengthConstraint() { ] . """ ); - final Model dataModel = model( """ + final Model dataModel = createModel( """ @prefix : . :Foo a :TestClass ; :testProperty "abc" . @@ -597,7 +590,7 @@ public void testMinLengthConstraint() { @Test public void testMaxLengthConstraint() { - final Model shapesModel = model( """ + final Model shapesModel = createModel( """ @prefix sh: . @prefix : . @@ -612,7 +605,7 @@ public void testMaxLengthConstraint() { ] . """ ); - final Model dataModel = model( """ + final Model dataModel = createModel( """ @prefix : . :Foo a :TestClass ; :testProperty "abcabc" . @@ -644,7 +637,7 @@ public void testMaxLengthConstraint() { @Test public void testPatternConstraint() { - final Model shapesModel = model( """ + final Model shapesModel = createModel( """ @prefix sh: . @prefix : . @@ -660,7 +653,7 @@ public void testPatternConstraint() { ] . """ ); - final Model dataModel = model( """ + final Model dataModel = createModel( """ @prefix : . :Foo a :TestClass ; :testProperty "y" . @@ -695,7 +688,7 @@ public void testPatternConstraint() { @Test public void testAllowedLanguageConstraint() { - final Model shapesModel = model( """ + final Model shapesModel = createModel( """ @prefix sh: . @prefix : . @@ -710,7 +703,7 @@ public void testAllowedLanguageConstraint() { ] . """ ); - final Model dataModel = model( """ + final Model dataModel = createModel( """ @prefix : . :Foo a :TestClass ; :testProperty "non valide"@fr . @@ -745,7 +738,7 @@ public void testAllowedLanguageConstraint() { @Test public void testEqualsConstraint() { - final Model shapesModel = model( """ + final Model shapesModel = createModel( """ @prefix sh: . @prefix : . @@ -760,7 +753,7 @@ public void testEqualsConstraint() { ] . """ ); - final Model dataModel = model( """ + final Model dataModel = createModel( """ @prefix : . :Foo a :TestClass ; :testProperty "some value" ; @@ -798,7 +791,7 @@ public void testEqualsConstraint() { @Test public void testDisjointConstraint() { - final Model shapesModel = model( """ + final Model shapesModel = createModel( """ @prefix sh: . @prefix : . @@ -813,7 +806,7 @@ public void testDisjointConstraint() { ] . """ ); - final Model dataModel = model( """ + final Model dataModel = createModel( """ @prefix : . :Foo a :TestClass ; :testProperty "some value" ; @@ -849,7 +842,7 @@ public void testDisjointConstraint() { @Test public void testLessThanConstraint() { - final Model shapesModel = model( """ + final Model shapesModel = createModel( """ @prefix sh: . @prefix : . @@ -864,7 +857,7 @@ public void testLessThanConstraint() { ] . """ ); - final Model dataModel = model( """ + final Model dataModel = createModel( """ @prefix : . :Foo a :TestClass ; :testProperty 10 ; @@ -901,7 +894,7 @@ public void testLessThanConstraint() { @Test public void testLessThanOrEqualsConstraint() { - final Model shapesModel = model( """ + final Model shapesModel = createModel( """ @prefix sh: . @prefix : . @@ -916,7 +909,7 @@ public void testLessThanOrEqualsConstraint() { ] . """ ); - final Model dataModel = model( """ + final Model dataModel = createModel( """ @prefix : . :Foo a :TestClass ; :testProperty 10 ; @@ -954,7 +947,7 @@ public void testLessThanOrEqualsConstraint() { @Test public void testUniqueLangConstraint() { - final Model shapesModel = model( """ + final Model shapesModel = createModel( """ @prefix sh: . @prefix : . @@ -969,7 +962,7 @@ public void testUniqueLangConstraint() { ] . """ ); - final Model dataModel = model( """ + final Model dataModel = createModel( """ @prefix : . :Foo a :TestClass ; :testProperty "hello"@en ; @@ -1004,7 +997,7 @@ public void testUniqueLangConstraint() { @Test public void testHasValueConstraint() { - final Model shapesModel = model( """ + final Model shapesModel = createModel( """ @prefix sh: . @prefix : . @@ -1019,7 +1012,7 @@ public void testHasValueConstraint() { ] . """ ); - final Model dataModel = model( """ + final Model dataModel = createModel( """ @prefix : . :Foo a :TestClass ; :testProperty "hello" . @@ -1053,7 +1046,7 @@ public void testHasValueConstraint() { @Test public void testAllowedValuesEvaluation() { - final Model shapesModel = model( """ + final Model shapesModel = createModel( """ @prefix sh: . @prefix : . @@ -1068,7 +1061,7 @@ public void testAllowedValuesEvaluation() { ] . """ ); - final Model dataModel = model( """ + final Model dataModel = createModel( """ @prefix : . :Foo a :TestClass ; :testProperty "baz" . @@ -1101,7 +1094,7 @@ public void testAllowedValuesEvaluation() { @Test public void testNodeConstraint() { - final Model shapesModel = model( """ + final Model shapesModel = createModel( """ @prefix sh: . @prefix : . @@ -1123,7 +1116,7 @@ public void testNodeConstraint() { ] . """ ); - final Model dataModel = model( """ + final Model dataModel = createModel( """ @prefix : . :Foo a :TestClass ; :testProperty :Bar . @@ -1157,7 +1150,7 @@ public void testNodeConstraint() { @Test public void testMultipleNodeConstraints() { - final Model shapesModel = model( """ + final Model shapesModel = createModel( """ @prefix sh: . @prefix : . @@ -1187,7 +1180,7 @@ public void testMultipleNodeConstraints() { ] . """ ); - final Model dataModel = model( """ + final Model dataModel = createModel( """ @prefix : . :Foo a :TestClass ; :myProperty :element . @@ -1214,7 +1207,7 @@ public void testMultipleNodeConstraints() { @Test public void testClosedConstraint() { - final Model shapesModel = model( """ + final Model shapesModel = createModel( """ @prefix rdf: . @prefix sh: . @prefix : . @@ -1231,7 +1224,7 @@ public void testClosedConstraint() { sh:ignoredProperties ( rdf:type ) . """ ); - final Model dataModel = model( """ + final Model dataModel = createModel( """ @prefix : . :Foo a :TestClass ; :testProperty "bar" ; @@ -1264,7 +1257,7 @@ public void testClosedConstraint() { @Test public void testSparqlConstraintEvaluation() { - final Model shapesModel = model( """ + final Model shapesModel = createModel( """ @prefix sh: . @prefix xsd: . @prefix : . @@ -1301,7 +1294,7 @@ public void testSparqlConstraintEvaluation() { ] . """ ); - final Model dataModel = model( """ + final Model dataModel = createModel( """ @prefix : . @prefix xsd: . :Foo a :TestClass ; @@ -1333,7 +1326,7 @@ public void testSparqlConstraintEvaluation() { @Test public void testBooleanJsConstraintEvaluation() { - final Model shapesModel = model( """ + final Model shapesModel = createModel( """ @prefix sh: . @prefix xsd: . @prefix : . @@ -1360,7 +1353,7 @@ public void testBooleanJsConstraintEvaluation() { """.replace( "$RESOURCE_URL", getClass().getClassLoader() .getResource( "JsConstraintTest.js" ).toString() ) ); - final Model dataModel = model( """ + final Model dataModel = createModel( """ @prefix : . @prefix xsd: . :Foo a :TestClass ; @@ -1389,7 +1382,7 @@ public void testBooleanJsConstraintEvaluation() { @Test public void testMessageObjectJsConstraintEvaluation() { - final Model shapesModel = model( """ + final Model shapesModel = createModel( """ @prefix sh: . @prefix xsd: . @prefix : . @@ -1416,7 +1409,7 @@ public void testMessageObjectJsConstraintEvaluation() { """.replace( "$RESOURCE_URL", getClass().getClassLoader() .getResource( "JsConstraintTest.js" ).toString() ) ); - final Model dataModel = model( """ + final Model dataModel = createModel( """ @prefix : . @prefix xsd: . :Foo a :TestClass ; @@ -1449,7 +1442,7 @@ public void testMessageObjectJsConstraintEvaluation() { @Test public void testSequencePath() { - final Model shapesModel = model( """ + final Model shapesModel = createModel( """ @prefix sh: . @prefix : . @@ -1464,7 +1457,7 @@ public void testSequencePath() { ] . """ ); - final Model dataModel = model( """ + final Model dataModel = createModel( """ @prefix : . :Foo a :TestClass ; :prop1 [ @@ -1497,7 +1490,7 @@ public void testSequencePath() { @Test public void testAlternativePath() { - final Model shapesModel = model( """ + final Model shapesModel = createModel( """ @prefix sh: . @prefix : . @@ -1512,7 +1505,7 @@ public void testAlternativePath() { ] . """ ); - final Model dataModel = model( """ + final Model dataModel = createModel( """ @prefix : . :Foo a :TestClass ; :prop1 42 ; @@ -1544,7 +1537,7 @@ public void testAlternativePath() { @Test public void testInversePath() { - final Model shapesModel = model( """ + final Model shapesModel = createModel( """ @prefix sh: . @prefix : . @@ -1559,7 +1552,7 @@ public void testInversePath() { ] . """ ); - final Model dataModel = model( """ + final Model dataModel = createModel( """ @prefix : . :Foo a :TestClass ; :testProperty :Bar ; @@ -1591,7 +1584,7 @@ public void testInversePath() { @Test public void testZeroOrMorePath() { - final Model shapesModel = model( """ + final Model shapesModel = createModel( """ @prefix sh: . @prefix : . @@ -1606,7 +1599,7 @@ public void testZeroOrMorePath() { ] . """ ); - final Model dataModel = model( """ + final Model dataModel = createModel( """ @prefix : . :Foo a :TestClass ; :testProperty [ @@ -1641,7 +1634,7 @@ public void testZeroOrMorePath() { @Test public void testOneOrMorePath() { - final Model shapesModel = model( """ + final Model shapesModel = createModel( """ @prefix sh: . @prefix : . @@ -1656,7 +1649,7 @@ public void testOneOrMorePath() { ] . """ ); - final Model dataModel = model( """ + final Model dataModel = createModel( """ @prefix : . :Foo a :TestClass ; :testProperty [ @@ -1691,7 +1684,7 @@ public void testOneOrMorePath() { @Test public void testZeroOrOnePath() { - final Model shapesModel = model( """ + final Model shapesModel = createModel( """ @prefix sh: . @prefix : . @@ -1706,7 +1699,7 @@ public void testZeroOrOnePath() { ] . """ ); - final Model dataModel = model( """ + final Model dataModel = createModel( """ @prefix : . :Foo a :TestClass ; :testProperty [ @@ -1739,7 +1732,7 @@ public void testZeroOrOnePath() { @Test public void testNotConstraint() { - final Model shapesModel = model( """ + final Model shapesModel = createModel( """ @prefix sh: . @prefix : . @@ -1756,7 +1749,7 @@ public void testNotConstraint() { ] . """ ); - final Model dataModel = model( """ + final Model dataModel = createModel( """ @prefix : . :Foo a :TestClass ; :testProperty [ @@ -1790,7 +1783,7 @@ public void testNotConstraint() { @Test public void testAndConstraint() { - final Model shapesModel = model( """ + final Model shapesModel = createModel( """ @prefix sh: . @prefix xsd: . @prefix : . @@ -1809,7 +1802,7 @@ public void testAndConstraint() { ] . """ ); - final Model dataModel = model( """ + final Model dataModel = createModel( """ @prefix : . :Foo a :TestClass ; :testProperty [ @@ -1827,7 +1820,7 @@ public void testAndConstraint() { final NodeKindViolation violation = (NodeKindViolation) finding; assertThat( violation.context().parentContext().map( EvaluationContext::element ) ).contains( element ); assertThat( violation.context().parentContext().get().shape().attributes().uri() ).hasValue( namespace + "MyShape" ); - assertThat( ((PredicatePath) violation.context().parentContext().get().propertyShape().get().path()).predicate().getURI() ) + assertThat( ( (PredicatePath) violation.context().parentContext().get().propertyShape().get().path() ).predicate().getURI() ) .endsWith( "testProperty" ); assertThat( violation.context().parentElementName() ).isEqualTo( ":Foo" ); assertThat( violation.allowedNodeKind() ).isEqualTo( Shape.NodeKind.IRI ); @@ -1843,7 +1836,7 @@ public void testAndConstraint() { @Test public void testOrConstraint() { - final Model shapesModel = model( """ + final Model shapesModel = createModel( """ @prefix sh: . @prefix xsd: . @prefix : . @@ -1862,7 +1855,7 @@ public void testOrConstraint() { ] . """ ); - final Model dataModel = model( """ + final Model dataModel = createModel( """ @prefix : . :Foo a :TestClass ; :testProperty 42 . @@ -1880,7 +1873,7 @@ public void testOrConstraint() { assertThat( violation.context().propertyName() ).isEqualTo( ":testProperty" ); assertThat( violation.context().elementName() ).isEqualTo( ":Foo" ); assertThat( violation ).isInstanceOf( OrViolation.class ); - assertThat( ((OrViolation) violation).violations() ) + assertThat( ( (OrViolation) violation ).violations() ) .hasSize( 2 ) .anySatisfy( subviolation -> assertThat( subviolation ).isInstanceOfSatisfying( InvalidValueViolation.class, invalidValueViolation -> { @@ -1901,7 +1894,7 @@ public void testOrConstraint() { @Test public void testXoneConstraintInPropertyShape() { - final Model shapesModel = model( """ + final Model shapesModel = createModel( """ @prefix sh: . @prefix xsd: . @prefix : . @@ -1919,7 +1912,7 @@ public void testXoneConstraintInPropertyShape() { ] . """ ); - final Model dataModel = model( """ + final Model dataModel = createModel( """ @prefix : . :Foo a :TestClass ; :testProperty 42 . @@ -1953,7 +1946,7 @@ public void testXoneConstraintInPropertyShape() { @Test void testXoneConstraintInPropertyShapeWithNoSubViolations() { - final Model shapesModel = model( """ + final Model shapesModel = createModel( """ @prefix sh: . @prefix xsd: . @prefix : . @@ -1980,7 +1973,7 @@ void testXoneConstraintInPropertyShapeWithNoSubViolations() { sh:hasValue 42 . """ ); - final Model dataModel = model( """ + final Model dataModel = createModel( """ @prefix : . :Foo a :TestClass ; :testProperty 42 . @@ -2005,7 +1998,7 @@ void testXoneConstraintInPropertyShapeWithNoSubViolations() { @Test void testXoneConstraintInNodeShapeExpectSuccess() { - final Model shapesModel = model( """ + final Model shapesModel = createModel( """ @prefix sh: . @prefix xsd: . @prefix : . @@ -2031,7 +2024,7 @@ void testXoneConstraintInNodeShapeExpectSuccess() { sh:minCount 1 . """ ); - final Model dataModel = model( """ + final Model dataModel = createModel( """ @prefix : . :Foo a :TestClass ; :foo 1 . @@ -2046,7 +2039,7 @@ void testXoneConstraintInNodeShapeExpectSuccess() { @Test void testXoneConstraintInNodeShapeExpectFailure() { - final Model shapesModel = model( + final Model shapesModel = createModel( """ @prefix sh: . @prefix xsd: . @@ -2074,7 +2067,7 @@ void testXoneConstraintInNodeShapeExpectFailure() { """ ); - final Model dataModel = model( """ + final Model dataModel = createModel( """ @prefix : . :Foo a :TestClass ; :testProperty 42 . @@ -2113,7 +2106,7 @@ void testXoneConstraintInNodeShapeExpectFailure() { @Test void testSparqlTargetWithGenericConstraint() { - final Model shapesModel = model( """ + final Model shapesModel = createModel( """ @prefix sh: . @prefix xsd: . @prefix : . @@ -2144,7 +2137,7 @@ void testSparqlTargetWithGenericConstraint() { ] . """ ); - final Model dataModel = model( """ + final Model dataModel = createModel( """ @prefix : . :Foo a :TestClass ; :testProperty "abc" . @@ -2161,7 +2154,7 @@ void testSparqlTargetWithGenericConstraint() { @Test void testSparqlTargetWithShapeSparqlConstraint() { - final Model shapesModel = model( """ + final Model shapesModel = createModel( """ @prefix sh: . @prefix xsd: . @prefix : . @@ -2206,7 +2199,7 @@ void testSparqlTargetWithShapeSparqlConstraint() { ] . """ ); - final Model dataModel = model( """ + final Model dataModel = createModel( """ @prefix : . @prefix xsd: . :Foo a :TestClass ; @@ -2227,7 +2220,7 @@ void testSparqlTargetWithShapeSparqlConstraint() { @Test void testSparqlTargetWithPropertySparqlConstraint() { - final Model shapesModel = model( """ + final Model shapesModel = createModel( """ @prefix sh: . @prefix xsd: . @prefix : . @@ -2272,7 +2265,7 @@ void testSparqlTargetWithPropertySparqlConstraint() { """ ); // important detail: ':testProperty' is missing on ':Foo', the SPARQLConstraint must run anyway - final Model dataModel = model( """ + final Model dataModel = createModel( """ @prefix : . @prefix xsd: . :Foo a :TestClass. @@ -2292,7 +2285,7 @@ void testSparqlTargetWithPropertySparqlConstraint() { @Test void testMultiElementValidation() { - final Model shapesModel = model( """ + final Model shapesModel = createModel( """ @prefix sh: . @prefix xsd: . @prefix : . @@ -2337,7 +2330,7 @@ void testMultiElementValidation() { ] . """ ); - final Model dataModel = model( """ + final Model dataModel = createModel( """ @prefix : . @prefix xsd: . :Foo a :TestClass ; @@ -2359,7 +2352,7 @@ void testMultiElementValidation() { @Test void testTargetObjectsOf() { - final Model shapesModel = model( """ + final Model shapesModel = createModel( """ @prefix sh: . @prefix xsd: . @prefix rdf: . @@ -2376,7 +2369,7 @@ void testTargetObjectsOf() { ] ; """ ); - final Model dataModel = model( """ + final Model dataModel = createModel( """ @prefix : . @prefix xsd: . :Foo a :TestClass ; @@ -2398,7 +2391,7 @@ void testTargetObjectsOf() { @Test void testNodeTargets() { - final Model shapesModel = model( """ + final Model shapesModel = createModel( """ @prefix sh: . @prefix xsd: . @prefix rdf: . @@ -2415,7 +2408,7 @@ void testNodeTargets() { ] ; """ ); - final Model dataModel = model( """ + final Model dataModel = createModel( """ @prefix : . @prefix xsd: . :Foo a :TestClass ; @@ -2433,12 +2426,4 @@ void testNodeTargets() { assertThat( violations.size() ).isEqualTo( 1 ); assertThat( violations.get( 0 ) ).isInstanceOf( DatatypeViolation.class ); } - - private Model model( final String ttlRepresentation ) { - final Model model = ModelFactory.createDefaultModel(); - final InputStream in = new ByteArrayInputStream( ttlRepresentation.getBytes( StandardCharsets.UTF_8 ) ); - RDFParserRegistry.registerLangTriples( Lang.TURTLE, ReaderRiotTurtle.factory ); - model.read( in, "", RDFLanguages.strLangTurtle ); - return model; - } } From a7ed928bced20529c7c84a90af78f0b44a226b1f Mon Sep 17 00:00:00 2001 From: "Textor Andreas (BCI/ESW17)" Date: Thu, 8 Aug 2024 11:06:26 +0200 Subject: [PATCH 15/33] Add AspectSerializerTest --- .../esmf/aspectmodel/AspectModelBuilder.java | 25 ++-- .../aspectmodel/serializer/PrettyPrinter.java | 4 +- .../serializer/AspectSerializerTest.java | 133 ++++++++++++++++++ 3 files changed, 149 insertions(+), 13 deletions(-) create mode 100644 core/esmf-aspect-model-serializer/src/test/java/org/eclipse/esmf/aspectmodel/serializer/AspectSerializerTest.java diff --git a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/AspectModelBuilder.java b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/AspectModelBuilder.java index 0a1d9b014..79891d402 100644 --- a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/AspectModelBuilder.java +++ b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/AspectModelBuilder.java @@ -94,17 +94,20 @@ private static void setNamespaces( final Collection files, fina final String namespaceUrn = optionalNamespaceUrn.get(); MetaModelBaseAttributes namespaceDefinition = null; AspectModelFile fileContainingNamespaceDefinition = null; - for ( final ModelElement element : elementsGroupedByNamespaceUrn.get( namespaceUrn ) ) { - final AspectModelFile elementFile = element.getSourceFile(); - if ( elementFile.sourceModel().contains( null, RDF.type, SammNs.SAMM.Namespace() ) ) { - final Model model = elementFile.sourceModel(); - final ModelElementFactory modelElementFactory = new ModelElementFactory( model, Map.of(), __ -> null ); - final Resource namespaceResource = model.listStatements( null, RDF.type, SammNs.SAMM.Namespace() ) - .mapWith( Statement::getSubject ) - .toList().iterator().next(); - namespaceDefinition = modelElementFactory.createBaseAttributes( namespaceResource ); - fileContainingNamespaceDefinition = elementFile; - break; + final List elementsForUrn = elementsGroupedByNamespaceUrn.get( namespaceUrn ); + if ( elementsForUrn != null ) { + for ( final ModelElement element : elementsForUrn ) { + final AspectModelFile elementFile = element.getSourceFile(); + if ( elementFile.sourceModel().contains( null, RDF.type, SammNs.SAMM.Namespace() ) ) { + final Model model = elementFile.sourceModel(); + final ModelElementFactory modelElementFactory = new ModelElementFactory( model, Map.of(), __ -> null ); + final Resource namespaceResource = model.listStatements( null, RDF.type, SammNs.SAMM.Namespace() ) + .mapWith( Statement::getSubject ) + .toList().iterator().next(); + namespaceDefinition = modelElementFactory.createBaseAttributes( namespaceResource ); + fileContainingNamespaceDefinition = elementFile; + break; + } } } diff --git a/core/esmf-aspect-model-serializer/src/main/java/org/eclipse/esmf/aspectmodel/serializer/PrettyPrinter.java b/core/esmf-aspect-model-serializer/src/main/java/org/eclipse/esmf/aspectmodel/serializer/PrettyPrinter.java index bcb2a7fcd..30a24efe7 100644 --- a/core/esmf-aspect-model-serializer/src/main/java/org/eclipse/esmf/aspectmodel/serializer/PrettyPrinter.java +++ b/core/esmf-aspect-model-serializer/src/main/java/org/eclipse/esmf/aspectmodel/serializer/PrettyPrinter.java @@ -64,8 +64,8 @@ import org.apache.jena.vocabulary.XSD; /** - * Serializes an {@link AspectModelFile} to RDF/Turtle while following the formatting rules for Aspect models. - * The model is expected to contain exactly one Aspect. + * Serializes an {@link AspectModelFile} to RDF/Turtle while following the formatting rules for Aspect Models. + * Initialize with {@link #PrettyPrinter(AspectModelFile, PrintWriter)}, then run {@link #print()}. */ public class PrettyPrinter { private static final String INDENT = " "; diff --git a/core/esmf-aspect-model-serializer/src/test/java/org/eclipse/esmf/aspectmodel/serializer/AspectSerializerTest.java b/core/esmf-aspect-model-serializer/src/test/java/org/eclipse/esmf/aspectmodel/serializer/AspectSerializerTest.java new file mode 100644 index 000000000..ad5981789 --- /dev/null +++ b/core/esmf-aspect-model-serializer/src/test/java/org/eclipse/esmf/aspectmodel/serializer/AspectSerializerTest.java @@ -0,0 +1,133 @@ +/* + * Copyright (c) 2024 Robert Bosch Manufacturing Solutions GmbH + * + * See the AUTHORS file(s) distributed with this work for additional + * information regarding authorship. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * SPDX-License-Identifier: MPL-2.0 + */ + +package org.eclipse.esmf.aspectmodel.serializer; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.eclipse.esmf.aspectmodel.RdfUtil.createModel; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Comparator; +import java.util.Optional; +import java.util.stream.Stream; + +import org.eclipse.esmf.aspectmodel.AspectModelBuilder; +import org.eclipse.esmf.aspectmodel.AspectModelFile; +import org.eclipse.esmf.aspectmodel.edit.AspectChangeContext; +import org.eclipse.esmf.aspectmodel.edit.ChangeGroup; +import org.eclipse.esmf.aspectmodel.edit.change.AddAspectModelFile; +import org.eclipse.esmf.aspectmodel.edit.change.MoveRenameAspectModelFile; +import org.eclipse.esmf.aspectmodel.resolver.modelfile.RawAspectModelFileBuilder; +import org.eclipse.esmf.metamodel.AspectModel; +import org.eclipse.esmf.test.TestAspect; +import org.eclipse.esmf.test.TestResources; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +public class AspectSerializerTest { + Path outputDirectory = null; + + @BeforeEach + void beforeEach() throws IOException { + outputDirectory = Files.createTempDirectory( "junit" ); + } + + @AfterEach + void afterEach() { + if ( outputDirectory != null ) { + final File outputDir = outputDirectory.toFile(); + if ( outputDir.exists() && outputDir.isDirectory() ) { + // Recursively delete temporary directory + try ( final Stream paths = Files.walk( outputDirectory ) ) { + paths.sorted( Comparator.reverseOrder() ) + .map( Path::toFile ) + .forEach( file -> { + if ( !file.delete() ) { + System.err.println( "Could not delete file " + file ); + } + } ); + } catch ( final IOException e ) { + throw new RuntimeException( e ); + } + } + } + } + + @Test + void testWriteAspectModelFileToFileSystem() { + final AspectModel aspectModel = TestResources.load( TestAspect.ASPECT ); + + // Change the loaded test model's source location to a file system location + final AspectChangeContext changeContext = new AspectChangeContext( aspectModel ); + final Path filePath = outputDirectory.resolve( "Aspect.ttl" ); + changeContext.applyChange( new MoveRenameAspectModelFile( aspectModel.files().iterator().next(), filePath.toUri() ) ); + + // Serialize the model file content to the file system + AspectSerializer.INSTANCE.write( aspectModel.files().iterator().next() ); + + final File writtenFile = filePath.toFile(); + assertThat( writtenFile ).content().contains( ":Aspect a samm:Aspect" ); + } + + @Test + void testWriteAspectModelToFileSystem() { + // Construct Aspect Model with two files from scratch + final AspectModel aspectModel = AspectModelBuilder.buildEmptyModel(); + final Path file1Path = outputDirectory.resolve( "Aspect1.ttl" ); + final AspectModelFile file1 = RawAspectModelFileBuilder.builder() + .sourceLocation( Optional.of( file1Path.toUri() ) ) + .sourceModel( createModel( """ + @prefix samm: . + @prefix xsd: . + @prefix : . + + :Aspect1 a samm:Aspect ; + samm:description "This is a test description"@en ; + samm:properties ( ) ; + samm:operations ( ) . + """ + ) ) + .build(); + final Path file2Path = outputDirectory.resolve( "Aspect2.ttl" ); + final AspectModelFile file2 = RawAspectModelFileBuilder.builder() + .sourceLocation( Optional.of( file2Path.toUri() ) ) + .sourceModel( createModel( """ + @prefix samm: . + @prefix xsd: . + @prefix : . + + :Aspect2 a samm:Aspect ; + samm:description "This is a test description"@en ; + samm:properties ( ) ; + samm:operations ( ) . + """ + ) ) + .build(); + final AspectChangeContext changeContext = new AspectChangeContext( aspectModel ); + changeContext.applyChange( new ChangeGroup( + new AddAspectModelFile( file1 ), + new AddAspectModelFile( file2 ) + ) ); + + // Serialize the model to the file system + AspectSerializer.INSTANCE.write( aspectModel ); + + assertThat( file1Path.toFile() ).content().contains( ":Aspect1 a samm:Aspect" ); + assertThat( file2Path.toFile() ).content().contains( ":Aspect2 a samm:Aspect" ); + } +} From b4d1f4f7f44cc2b60b2b7a093d6c270ab30fa432 Mon Sep 17 00:00:00 2001 From: "Textor Andreas (BCI/ESW17)" Date: Thu, 8 Aug 2024 11:11:06 +0200 Subject: [PATCH 16/33] Enable aspect prettyprint command for all Aspect Model files --- .../org/eclipse/esmf/aspect/AspectPrettyPrintCommand.java | 5 ----- 1 file changed, 5 deletions(-) diff --git a/tools/samm-cli/src/main/java/org/eclipse/esmf/aspect/AspectPrettyPrintCommand.java b/tools/samm-cli/src/main/java/org/eclipse/esmf/aspect/AspectPrettyPrintCommand.java index 4f08f6f06..e9f8c3c98 100644 --- a/tools/samm-cli/src/main/java/org/eclipse/esmf/aspect/AspectPrettyPrintCommand.java +++ b/tools/samm-cli/src/main/java/org/eclipse/esmf/aspect/AspectPrettyPrintCommand.java @@ -20,10 +20,8 @@ import org.eclipse.esmf.ExternalResolverMixin; import org.eclipse.esmf.LoggingMixin; import org.eclipse.esmf.aspectmodel.serializer.PrettyPrinter; -import org.eclipse.esmf.exception.CommandException; import org.eclipse.esmf.metamodel.AspectModel; -import org.apache.commons.io.FilenameUtils; import picocli.CommandLine; @CommandLine.Command( name = AspectPrettyPrintCommand.COMMAND_NAME, @@ -58,9 +56,6 @@ public void run() { .stream() .filter( file -> file.sourceLocation().map( sourceLocation -> sourceLocation.equals( inputFile.toURI() ) ).orElse( false ) ) .forEach( sourceModel -> { - if ( sourceModel.aspects().size() != 1 ) { - throw new CommandException( "Can only pretty-print files that contain exactly one aspect" ); - } new PrettyPrinter( sourceModel, printWriter ).print(); printWriter.flush(); printWriter.close(); From 6855fcf9e12707860003b52dbebb629256090faa Mon Sep 17 00:00:00 2001 From: "Textor Andreas (BCI/ESW17)" Date: Thu, 8 Aug 2024 12:45:22 +0200 Subject: [PATCH 17/33] Enable aspect prettyprint to overwrite the input file in place --- .../modules/tooling-guide/pages/samm-cli.adoc | 3 +- .../esmf/aspect/AspectPrettyPrintCommand.java | 47 ++++++++++++++----- .../java/org/eclipse/esmf/SammCliTest.java | 23 +++++++-- 3 files changed, 57 insertions(+), 16 deletions(-) diff --git a/documentation/developer-guide/modules/tooling-guide/pages/samm-cli.adoc b/documentation/developer-guide/modules/tooling-guide/pages/samm-cli.adoc index 0c4ac4887..8accac350 100644 --- a/documentation/developer-guide/modules/tooling-guide/pages/samm-cli.adoc +++ b/documentation/developer-guide/modules/tooling-guide/pages/samm-cli.adoc @@ -61,8 +61,9 @@ The available options and their meaning can also be seen in the help text of the | aspect help | Get help for `aspect` subcommands | `samm aspect help validate` .2+| [[aspect-validate]] aspect validate | Validate Aspect Model | `samm aspect AspectModel.ttl validate` | _--custom-resolver_ : use an external resolver for the resolution of the model elements | `samm aspect AspectModel.ttl validate --custom-resolver myresolver.sh` -.2+| [[aspect-prettyprint]] aspect prettyprint | Pretty-print Aspect Model | `samm aspect AspectModel.ttl prettyprint` +.3+| [[aspect-prettyprint]] aspect prettyprint | Pretty-print Aspect Model | `samm aspect AspectModel.ttl prettyprint` | _--output, -o_ : the output will be saved to the given file | `samm aspect AspectModel.ttl prettyprint -o c:\Results\PrettyPrinted.ttl` + | _--overwrite, -w_ : Overwrite the input file | `samm aspect AspectModel.ttl prettyprint -w .5+| [[aspect-to-html]] aspect to html | Generate HTML documentation for an Aspect Model | `samm aspect AspectModel.ttl to html` | _--output, -o_ : the output will be saved to the given file | `samm aspect AspectModel.ttl to html -o c:\Model.html` | _--css, -c_ : CSS file with custom styles to be included in the generated HTML diff --git a/tools/samm-cli/src/main/java/org/eclipse/esmf/aspect/AspectPrettyPrintCommand.java b/tools/samm-cli/src/main/java/org/eclipse/esmf/aspect/AspectPrettyPrintCommand.java index e9f8c3c98..353656c0d 100644 --- a/tools/samm-cli/src/main/java/org/eclipse/esmf/aspect/AspectPrettyPrintCommand.java +++ b/tools/samm-cli/src/main/java/org/eclipse/esmf/aspect/AspectPrettyPrintCommand.java @@ -14,12 +14,18 @@ package org.eclipse.esmf.aspect; import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.OutputStream; import java.io.PrintWriter; +import java.net.URI; import org.eclipse.esmf.AbstractCommand; import org.eclipse.esmf.ExternalResolverMixin; import org.eclipse.esmf.LoggingMixin; +import org.eclipse.esmf.aspectmodel.AspectModelFile; import org.eclipse.esmf.aspectmodel.serializer.PrettyPrinter; +import org.eclipse.esmf.exception.CommandException; import org.eclipse.esmf.metamodel.AspectModel; import picocli.CommandLine; @@ -42,24 +48,41 @@ public class AspectPrettyPrintCommand extends AbstractCommand { private ExternalResolverMixin customResolver; @CommandLine.Option( names = { "--output", "-o" }, description = "Output file path (default: stdout)" ) - private String outputFilePath = "-"; + String outputFilePath = "-"; + + @CommandLine.Option( names = { "--overwrite", "-w" }, description = "Overwrite the input file" ) + boolean overwrite; @CommandLine.ParentCommand private AspectCommand parentCommand; @Override public void run() { - try ( final PrintWriter printWriter = new PrintWriter( getStreamForFile( outputFilePath ) ) ) { - final File inputFile = new File( parentCommand.getInput() ).getAbsoluteFile(); - final AspectModel aspectModel = loadAspectModelOrFail( parentCommand.getInput(), customResolver ); - aspectModel.files() - .stream() - .filter( file -> file.sourceLocation().map( sourceLocation -> sourceLocation.equals( inputFile.toURI() ) ).orElse( false ) ) - .forEach( sourceModel -> { - new PrettyPrinter( sourceModel, printWriter ).print(); - printWriter.flush(); - printWriter.close(); - } ); + final File inputFile = new File( parentCommand.getInput() ).getAbsoluteFile(); + final AspectModel aspectModel = loadAspectModelOrFail( parentCommand.getInput(), customResolver ); + + for ( final AspectModelFile sourceFile : aspectModel.files() ) { + if ( !sourceFile.sourceLocation().map( uri -> uri.equals( inputFile.toURI() ) ).orElse( false ) ) { + continue; + } + + OutputStream outputStream = null; + if ( overwrite ) { + final URI fileUri = sourceFile.sourceLocation().orElseThrow(); + try { + outputStream = new FileOutputStream( new File( fileUri ) ); + } catch ( final FileNotFoundException exception ) { + throw new CommandException( "Can not write to " + fileUri ); + } + } + if ( outputStream == null ) { + outputStream = getStreamForFile( outputFilePath ); + } + + try ( final PrintWriter printWriter = new PrintWriter( outputStream ) ) { + new PrettyPrinter( sourceFile, printWriter ).print(); + printWriter.flush(); + } } } } diff --git a/tools/samm-cli/src/test/java/org/eclipse/esmf/SammCliTest.java b/tools/samm-cli/src/test/java/org/eclipse/esmf/SammCliTest.java index 81bde36f5..09d01c199 100644 --- a/tools/samm-cli/src/test/java/org/eclipse/esmf/SammCliTest.java +++ b/tools/samm-cli/src/test/java/org/eclipse/esmf/SammCliTest.java @@ -39,6 +39,7 @@ import org.eclipse.esmf.test.TestAspect; import org.eclipse.esmf.test.TestModel; +import org.apache.commons.io.FileUtils; import org.apache.tika.config.TikaConfig; import org.apache.tika.exception.TikaException; import org.apache.tika.metadata.Metadata; @@ -149,6 +150,22 @@ void testAspectPrettyPrintToStdout() { assertThat( result.stderr() ).isEmpty(); } + @Test + void testAspectPrettyPrintOverwrite() throws IOException { + final File targetFile = outputFile( "output.ttl" ); + assertThat( targetFile ).content().contains( "@prefix xsd:" ); + FileUtils.copyFile( new File( defaultInputFile ), targetFile ); + + final ExecutionResult result = sammCli.runAndExpectSuccess( "--disable-color", "aspect", targetFile.getAbsolutePath(), "prettyprint", + "--overwrite" ); + assertThat( result.stdout() ).isEmpty(); + assertThat( result.stderr() ).isEmpty(); + assertThat( targetFile ).exists(); + assertThat( targetFile ).content().contains( "@prefix" ); + // The xsd prefix is not actually used in the file, so it is removed by the pretty printer + assertThat( targetFile ).content().doesNotContain( "@prefix xsd:" ); + } + @Test void testAspectValidateValidModel() { final ExecutionResult result = sammCli.runAndExpectSuccess( "--disable-color", "aspect", defaultInputFile, "validate" ); @@ -1052,7 +1069,7 @@ void testAspectToSqlWithCustomColumnToStdout() { * Returns the File object for a test model file */ private File inputFile( final TestModel testModel ) { - final boolean isValid = !(testModel instanceof InvalidTestAspect); + final boolean isValid = !( testModel instanceof InvalidTestAspect ); final String resourcePath = String.format( "%s/../../core/esmf-test-aspect-models/src/main/resources/%s/org.eclipse.esmf.test/1.0.0/%s.ttl", System.getProperty( "user.dir" ), isValid ? "valid" : "invalid", testModel.getName() ); @@ -1103,8 +1120,8 @@ private String resolverCommand() { // are not resolved to the file system but to the jar) try { final String resolverScript = new File( - System.getProperty( "user.dir" ) + "/target/test-classes/model_resolver" + (OS.WINDOWS.isCurrentOs() - ? ".bat" : ".sh") ).getCanonicalPath(); + System.getProperty( "user.dir" ) + "/target/test-classes/model_resolver" + ( OS.WINDOWS.isCurrentOs() + ? ".bat" : ".sh" ) ).getCanonicalPath(); final String modelsRoot = new File( System.getProperty( "user.dir" ) + "/target/classes/valid" ).getCanonicalPath(); final String metaModelVersion = KnownVersion.getLatest().toString().toLowerCase(); return resolverScript + " " + modelsRoot + " " + metaModelVersion; From 54a51fda4b150597c48817463f31db21ce1ccb74 Mon Sep 17 00:00:00 2001 From: "Textor Andreas (BCI/ESW17)" Date: Thu, 8 Aug 2024 15:27:20 +0200 Subject: [PATCH 18/33] Fix serialization of programmatically created Aspect Models --- .../vocabulary/SimpleRdfNamespace.java | 34 +++++++++++++++++++ .../serializer/AspectSerializer.java | 23 ++++++++++++- .../serializer/RdfModelCreatorVisitor.java | 2 +- .../RdfModelCreatorVisitorTest.java | 13 ++----- .../java/org/eclipse/esmf/SammCliTest.java | 2 +- 5 files changed, 60 insertions(+), 14 deletions(-) create mode 100644 core/esmf-aspect-meta-model-interface/src/main/java/org/eclipse/esmf/metamodel/vocabulary/SimpleRdfNamespace.java diff --git a/core/esmf-aspect-meta-model-interface/src/main/java/org/eclipse/esmf/metamodel/vocabulary/SimpleRdfNamespace.java b/core/esmf-aspect-meta-model-interface/src/main/java/org/eclipse/esmf/metamodel/vocabulary/SimpleRdfNamespace.java new file mode 100644 index 000000000..151f499f2 --- /dev/null +++ b/core/esmf-aspect-meta-model-interface/src/main/java/org/eclipse/esmf/metamodel/vocabulary/SimpleRdfNamespace.java @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2024 Robert Bosch Manufacturing Solutions GmbH + * + * See the AUTHORS file(s) distributed with this work for additional + * information regarding authorship. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * SPDX-License-Identifier: MPL-2.0 + */ + +package org.eclipse.esmf.metamodel.vocabulary; + +public class SimpleRdfNamespace implements RdfNamespace { + private final String shortForm; + private final String uri; + + public SimpleRdfNamespace( final String shortForm, final String uri ) { + this.shortForm = shortForm; + this.uri = uri; + } + + @Override + public String getShortForm() { + return shortForm; + } + + @Override + public String getUri() { + return uri; + } +} diff --git a/core/esmf-aspect-model-serializer/src/main/java/org/eclipse/esmf/aspectmodel/serializer/AspectSerializer.java b/core/esmf-aspect-model-serializer/src/main/java/org/eclipse/esmf/aspectmodel/serializer/AspectSerializer.java index c2936fb3d..a876be00f 100644 --- a/core/esmf-aspect-model-serializer/src/main/java/org/eclipse/esmf/aspectmodel/serializer/AspectSerializer.java +++ b/core/esmf-aspect-model-serializer/src/main/java/org/eclipse/esmf/aspectmodel/serializer/AspectSerializer.java @@ -25,12 +25,20 @@ import java.net.URL; import java.nio.charset.StandardCharsets; import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.function.Function; +import org.eclipse.esmf.aspectmodel.AspectModelBuilder; import org.eclipse.esmf.aspectmodel.AspectModelFile; +import org.eclipse.esmf.aspectmodel.RdfUtil; +import org.eclipse.esmf.aspectmodel.resolver.modelfile.RawAspectModelFileBuilder; import org.eclipse.esmf.metamodel.Aspect; import org.eclipse.esmf.metamodel.AspectModel; +import org.eclipse.esmf.metamodel.vocabulary.RdfNamespace; +import org.eclipse.esmf.metamodel.vocabulary.SimpleRdfNamespace; + +import org.apache.jena.rdf.model.Model; /** * Functions to write Aspect Models and Aspect Model files to Strings or their respective source locations @@ -114,7 +122,20 @@ public void registerProtocolHandler( final String protocol, final Function model.add( elementResource, SammNs.SAMM.see(), ResourceFactory.createResource( seeValue ) ) ); + element.getSee().forEach( seeValue -> model.add( elementResource, SammNs.SAMM.see(), createResource( seeValue ) ) ); element.getPreferredNames().stream().map( this::serializeLocalizedString ).forEach( preferredName -> model.add( elementResource, SammNs.SAMM.preferredName(), preferredName ) ); element.getDescriptions().stream().map( this::serializeLocalizedString ).forEach( description -> diff --git a/core/esmf-aspect-model-serializer/src/test/java/org/eclipse/esmf/aspectmodel/serializer/RdfModelCreatorVisitorTest.java b/core/esmf-aspect-model-serializer/src/test/java/org/eclipse/esmf/aspectmodel/serializer/RdfModelCreatorVisitorTest.java index 01667f490..63ede1573 100644 --- a/core/esmf-aspect-model-serializer/src/test/java/org/eclipse/esmf/aspectmodel/serializer/RdfModelCreatorVisitorTest.java +++ b/core/esmf-aspect-model-serializer/src/test/java/org/eclipse/esmf/aspectmodel/serializer/RdfModelCreatorVisitorTest.java @@ -23,6 +23,7 @@ import org.eclipse.esmf.metamodel.Aspect; import org.eclipse.esmf.metamodel.AspectModel; import org.eclipse.esmf.metamodel.vocabulary.RdfNamespace; +import org.eclipse.esmf.metamodel.vocabulary.SimpleRdfNamespace; import org.eclipse.esmf.samm.KnownVersion; import org.eclipse.esmf.test.TestAspect; import org.eclipse.esmf.test.TestModel; @@ -65,17 +66,7 @@ public void testRdfModelCreatorVisitor( final TestAspect testAspect ) { final AspectModel aspectModel = TestResources.load( testAspect ); final Aspect aspect = aspectModel.aspect(); - final RdfNamespace namespace = new RdfNamespace() { - @Override - public String getShortForm() { - return ""; - } - - @Override - public String getUri() { - return aspect.urn().getUrnPrefix(); - } - }; + final RdfNamespace namespace = new SimpleRdfNamespace( "", aspect.urn().getUrnPrefix() ); final RdfModelCreatorVisitor visitor = new RdfModelCreatorVisitor( namespace ); final Model serializedModel = visitor.visitAspect( aspect, null ).model(); diff --git a/tools/samm-cli/src/test/java/org/eclipse/esmf/SammCliTest.java b/tools/samm-cli/src/test/java/org/eclipse/esmf/SammCliTest.java index 09d01c199..885720c4b 100644 --- a/tools/samm-cli/src/test/java/org/eclipse/esmf/SammCliTest.java +++ b/tools/samm-cli/src/test/java/org/eclipse/esmf/SammCliTest.java @@ -153,8 +153,8 @@ void testAspectPrettyPrintToStdout() { @Test void testAspectPrettyPrintOverwrite() throws IOException { final File targetFile = outputFile( "output.ttl" ); - assertThat( targetFile ).content().contains( "@prefix xsd:" ); FileUtils.copyFile( new File( defaultInputFile ), targetFile ); + assertThat( targetFile ).content().contains( "@prefix xsd:" ); final ExecutionResult result = sammCli.runAndExpectSuccess( "--disable-color", "aspect", targetFile.getAbsolutePath(), "prettyprint", "--overwrite" ); From 9eb580aefa2467ba8dd3dc5721323a61056c72e0 Mon Sep 17 00:00:00 2001 From: "Textor Andreas (BCI/ESW17)" Date: Thu, 8 Aug 2024 15:28:14 +0200 Subject: [PATCH 19/33] Create working version of help for subcommands --- .../modules/tooling-guide/pages/samm-cli.adoc | 11 ++- .../java/org/eclipse/esmf/LoggingMixin.java | 2 +- .../main/java/org/eclipse/esmf/SammCli.java | 94 ++++++++++++++++++- .../eclipse/esmf/aspect/AspectCommand.java | 3 +- .../esmf/aspect/AspectPrettyPrintCommand.java | 3 +- .../eclipse/esmf/aspect/AspectToCommand.java | 6 +- .../esmf/aspect/AspectValidateCommand.java | 3 +- .../esmf/aspect/to/AspectToAasCommand.java | 4 +- .../aspect/to/AspectToAsyncapiCommand.java | 3 +- .../esmf/aspect/to/AspectToHtmlCommand.java | 3 +- .../esmf/aspect/to/AspectToJavaCommand.java | 3 +- .../esmf/aspect/to/AspectToJsonCommand.java | 3 +- .../aspect/to/AspectToJsonSchemaCommand.java | 3 +- .../aspect/to/AspectToOpenapiCommand.java | 5 +- .../esmf/aspect/to/AspectToPngCommand.java | 3 +- .../esmf/aspect/to/AspectToSqlCommand.java | 3 +- .../esmf/aspect/to/AspectToSvgCommand.java | 3 +- .../java/org/eclipse/esmf/SammCliTest.java | 16 ++++ 18 files changed, 132 insertions(+), 39 deletions(-) diff --git a/documentation/developer-guide/modules/tooling-guide/pages/samm-cli.adoc b/documentation/developer-guide/modules/tooling-guide/pages/samm-cli.adoc index 8accac350..76fed04aa 100644 --- a/documentation/developer-guide/modules/tooling-guide/pages/samm-cli.adoc +++ b/documentation/developer-guide/modules/tooling-guide/pages/samm-cli.adoc @@ -48,8 +48,8 @@ alias samm='java -jar /location/to/samm-cli-{esmf-sdk-version}.jar' Successful execution of a command is signaled by returning 0. In case of a logical or other internal error the error code 1 is being returned. Missing or wrong command parameters result in error code 2 being returned. -To get help for a certain subcommand, add 'help' before the subcommand name or add `--help` to the end, e.g., `samm help aspect` -or `samm aspect --help`. +To get an overview of all commands and subcommands, use `samm help`. +To get help for a certain subcommand, use `help` before the subcommand name, e.g., `samm help aspect`, `samm help aspect validate` or `samm help aas to aspect`. Each subcommand can have its own set of options which allow the user to further fine-tune the execution of the command. The available options and their meaning can also be seen in the help text of the individual subcommands. @@ -57,13 +57,14 @@ The available options and their meaning can also be seen in the help text of the |=== | Command | Description/Options | Examples | help | Get overview of all commands | `samm help` -| help | Get help for a specific subcommand | `samm help aspect` -| aspect help | Get help for `aspect` subcommands | `samm aspect help validate` +.3+| [[help]] help | Get help for a list of subcommands | `samm help aspect` + | | `samm help aspect to svg` + | | `samm help aspect validate` .2+| [[aspect-validate]] aspect validate | Validate Aspect Model | `samm aspect AspectModel.ttl validate` | _--custom-resolver_ : use an external resolver for the resolution of the model elements | `samm aspect AspectModel.ttl validate --custom-resolver myresolver.sh` .3+| [[aspect-prettyprint]] aspect prettyprint | Pretty-print Aspect Model | `samm aspect AspectModel.ttl prettyprint` | _--output, -o_ : the output will be saved to the given file | `samm aspect AspectModel.ttl prettyprint -o c:\Results\PrettyPrinted.ttl` - | _--overwrite, -w_ : Overwrite the input file | `samm aspect AspectModel.ttl prettyprint -w + | _--overwrite, -w_ : Overwrite the input file | `samm aspect AspectModel.ttl prettyprint -w` .5+| [[aspect-to-html]] aspect to html | Generate HTML documentation for an Aspect Model | `samm aspect AspectModel.ttl to html` | _--output, -o_ : the output will be saved to the given file | `samm aspect AspectModel.ttl to html -o c:\Model.html` | _--css, -c_ : CSS file with custom styles to be included in the generated HTML diff --git a/tools/samm-cli/src/main/java/org/eclipse/esmf/LoggingMixin.java b/tools/samm-cli/src/main/java/org/eclipse/esmf/LoggingMixin.java index 35920bbcf..ac5283f3d 100644 --- a/tools/samm-cli/src/main/java/org/eclipse/esmf/LoggingMixin.java +++ b/tools/samm-cli/src/main/java/org/eclipse/esmf/LoggingMixin.java @@ -32,7 +32,7 @@ public class LoggingMixin { private boolean[] verbosity = new boolean[0]; private static LoggingMixin getTopLevelCommandLoggingMixin( final CommandLine.Model.CommandSpec commandSpec ) { - return ((SammCli) commandSpec.root().userObject()).loggingMixin; + return ( (SammCli) commandSpec.root().userObject() ).loggingMixin; } public static int executionStrategy( final CommandLine.ParseResult parseResult ) { diff --git a/tools/samm-cli/src/main/java/org/eclipse/esmf/SammCli.java b/tools/samm-cli/src/main/java/org/eclipse/esmf/SammCli.java index ae16fce4d..da75c270a 100644 --- a/tools/samm-cli/src/main/java/org/eclipse/esmf/SammCli.java +++ b/tools/samm-cli/src/main/java/org/eclipse/esmf/SammCli.java @@ -12,13 +12,24 @@ */ package org.eclipse.esmf; +import static picocli.CommandLine.Model.UsageMessageSpec.SECTION_KEY_COMMAND_LIST; + import java.io.File; import java.io.IOException; import java.io.InputStream; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; import java.util.Properties; import org.eclipse.esmf.aas.AasCommand; +import org.eclipse.esmf.aas.AasToCommand; +import org.eclipse.esmf.aas.to.AasToAspectCommand; import org.eclipse.esmf.aspect.AspectCommand; +import org.eclipse.esmf.aspect.AspectPrettyPrintCommand; +import org.eclipse.esmf.aspect.AspectToCommand; +import org.eclipse.esmf.aspect.to.AspectToSvgCommand; import org.eclipse.esmf.substitution.IsWindows; import org.fusesource.jansi.AnsiConsole; @@ -31,7 +42,13 @@ descriptionHeading = "%n@|bold Description|@:%n%n", parameterListHeading = "%n@|bold Parameters|@:%n", optionListHeading = "%n@|bold Options|@:%n", - footer = "%nRun @|bold " + SammCli.COMMAND_NAME + " help |@ to display its help." + footer = "%nRun @|bold " + SammCli.COMMAND_NAME + " help |@ to display its help, e.g.:%n" + + " @|bold " + SammCli.COMMAND_NAME + " help " + + AspectCommand.COMMAND_NAME + " " + AspectToCommand.COMMAND_NAME + " " + AspectToSvgCommand.COMMAND_NAME + "|@%n" + + "or @|bold " + SammCli.COMMAND_NAME + " help " + + AspectCommand.COMMAND_NAME + " " + AspectPrettyPrintCommand.COMMAND_NAME + "|@%n" + + "or @|bold " + SammCli.COMMAND_NAME + " help " + + AasCommand.COMMAND_NAME + " " + AasToCommand.COMMAND_NAME + " " + AasToAspectCommand.COMMAND_NAME + "|@%n" + "%nDocumentation: https://eclipse-esmf.github.io/esmf-documentation/index.html" ) @SuppressWarnings( "squid:S1147" ) // System.exit is really required here, this is a CLI tool @@ -40,12 +57,48 @@ public class SammCli extends AbstractCommand { private final CommandLine commandLine; + static class CustomCommandListRenderer implements CommandLine.IHelpSectionRenderer { + @Override + public String render( final CommandLine.Help help ) { + final CommandLine.Model.CommandSpec spec = help.commandSpec(); + if ( spec.subcommands().isEmpty() ) { + return ""; + } + + final CommandLine.Help.TextTable textTable = CommandLine.Help.TextTable.forColumns( help.colorScheme(), + new CommandLine.Help.Column( 15, 2, CommandLine.Help.Column.Overflow.SPAN ), + new CommandLine.Help.Column( spec.usageMessage().width() - 15, 2, CommandLine.Help.Column.Overflow.WRAP ) ); + textTable.setAdjustLineBreaksForWideCJKCharacters( spec.usageMessage().adjustLineBreaksForWideCJKCharacters() ); + spec.subcommands().values().forEach( subcommand -> addHierarchy( subcommand, textTable, "" ) ); + return textTable.toString(); + } + + private void addHierarchy( final CommandLine cmd, final CommandLine.Help.TextTable textTable, final String indent ) { + final String names = cmd.getCommandSpec().names().toString(); + final String formattedNames = names.substring( 1, names.length() - 1 ); + final String description = description( cmd.getCommandSpec().usageMessage() ); + textTable.addRowValues( indent + formattedNames, description ); + cmd.getSubcommands().values().forEach( sub -> addHierarchy( sub, textTable, indent + " " ) ); + } + + private String description( final CommandLine.Model.UsageMessageSpec usageMessage ) { + if ( usageMessage.header().length > 0 ) { + return usageMessage.header()[0]; + } + if ( usageMessage.description().length > 0 ) { + return usageMessage.description()[0]; + } + return ""; + } + } + public SammCli() { final CommandLine initialCommandLine = new CommandLine( this ) .addSubcommand( new AspectCommand() ) .addSubcommand( new AasCommand() ) .setCaseInsensitiveEnumValuesAllowed( true ) .setExecutionStrategy( LoggingMixin::executionStrategy ); + initialCommandLine.getHelpSectionMap().put( SECTION_KEY_COMMAND_LIST, new CustomCommandListRenderer() ); final CommandLine.IExecutionExceptionHandler defaultExecutionExceptionHandler = initialCommandLine.getExecutionExceptionHandler(); commandLine = initialCommandLine.setExecutionExceptionHandler( new CommandLine.IExecutionExceptionHandler() { @Override @@ -131,7 +184,40 @@ public static void main( final String[] argv ) { } final SammCli command = new SammCli(); - final int exitCode = command.commandLine.execute( argv ); + // Explicitly allow 'samm help command subcommand...' also if the subcommand is 'to' (e.g., aspect to, aas to) and + // usually receives a mandatory input file as its first parameter, e.g.: + // What a user wants to enter: What we need to provide to picocli + // "help aspect to sql" -> "aspect _ to help sql" + final String[] adjustedArgv; + final List argvList = Arrays.asList( argv ); + final int helpAspectToIndex = Collections.indexOfSubList( argvList, + List.of( "help", AspectCommand.COMMAND_NAME, AspectToCommand.COMMAND_NAME ) ); + final int helpAasToIndex = Collections.indexOfSubList( argvList, + List.of( "help", AasCommand.COMMAND_NAME, AasToCommand.COMMAND_NAME ) ); + final int helpAspectXIndex = Collections.indexOfSubList( argvList, List.of( "help", AspectCommand.COMMAND_NAME ) ); + final int helpAasXIndex = Collections.indexOfSubList( argvList, List.of( "help", AasCommand.COMMAND_NAME ) ); + if ( helpAspectToIndex != -1 || helpAasToIndex != -1 ) { + final int index = Integer.max( helpAspectToIndex, helpAasToIndex ); + final List customArgv = new ArrayList<>( argvList.subList( 0, index ) ); + customArgv.add( argvList.get( index + 1 ) ); + customArgv.add( "_" ); + customArgv.add( argvList.get( index + 2 ) ); + customArgv.add( "help" ); + customArgv.addAll( argvList.subList( index + 3, argvList.size() ) ); + adjustedArgv = customArgv.toArray( new String[] {} ); + } else if ( helpAspectXIndex != -1 || helpAasXIndex != -1 ) { + // "help aspect prettyprint" -> "aspect help prettyprint" + final int index = Integer.max( helpAspectXIndex, helpAasXIndex ); + final List customArgv = new ArrayList<>( argvList.subList( 0, index ) ); + customArgv.add( argvList.get( index + 1 ) ); + customArgv.add( "help" ); + customArgv.addAll( argvList.subList( index + 2, argvList.size() ) ); + adjustedArgv = customArgv.toArray( new String[] {} ); + } else { + adjustedArgv = argv; + } + + final int exitCode = command.commandLine.execute( adjustedArgv ); if ( !disableColor ) { AnsiConsole.systemUninstall(); } @@ -166,6 +252,8 @@ public void run() { System.exit( 0 ); } System.out.println( commandLine.getHelp().fullSynopsis() ); - System.out.println( format( "Run @|bold " + commandLine.getCommandName() + " help|@ for help." ) ); + System.out.println( format( "Run @|bold " + commandLine.getCommandName() + " help|@ for help, e.g.:" ) ); + System.out.println( format( " @|bold " + commandLine.getCommandName() + " help " + + AspectCommand.COMMAND_NAME + " " + AspectToCommand.COMMAND_NAME + " " + AspectToSvgCommand.COMMAND_NAME + "|@" ) ); } } diff --git a/tools/samm-cli/src/main/java/org/eclipse/esmf/aspect/AspectCommand.java b/tools/samm-cli/src/main/java/org/eclipse/esmf/aspect/AspectCommand.java index 13cb0e67e..2f07ae2b9 100644 --- a/tools/samm-cli/src/main/java/org/eclipse/esmf/aspect/AspectCommand.java +++ b/tools/samm-cli/src/main/java/org/eclipse/esmf/aspect/AspectCommand.java @@ -30,8 +30,7 @@ headerHeading = "@|bold Usage|@:%n%n", descriptionHeading = "%n@|bold Description|@:%n%n", parameterListHeading = "%n@|bold Parameters|@:%n", - optionListHeading = "%n@|bold Options|@:%n", - mixinStandardHelpOptions = true + optionListHeading = "%n@|bold Options|@:%n" ) public class AspectCommand extends AbstractCommand { public static final String COMMAND_NAME = "aspect"; diff --git a/tools/samm-cli/src/main/java/org/eclipse/esmf/aspect/AspectPrettyPrintCommand.java b/tools/samm-cli/src/main/java/org/eclipse/esmf/aspect/AspectPrettyPrintCommand.java index 353656c0d..6d5294020 100644 --- a/tools/samm-cli/src/main/java/org/eclipse/esmf/aspect/AspectPrettyPrintCommand.java +++ b/tools/samm-cli/src/main/java/org/eclipse/esmf/aspect/AspectPrettyPrintCommand.java @@ -35,8 +35,7 @@ headerHeading = "@|bold Usage|@:%n%n", descriptionHeading = "%n@|bold Description|@:%n%n", parameterListHeading = "%n@|bold Parameters|@:%n", - optionListHeading = "%n@|bold Options|@:%n", - mixinStandardHelpOptions = true + optionListHeading = "%n@|bold Options|@:%n" ) public class AspectPrettyPrintCommand extends AbstractCommand { public static final String COMMAND_NAME = "prettyprint"; diff --git a/tools/samm-cli/src/main/java/org/eclipse/esmf/aspect/AspectToCommand.java b/tools/samm-cli/src/main/java/org/eclipse/esmf/aspect/AspectToCommand.java index 6bcc10462..57aac58d0 100644 --- a/tools/samm-cli/src/main/java/org/eclipse/esmf/aspect/AspectToCommand.java +++ b/tools/samm-cli/src/main/java/org/eclipse/esmf/aspect/AspectToCommand.java @@ -29,7 +29,8 @@ import picocli.CommandLine; -@CommandLine.Command( name = AspectToCommand.COMMAND_NAME, description = "Transforms an Aspect Model into another format", +@CommandLine.Command( name = AspectToCommand.COMMAND_NAME, + description = "Transforms an Aspect Model into another format", subcommands = { CommandLine.HelpCommand.class, AspectToHtmlCommand.class, @@ -45,8 +46,7 @@ }, descriptionHeading = "%n@|bold Description|@:%n%n", parameterListHeading = "%n@|bold Parameters|@:%n", - optionListHeading = "%n@|bold Options|@:%n", - mixinStandardHelpOptions = true + optionListHeading = "%n@|bold Options|@:%n" ) public class AspectToCommand extends AbstractCommand { public static final String COMMAND_NAME = "to"; diff --git a/tools/samm-cli/src/main/java/org/eclipse/esmf/aspect/AspectValidateCommand.java b/tools/samm-cli/src/main/java/org/eclipse/esmf/aspect/AspectValidateCommand.java index 1e8fa37ac..0cdcc964a 100644 --- a/tools/samm-cli/src/main/java/org/eclipse/esmf/aspect/AspectValidateCommand.java +++ b/tools/samm-cli/src/main/java/org/eclipse/esmf/aspect/AspectValidateCommand.java @@ -34,8 +34,7 @@ headerHeading = "@|bold Usage|@:%n%n", descriptionHeading = "%n@|bold Description|@:%n%n", parameterListHeading = "%n@|bold Parameters|@:%n", - optionListHeading = "%n@|bold Options|@:%n", - mixinStandardHelpOptions = true + optionListHeading = "%n@|bold Options|@:%n" ) @SuppressWarnings( "UseOfSystemOutOrSystemErr" ) public class AspectValidateCommand extends AbstractCommand { diff --git a/tools/samm-cli/src/main/java/org/eclipse/esmf/aspect/to/AspectToAasCommand.java b/tools/samm-cli/src/main/java/org/eclipse/esmf/aspect/to/AspectToAasCommand.java index 873c2574d..56204146e 100644 --- a/tools/samm-cli/src/main/java/org/eclipse/esmf/aspect/to/AspectToAasCommand.java +++ b/tools/samm-cli/src/main/java/org/eclipse/esmf/aspect/to/AspectToAasCommand.java @@ -35,8 +35,8 @@ description = "Generate Asset Administration Shell (AAS) submodel template for an Aspect Model", descriptionHeading = "%n@|bold Description|@:%n%n", parameterListHeading = "%n@|bold Parameters|@:%n", - optionListHeading = "%n@|bold Options|@:%n", - mixinStandardHelpOptions = true ) + optionListHeading = "%n@|bold Options|@:%n" +) public class AspectToAasCommand extends AbstractCommand { public static final String COMMAND_NAME = "aas"; private static final Logger LOG = LoggerFactory.getLogger( AspectToAasCommand.class ); diff --git a/tools/samm-cli/src/main/java/org/eclipse/esmf/aspect/to/AspectToAsyncapiCommand.java b/tools/samm-cli/src/main/java/org/eclipse/esmf/aspect/to/AspectToAsyncapiCommand.java index 77c36606b..c20733db7 100644 --- a/tools/samm-cli/src/main/java/org/eclipse/esmf/aspect/to/AspectToAsyncapiCommand.java +++ b/tools/samm-cli/src/main/java/org/eclipse/esmf/aspect/to/AspectToAsyncapiCommand.java @@ -46,8 +46,7 @@ description = "Generate AsyncAPI specification for an Aspect Model", descriptionHeading = "%n@|bold Description|@:%n%n", parameterListHeading = "%n@|bold Parameters|@:%n", - optionListHeading = "%n@|bold Options|@:%n", - mixinStandardHelpOptions = true + optionListHeading = "%n@|bold Options|@:%n" ) public class AspectToAsyncapiCommand extends AbstractCommand { diff --git a/tools/samm-cli/src/main/java/org/eclipse/esmf/aspect/to/AspectToHtmlCommand.java b/tools/samm-cli/src/main/java/org/eclipse/esmf/aspect/to/AspectToHtmlCommand.java index a879d9506..53e5e7761 100644 --- a/tools/samm-cli/src/main/java/org/eclipse/esmf/aspect/to/AspectToHtmlCommand.java +++ b/tools/samm-cli/src/main/java/org/eclipse/esmf/aspect/to/AspectToHtmlCommand.java @@ -33,8 +33,7 @@ description = "Generate HTML documentation for an Aspect Model", descriptionHeading = "%n@|bold Description|@:%n%n", parameterListHeading = "%n@|bold Parameters|@:%n", - optionListHeading = "%n@|bold Options|@:%n", - mixinStandardHelpOptions = true + optionListHeading = "%n@|bold Options|@:%n" ) public class AspectToHtmlCommand extends AbstractCommand { public static final String COMMAND_NAME = "html"; diff --git a/tools/samm-cli/src/main/java/org/eclipse/esmf/aspect/to/AspectToJavaCommand.java b/tools/samm-cli/src/main/java/org/eclipse/esmf/aspect/to/AspectToJavaCommand.java index 4c304d6ca..4cf507171 100644 --- a/tools/samm-cli/src/main/java/org/eclipse/esmf/aspect/to/AspectToJavaCommand.java +++ b/tools/samm-cli/src/main/java/org/eclipse/esmf/aspect/to/AspectToJavaCommand.java @@ -34,8 +34,7 @@ description = "Generate Java domain classes for an Aspect Model", descriptionHeading = "%n@|bold Description|@:%n%n", parameterListHeading = "%n@|bold Parameters|@:%n", - optionListHeading = "%n@|bold Options|@:%n", - mixinStandardHelpOptions = true + optionListHeading = "%n@|bold Options|@:%n" ) public class AspectToJavaCommand extends AbstractCommand { public static final String COMMAND_NAME = "java"; diff --git a/tools/samm-cli/src/main/java/org/eclipse/esmf/aspect/to/AspectToJsonCommand.java b/tools/samm-cli/src/main/java/org/eclipse/esmf/aspect/to/AspectToJsonCommand.java index 753fce7ab..670466d4a 100644 --- a/tools/samm-cli/src/main/java/org/eclipse/esmf/aspect/to/AspectToJsonCommand.java +++ b/tools/samm-cli/src/main/java/org/eclipse/esmf/aspect/to/AspectToJsonCommand.java @@ -28,8 +28,7 @@ description = "Generate OpenAPI JSON specification for an Aspect Model", descriptionHeading = "%n@|bold Description|@:%n%n", parameterListHeading = "%n@|bold Parameters|@:%n", - optionListHeading = "%n@|bold Options|@:%n", - mixinStandardHelpOptions = true + optionListHeading = "%n@|bold Options|@:%n" ) public class AspectToJsonCommand extends AbstractCommand { public static final String COMMAND_NAME = "json"; diff --git a/tools/samm-cli/src/main/java/org/eclipse/esmf/aspect/to/AspectToJsonSchemaCommand.java b/tools/samm-cli/src/main/java/org/eclipse/esmf/aspect/to/AspectToJsonSchemaCommand.java index 950447546..0c5eb64b7 100644 --- a/tools/samm-cli/src/main/java/org/eclipse/esmf/aspect/to/AspectToJsonSchemaCommand.java +++ b/tools/samm-cli/src/main/java/org/eclipse/esmf/aspect/to/AspectToJsonSchemaCommand.java @@ -35,8 +35,7 @@ description = "Generate JSON schema for an Aspect Model", descriptionHeading = "%n@|bold Description|@:%n%n", parameterListHeading = "%n@|bold Parameters|@:%n", - optionListHeading = "%n@|bold Options|@:%n", - mixinStandardHelpOptions = true + optionListHeading = "%n@|bold Options|@:%n" ) public class AspectToJsonSchemaCommand extends AbstractCommand { public static final String COMMAND_NAME = "schema"; diff --git a/tools/samm-cli/src/main/java/org/eclipse/esmf/aspect/to/AspectToOpenapiCommand.java b/tools/samm-cli/src/main/java/org/eclipse/esmf/aspect/to/AspectToOpenapiCommand.java index 6481b985e..6fd7a7b0b 100644 --- a/tools/samm-cli/src/main/java/org/eclipse/esmf/aspect/to/AspectToOpenapiCommand.java +++ b/tools/samm-cli/src/main/java/org/eclipse/esmf/aspect/to/AspectToOpenapiCommand.java @@ -56,8 +56,7 @@ description = "Generate OpenAPI specification for an Aspect Model", descriptionHeading = "%n@|bold Description|@:%n%n", parameterListHeading = "%n@|bold Parameters|@:%n", - optionListHeading = "%n@|bold Options|@:%n", - mixinStandardHelpOptions = true + optionListHeading = "%n@|bold Options|@:%n" ) public class AspectToOpenapiCommand extends AbstractCommand { public static final String COMMAND_NAME = "openapi"; @@ -214,7 +213,7 @@ private void writeSchemaWithSeparateFiles( final OpenApiSchemaArtifact openApiSp } } - private ObjectNode readFile( String file ) throws CommandException { + private ObjectNode readFile( final String file ) throws CommandException { if ( StringUtils.isBlank( file ) ) { return null; } diff --git a/tools/samm-cli/src/main/java/org/eclipse/esmf/aspect/to/AspectToPngCommand.java b/tools/samm-cli/src/main/java/org/eclipse/esmf/aspect/to/AspectToPngCommand.java index 9cd56b296..b7ded2875 100644 --- a/tools/samm-cli/src/main/java/org/eclipse/esmf/aspect/to/AspectToPngCommand.java +++ b/tools/samm-cli/src/main/java/org/eclipse/esmf/aspect/to/AspectToPngCommand.java @@ -28,8 +28,7 @@ description = "Generate PNG diagram for an Aspect Model", descriptionHeading = "%n@|bold Description|@:%n%n", parameterListHeading = "%n@|bold Parameters|@:%n", - optionListHeading = "%n@|bold Options|@:%n", - mixinStandardHelpOptions = true + optionListHeading = "%n@|bold Options|@:%n" ) public class AspectToPngCommand extends AbstractCommand { public static final String COMMAND_NAME = "png"; diff --git a/tools/samm-cli/src/main/java/org/eclipse/esmf/aspect/to/AspectToSqlCommand.java b/tools/samm-cli/src/main/java/org/eclipse/esmf/aspect/to/AspectToSqlCommand.java index b4adb80b3..eb323768d 100644 --- a/tools/samm-cli/src/main/java/org/eclipse/esmf/aspect/to/AspectToSqlCommand.java +++ b/tools/samm-cli/src/main/java/org/eclipse/esmf/aspect/to/AspectToSqlCommand.java @@ -38,8 +38,7 @@ description = "Generate SQL table creation script for an Aspect Model", descriptionHeading = "%n@|bold Description|@:%n%n", parameterListHeading = "%n@|bold Parameters|@:%n", - optionListHeading = "%n@|bold Options|@:%n", - mixinStandardHelpOptions = true + optionListHeading = "%n@|bold Options|@:%n" ) public class AspectToSqlCommand extends AbstractCommand { public static final String COMMAND_NAME = "sql"; diff --git a/tools/samm-cli/src/main/java/org/eclipse/esmf/aspect/to/AspectToSvgCommand.java b/tools/samm-cli/src/main/java/org/eclipse/esmf/aspect/to/AspectToSvgCommand.java index 059b796d4..ff0eadbc2 100644 --- a/tools/samm-cli/src/main/java/org/eclipse/esmf/aspect/to/AspectToSvgCommand.java +++ b/tools/samm-cli/src/main/java/org/eclipse/esmf/aspect/to/AspectToSvgCommand.java @@ -28,8 +28,7 @@ description = "Generate SVG diagram for an Aspect Model", descriptionHeading = "%n@|bold Description|@:%n%n", parameterListHeading = "%n@|bold Parameters|@:%n", - optionListHeading = "%n@|bold Options|@:%n", - mixinStandardHelpOptions = true + optionListHeading = "%n@|bold Options|@:%n" ) public class AspectToSvgCommand extends AbstractCommand { public static final String COMMAND_NAME = "svg"; diff --git a/tools/samm-cli/src/test/java/org/eclipse/esmf/SammCliTest.java b/tools/samm-cli/src/test/java/org/eclipse/esmf/SammCliTest.java index 885720c4b..9138f5ccc 100644 --- a/tools/samm-cli/src/test/java/org/eclipse/esmf/SammCliTest.java +++ b/tools/samm-cli/src/test/java/org/eclipse/esmf/SammCliTest.java @@ -125,6 +125,22 @@ void testHelp() { assertThat( result.stderr() ).isEmpty(); } + @Test + void testSubCommandHelp() { + final ExecutionResult result = sammCli.runAndExpectSuccess( "--disable-color", "help", "aspect", "prettyprint" ); + assertThat( result.stdout() ).contains( "Usage:" ); + assertThat( result.stdout() ).contains( "--overwrite" ); + assertThat( result.stderr() ).isEmpty(); + } + + @Test + void testSubSubCommandHelp() { + final ExecutionResult result = sammCli.runAndExpectSuccess( "--disable-color", "help", "aspect", "to", "svg" ); + assertThat( result.stdout() ).contains( "Usage:" ); + assertThat( result.stdout() ).contains( "--language" ); + assertThat( result.stderr() ).isEmpty(); + } + @Test void testVerboseOutput() { final ExecutionResult result = sammCli.runAndExpectSuccess( "--disable-color", "aspect", defaultInputFile, "validate", "-vvv" ); From 9cf73b9f0acff73c05e7676b1bb10bbde395610b Mon Sep 17 00:00:00 2001 From: "Textor Andreas (BCI/ESW17)" Date: Fri, 9 Aug 2024 15:27:24 +0200 Subject: [PATCH 20/33] Add AspectEditMoveCommand --- .../org/eclipse/esmf/AbstractCommand.java | 10 +- .../main/java/org/eclipse/esmf/SammCli.java | 38 ++- .../eclipse/esmf/aspect/AspectCommand.java | 3 +- .../esmf/aspect/AspectEditCommand.java | 47 +++ .../aspect/edit/AspectEditMoveCommand.java | 274 ++++++++++++++++++ 5 files changed, 356 insertions(+), 16 deletions(-) create mode 100644 tools/samm-cli/src/main/java/org/eclipse/esmf/aspect/AspectEditCommand.java create mode 100644 tools/samm-cli/src/main/java/org/eclipse/esmf/aspect/edit/AspectEditMoveCommand.java diff --git a/tools/samm-cli/src/main/java/org/eclipse/esmf/AbstractCommand.java b/tools/samm-cli/src/main/java/org/eclipse/esmf/AbstractCommand.java index 50a600624..f9df3a945 100644 --- a/tools/samm-cli/src/main/java/org/eclipse/esmf/AbstractCommand.java +++ b/tools/samm-cli/src/main/java/org/eclipse/esmf/AbstractCommand.java @@ -54,12 +54,16 @@ protected AspectModel loadAspectModelOrFail( final String modelFileName, final E return loadAspectModelOrFail( modelFileName, resolverConfig, false ); } - protected AspectModel loadAspectModelOrFail( final String modelFileName, final ExternalResolverMixin resolverConfig, - final boolean details ) { + protected File getInputFile( final String modelFileName ) { final File inputFile = new File( modelFileName ); - final File absoluteFile = inputFile.isAbsolute() + return inputFile.isAbsolute() ? inputFile : Path.of( System.getProperty( "user.dir" ) ).resolve( inputFile.toPath() ).toFile().getAbsoluteFile(); + } + + protected AspectModel loadAspectModelOrFail( final String modelFileName, final ExternalResolverMixin resolverConfig, + final boolean details ) { + final File absoluteFile = getInputFile( modelFileName ); final ResolutionStrategy resolveFromWorkspace = new FileSystemStrategy( modelsRootForFile( absoluteFile ) ); final ResolutionStrategy resolveFromCurrentDirectory = AspectModelLoader.DEFAULT_STRATEGY.get(); diff --git a/tools/samm-cli/src/main/java/org/eclipse/esmf/SammCli.java b/tools/samm-cli/src/main/java/org/eclipse/esmf/SammCli.java index da75c270a..a1611ebac 100644 --- a/tools/samm-cli/src/main/java/org/eclipse/esmf/SammCli.java +++ b/tools/samm-cli/src/main/java/org/eclipse/esmf/SammCli.java @@ -22,11 +22,13 @@ import java.util.Collections; import java.util.List; import java.util.Properties; +import java.util.stream.IntStream; import org.eclipse.esmf.aas.AasCommand; import org.eclipse.esmf.aas.AasToCommand; import org.eclipse.esmf.aas.to.AasToAspectCommand; import org.eclipse.esmf.aspect.AspectCommand; +import org.eclipse.esmf.aspect.AspectEditCommand; import org.eclipse.esmf.aspect.AspectPrettyPrintCommand; import org.eclipse.esmf.aspect.AspectToCommand; import org.eclipse.esmf.aspect.to.AspectToSvgCommand; @@ -184,20 +186,37 @@ public static void main( final String[] argv ) { } final SammCli command = new SammCli(); - // Explicitly allow 'samm help command subcommand...' also if the subcommand is 'to' (e.g., aspect to, aas to) and - // usually receives a mandatory input file as its first parameter, e.g.: - // What a user wants to enter: What we need to provide to picocli - // "help aspect to sql" -> "aspect _ to help sql" + final String[] adjustedArgv = adjustCommandLineArguments( argv ); + + final int exitCode = command.commandLine.execute( adjustedArgv ); + if ( !disableColor ) { + AnsiConsole.systemUninstall(); + } + System.exit( exitCode ); + } + + /** + * Explicitly allow 'samm help command subcommand...' also if the subcommand is 'to' (e.g., aspect to, aas to) and + * usually receives a mandatory input file as its first parameter, e.g.: + * What a user wants to enter: "help aspect to sql" + * What we need to provide to picocli: "aspect _ to help sql" + * + * @param argv the original command line arguments + * @return the adjusted command line arguments + */ + private static String[] adjustCommandLineArguments( final String[] argv ) { final String[] adjustedArgv; final List argvList = Arrays.asList( argv ); final int helpAspectToIndex = Collections.indexOfSubList( argvList, List.of( "help", AspectCommand.COMMAND_NAME, AspectToCommand.COMMAND_NAME ) ); final int helpAasToIndex = Collections.indexOfSubList( argvList, List.of( "help", AasCommand.COMMAND_NAME, AasToCommand.COMMAND_NAME ) ); + final int helpAspectEditIndex = Collections.indexOfSubList( argvList, + List.of( "help", AspectCommand.COMMAND_NAME, AspectEditCommand.COMMAND_NAME ) ); final int helpAspectXIndex = Collections.indexOfSubList( argvList, List.of( "help", AspectCommand.COMMAND_NAME ) ); final int helpAasXIndex = Collections.indexOfSubList( argvList, List.of( "help", AasCommand.COMMAND_NAME ) ); - if ( helpAspectToIndex != -1 || helpAasToIndex != -1 ) { - final int index = Integer.max( helpAspectToIndex, helpAasToIndex ); + if ( helpAspectToIndex != -1 || helpAasToIndex != -1 || helpAspectEditIndex != -1 ) { + final int index = IntStream.of( helpAspectToIndex, helpAasToIndex, helpAspectEditIndex ).max().getAsInt(); final List customArgv = new ArrayList<>( argvList.subList( 0, index ) ); customArgv.add( argvList.get( index + 1 ) ); customArgv.add( "_" ); @@ -216,12 +235,7 @@ public static void main( final String[] argv ) { } else { adjustedArgv = argv; } - - final int exitCode = command.commandLine.execute( adjustedArgv ); - if ( !disableColor ) { - AnsiConsole.systemUninstall(); - } - System.exit( exitCode ); + return adjustedArgv; } protected String format( final String string ) { diff --git a/tools/samm-cli/src/main/java/org/eclipse/esmf/aspect/AspectCommand.java b/tools/samm-cli/src/main/java/org/eclipse/esmf/aspect/AspectCommand.java index 2f07ae2b9..e4d54cb8a 100644 --- a/tools/samm-cli/src/main/java/org/eclipse/esmf/aspect/AspectCommand.java +++ b/tools/samm-cli/src/main/java/org/eclipse/esmf/aspect/AspectCommand.java @@ -25,7 +25,8 @@ CommandLine.HelpCommand.class, AspectToCommand.class, AspectPrettyPrintCommand.class, - AspectValidateCommand.class + AspectValidateCommand.class, + AspectEditCommand.class }, headerHeading = "@|bold Usage|@:%n%n", descriptionHeading = "%n@|bold Description|@:%n%n", diff --git a/tools/samm-cli/src/main/java/org/eclipse/esmf/aspect/AspectEditCommand.java b/tools/samm-cli/src/main/java/org/eclipse/esmf/aspect/AspectEditCommand.java new file mode 100644 index 000000000..4c666a1fc --- /dev/null +++ b/tools/samm-cli/src/main/java/org/eclipse/esmf/aspect/AspectEditCommand.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2024 Robert Bosch Manufacturing Solutions GmbH + * + * See the AUTHORS file(s) distributed with this work for additional + * information regarding authorship. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * SPDX-License-Identifier: MPL-2.0 + */ + +package org.eclipse.esmf.aspect; + +import org.eclipse.esmf.AbstractCommand; +import org.eclipse.esmf.LoggingMixin; +import org.eclipse.esmf.aspect.edit.AspectEditMoveCommand; +import org.eclipse.esmf.exception.SubCommandException; + +import picocli.CommandLine; + +@CommandLine.Command( name = AspectEditCommand.COMMAND_NAME, + description = "Edit (refactor) an Aspect Model", + subcommands = { + CommandLine.HelpCommand.class, + AspectEditMoveCommand.class + }, + headerHeading = "@|bold Usage|@:%n%n", + descriptionHeading = "%n@|bold Description|@:%n%n", + parameterListHeading = "%n@|bold Parameters|@:%n", + optionListHeading = "%n@|bold Options|@:%n" +) +public class AspectEditCommand extends AbstractCommand { + public static final String COMMAND_NAME = "edit"; + + @CommandLine.Mixin + private LoggingMixin loggingMixin; + + @CommandLine.ParentCommand + public AspectCommand parentCommand; + + @Override + public void run() { + throw new SubCommandException( COMMAND_NAME ); + } +} diff --git a/tools/samm-cli/src/main/java/org/eclipse/esmf/aspect/edit/AspectEditMoveCommand.java b/tools/samm-cli/src/main/java/org/eclipse/esmf/aspect/edit/AspectEditMoveCommand.java new file mode 100644 index 000000000..aca4053f3 --- /dev/null +++ b/tools/samm-cli/src/main/java/org/eclipse/esmf/aspect/edit/AspectEditMoveCommand.java @@ -0,0 +1,274 @@ +/* + * Copyright (c) 2024 Robert Bosch Manufacturing Solutions GmbH + * + * See the AUTHORS file(s) distributed with this work for additional + * information regarding authorship. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * SPDX-License-Identifier: MPL-2.0 + */ + +package org.eclipse.esmf.aspect.edit; + +import java.io.File; +import java.net.URI; +import java.net.URL; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +import org.eclipse.esmf.AbstractCommand; +import org.eclipse.esmf.ExternalResolverMixin; +import org.eclipse.esmf.LoggingMixin; +import org.eclipse.esmf.aspect.AspectEditCommand; +import org.eclipse.esmf.aspectmodel.AspectModelBuilder; +import org.eclipse.esmf.aspectmodel.AspectModelFile; +import org.eclipse.esmf.aspectmodel.edit.AspectChangeContext; +import org.eclipse.esmf.aspectmodel.edit.AspectChangeContextConfig; +import org.eclipse.esmf.aspectmodel.edit.AspectChangeContextConfigBuilder; +import org.eclipse.esmf.aspectmodel.edit.Change; +import org.eclipse.esmf.aspectmodel.edit.ChangeReport; +import org.eclipse.esmf.aspectmodel.edit.ChangeReportFormatter; +import org.eclipse.esmf.aspectmodel.edit.change.MoveElementToExistingFile; +import org.eclipse.esmf.aspectmodel.serializer.AspectSerializer; +import org.eclipse.esmf.exception.CommandException; +import org.eclipse.esmf.metamodel.AspectModel; +import org.eclipse.esmf.metamodel.ModelElement; + +import picocli.CommandLine; + +@SuppressWarnings( "UseOfSystemOutOrSystemErr" ) +@CommandLine.Command( name = AspectEditMoveCommand.COMMAND_NAME, + description = "Move elements to other files or namespaces", + descriptionHeading = "%n@|bold Description|@:%n%n", + parameterListHeading = "%n@|bold Parameters|@:%n", + optionListHeading = "%n@|bold Options|@:%n" +) +public class AspectEditMoveCommand extends AbstractCommand { + public static final String COMMAND_NAME = "move"; + + @CommandLine.ParentCommand + private AspectEditCommand parentCommand; + + @CommandLine.Mixin + private LoggingMixin loggingMixin; + + @CommandLine.Mixin + private ExternalResolverMixin customResolver; + + @CommandLine.Parameters( + paramLabel = "ELEMENT", + description = "Full URN or local name of the model element to move", + arity = "1", + index = "0" + ) + private String elementName; + + @CommandLine.Parameters( + paramLabel = "TARGETFILE", + description = "Target file to move the element to", + arity = "1", + index = "1" + ) + private String targetFile; + + @CommandLine.Parameters( + paramLabel = "TARGETNAMESPACE", + description = "Target namespace to move the element to", + arity = "0..1", + index = "2" + ) + private String targetNamespace; + + @CommandLine.Option( + names = { "--dry-run" }, + description = "Emulate edit operation and print a report of changes that would be performed" + ) + private boolean dryRun; + + @CommandLine.Option( + names = { "--details" }, + description = "When using --dry-run, print details about RDF statements that are added and removed" + ) + private boolean details; + + @CommandLine.Option( + names = { "--copy-file-header" }, + description = "If set, newly created files will contain the same file header (e.g., copyright notice) as the source file of the " + + "move operation" + ) + private boolean copyHeader; + + @CommandLine.Option( + names = { "--force" }, + description = "Force creation/overwriting of existing files" + ) + private boolean force; + + @Override + public void run() { + final File file = new File( targetFile ); + if ( file.exists() ) { + if ( targetNamespace == null ) { + moveElementToExistingFile(); + } else { + moveElementToOtherNamespaceExistingFile(); + } + } else { + if ( targetNamespace == null ) { + moveElementToNewFile(); + } else { + moveElementToOtherNamespaceNewFile(); + } + } + } + + /** + * Supports the case {@code samm aspect Aspect.ttl edit move :MyAspect newFile.ttl} + */ + private void moveElementToNewFile() { + // TODO + } + + /** + * Supports the case {@code samm aspect Aspect.ttl edit move :MyAspect existingFile.ttl urn:samm:com.example.othernamespace:1.0.0} + */ + private void moveElementToOtherNamespaceExistingFile() { + // TODO + } + + /** + * Supports the case {@code samm aspect Aspect.ttl edit move :MyAspect newFile.ttl urn:samm:com.example.othernamespace:1.0.0} + */ + private void moveElementToOtherNamespaceNewFile() { + // TODO + } + + /** + * Support the case {@code samm aspect Aspect.ttl edit move MyAspect existingFile.ttl} + */ + private void moveElementToExistingFile() { + final AspectModel sourceAspectModel = loadAspectModelOrFail( parentCommand.parentCommand.getInput(), customResolver ); + final AspectModel targetAspectModel = loadAspectModelOrFail( targetFile, customResolver ); + + // Create a consistent in-memory representation of both the source and target models. + // On this Aspect Model we can perform the refactoring operation + final AspectModel aspectModel = AspectModelBuilder.merge( sourceAspectModel, targetAspectModel ); + + // Find the loaded AspectModelFile that corresponds to the input targetFile + final URI targetFileUri = getInputFile( targetFile ).toURI(); + final AspectModelFile targetAspectModelFile = aspectModel.files().stream() + .filter( file -> file.sourceLocation().map( uri -> uri.equals( targetFileUri ) ).orElse( false ) ) + .findFirst() + .orElseThrow( () -> new CommandException( "Could not determine target file" ) ); + + // Determine the URN for the element to move + final List potentialElements = aspectModel.elements().stream() + .filter( element -> !element.isAnonymous() ) + .filter( element -> element.urn().toString().endsWith( elementName ) ) + .toList(); + if ( potentialElements.isEmpty() ) { + System.out.println( "Could not find element to move: " + elementName ); + System.exit( 1 ); + } + if ( potentialElements.size() > 1 ) { + System.out.println( "Found more than one element identified by " + elementName + ": " + + potentialElements.stream() + .map( element -> element.urn().toString() ).collect( + Collectors.joining( ", ", "[", "]" ) ) + + "\nPlease use the element's full URN." ); + System.exit( 1 ); + } + + // Perform the refactoring + final AspectChangeContextConfig config = AspectChangeContextConfigBuilder.builder() + .detailedChangeReport( details ) + .build(); + final AspectChangeContext changeContext = new AspectChangeContext( config, aspectModel ); + final ModelElement modelElement = potentialElements.get( 0 ); + final Change move = new MoveElementToExistingFile( modelElement, targetAspectModelFile ); + final ChangeReport changeReport = changeContext.applyChange( move ); + if ( dryRun ) { + System.out.println( "Changes to be performed" ); + System.out.println( "=======================" ); + System.out.println( ChangeReportFormatter.INSTANCE.apply( changeReport, config ) ); + System.exit( 0 ); + } + + checkFilesystemConsistency( changeContext ); + performFileSystemWrite( changeContext ); + } + + private void performFileSystemWrite( final AspectChangeContext changeContext ) { + for ( final AspectModelFile fileToRemove : changeContext.removedFiles() ) { + final File file = Paths.get( fileToRemove.sourceLocation().orElseThrow() ).toFile(); + if ( !file.delete() ) { + throw new CommandException( "Could not delete file: " + file ); + } + } + for ( final AspectModelFile fileToCreate : changeContext.createdFiles() ) { + final File file = Paths.get( fileToCreate.sourceLocation().orElseThrow() ).toFile(); + file.getParentFile().mkdirs(); + AspectSerializer.INSTANCE.write( fileToCreate ); + } + for ( final AspectModelFile fileToModify : changeContext.modifiedFiles() ) { + AspectSerializer.INSTANCE.write( fileToModify ); + } + } + + private void checkFilesystemConsistency( final AspectChangeContext changeContext ) { + final List messages = new ArrayList<>(); + for ( final AspectModelFile fileToRemove : changeContext.removedFiles() ) { + final URL url = AspectSerializer.INSTANCE.aspectModelFileUrl( fileToRemove ); + if ( !url.getProtocol().equals( "file" ) ) { + messages.add( "File should be removed, but it is not identified by a file: URL: " + url ); + } + final File file = new File( URI.create( url.toString() ) ); + if ( !file.exists() ) { + messages.add( "File should be removed, but it does not exist: " + file ); + } + } + + for ( final AspectModelFile fileToCreate : changeContext.createdFiles() ) { + final URL url = AspectSerializer.INSTANCE.aspectModelFileUrl( fileToCreate ); + if ( !url.getProtocol().equals( "file" ) ) { + messages.add( "New file should be written, but it is not identified by a file: URL: " + url ); + } + final File file = new File( URI.create( url.toString() ) ); + if ( file.exists() && !force ) { + messages.add( + "New file should be written, but it already exists: " + file + ". Use the --force flag to force overwriting." ); + } + if ( file.exists() && force && !file.canWrite() ) { + messages.add( "New file should be written, but it is not writable:" + file ); + } + } + + for ( final AspectModelFile fileToModify : changeContext.modifiedFiles() ) { + final URL url = AspectSerializer.INSTANCE.aspectModelFileUrl( fileToModify ); + if ( !url.getProtocol().equals( "file" ) ) { + messages.add( "File should be modified, but it is not identified by a file: URL: " + url ); + } + final File file = new File( URI.create( url.toString() ) ); + if ( !file.exists() ) { + messages.add( "File should be modified, but it does not exist: " + file ); + } + if ( !file.canWrite() ) { + messages.add( "File should be modified, but it is not writable: " + file ); + } + if ( !file.isFile() ) { + messages.add( "File should be modified, but it is not a regular file: " + file ); + } + } + + if ( !messages.isEmpty() ) { + System.out.println( "Encountered problems, canceling writing." ); + messages.forEach( message -> System.out.println( "- " + message ) ); + System.exit( 1 ); + } + } +} From 50fb68b0782dd9cc6d2d184ebd96f24ae43cc2da Mon Sep 17 00:00:00 2001 From: "Textor Andreas (BCI/ESW17)" Date: Fri, 9 Aug 2024 15:27:47 +0200 Subject: [PATCH 21/33] Add method to merge AspectModels in AspectModelBuilder --- .../esmf/aspectmodel/AspectModelBuilder.java | 13 +++++++ .../aspectmodel/AspectModelBuilderTest.java | 35 +++++++++++++++++++ 2 files changed, 48 insertions(+) create mode 100644 core/esmf-aspect-meta-model-java/src/test/java/org/eclipse/esmf/aspectmodel/AspectModelBuilderTest.java diff --git a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/AspectModelBuilder.java b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/AspectModelBuilder.java index 79891d402..68e815c03 100644 --- a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/AspectModelBuilder.java +++ b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/AspectModelBuilder.java @@ -13,12 +13,14 @@ package org.eclipse.esmf.aspectmodel; +import java.net.URI; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Optional; +import java.util.Set; import java.util.stream.Collectors; import org.eclipse.esmf.aspectmodel.loader.MetaModelBaseAttributes; @@ -116,4 +118,15 @@ private static void setNamespaces( final Collection files, fina ( (DefaultAspectModelFile) file ).setNamespace( namespace ); } } + + public static AspectModel merge( final AspectModel aspectModel1, final AspectModel aspectModel2 ) { + final List files = new ArrayList<>( aspectModel1.files() ); + final Set locations = aspectModel1.files().stream() + .flatMap( f -> f.sourceLocation().stream() ) + .collect( Collectors.toSet() ); + for ( final AspectModelFile file : aspectModel2.files() ) { + file.sourceLocation().filter( uri -> !locations.contains( uri ) ).ifPresent( uri -> files.add( file ) ); + } + return buildAspectModelFromFiles( files ); + } } diff --git a/core/esmf-aspect-meta-model-java/src/test/java/org/eclipse/esmf/aspectmodel/AspectModelBuilderTest.java b/core/esmf-aspect-meta-model-java/src/test/java/org/eclipse/esmf/aspectmodel/AspectModelBuilderTest.java new file mode 100644 index 000000000..5f5a00593 --- /dev/null +++ b/core/esmf-aspect-meta-model-java/src/test/java/org/eclipse/esmf/aspectmodel/AspectModelBuilderTest.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2024 Robert Bosch Manufacturing Solutions GmbH + * + * See the AUTHORS file(s) distributed with this work for additional + * information regarding authorship. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * SPDX-License-Identifier: MPL-2.0 + */ + +package org.eclipse.esmf.aspectmodel; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.eclipse.esmf.metamodel.AspectModel; +import org.eclipse.esmf.test.TestAspect; +import org.eclipse.esmf.test.TestResources; + +import org.junit.jupiter.api.Test; + +public class AspectModelBuilderTest { + @Test + void testMergeAspectModels() { + final AspectModel a1 = TestResources.load( TestAspect.ASPECT ); + final AspectModel a2 = TestResources.load( TestAspect.ASPECT_WITH_PROPERTY ); + assertThat( a1.aspects() ).hasSize( 1 ); + assertThat( a2.aspects() ).hasSize( 1 ); + final AspectModel merged = AspectModelBuilder.merge( a1, a2 ); + assertThat( merged.aspects() ).hasSize( 2 ); + assertThat( merged.elements().size() ).isEqualTo( a1.elements().size() + a2.elements().size() ); + } +} From c1e05b8bafb1d3145ba7cc927838220efb5c9916 Mon Sep 17 00:00:00 2001 From: "Textor Andreas (BCI/ESW17)" Date: Fri, 9 Aug 2024 15:29:44 +0200 Subject: [PATCH 22/33] Track adding/removing/changing files in AspectChangeContext --- .../aspectmodel/edit/AspectChangeContext.java | 94 ++++++++++++++++++- .../esmf/aspectmodel/edit/ChangeContext.java | 22 ++++- .../edit/change/AddAspectModelFile.java | 6 +- .../edit/change/EditAspectModel.java | 42 ++++----- .../change/MoveElementToExistingFile.java | 2 +- .../change/MoveRenameAspectModelFile.java | 2 +- .../edit/change/RemoveAspectModelFile.java | 2 +- .../edit/change/StructuralChange.java | 2 +- .../edit/AspectChangeContextTest.java | 68 +++++++++++--- 9 files changed, 190 insertions(+), 50 deletions(-) diff --git a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/AspectChangeContext.java b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/AspectChangeContext.java index a40739f6e..cbc39ca3e 100644 --- a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/AspectChangeContext.java +++ b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/AspectChangeContext.java @@ -13,21 +13,42 @@ package org.eclipse.esmf.aspectmodel.edit; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; import java.util.ArrayDeque; import java.util.Deque; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Stream; import org.eclipse.esmf.aspectmodel.AspectModelBuilder; +import org.eclipse.esmf.aspectmodel.AspectModelFile; import org.eclipse.esmf.metamodel.AspectModel; import org.eclipse.esmf.metamodel.impl.DefaultAspectModel; -public class AspectChangeContext { +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class AspectChangeContext implements ChangeContext { + private static final Logger LOG = LoggerFactory.getLogger( AspectChangeContext.class ); + private final Deque undoStack = new ArrayDeque<>(); private final Deque redoStack = new ArrayDeque<>(); private final DefaultAspectModel aspectModel; private final AspectChangeContextConfig config; + private final Map fileState = new HashMap<>(); + private boolean isUndoOperation = false; + + private enum FileState { + CREATED, CHANGED, REMOVED + } public AspectChangeContext( final AspectChangeContextConfig config, final AspectModel aspectModel ) { this.config = config; + resetFileStates(); if ( aspectModel instanceof final DefaultAspectModel defaultAspectModel ) { this.aspectModel = defaultAspectModel; } else { @@ -40,7 +61,9 @@ public AspectChangeContext( final AspectModel aspectModel ) { } public synchronized ChangeReport applyChange( final Change change ) { - final ChangeReport result = change.fire( new ChangeContext( aspectModel.files(), config ) ); + resetFileStates(); + isUndoOperation = false; + final ChangeReport result = change.fire( this ); updateAspectModelAfterChange(); undoStack.offerLast( change.reverse() ); return result; @@ -50,8 +73,10 @@ public synchronized void undoChange() { if ( undoStack.isEmpty() ) { return; } + isUndoOperation = true; + resetFileStates(); final Change change = undoStack.pollLast(); - change.fire( new ChangeContext( aspectModel.files(), config ) ); + change.fire( this ); updateAspectModelAfterChange(); redoStack.offerLast( change.reverse() ); } @@ -60,8 +85,10 @@ public synchronized void redoChange() { if ( redoStack.isEmpty() ) { return; } + resetFileStates(); final Change change = redoStack.pollLast(); - change.fire( new ChangeContext( aspectModel.files(), config ) ); + isUndoOperation = false; + change.fire( this ); updateAspectModelAfterChange(); undoStack.offerLast( change.reverse() ); } @@ -72,4 +99,63 @@ private void updateAspectModelAfterChange() { aspectModel.setElements( updatedModel.elements() ); aspectModel.setFiles( updatedModel.files() ); } + + @Override + public Stream aspectModelFiles() { + return aspectModel.files().stream(); + } + + @Override + public AspectChangeContextConfig config() { + return config; + } + + @Override + public List createdFiles() { + return fileState.entrySet().stream() + .filter( entry -> entry.getValue() == FileState.CREATED ) + .map( Map.Entry::getKey ) + .toList(); + } + + @Override + public List modifiedFiles() { + return fileState.entrySet().stream() + .filter( entry -> entry.getValue() == FileState.CHANGED ) + .map( Map.Entry::getKey ) + .toList(); + } + + @Override + public List removedFiles() { + return fileState.entrySet().stream() + .filter( entry -> entry.getValue() == FileState.REMOVED ) + .map( Map.Entry::getKey ) + .toList(); + } + + @Override + public void resetFileStates() { + fileState.clear(); + } + + @Override + public void indicateFileIsAdded( final AspectModelFile file ) { + fileState.put( file, FileState.CREATED ); + aspectModel.files().add( file ); + } + + @Override + public void indicateFileIsRemoved( final AspectModelFile file ) { + fileState.put( file, FileState.REMOVED ); + aspectModel.files().remove( file ); + } + + @Override + public void indicateFileHasChanged( final AspectModelFile file ) { + // If the file was newly created, keep this state even if we now change the file content + if ( fileState.get( file ) != FileState.CREATED ) { + fileState.put( file, FileState.CHANGED ); + } + } } diff --git a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/ChangeContext.java b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/ChangeContext.java index edbec19f5..b6476a4b1 100644 --- a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/ChangeContext.java +++ b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/ChangeContext.java @@ -14,10 +14,26 @@ package org.eclipse.esmf.aspectmodel.edit; import java.util.List; +import java.util.stream.Stream; import org.eclipse.esmf.aspectmodel.AspectModelFile; -public record ChangeContext( - List aspectModelFiles, - AspectChangeContextConfig config ) { +public interface ChangeContext { + Stream aspectModelFiles(); + + AspectChangeContextConfig config(); + + List createdFiles(); + + List modifiedFiles(); + + List removedFiles(); + + void indicateFileIsAdded( AspectModelFile file ); + + void indicateFileIsRemoved( AspectModelFile file ); + + void indicateFileHasChanged( AspectModelFile file ); + + void resetFileStates(); } diff --git a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/AddAspectModelFile.java b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/AddAspectModelFile.java index 19b74bd59..9391c4cf6 100644 --- a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/AddAspectModelFile.java +++ b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/AddAspectModelFile.java @@ -33,7 +33,7 @@ public AddAspectModelFile( final AspectModelFile newFile ) { @Override public ChangeReport fire( final ChangeContext changeContext ) { - changeContext.aspectModelFiles().add( newFile ); + changeContext.indicateFileIsAdded( newFile ); final Model contentToAdd = ModelFactory.createDefaultModel(); contentToAdd.add( newFile.sourceModel() ); return new ChangeReport.EntryWithDetails( "Add file " + show( newFile ), @@ -46,7 +46,7 @@ public Change reverse() { @Override public ChangeReport fire( final ChangeContext changeContext ) { final AspectModelFile file = fileToRemove( changeContext ); - changeContext.aspectModelFiles().remove( file ); + changeContext.indicateFileIsRemoved( file ); return new ChangeReport.EntryWithDetails( "Remove file " + show( file ), Map.of( "model content to remove", file.sourceModel() ) ); } @@ -57,7 +57,7 @@ public Change reverse() { } private AspectModelFile fileToRemove( final ChangeContext changeContext ) { - return changeContext.aspectModelFiles().stream() + return changeContext.aspectModelFiles() .filter( file -> file.sourceLocation().equals( newFile.sourceLocation() ) ) .findFirst() .orElseThrow( () -> new ModelChangeException( "Unable to remove Aspect Model File" ) ); diff --git a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/EditAspectModel.java b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/EditAspectModel.java index 14a062c90..d48b2e269 100644 --- a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/EditAspectModel.java +++ b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/EditAspectModel.java @@ -28,41 +28,37 @@ protected record ModelChanges( Model add, Model remove, String description ) { public static final ModelChanges NONE = new ModelChanges( null, null, "" ); } - protected Map changesPerFile = null; - - synchronized protected void prepare( final ChangeContext changeContext ) { - if ( changesPerFile == null ) { - changesPerFile = changeContext.aspectModelFiles().stream() - .map( file -> new AbstractMap.SimpleEntry<>( file, calculateChangesForFile( file ) ) ) - .filter( entry -> entry.getValue() != ModelChanges.NONE ) - .collect( Collectors.toMap( AbstractMap.SimpleEntry::getKey, AbstractMap.SimpleEntry::getValue ) ); - } - } - abstract protected ModelChanges calculateChangesForFile( AspectModelFile aspectModelFile ); @Override public ChangeReport fire( final ChangeContext changeContext ) { - prepare( changeContext ); + final Map changesPerFile = changeContext.aspectModelFiles() + .map( file -> new AbstractMap.SimpleEntry<>( file, calculateChangesForFile( file ) ) ) + .filter( entry -> entry.getValue() != ModelChanges.NONE ) + .collect( Collectors.toMap( AbstractMap.SimpleEntry::getKey, AbstractMap.SimpleEntry::getValue ) ); + changesPerFile.forEach( ( file, modelChanges ) -> { - if ( changeContext.aspectModelFiles().contains( file ) ) { + if ( changeContext.aspectModelFiles().anyMatch( file::equals ) ) { file.sourceModel().add( modelChanges.add() ); file.sourceModel().remove( modelChanges.remove() ); + + if ( !modelChanges.add().isEmpty() || !modelChanges.remove().isEmpty() ) { + changeContext.indicateFileHasChanged( file ); + } } } ); return new ChangeReport.MultipleEntries( changesPerFile.entrySet().stream(). map( entry -> { - final AspectModelFile file = entry.getKey(); - final ModelChanges modelChanges = entry.getValue(); - return new ChangeReport.EntryWithDetails( modelChanges.description(), Map.of( - "Add content in " + show( file ), modelChanges.add(), - "Remove content from " + show( file ), modelChanges.remove() ) - .entrySet().stream() - .filter( descriptionEntry -> !descriptionEntry.getValue().isEmpty() ) - .collect( Collectors.toMap( Map.Entry::getKey, Map.Entry::getValue ) ) ); - } - ).toList() + final AspectModelFile file = entry.getKey(); + final ModelChanges modelChanges = entry.getValue(); + return new ChangeReport.EntryWithDetails( modelChanges.description(), Map.of( + "Add content in " + show( file ), modelChanges.add(), + "Remove content from " + show( file ), modelChanges.remove() ) + .entrySet().stream() + .filter( descriptionEntry -> !descriptionEntry.getValue().isEmpty() ) + .collect( Collectors.toMap( Map.Entry::getKey, Map.Entry::getValue ) ) ); + } ).toList() ); } } diff --git a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/MoveElementToExistingFile.java b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/MoveElementToExistingFile.java index 23608c5cd..83333a07e 100644 --- a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/MoveElementToExistingFile.java +++ b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/MoveElementToExistingFile.java @@ -48,7 +48,7 @@ public MoveElementToExistingFile( final AspectModelUrn elementUrn, final AspectM @Override public ChangeReport fire( final ChangeContext changeContext ) { - final AspectModelFile targetFile = changeContext.aspectModelFiles().stream() + final AspectModelFile targetFile = changeContext.aspectModelFiles() .filter( file -> file.sourceLocation().equals( targetFileLocation ) ) .findFirst() .orElseThrow( () -> new ModelChangeException( "Can not determine target file to move element" ) ); diff --git a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/MoveRenameAspectModelFile.java b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/MoveRenameAspectModelFile.java index ac4b83d62..ef1ed861f 100644 --- a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/MoveRenameAspectModelFile.java +++ b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/MoveRenameAspectModelFile.java @@ -40,7 +40,7 @@ public MoveRenameAspectModelFile( final AspectModelFile file, final Optional file.sourceLocation().equals( file.sourceLocation() ) ) .findFirst() .orElseThrow( () -> new ModelChangeException( "Can not find file to move/rename" ) ); diff --git a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/RemoveAspectModelFile.java b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/RemoveAspectModelFile.java index cb8d551c8..b80b29c10 100644 --- a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/RemoveAspectModelFile.java +++ b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/RemoveAspectModelFile.java @@ -29,7 +29,7 @@ public RemoveAspectModelFile( final AspectModelFile fileToRemove ) { @Override public ChangeReport fire( final ChangeContext changeContext ) { - changeContext.aspectModelFiles().remove( fileToRemove ); + changeContext.indicateFileIsRemoved( fileToRemove ); return new ChangeReport.EntryWithDetails( "Remove file " + show( fileToRemove ), Map.of( "model content", fileToRemove.sourceModel() ) ); } diff --git a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/StructuralChange.java b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/StructuralChange.java index b8fab5bb8..a6af4bd28 100644 --- a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/StructuralChange.java +++ b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/StructuralChange.java @@ -25,7 +25,7 @@ public abstract class StructuralChange extends AbstractChange { protected AspectModelFile sourceFile( final ChangeContext changeContext, final AspectModelUrn elementUrn ) { - return changeContext.aspectModelFiles().stream().filter( aspectModelFile -> { + return changeContext.aspectModelFiles().filter( aspectModelFile -> { final Resource elementResource = aspectModelFile.sourceModel().createResource( elementUrn.toString() ); return Streams.stream( aspectModelFile.sourceModel().listStatements( elementResource, RDF.type, (RDFNode) null ) ) .count() == 1; diff --git a/core/esmf-aspect-meta-model-java/src/test/java/org/eclipse/esmf/aspectmodel/edit/AspectChangeContextTest.java b/core/esmf-aspect-meta-model-java/src/test/java/org/eclipse/esmf/aspectmodel/edit/AspectChangeContextTest.java index 609719f88..5c2e77304 100644 --- a/core/esmf-aspect-meta-model-java/src/test/java/org/eclipse/esmf/aspectmodel/edit/AspectChangeContextTest.java +++ b/core/esmf-aspect-meta-model-java/src/test/java/org/eclipse/esmf/aspectmodel/edit/AspectChangeContextTest.java @@ -61,10 +61,13 @@ void testRenameElement() { assertThat( aspectModel.aspect().getName() ).isEqualTo( newName ); assertThat( aspectModel.files().get( 0 ).sourceModel().listStatements( null, RDF.type, SammNs.SAMM.Aspect() ) .nextStatement().getSubject().getURI() ).endsWith( newName ); + assertThat( ctx.modifiedFiles() ).hasSize( 1 ); ctx.undoChange(); + assertThat( ctx.modifiedFiles() ).hasSize( 1 ); assertThat( aspectModel.aspect().getName() ).isEqualTo( originalName ); ctx.redoChange(); + assertThat( ctx.modifiedFiles() ).hasSize( 1 ); assertThat( aspectModel.aspect().getName() ).isEqualTo( newName ); } @@ -94,9 +97,7 @@ void testChangeGroups() { final Aspect aspect = aspectModel.aspect(); final AspectModelUrn aspectUrn = aspect.urn(); - final String oldAspectName = aspect.getName(); final Property property = aspect.getProperties().get( 0 ); - final String oldPropertyName = property.getName(); final String newAspectName = "RenamedAspect"; final String newPropertyName = "renamedProperty"; @@ -121,10 +122,14 @@ void testCreateFile() { .build(); final Change addFile = new AddAspectModelFile( aspectModelFile ); ctx.applyChange( addFile ); + assertThat( ctx.createdFiles() ).hasSize( 1 ); assertThat( aspectModel.files() ).hasSize( 1 ); ctx.undoChange(); + assertThat( ctx.createdFiles() ).isEmpty(); + assertThat( ctx.removedFiles() ).hasSize( 1 ); assertThat( aspectModel.files() ).isEmpty(); ctx.redoChange(); + assertThat( ctx.createdFiles() ).hasSize( 1 ); assertThat( aspectModel.files() ).hasSize( 1 ); } @@ -138,11 +143,13 @@ void testRemoveFile() { assertThat( aspectModel.elements() ).isEmpty(); final AspectChangeContext ctx = new AspectChangeContext( aspectModel ); ctx.applyChange( new RemoveAspectModelFile( aspectModel.files().get( 0 ) ) ); + assertThat( ctx.removedFiles() ).hasSize( 1 ); assertThat( aspectModel.files() ).isEmpty(); - ctx.undoChange(); + assertThat( ctx.removedFiles() ).isEmpty(); assertThat( aspectModel.files() ).hasSize( 1 ); ctx.redoChange(); + assertThat( ctx.removedFiles() ).hasSize( 1 ); assertThat( aspectModel.files() ).isEmpty(); } @@ -168,12 +175,15 @@ void testCreateFileWithElementDefinition() { .build(); final Change addFile = new AddAspectModelFile( aspectModelFile ); ctx.applyChange( addFile ); + assertThat( ctx.createdFiles() ).hasSize( 1 ); assertThat( aspectModel.files() ).hasSize( 1 ); assertThat( aspectModel.aspects() ).hasSize( 1 ); assertThat( aspectModel.aspect().getName() ).isEqualTo( "Aspect" ); ctx.undoChange(); + assertThat( ctx.createdFiles() ).isEmpty(); assertThat( aspectModel.files() ).isEmpty(); ctx.redoChange(); + assertThat( ctx.createdFiles() ).hasSize( 1 ); assertThat( aspectModel.files() ).hasSize( 1 ); } @@ -203,12 +213,16 @@ void testCreateFileThenAddElementDefinition() { ); ctx.applyChange( changes ); + assertThat( ctx.createdFiles() ).hasSize( 1 ); assertThat( aspectModel.files() ).hasSize( 1 ); assertThat( aspectModel.aspects() ).hasSize( 1 ); assertThat( aspectModel.aspect().getName() ).isEqualTo( "Aspect" ); ctx.undoChange(); + assertThat( ctx.createdFiles() ).isEmpty(); + assertThat( ctx.removedFiles() ).hasSize( 1 ); assertThat( aspectModel.files() ).isEmpty(); ctx.redoChange(); + assertThat( ctx.createdFiles() ).hasSize( 1 ); assertThat( aspectModel.files() ).hasSize( 1 ); } @@ -221,20 +235,26 @@ void testMoveElementToNewFile() { final URI sourceLocation = URI.create( "file:///temp/test.ttl" ); final Change move = new MoveElementToNewFile( aspectModel.aspect(), Optional.of( sourceLocation ) ); - final ChangeReport changeReport = ctx.applyChange( move ); - System.out.println( ChangeReportFormatter.INSTANCE.apply( changeReport, AspectChangeContextConfig.DEFAULT ) ); + ctx.applyChange( move ); + assertThat( ctx.createdFiles() ).hasSize( 1 ); + assertThat( ctx.modifiedFiles() ).hasSize( 1 ); assertThat( aspectModel.files() ).hasSize( 2 ); assertThat( aspectModel.aspect().getSourceFile().sourceLocation() ).contains( sourceLocation ); assertThat( aspectModel.aspect().getSourceFile().sourceModel().listStatements( null, RDF.type, SammNs.SAMM.Aspect() ).nextStatement() .getSubject().getURI() ).isEqualTo( aspectModel.aspect().urn().toString() ); ctx.undoChange(); + assertThat( ctx.createdFiles() ).isEmpty(); + assertThat( ctx.removedFiles() ).hasSize( 1 ); + assertThat( ctx.modifiedFiles() ).hasSize( 1 ); assertThat( aspectModel.files() ).hasSize( 1 ); assertThat( aspectModel.aspect().getSourceFile().sourceLocation() ).contains( originalSourceLocation ); assertThat( aspectModel.aspect().getSourceFile().sourceModel().listStatements( null, RDF.type, SammNs.SAMM.Aspect() ).nextStatement() .getSubject().getURI() ).isEqualTo( aspectModel.aspect().urn().toString() ); ctx.redoChange(); + assertThat( ctx.createdFiles() ).hasSize( 1 ); + assertThat( ctx.modifiedFiles() ).hasSize( 1 ); assertThat( aspectModel.files() ).hasSize( 2 ); assertThat( aspectModel.aspect().getSourceFile().sourceLocation() ).contains( sourceLocation ); assertThat( aspectModel.aspect().getSourceFile().sourceModel().listStatements( null, RDF.type, SammNs.SAMM.Aspect() ).nextStatement() @@ -270,12 +290,16 @@ void testMoveElementToExistingFile() { assertThat( aspectModel.aspect().getSourceFile().sourceLocation() ).isEqualTo( file1Location ); final Change move = new MoveElementToExistingFile( aspectModel.aspect(), file2 ); - - final ChangeReport changeReport = ctx.applyChange( move ); - System.out.println( ChangeReportFormatter.INSTANCE.apply( changeReport, AspectChangeContextConfig.DEFAULT ) ); + ctx.applyChange( move ); + assertThat( ctx.modifiedFiles() ).hasSize( 2 ); + assertThat( ctx.createdFiles() ).isEmpty(); + assertThat( ctx.removedFiles() ).isEmpty(); assertThat( aspectModel.aspect().getSourceFile().sourceLocation() ).isEqualTo( file2Location ); ctx.undoChange(); + assertThat( ctx.modifiedFiles() ).hasSize( 2 ); + assertThat( ctx.createdFiles() ).isEmpty(); + assertThat( ctx.removedFiles() ).isEmpty(); assertThat( aspectModel.aspect().getSourceFile().sourceLocation() ).isEqualTo( file1Location ); } @@ -291,9 +315,9 @@ void testMoveElementToOtherNamespaceNewFile() { final Namespace targetNamespace = new DefaultNamespace( targetUrn, List.of(), Optional.empty() ); final Change move = new MoveElementToOtherNamespaceNewFile( aspectModel.aspect(), targetNamespace, Optional.of( sourceLocation ) ); - final ChangeReport changeReport = ctx.applyChange( move ); - System.out.println( ChangeReportFormatter.INSTANCE.apply( changeReport, AspectChangeContextConfig.DEFAULT ) ); - + ctx.applyChange( move ); + assertThat( ctx.modifiedFiles() ).hasSize( 1 ); + assertThat( ctx.createdFiles() ).hasSize( 1 ); assertThat( aspectModel.files() ).hasSize( 2 ); assertThat( aspectModel.aspect().getSourceFile().sourceLocation() ).contains( sourceLocation ); assertThat( aspectModel.aspect().getSourceFile().sourceModel().listStatements( null, RDF.type, SammNs.SAMM.Aspect() ).nextStatement() @@ -301,12 +325,17 @@ void testMoveElementToOtherNamespaceNewFile() { assertThat( aspectModel.aspect().urn() ).isEqualTo( targetUrn ); ctx.undoChange(); + assertThat( ctx.modifiedFiles() ).hasSize( 1 ); + assertThat( ctx.removedFiles() ).hasSize( 1 ); + assertThat( ctx.createdFiles() ).isEmpty(); assertThat( aspectModel.files() ).hasSize( 1 ); assertThat( aspectModel.aspect().getSourceFile().sourceLocation() ).contains( originalSourceLocation ); assertThat( aspectModel.aspect().getSourceFile().sourceModel().listStatements( null, RDF.type, SammNs.SAMM.Aspect() ).nextStatement() .getSubject().getURI() ).isEqualTo( aspectModel.aspect().urn().toString() ); ctx.redoChange(); + assertThat( ctx.modifiedFiles() ).hasSize( 1 ); + assertThat( ctx.createdFiles() ).hasSize( 1 ); assertThat( aspectModel.files() ).hasSize( 2 ); assertThat( aspectModel.aspect().getSourceFile().sourceLocation() ).contains( sourceLocation ); assertThat( aspectModel.aspect().getSourceFile().sourceModel().listStatements( null, RDF.type, SammNs.SAMM.Aspect() ).nextStatement() @@ -339,14 +368,16 @@ void testMoveElementToOtherNamespaceExistingFile() { new AddAspectModelFile( file1 ), new AddAspectModelFile( file2 ) ) ); + ctx.resetFileStates(); assertThat( aspectModel.aspect().getSourceFile().sourceLocation() ).isEqualTo( file1Location ); final AspectModelUrn targetUrn = AspectModelUrn.fromUrn( "urn:samm:org.eclipse.esmf.example.new:1.0.0#Aspect" ); final Namespace targetNamespace = new DefaultNamespace( targetUrn, List.of(), Optional.empty() ); final Change move = new MoveElementToOtherNamespaceExistingFile( aspectModel.aspect(), file2, targetNamespace ); - final ChangeReport changeReport = ctx.applyChange( move ); - System.out.println( ChangeReportFormatter.INSTANCE.apply( changeReport, AspectChangeContextConfig.DEFAULT ) ); + ctx.applyChange( move ); + assertThat( ctx.modifiedFiles() ).hasSize( 2 ); + assertThat( ctx.createdFiles() ).isEmpty(); assertThat( aspectModel.aspect().getSourceFile().sourceLocation() ).isEqualTo( file2Location ); assertThat( aspectModel.aspect().getSourceFile().sourceModel().listStatements( null, RDF.type, SammNs.SAMM.Aspect() ).nextStatement() @@ -354,6 +385,8 @@ void testMoveElementToOtherNamespaceExistingFile() { assertThat( aspectModel.aspect().urn() ).isEqualTo( targetUrn ); ctx.undoChange(); + assertThat( ctx.modifiedFiles() ).hasSize( 2 ); + assertThat( ctx.createdFiles() ).isEmpty(); assertThat( aspectModel.aspect().getSourceFile().sourceLocation() ).isEqualTo( file1Location ); assertThat( aspectModel.aspect().getSourceFile().sourceModel().listStatements( null, RDF.type, SammNs.SAMM.Aspect() ).nextStatement() .getSubject().getURI() ).isEqualTo( aspectModel.aspect().urn().toString() ); @@ -372,14 +405,23 @@ void testMoveRenameFile() { final Change renameFile = new MoveRenameAspectModelFile( aspect.getSourceFile(), newLocation ); ctx.applyChange( renameFile ); + assertThat( ctx.modifiedFiles() ).isEmpty(); + assertThat( ctx.createdFiles() ).hasSize( 1 ); + assertThat( ctx.removedFiles() ).hasSize( 1 ); assertThat( aspectModel.files() ).hasSize( 1 ); assertThat( aspectModel.aspect().getSourceFile().sourceLocation() ).contains( newLocation ); ctx.undoChange(); + assertThat( ctx.modifiedFiles() ).isEmpty(); + assertThat( ctx.createdFiles() ).hasSize( 1 ); + assertThat( ctx.removedFiles() ).hasSize( 1 ); assertThat( aspectModel.files() ).hasSize( 1 ); assertThat( aspectModel.aspect().getSourceFile().sourceLocation() ).contains( originalLocation ); ctx.redoChange(); + assertThat( ctx.modifiedFiles() ).isEmpty(); + assertThat( ctx.createdFiles() ).hasSize( 1 ); + assertThat( ctx.removedFiles() ).hasSize( 1 ); assertThat( aspectModel.files() ).hasSize( 1 ); assertThat( aspectModel.aspect().getSourceFile().sourceLocation() ).contains( newLocation ); } From 4284690c96f1db8ed92ac6330a138497120a1cc0 Mon Sep 17 00:00:00 2001 From: "Textor Andreas (BCI/ESW17)" Date: Fri, 9 Aug 2024 16:08:56 +0200 Subject: [PATCH 23/33] Switch for different variants in AspectEditMoveCommand --- .../serializer/AspectSerializer.java | 23 ++++-- .../org/eclipse/esmf/AbstractCommand.java | 2 +- .../aspect/edit/AspectEditMoveCommand.java | 71 ++++++++++++++----- 3 files changed, 74 insertions(+), 22 deletions(-) diff --git a/core/esmf-aspect-model-serializer/src/main/java/org/eclipse/esmf/aspectmodel/serializer/AspectSerializer.java b/core/esmf-aspect-model-serializer/src/main/java/org/eclipse/esmf/aspectmodel/serializer/AspectSerializer.java index a876be00f..bf6fb4d2b 100644 --- a/core/esmf-aspect-model-serializer/src/main/java/org/eclipse/esmf/aspectmodel/serializer/AspectSerializer.java +++ b/core/esmf-aspect-model-serializer/src/main/java/org/eclipse/esmf/aspectmodel/serializer/AspectSerializer.java @@ -72,7 +72,8 @@ public String apply( final Aspect aspect ) { } /** - * Serializes all files of an Aspect Model to their respective source locations + * Serializes all files of an Aspect Model to their respective source locations. + * Attention: This method does not check validity of paths or existance of files and will overwrite without further checks. * * @param aspectModel the Aspect Model */ @@ -81,11 +82,13 @@ public void write( final AspectModel aspectModel ) { } /** - * Writes the content of an Aspect Model file to its defined source location + * Returns a URL for the Aspect Model file if it can be determined. * - * @param aspectModelFile the Aspect Model file + * @param aspectModelFile the input Aspect Model file + * @return the Aspect Model file's location as URL + * @throws SerializationException if the file has no source location or the source location URI is no URL */ - public void write( final AspectModelFile aspectModelFile ) { + public URL aspectModelFileUrl( final AspectModelFile aspectModelFile ) { if ( aspectModelFile.sourceLocation().isEmpty() ) { throw new SerializationException( "Aspect Model file has no source location" ); } @@ -98,13 +101,23 @@ public void write( final AspectModelFile aspectModelFile ) { throw new SerializationException( "Aspect Model file can only be written to locations given by URLs" ); } + return url; + } + + /** + * Writes the content of an Aspect Model file to its defined source location + * + * @param aspectModelFile the Aspect Model file + */ + public void write( final AspectModelFile aspectModelFile ) { + final URL url = aspectModelFileUrl( aspectModelFile ); final Function protocolHandler = protocolHandlers.get( url.getProtocol() ); if ( protocolHandler == null ) { throw new SerializationException( "Don't know how to write " + url.getProtocol() + " URLs: " + url ); } final String content = aspectModelFileToString( aspectModelFile ); - try ( final OutputStream out = protocolHandler.apply( uri ) ) { + try ( final OutputStream out = protocolHandler.apply( aspectModelFile.sourceLocation().get() ) ) { out.write( content.getBytes( StandardCharsets.UTF_8 ) ); } catch ( final IOException exception ) { throw new SerializationException( exception ); diff --git a/tools/samm-cli/src/main/java/org/eclipse/esmf/AbstractCommand.java b/tools/samm-cli/src/main/java/org/eclipse/esmf/AbstractCommand.java index f9df3a945..2d988c54d 100644 --- a/tools/samm-cli/src/main/java/org/eclipse/esmf/AbstractCommand.java +++ b/tools/samm-cli/src/main/java/org/eclipse/esmf/AbstractCommand.java @@ -46,7 +46,7 @@ import org.apache.commons.io.FilenameUtils; public abstract class AbstractCommand implements Runnable { - private Path modelsRootForFile( final File file ) { + protected Path modelsRootForFile( final File file ) { return file.toPath().getParent().getParent().getParent(); } diff --git a/tools/samm-cli/src/main/java/org/eclipse/esmf/aspect/edit/AspectEditMoveCommand.java b/tools/samm-cli/src/main/java/org/eclipse/esmf/aspect/edit/AspectEditMoveCommand.java index aca4053f3..7424d59a2 100644 --- a/tools/samm-cli/src/main/java/org/eclipse/esmf/aspect/edit/AspectEditMoveCommand.java +++ b/tools/samm-cli/src/main/java/org/eclipse/esmf/aspect/edit/AspectEditMoveCommand.java @@ -19,6 +19,7 @@ import java.nio.file.Paths; import java.util.ArrayList; import java.util.List; +import java.util.Optional; import java.util.stream.Collectors; import org.eclipse.esmf.AbstractCommand; @@ -34,7 +35,9 @@ import org.eclipse.esmf.aspectmodel.edit.ChangeReport; import org.eclipse.esmf.aspectmodel.edit.ChangeReportFormatter; import org.eclipse.esmf.aspectmodel.edit.change.MoveElementToExistingFile; +import org.eclipse.esmf.aspectmodel.edit.change.MoveElementToNewFile; import org.eclipse.esmf.aspectmodel.serializer.AspectSerializer; +import org.eclipse.esmf.aspectmodel.urn.AspectModelUrn; import org.eclipse.esmf.exception.CommandException; import org.eclipse.esmf.metamodel.AspectModel; import org.eclipse.esmf.metamodel.ModelElement; @@ -111,16 +114,26 @@ public class AspectEditMoveCommand extends AbstractCommand { @Override public void run() { - final File file = new File( targetFile ); - if ( file.exists() ) { - if ( targetNamespace == null ) { + final String input = parentCommand.parentCommand.getInput(); + + if ( targetNamespace == null ) { + final File targetFileRelativeToInput = getInputFile( input ).toPath().getParent().resolve( targetFile ).toFile(); + if ( targetFileRelativeToInput.exists() ) { moveElementToExistingFile(); } else { - moveElementToOtherNamespaceExistingFile(); + moveElementToNewFile(); } } else { - if ( targetNamespace == null ) { - moveElementToNewFile(); + final File inputFile = getInputFile( input ); + final AspectModelUrn targetNamespaceUrn = AspectModelUrn.from( targetNamespace ) + .getOrElseThrow( () -> new CommandException( "Target namespace is invalid: " + targetNamespace ) ); + final File targetFileInNewNamespace = modelsRootForFile( inputFile ) + .resolve( targetNamespaceUrn.getNamespaceMainPart() ) + .resolve( targetNamespaceUrn.getVersion() ) + .resolve( targetFile ) + .toFile(); + if ( targetFileInNewNamespace.exists() ) { + moveElementToOtherNamespaceExistingFile(); } else { moveElementToOtherNamespaceNewFile(); } @@ -131,7 +144,21 @@ public void run() { * Supports the case {@code samm aspect Aspect.ttl edit move :MyAspect newFile.ttl} */ private void moveElementToNewFile() { - // TODO + final String input = parentCommand.parentCommand.getInput(); + final AspectModel aspectModel = loadAspectModelOrFail( input, customResolver ); + + // Do refactoring + final ModelElement modelElement = determineModelElementToMove( aspectModel ); + final URI targetFileUri = getInputFile( input ).toPath().getParent().resolve( targetFile ).toUri(); + final List headerCommentForNewFile = copyHeader + ? modelElement.getSourceFile().headerComment() + : List.of(); + final Change move = new MoveElementToNewFile( modelElement, headerCommentForNewFile, Optional.of( targetFileUri ) ); + final AspectChangeContext changeContext = performRefactoring( aspectModel, move ); + + // Check & write changes to file system + checkFilesystemConsistency( changeContext ); + performFileSystemWrite( changeContext ); } /** @@ -160,13 +187,27 @@ private void moveElementToExistingFile() { final AspectModel aspectModel = AspectModelBuilder.merge( sourceAspectModel, targetAspectModel ); // Find the loaded AspectModelFile that corresponds to the input targetFile + final AspectModelFile targetAspectModelFile = determineTargetAspectModelFile( aspectModel ); + + // Do refactoring + final ModelElement modelElement = determineModelElementToMove( aspectModel ); + final Change move = new MoveElementToExistingFile( modelElement, targetAspectModelFile ); + final AspectChangeContext changeContext = performRefactoring( aspectModel, move ); + + // Check & write changes to file system + checkFilesystemConsistency( changeContext ); + performFileSystemWrite( changeContext ); + } + + private AspectModelFile determineTargetAspectModelFile( final AspectModel aspectModel ) { final URI targetFileUri = getInputFile( targetFile ).toURI(); - final AspectModelFile targetAspectModelFile = aspectModel.files().stream() + return aspectModel.files().stream() .filter( file -> file.sourceLocation().map( uri -> uri.equals( targetFileUri ) ).orElse( false ) ) .findFirst() .orElseThrow( () -> new CommandException( "Could not determine target file" ) ); + } - // Determine the URN for the element to move + private ModelElement determineModelElementToMove( final AspectModel aspectModel ) { final List potentialElements = aspectModel.elements().stream() .filter( element -> !element.isAnonymous() ) .filter( element -> element.urn().toString().endsWith( elementName ) ) @@ -183,24 +224,22 @@ private void moveElementToExistingFile() { + "\nPlease use the element's full URN." ); System.exit( 1 ); } + return potentialElements.get( 0 ); + } - // Perform the refactoring + private AspectChangeContext performRefactoring( final AspectModel aspectModel, final Change change ) { final AspectChangeContextConfig config = AspectChangeContextConfigBuilder.builder() .detailedChangeReport( details ) .build(); final AspectChangeContext changeContext = new AspectChangeContext( config, aspectModel ); - final ModelElement modelElement = potentialElements.get( 0 ); - final Change move = new MoveElementToExistingFile( modelElement, targetAspectModelFile ); - final ChangeReport changeReport = changeContext.applyChange( move ); + final ChangeReport changeReport = changeContext.applyChange( change ); if ( dryRun ) { System.out.println( "Changes to be performed" ); System.out.println( "=======================" ); System.out.println( ChangeReportFormatter.INSTANCE.apply( changeReport, config ) ); System.exit( 0 ); } - - checkFilesystemConsistency( changeContext ); - performFileSystemWrite( changeContext ); + return changeContext; } private void performFileSystemWrite( final AspectChangeContext changeContext ) { From 6ba82f73d84aac4c37edefa852b8d79158ecafda Mon Sep 17 00:00:00 2001 From: "Textor Andreas (BCI/ESW17)" Date: Mon, 12 Aug 2024 16:36:21 +0200 Subject: [PATCH 24/33] Implement MoveElementToOtherNamespaceNewFile --- .../aspectmodel/edit/AspectChangeContext.java | 31 ++- .../edit/change/EditAspectModel.java | 4 +- .../org/eclipse/esmf/AbstractCommand.java | 10 +- .../main/java/org/eclipse/esmf/SammCli.java | 3 +- .../aspect/edit/AspectEditMoveCommand.java | 136 +++++++---- .../java/org/eclipse/esmf/SammCliTest.java | 214 ++++++++++++++++++ 6 files changed, 341 insertions(+), 57 deletions(-) diff --git a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/AspectChangeContext.java b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/AspectChangeContext.java index cbc39ca3e..4af5464fb 100644 --- a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/AspectChangeContext.java +++ b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/AspectChangeContext.java @@ -13,19 +13,17 @@ package org.eclipse.esmf.aspectmodel.edit; -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; import java.util.ArrayDeque; import java.util.Deque; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.stream.Stream; -import org.eclipse.esmf.aspectmodel.AspectModelBuilder; import org.eclipse.esmf.aspectmodel.AspectModelFile; +import org.eclipse.esmf.aspectmodel.loader.AspectModelLoader; +import org.eclipse.esmf.aspectmodel.resolver.modelfile.RawAspectModelFile; import org.eclipse.esmf.metamodel.AspectModel; import org.eclipse.esmf.metamodel.impl.DefaultAspectModel; @@ -94,10 +92,31 @@ public synchronized void redoChange() { } private void updateAspectModelAfterChange() { - final AspectModel updatedModel = AspectModelBuilder.buildAspectModelFromFiles( aspectModel.files() ); + final AspectModel updatedModel = AspectModelLoader.buildAspectModelFromFiles( aspectModel.files() ); aspectModel.setMergedModel( updatedModel.mergedModel() ); aspectModel.setElements( updatedModel.elements() ); aspectModel.setFiles( updatedModel.files() ); + + final Map updatedFileState = new HashMap<>(); + for ( final Map.Entry stateEntry : fileState.entrySet() ) { + final AspectModelFile file = stateEntry.getKey(); + final FileState state = stateEntry.getValue(); + + if ( file instanceof final RawAspectModelFile rawFile ) { + final Optional updatedAspectModelFile = aspectModel.files().stream() + .filter( f -> f.sourceLocation().isPresent() ) + .filter( f -> f.sourceLocation().equals( file.sourceLocation() ) ) + .findFirst(); + if ( updatedAspectModelFile.isEmpty() ) { + continue; + } + updatedFileState.put( updatedAspectModelFile.get(), state ); + } else { + updatedFileState.put( file, state ); + } + } + fileState.clear(); + fileState.putAll( updatedFileState ); } @Override diff --git a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/EditAspectModel.java b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/EditAspectModel.java index d48b2e269..ab89a6ce3 100644 --- a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/EditAspectModel.java +++ b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/EditAspectModel.java @@ -33,9 +33,9 @@ protected record ModelChanges( Model add, Model remove, String description ) { @Override public ChangeReport fire( final ChangeContext changeContext ) { final Map changesPerFile = changeContext.aspectModelFiles() - .map( file -> new AbstractMap.SimpleEntry<>( file, calculateChangesForFile( file ) ) ) + .map( file -> Map.entry( file, calculateChangesForFile( file ) ) ) .filter( entry -> entry.getValue() != ModelChanges.NONE ) - .collect( Collectors.toMap( AbstractMap.SimpleEntry::getKey, AbstractMap.SimpleEntry::getValue ) ); + .collect( Collectors.toMap( Map.Entry::getKey, Map.Entry::getValue ) ); changesPerFile.forEach( ( file, modelChanges ) -> { if ( changeContext.aspectModelFiles().anyMatch( file::equals ) ) { diff --git a/tools/samm-cli/src/main/java/org/eclipse/esmf/AbstractCommand.java b/tools/samm-cli/src/main/java/org/eclipse/esmf/AbstractCommand.java index 2d988c54d..7a451d947 100644 --- a/tools/samm-cli/src/main/java/org/eclipse/esmf/AbstractCommand.java +++ b/tools/samm-cli/src/main/java/org/eclipse/esmf/AbstractCommand.java @@ -61,10 +61,9 @@ protected File getInputFile( final String modelFileName ) { : Path.of( System.getProperty( "user.dir" ) ).resolve( inputFile.toPath() ).toFile().getAbsoluteFile(); } - protected AspectModel loadAspectModelOrFail( final String modelFileName, final ExternalResolverMixin resolverConfig, + protected AspectModel loadAspectModelOrFail( final File modelFile, final ExternalResolverMixin resolverConfig, final boolean details ) { - final File absoluteFile = getInputFile( modelFileName ); - + final File absoluteFile = modelFile.getAbsoluteFile(); final ResolutionStrategy resolveFromWorkspace = new FileSystemStrategy( modelsRootForFile( absoluteFile ) ); final ResolutionStrategy resolveFromCurrentDirectory = AspectModelLoader.DEFAULT_STRATEGY.get(); final ResolutionStrategy resolutionStrategy = resolverConfig.commandLine.isBlank() @@ -87,6 +86,11 @@ protected AspectModel loadAspectModelOrFail( final String modelFileName, final E return validModelOrViolations.get(); } + protected AspectModel loadAspectModelOrFail( final String modelFileName, final ExternalResolverMixin resolverConfig, + final boolean details ) { + return loadAspectModelOrFail( getInputFile( modelFileName ), resolverConfig, details ); + } + protected Aspect loadAspectOrFail( final String modelFileName, final ExternalResolverMixin resolverConfig ) { final File inputFile = new File( modelFileName ); final AspectModel aspectModel = loadAspectModelOrFail( modelFileName, resolverConfig ); diff --git a/tools/samm-cli/src/main/java/org/eclipse/esmf/SammCli.java b/tools/samm-cli/src/main/java/org/eclipse/esmf/SammCli.java index a1611ebac..02a16de03 100644 --- a/tools/samm-cli/src/main/java/org/eclipse/esmf/SammCli.java +++ b/tools/samm-cli/src/main/java/org/eclipse/esmf/SammCli.java @@ -105,8 +105,7 @@ public SammCli() { commandLine = initialCommandLine.setExecutionExceptionHandler( new CommandLine.IExecutionExceptionHandler() { @Override public int handleExecutionException( final Exception exception, final CommandLine commandLine, - final CommandLine.ParseResult parseResult ) - throws Exception { + final CommandLine.ParseResult parseResult ) throws Exception { if ( exception.getClass().getName() .equals( String.format( "%s.MainClassProcessLauncher$SystemExitCaptured", SammCli.class.getPackageName() ) ) ) { // If the exception we encounter is a SystemExitCaptured, this is part of the security manager in the test suite that diff --git a/tools/samm-cli/src/main/java/org/eclipse/esmf/aspect/edit/AspectEditMoveCommand.java b/tools/samm-cli/src/main/java/org/eclipse/esmf/aspect/edit/AspectEditMoveCommand.java index 7424d59a2..a02804cdf 100644 --- a/tools/samm-cli/src/main/java/org/eclipse/esmf/aspect/edit/AspectEditMoveCommand.java +++ b/tools/samm-cli/src/main/java/org/eclipse/esmf/aspect/edit/AspectEditMoveCommand.java @@ -26,7 +26,6 @@ import org.eclipse.esmf.ExternalResolverMixin; import org.eclipse.esmf.LoggingMixin; import org.eclipse.esmf.aspect.AspectEditCommand; -import org.eclipse.esmf.aspectmodel.AspectModelBuilder; import org.eclipse.esmf.aspectmodel.AspectModelFile; import org.eclipse.esmf.aspectmodel.edit.AspectChangeContext; import org.eclipse.esmf.aspectmodel.edit.AspectChangeContextConfig; @@ -36,11 +35,16 @@ import org.eclipse.esmf.aspectmodel.edit.ChangeReportFormatter; import org.eclipse.esmf.aspectmodel.edit.change.MoveElementToExistingFile; import org.eclipse.esmf.aspectmodel.edit.change.MoveElementToNewFile; +import org.eclipse.esmf.aspectmodel.edit.change.MoveElementToOtherNamespaceExistingFile; +import org.eclipse.esmf.aspectmodel.edit.change.MoveElementToOtherNamespaceNewFile; +import org.eclipse.esmf.aspectmodel.loader.AspectModelLoader; import org.eclipse.esmf.aspectmodel.serializer.AspectSerializer; import org.eclipse.esmf.aspectmodel.urn.AspectModelUrn; import org.eclipse.esmf.exception.CommandException; import org.eclipse.esmf.metamodel.AspectModel; import org.eclipse.esmf.metamodel.ModelElement; +import org.eclipse.esmf.metamodel.Namespace; +import org.eclipse.esmf.metamodel.impl.DefaultNamespace; import picocli.CommandLine; @@ -116,32 +120,35 @@ public class AspectEditMoveCommand extends AbstractCommand { public void run() { final String input = parentCommand.parentCommand.getInput(); + // Move to other/new file in same namespace if ( targetNamespace == null ) { final File targetFileRelativeToInput = getInputFile( input ).toPath().getParent().resolve( targetFile ).toFile(); if ( targetFileRelativeToInput.exists() ) { - moveElementToExistingFile(); + moveElementToExistingFile( targetFileRelativeToInput ); } else { moveElementToNewFile(); } + return; + } + + // Move to other/new file in other namespace + final File inputFile = getInputFile( input ); + final AspectModelUrn targetNamespaceUrn = AspectModelUrn.from( targetNamespace ) + .getOrElseThrow( () -> new CommandException( "Target namespace is invalid: " + targetNamespace ) ); + final File targetFileInOtherNamespace = modelsRootForFile( inputFile ) + .resolve( targetNamespaceUrn.getNamespaceMainPart() ) + .resolve( targetNamespaceUrn.getVersion() ) + .resolve( targetFile ) + .toFile(); + if ( targetFileInOtherNamespace.exists() ) { + moveElementToOtherNamespaceExistingFile( targetNamespaceUrn, targetFileInOtherNamespace ); } else { - final File inputFile = getInputFile( input ); - final AspectModelUrn targetNamespaceUrn = AspectModelUrn.from( targetNamespace ) - .getOrElseThrow( () -> new CommandException( "Target namespace is invalid: " + targetNamespace ) ); - final File targetFileInNewNamespace = modelsRootForFile( inputFile ) - .resolve( targetNamespaceUrn.getNamespaceMainPart() ) - .resolve( targetNamespaceUrn.getVersion() ) - .resolve( targetFile ) - .toFile(); - if ( targetFileInNewNamespace.exists() ) { - moveElementToOtherNamespaceExistingFile(); - } else { - moveElementToOtherNamespaceNewFile(); - } + moveElementToOtherNamespaceNewFile( targetNamespaceUrn, targetFileInOtherNamespace ); } } /** - * Supports the case {@code samm aspect Aspect.ttl edit move :MyAspect newFile.ttl} + * Supports the case {@code samm aspect Aspect.ttl edit move MyAspect newFile.ttl} */ private void moveElementToNewFile() { final String input = parentCommand.parentCommand.getInput(); @@ -149,60 +156,101 @@ private void moveElementToNewFile() { // Do refactoring final ModelElement modelElement = determineModelElementToMove( aspectModel ); + if ( targetFile.contains( File.separator ) ) { + throw new CommandException( "The target file name should not contain a path; only a file name." ); + } final URI targetFileUri = getInputFile( input ).toPath().getParent().resolve( targetFile ).toUri(); final List headerCommentForNewFile = copyHeader ? modelElement.getSourceFile().headerComment() : List.of(); final Change move = new MoveElementToNewFile( modelElement, headerCommentForNewFile, Optional.of( targetFileUri ) ); - final AspectChangeContext changeContext = performRefactoring( aspectModel, move ); - - // Check & write changes to file system - checkFilesystemConsistency( changeContext ); - performFileSystemWrite( changeContext ); + performRefactoring( aspectModel, move ).ifPresent( changeContext -> { + // Check & write changes to file system + checkFilesystemConsistency( changeContext ); + performFileSystemWrite( changeContext ); + } ); } /** - * Supports the case {@code samm aspect Aspect.ttl edit move :MyAspect existingFile.ttl urn:samm:com.example.othernamespace:1.0.0} + * Supports the case {@code samm aspect Aspect.ttl edit move MyAspect newFile.ttl urn:samm:com.example.othernamespace:1.0.0} */ - private void moveElementToOtherNamespaceExistingFile() { - // TODO + private void moveElementToOtherNamespaceNewFile( final AspectModelUrn targetNamespaceUrn, final File targetFileInNewNamespace ) { + final String input = parentCommand.parentCommand.getInput(); + final AspectModel aspectModel = loadAspectModelOrFail( input, customResolver ); + + // Do refactoring + final ModelElement modelElement = determineModelElementToMove( aspectModel ); + if ( targetFile.contains( File.separator ) ) { + throw new CommandException( "The target file name should not contain a path; only a file name: " + targetFile ); + } + + final Namespace namespace = new DefaultNamespace( targetNamespaceUrn, List.of(), Optional.empty() ); + final List headerCommentForNewFile = copyHeader + ? modelElement.getSourceFile().headerComment() + : List.of(); + + final Change move = new MoveElementToOtherNamespaceNewFile( modelElement, namespace, headerCommentForNewFile, + Optional.of( targetFileInNewNamespace.toURI() ) ); + performRefactoring( aspectModel, move ).ifPresent( changeContext -> { + // Check & write changes to file system + checkFilesystemConsistency( changeContext ); + performFileSystemWrite( changeContext ); + } ); } /** - * Supports the case {@code samm aspect Aspect.ttl edit move :MyAspect newFile.ttl urn:samm:com.example.othernamespace:1.0.0} + * Support the case {@code samm aspect Aspect.ttl edit move MyAspect existingFile.ttl} */ - private void moveElementToOtherNamespaceNewFile() { - // TODO + private void moveElementToExistingFile( final File targetFileRelativeToInput ) { + final AspectModel sourceAspectModel = loadAspectModelOrFail( parentCommand.parentCommand.getInput(), customResolver ); + final AspectModel targetAspectModel = loadAspectModelOrFail( targetFileRelativeToInput, customResolver, false ); + + // Create a consistent in-memory representation of both the source and target models. + // On this Aspect Model we can perform the refactoring operation + final AspectModel aspectModel = AspectModelLoader.merge( sourceAspectModel, targetAspectModel ); + + // Find the loaded AspectModelFile that corresponds to the input targetFile + final ModelElement modelElement = determineModelElementToMove( aspectModel ); + final AspectModelFile targetAspectModelFile = determineTargetAspectModelFile( aspectModel, modelElement.getSourceFile().namespace() ); + + // Do refactoring + final Change move = new MoveElementToExistingFile( modelElement, targetAspectModelFile ); + performRefactoring( aspectModel, move ).ifPresent( changeContext -> { + // Check & write changes to file system + checkFilesystemConsistency( changeContext ); + performFileSystemWrite( changeContext ); + } ); } /** - * Support the case {@code samm aspect Aspect.ttl edit move MyAspect existingFile.ttl} + * Supports the case {@code samm aspect Aspect.ttl edit move MyAspect existingFile.ttl urn:samm:com.example.othernamespace:1.0.0} */ - private void moveElementToExistingFile() { + private void moveElementToOtherNamespaceExistingFile( final AspectModelUrn targetNamespaceUrn, final File targetFileInOtherNamespace ) { final AspectModel sourceAspectModel = loadAspectModelOrFail( parentCommand.parentCommand.getInput(), customResolver ); - final AspectModel targetAspectModel = loadAspectModelOrFail( targetFile, customResolver ); + final AspectModel targetAspectModel = loadAspectModelOrFail( targetFileInOtherNamespace, customResolver, false ); // Create a consistent in-memory representation of both the source and target models. // On this Aspect Model we can perform the refactoring operation - final AspectModel aspectModel = AspectModelBuilder.merge( sourceAspectModel, targetAspectModel ); + final AspectModel aspectModel = AspectModelLoader.merge( sourceAspectModel, targetAspectModel ); // Find the loaded AspectModelFile that corresponds to the input targetFile - final AspectModelFile targetAspectModelFile = determineTargetAspectModelFile( aspectModel ); + final Namespace namespace = new DefaultNamespace( targetNamespaceUrn, List.of(), Optional.empty() ); + final AspectModelFile targetAspectModelFile = determineTargetAspectModelFile( aspectModel, namespace ); // Do refactoring final ModelElement modelElement = determineModelElementToMove( aspectModel ); - final Change move = new MoveElementToExistingFile( modelElement, targetAspectModelFile ); - final AspectChangeContext changeContext = performRefactoring( aspectModel, move ); - - // Check & write changes to file system - checkFilesystemConsistency( changeContext ); - performFileSystemWrite( changeContext ); + final Change move = new MoveElementToOtherNamespaceExistingFile( modelElement, targetAspectModelFile, namespace ); + performRefactoring( aspectModel, move ).ifPresent( changeContext -> { + // Check & write changes to file system + checkFilesystemConsistency( changeContext ); + performFileSystemWrite( changeContext ); + } ); } - private AspectModelFile determineTargetAspectModelFile( final AspectModel aspectModel ) { - final URI targetFileUri = getInputFile( targetFile ).toURI(); + private AspectModelFile determineTargetAspectModelFile( final AspectModel aspectModel, final Namespace targetNamespace ) { return aspectModel.files().stream() - .filter( file -> file.sourceLocation().map( uri -> uri.equals( targetFileUri ) ).orElse( false ) ) + .filter( file -> file.namespace().urn().equals( targetNamespace.urn() ) + && file.sourceLocation().map( uri -> Paths.get( uri ).toFile().getName().equals( targetFile ) ).orElse( false ) ) .findFirst() .orElseThrow( () -> new CommandException( "Could not determine target file" ) ); } @@ -227,7 +275,7 @@ private ModelElement determineModelElementToMove( final AspectModel aspectModel return potentialElements.get( 0 ); } - private AspectChangeContext performRefactoring( final AspectModel aspectModel, final Change change ) { + private Optional performRefactoring( final AspectModel aspectModel, final Change change ) { final AspectChangeContextConfig config = AspectChangeContextConfigBuilder.builder() .detailedChangeReport( details ) .build(); @@ -237,9 +285,9 @@ private AspectChangeContext performRefactoring( final AspectModel aspectModel, f System.out.println( "Changes to be performed" ); System.out.println( "=======================" ); System.out.println( ChangeReportFormatter.INSTANCE.apply( changeReport, config ) ); - System.exit( 0 ); + return Optional.empty(); } - return changeContext; + return Optional.of( changeContext ); } private void performFileSystemWrite( final AspectChangeContext changeContext ) { diff --git a/tools/samm-cli/src/test/java/org/eclipse/esmf/SammCliTest.java b/tools/samm-cli/src/test/java/org/eclipse/esmf/SammCliTest.java index 9138f5ccc..baee7e37b 100644 --- a/tools/samm-cli/src/test/java/org/eclipse/esmf/SammCliTest.java +++ b/tools/samm-cli/src/test/java/org/eclipse/esmf/SammCliTest.java @@ -22,6 +22,7 @@ import java.io.FileInputStream; import java.io.FileWriter; import java.io.IOException; +import java.io.PrintWriter; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; @@ -34,6 +35,7 @@ import org.eclipse.esmf.ProcessLauncher.ExecutionResult; import org.eclipse.esmf.aspect.AspectValidateCommand; import org.eclipse.esmf.aspectmodel.shacl.violation.InvalidSyntaxViolation; +import org.eclipse.esmf.aspectmodel.urn.AspectModelUrn; import org.eclipse.esmf.samm.KnownVersion; import org.eclipse.esmf.test.InvalidTestAspect; import org.eclipse.esmf.test.TestAspect; @@ -1081,6 +1083,218 @@ void testAspectToSqlWithCustomColumnToStdout() { assertThat( result.stderr() ).isEmpty(); } + @Test + void testAspectEditMoveExistingFile() throws IOException { + // Set up file system structure of writable files + final Path modelLocation = outputDirectory.resolve( testModel.getUrn().getNamespaceMainPart() ) + .resolve( testModel.getUrn().getVersion() ); + modelLocation.toFile().mkdirs(); + final File inputFile = modelLocation.resolve( "AspectWithEntity.ttl" ).toFile(); + FileUtils.copyFile( inputFile( testModel ).getAbsoluteFile(), inputFile ); + final File targetFile = modelLocation.resolve( "target.ttl" ).toFile(); + Files.createFile( targetFile.toPath() ); + try ( final PrintWriter out = new PrintWriter( targetFile ) ) { + out.printf( "@prefix : <%s> .", testModel.getUrn().getUrnPrefix() ); + } + + // Run refactoring + assertThat( inputFile ).content().contains( ":AspectWithEntity" ); + assertThat( targetFile ).content().doesNotContain( ":AspectWithEntity" ); + final ExecutionResult result = sammCli.runAndExpectSuccess( "--disable-color", "aspect", inputFile.getAbsolutePath(), + "edit", "move", "AspectWithEntity", targetFile.getName() ); + assertThat( result.stdout() ).isEmpty(); + assertThat( result.stderr() ).isEmpty(); + assertThat( inputFile ).content().doesNotContain( ":AspectWithEntity" ); + assertThat( targetFile ).content().contains( ":AspectWithEntity" ); + } + + @Test + void testAspectEditMoveExistingFileDryRun() throws IOException { + // Set up file system structure of writable files + final Path modelLocation = outputDirectory.resolve( testModel.getUrn().getNamespaceMainPart() ) + .resolve( testModel.getUrn().getVersion() ); + modelLocation.toFile().mkdirs(); + final File inputFile = modelLocation.resolve( "AspectWithEntity.ttl" ).toFile(); + FileUtils.copyFile( inputFile( testModel ).getAbsoluteFile(), inputFile ); + final File targetFile = modelLocation.resolve( "target.ttl" ).toFile(); + Files.createFile( targetFile.toPath() ); + try ( final PrintWriter out = new PrintWriter( targetFile ) ) { + out.printf( "@prefix : <%s> .", testModel.getUrn().getUrnPrefix() ); + } + + // Run refactoring + final ExecutionResult result = sammCli.runAndExpectSuccess( "--disable-color", "aspect", inputFile.getAbsolutePath(), + "edit", "move", "--dry-run", "--details", "AspectWithEntity", targetFile.getName() ); + assertThat( result.stdout() ).contains( "Changes to be performed" ); + assertThat( result.stdout() ).contains( "Remove definition of urn:samm:org.eclipse.esmf.test:1.0.0#AspectWithEntity" ); + assertThat( result.stdout() ).contains( "Add definition of urn:samm:org.eclipse.esmf.test:1.0.0#AspectWithEntity" ); + assertThat( result.stderr() ).isEmpty(); + assertThat( inputFile ).content().contains( ":AspectWithEntity" ); + assertThat( targetFile ).content().doesNotContain( ":AspectWithEntity" ); + } + + @Test + void testAspectEditMoveNewFile() throws IOException { + // Set up file system structure of writable files + final Path modelLocation = outputDirectory.resolve( testModel.getUrn().getNamespaceMainPart() ) + .resolve( testModel.getUrn().getVersion() ); + modelLocation.toFile().mkdirs(); + final File inputFile = modelLocation.resolve( "AspectWithEntity.ttl" ).toFile(); + FileUtils.copyFile( inputFile( testModel ).getAbsoluteFile(), inputFile ); + final File targetFile = modelLocation.resolve( "target.ttl" ).toFile(); + + // Run refactoring + assertThat( inputFile ).content().contains( ":AspectWithEntity" ); + assertThat( targetFile ).doesNotExist(); + final ExecutionResult result = sammCli.runAndExpectSuccess( "--disable-color", "aspect", inputFile.getAbsolutePath(), + "edit", "move", "--copy-file-header", "AspectWithEntity", targetFile.getName() ); + assertThat( result.stdout() ).isEmpty(); + assertThat( result.stderr() ).isEmpty(); + assertThat( inputFile ).content().doesNotContain( ":AspectWithEntity" ); + assertThat( targetFile ).exists(); + assertThat( targetFile ).content().contains( ":AspectWithEntity" ); + assertThat( targetFile ).content().contains( "# Copyright" ); + } + + @Test + void testAspectEditMoveNewFileDryRun() throws IOException { + // Set up file system structure of writable files + final Path modelLocation = outputDirectory.resolve( testModel.getUrn().getNamespaceMainPart() ) + .resolve( testModel.getUrn().getVersion() ); + modelLocation.toFile().mkdirs(); + final File inputFile = modelLocation.resolve( "AspectWithEntity.ttl" ).toFile(); + FileUtils.copyFile( inputFile( testModel ).getAbsoluteFile(), inputFile ); + final File targetFile = modelLocation.resolve( "target.ttl" ).toFile(); + + // Run refactoring + assertThat( inputFile ).content().contains( ":AspectWithEntity" ); + assertThat( targetFile ).doesNotExist(); + final ExecutionResult result = sammCli.runAndExpectSuccess( "--disable-color", "aspect", inputFile.getAbsolutePath(), + "edit", "move", "--dry-run", "--details", "AspectWithEntity", targetFile.getName() ); + assertThat( result.stdout() ).contains( "Changes to be performed" ); + assertThat( result.stdout() ).contains( "Remove definition of urn:samm:org.eclipse.esmf.test:1.0.0#AspectWithEntity" ); + assertThat( result.stdout() ).contains( "Add definition of urn:samm:org.eclipse.esmf.test:1.0.0#AspectWithEntity" ); + assertThat( result.stderr() ).isEmpty(); + assertThat( inputFile ).content().contains( ":AspectWithEntity" ); + assertThat( targetFile ).doesNotExist(); + } + + @Test + void testAspectEditMoveOtherNamespaceExistingFile() throws IOException { + // Set up file system structure of writable files + final Path modelLocationNs1 = outputDirectory.resolve( testModel.getUrn().getNamespaceMainPart() ) + .resolve( testModel.getUrn().getVersion() ); + modelLocationNs1.toFile().mkdirs(); + final File inputFile = modelLocationNs1.resolve( "AspectWithEntity.ttl" ).toFile(); + FileUtils.copyFile( inputFile( testModel ).getAbsoluteFile(), inputFile ); + final String targetNamespace = "urn:samm:org.eclipse.example.newnamespace:1.0.0"; + final AspectModelUrn newNamespace = AspectModelUrn.fromUrn( targetNamespace ); + final Path modelLocationNs2 = outputDirectory.resolve( newNamespace.getNamespaceMainPart() ) + .resolve( newNamespace.getVersion() ); + modelLocationNs2.toFile().mkdirs(); + final File targetFile = modelLocationNs2.resolve( "target.ttl" ).toFile(); + Files.createFile( targetFile.toPath() ); + try ( final PrintWriter out = new PrintWriter( targetFile ) ) { + out.printf( "@prefix : <%s#> .", targetNamespace ); + } + + // Run refactoring + assertThat( inputFile ).content().contains( ":AspectWithEntity" ); + assertThat( targetFile ).content().doesNotContain( ":AspectWithEntity" ); + final ExecutionResult result = sammCli.runAndExpectSuccess( "--disable-color", "aspect", inputFile.getAbsolutePath(), + "edit", "move", "AspectWithEntity", targetFile.getName(), targetNamespace ); + assertThat( result.stdout() ).isEmpty(); + assertThat( result.stderr() ).isEmpty(); + assertThat( inputFile ).content().doesNotContain( ":AspectWithEntity" ); + assertThat( targetFile ).content().contains( ":AspectWithEntity" ); + } + + @Test + void testAspectEditMoveOtherNamespaceExistingFileDryRun() throws IOException { + // Set up file system structure of writable files + final Path modelLocationNs1 = outputDirectory.resolve( testModel.getUrn().getNamespaceMainPart() ) + .resolve( testModel.getUrn().getVersion() ); + modelLocationNs1.toFile().mkdirs(); + final File inputFile = modelLocationNs1.resolve( "AspectWithEntity.ttl" ).toFile(); + FileUtils.copyFile( inputFile( testModel ).getAbsoluteFile(), inputFile ); + final String targetNamespace = "urn:samm:org.eclipse.example.newnamespace:1.0.0"; + final AspectModelUrn newNamespace = AspectModelUrn.fromUrn( targetNamespace ); + final Path modelLocationNs2 = outputDirectory.resolve( newNamespace.getNamespaceMainPart() ) + .resolve( newNamespace.getVersion() ); + modelLocationNs2.toFile().mkdirs(); + final File targetFile = modelLocationNs2.resolve( "target.ttl" ).toFile(); + Files.createFile( targetFile.toPath() ); + try ( final PrintWriter out = new PrintWriter( targetFile ) ) { + out.printf( "@prefix : <%s#> .", targetNamespace ); + } + + // Run refactoring + assertThat( inputFile ).content().contains( ":AspectWithEntity" ); + assertThat( targetFile ).content().doesNotContain( ":AspectWithEntity" ); + final ExecutionResult result = sammCli.runAndExpectSuccess( "--disable-color", "aspect", inputFile.getAbsolutePath(), + "edit", "move", "--dry-run", "--details", "AspectWithEntity", targetFile.getName(), targetNamespace ); + assertThat( result.stdout() ).contains( "Changes to be performed" ); + assertThat( result.stdout() ).contains( "Remove definition of urn:samm:org.eclipse.esmf.test:1.0.0#AspectWithEntity" ); + assertThat( result.stdout() ).contains( "Add definition of urn:samm:org.eclipse.esmf.test:1.0.0#AspectWithEntity" ); + assertThat( result.stderr() ).isEmpty(); + assertThat( inputFile ).content().contains( ":AspectWithEntity" ); + assertThat( targetFile ).content().doesNotContain( ":AspectWithEntity" ); + } + + @Test + void testAspectEditMoveOtherNamespaceNewFile() throws IOException { + // Set up file system structure of writable files + final Path modelLocationNs1 = outputDirectory.resolve( testModel.getUrn().getNamespaceMainPart() ) + .resolve( testModel.getUrn().getVersion() ); + modelLocationNs1.toFile().mkdirs(); + final File inputFile = modelLocationNs1.resolve( "AspectWithEntity.ttl" ).toFile(); + FileUtils.copyFile( inputFile( testModel ).getAbsoluteFile(), inputFile ); + final String targetNamespace = "urn:samm:org.eclipse.example.newnamespace:1.0.0"; + final AspectModelUrn newNamespace = AspectModelUrn.fromUrn( targetNamespace ); + final Path modelLocationNs2 = outputDirectory.resolve( newNamespace.getNamespaceMainPart() ) + .resolve( newNamespace.getVersion() ); + final File targetFile = modelLocationNs2.resolve( "target.ttl" ).toFile(); + + // Run refactoring + assertThat( inputFile ).content().contains( ":AspectWithEntity" ); + assertThat( targetFile ).doesNotExist(); + final ExecutionResult result = sammCli.runAndExpectSuccess( "--disable-color", "aspect", inputFile.getAbsolutePath(), + "edit", "move", "--copy-file-header", "AspectWithEntity", targetFile.getName(), targetNamespace ); + assertThat( result.stdout() ).isEmpty(); + assertThat( result.stderr() ).isEmpty(); + assertThat( inputFile ).content().doesNotContain( ":AspectWithEntity" ); + assertThat( targetFile ).exists(); + assertThat( targetFile ).content().contains( ":AspectWithEntity" ); + assertThat( targetFile ).content().contains( "# Copyright" ); + } + + @Test + void testAspectEditMoveOtherNamespaceNewFileDryRun() throws IOException { + // Set up file system structure of writable files + final Path modelLocationNs1 = outputDirectory.resolve( testModel.getUrn().getNamespaceMainPart() ) + .resolve( testModel.getUrn().getVersion() ); + modelLocationNs1.toFile().mkdirs(); + final File inputFile = modelLocationNs1.resolve( "AspectWithEntity.ttl" ).toFile(); + FileUtils.copyFile( inputFile( testModel ).getAbsoluteFile(), inputFile ); + final String targetNamespace = "urn:samm:org.eclipse.example.newnamespace:1.0.0"; + final AspectModelUrn newNamespace = AspectModelUrn.fromUrn( targetNamespace ); + final Path modelLocationNs2 = outputDirectory.resolve( newNamespace.getNamespaceMainPart() ) + .resolve( newNamespace.getVersion() ); + final File targetFile = modelLocationNs2.resolve( "target.ttl" ).toFile(); + + // Run refactoring + assertThat( inputFile ).content().contains( ":AspectWithEntity" ); + assertThat( targetFile ).doesNotExist(); + final ExecutionResult result = sammCli.runAndExpectSuccess( "--disable-color", "aspect", inputFile.getAbsolutePath(), + "edit", "move", "--dry-run", "--details", "AspectWithEntity", targetFile.getName(), targetNamespace ); + assertThat( result.stdout() ).contains( "Changes to be performed" ); + assertThat( result.stdout() ).contains( "Remove definition of urn:samm:org.eclipse.esmf.test:1.0.0#AspectWithEntity" ); + assertThat( result.stdout() ).contains( "Add definition of urn:samm:org.eclipse.esmf.test:1.0.0#AspectWithEntity" ); + assertThat( result.stderr() ).isEmpty(); + assertThat( inputFile ).content().contains( ":AspectWithEntity" ); + assertThat( targetFile ).doesNotExist(); + } + /** * Returns the File object for a test model file */ From 4f680dacf19d836bfaf2c7b168fe631718ebb37b Mon Sep 17 00:00:00 2001 From: "Textor Andreas (BCI/ESW17)" Date: Mon, 12 Aug 2024 16:41:42 +0200 Subject: [PATCH 25/33] Incorporate AspectModelBuilder functionality in AspectModelLoader Creating an AspectModel from a list of AspectModelFiles should be located next to the other methods that create an AspectModel from various input sources --- .../esmf/aspectmodel/AspectModelBuilder.java | 132 ------------------ .../aspectmodel/edit/AspectChangeContext.java | 2 +- .../aspectmodel/loader/AspectModelLoader.java | 107 +++++++++++++- .../aspectmodel/AspectModelBuilderTest.java | 35 ----- .../edit/AspectChangeContextTest.java | 14 +- .../loader/AspectModelLoaderTest.java | 11 ++ .../serializer/AspectSerializer.java | 4 +- .../serializer/AspectSerializerTest.java | 4 +- .../aspect/edit/AspectEditMoveCommand.java | 4 +- 9 files changed, 127 insertions(+), 186 deletions(-) delete mode 100644 core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/AspectModelBuilder.java delete mode 100644 core/esmf-aspect-meta-model-java/src/test/java/org/eclipse/esmf/aspectmodel/AspectModelBuilderTest.java diff --git a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/AspectModelBuilder.java b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/AspectModelBuilder.java deleted file mode 100644 index 68e815c03..000000000 --- a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/AspectModelBuilder.java +++ /dev/null @@ -1,132 +0,0 @@ -/* - * Copyright (c) 2024 Robert Bosch Manufacturing Solutions GmbH - * - * See the AUTHORS file(s) distributed with this work for additional - * information regarding authorship. - * - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at https://mozilla.org/MPL/2.0/. - * - * SPDX-License-Identifier: MPL-2.0 - */ - -package org.eclipse.esmf.aspectmodel; - -import java.net.URI; -import java.util.ArrayList; -import java.util.Collection; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.Set; -import java.util.stream.Collectors; - -import org.eclipse.esmf.aspectmodel.loader.MetaModelBaseAttributes; -import org.eclipse.esmf.aspectmodel.loader.ModelElementFactory; -import org.eclipse.esmf.aspectmodel.resolver.modelfile.DefaultAspectModelFile; -import org.eclipse.esmf.aspectmodel.resolver.modelfile.MetaModelFile; -import org.eclipse.esmf.metamodel.AspectModel; -import org.eclipse.esmf.metamodel.ModelElement; -import org.eclipse.esmf.metamodel.Namespace; -import org.eclipse.esmf.metamodel.impl.DefaultAspectModel; -import org.eclipse.esmf.metamodel.impl.DefaultNamespace; -import org.eclipse.esmf.metamodel.vocabulary.SammNs; - -import org.apache.jena.rdf.model.Model; -import org.apache.jena.rdf.model.ModelFactory; -import org.apache.jena.rdf.model.RDFNode; -import org.apache.jena.rdf.model.Resource; -import org.apache.jena.rdf.model.Statement; -import org.apache.jena.vocabulary.RDF; - -public class AspectModelBuilder { - public static AspectModel buildEmptyModel() { - return new DefaultAspectModel( new ArrayList<>(), ModelFactory.createDefaultModel(), new ArrayList<>() ); - } - - public static AspectModel buildAspectModelFromFiles( final Collection inputFiles ) { - final Model mergedModel = ModelFactory.createDefaultModel(); - mergedModel.add( MetaModelFile.metaModelDefinitions() ); - for ( final AspectModelFile file : inputFiles ) { - mergedModel.add( file.sourceModel() ); - } - - final List elements = new ArrayList<>(); - final List files = new ArrayList<>(); - final Map namespaceDefinitions = new HashMap<>(); - for ( final AspectModelFile file : inputFiles ) { - final DefaultAspectModelFile aspectModelFile = new DefaultAspectModelFile( file.sourceModel(), file.headerComment(), - file.sourceLocation() ); - files.add( aspectModelFile ); - final Model model = file.sourceModel(); - final ModelElementFactory modelElementFactory = new ModelElementFactory( mergedModel, Map.of(), element -> aspectModelFile ); - final List fileElements = model.listStatements( null, RDF.type, (RDFNode) null ).toList().stream() - .filter( statement -> !statement.getObject().isURIResource() || !statement.getResource().equals( SammNs.SAMM.Namespace() ) ) - .map( Statement::getSubject ) - .filter( RDFNode::isURIResource ) - .map( resource -> mergedModel.createResource( resource.getURI() ) ) - .map( resource -> modelElementFactory.create( ModelElement.class, resource ) ) - .toList(); - aspectModelFile.setElements( fileElements ); - elements.addAll( fileElements ); - } - - setNamespaces( files, elements ); - return new DefaultAspectModel( files, mergedModel, elements ); - } - - private static void setNamespaces( final Collection files, final Collection elements ) { - final Map> elementsGroupedByNamespaceUrn = elements.stream() - .filter( element -> !element.isAnonymous() ) - .collect( Collectors.groupingBy( element -> element.urn().getNamespaceIdentifier() ) ); - for ( final AspectModelFile file : files ) { - final Optional optionalNamespaceUrn = - Optional.ofNullable( file.sourceModel().getNsPrefixURI( "" ) ) - .map( urnPrefix -> urnPrefix.split( "#" )[0] ) - .or( () -> file.elements().stream() - .filter( element -> !element.isAnonymous() ) - .map( element -> element.urn().getNamespaceIdentifier() ) - .findAny() ); - if ( optionalNamespaceUrn.isEmpty() ) { - return; - } - - final String namespaceUrn = optionalNamespaceUrn.get(); - MetaModelBaseAttributes namespaceDefinition = null; - AspectModelFile fileContainingNamespaceDefinition = null; - final List elementsForUrn = elementsGroupedByNamespaceUrn.get( namespaceUrn ); - if ( elementsForUrn != null ) { - for ( final ModelElement element : elementsForUrn ) { - final AspectModelFile elementFile = element.getSourceFile(); - if ( elementFile.sourceModel().contains( null, RDF.type, SammNs.SAMM.Namespace() ) ) { - final Model model = elementFile.sourceModel(); - final ModelElementFactory modelElementFactory = new ModelElementFactory( model, Map.of(), __ -> null ); - final Resource namespaceResource = model.listStatements( null, RDF.type, SammNs.SAMM.Namespace() ) - .mapWith( Statement::getSubject ) - .toList().iterator().next(); - namespaceDefinition = modelElementFactory.createBaseAttributes( namespaceResource ); - fileContainingNamespaceDefinition = elementFile; - break; - } - } - } - - final Namespace namespace = new DefaultNamespace( namespaceUrn, elementsGroupedByNamespaceUrn.get( namespaceUrn ), - Optional.ofNullable( fileContainingNamespaceDefinition ), Optional.ofNullable( namespaceDefinition ) ); - ( (DefaultAspectModelFile) file ).setNamespace( namespace ); - } - } - - public static AspectModel merge( final AspectModel aspectModel1, final AspectModel aspectModel2 ) { - final List files = new ArrayList<>( aspectModel1.files() ); - final Set locations = aspectModel1.files().stream() - .flatMap( f -> f.sourceLocation().stream() ) - .collect( Collectors.toSet() ); - for ( final AspectModelFile file : aspectModel2.files() ) { - file.sourceLocation().filter( uri -> !locations.contains( uri ) ).ifPresent( uri -> files.add( file ) ); - } - return buildAspectModelFromFiles( files ); - } -} diff --git a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/AspectChangeContext.java b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/AspectChangeContext.java index 4af5464fb..42ec97f88 100644 --- a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/AspectChangeContext.java +++ b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/AspectChangeContext.java @@ -92,7 +92,7 @@ public synchronized void redoChange() { } private void updateAspectModelAfterChange() { - final AspectModel updatedModel = AspectModelLoader.buildAspectModelFromFiles( aspectModel.files() ); + final AspectModel updatedModel = new AspectModelLoader().loadAspectModelFiles( aspectModel.files() ); aspectModel.setMergedModel( updatedModel.mergedModel() ); aspectModel.setElements( updatedModel.elements() ); aspectModel.setFiles( updatedModel.files() ); diff --git a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/loader/AspectModelLoader.java b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/loader/AspectModelLoader.java index d7ec51fc2..f844ff253 100644 --- a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/loader/AspectModelLoader.java +++ b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/loader/AspectModelLoader.java @@ -26,15 +26,17 @@ import java.util.ArrayList; import java.util.Collection; import java.util.Deque; +import java.util.HashMap; import java.util.HashSet; import java.util.List; +import java.util.Map; import java.util.Optional; import java.util.Set; import java.util.function.Supplier; +import java.util.stream.Collectors; import java.util.zip.ZipEntry; import java.util.zip.ZipInputStream; -import org.eclipse.esmf.aspectmodel.AspectModelBuilder; import org.eclipse.esmf.aspectmodel.AspectModelFile; import org.eclipse.esmf.aspectmodel.RdfUtil; import org.eclipse.esmf.aspectmodel.resolver.AspectModelFileLoader; @@ -44,15 +46,22 @@ import org.eclipse.esmf.aspectmodel.resolver.ResolutionStrategy; import org.eclipse.esmf.aspectmodel.resolver.ResolutionStrategySupport; import org.eclipse.esmf.aspectmodel.resolver.fs.FlatModelsRoot; +import org.eclipse.esmf.aspectmodel.resolver.modelfile.DefaultAspectModelFile; +import org.eclipse.esmf.aspectmodel.resolver.modelfile.MetaModelFile; import org.eclipse.esmf.aspectmodel.urn.AspectModelUrn; import org.eclipse.esmf.aspectmodel.urn.ElementType; import org.eclipse.esmf.aspectmodel.urn.UrnSyntaxException; import org.eclipse.esmf.aspectmodel.versionupdate.MetaModelVersionMigrator; import org.eclipse.esmf.metamodel.AspectModel; +import org.eclipse.esmf.metamodel.ModelElement; +import org.eclipse.esmf.metamodel.Namespace; +import org.eclipse.esmf.metamodel.impl.DefaultAspectModel; +import org.eclipse.esmf.metamodel.impl.DefaultNamespace; import org.eclipse.esmf.metamodel.vocabulary.SammNs; import com.google.common.collect.Streams; import org.apache.jena.rdf.model.Model; +import org.apache.jena.rdf.model.ModelFactory; import org.apache.jena.rdf.model.RDFNode; import org.apache.jena.rdf.model.Resource; import org.apache.jena.rdf.model.Statement; @@ -132,7 +141,7 @@ public AspectModel load( final Collection files ) { .toList(); final LoaderContext loaderContext = new LoaderContext(); resolve( migratedFiles, loaderContext ); - return AspectModelBuilder.buildAspectModelFromFiles( loaderContext.loadedFiles() ); + return loadAspectModelFiles( loaderContext.loadedFiles() ); } /** @@ -157,7 +166,7 @@ public AspectModel loadUrns( final Collection urns ) { loaderContext.unresolvedUrns().add( inputUrn.toString() ); } resolve( List.of(), loaderContext ); - return AspectModelBuilder.buildAspectModelFromFiles( loaderContext.loadedFiles() ); + return loadAspectModelFiles( loaderContext.loadedFiles() ); } /** @@ -172,7 +181,7 @@ public AspectModel load( final InputStream inputStream, final Optional sour final AspectModelFile migratedModel = migrate( rawFile ); final LoaderContext loaderContext = new LoaderContext(); resolve( List.of( migratedModel ), loaderContext ); - return AspectModelBuilder.buildAspectModelFromFiles( loaderContext.loadedFiles() ); + return loadAspectModelFiles( loaderContext.loadedFiles() ); } /** @@ -247,7 +256,7 @@ private AspectModel loadNamespacePackageFromStream( final InputStream inputStrea final LoaderContext loaderContext = new LoaderContext(); resolve( aspectModelFiles, loaderContext ); - return AspectModelBuilder.buildAspectModelFromFiles( loaderContext.loadedFiles() ); + return loadAspectModelFiles( loaderContext.loadedFiles() ); } private boolean containsFolderInNamespacePackage( final InputStream inputStream ) { @@ -402,4 +411,92 @@ public boolean containsDefinition( final AspectModelFile aspectModelFile, final LOG.debug( "Checking if model contains {}: {}", urn, result ); return result; } + + public AspectModel emptyModel() { + return new DefaultAspectModel( new ArrayList<>(), ModelFactory.createDefaultModel(), new ArrayList<>() ); + } + + public AspectModel loadAspectModelFiles( final Collection inputFiles ) { + final Model mergedModel = ModelFactory.createDefaultModel(); + mergedModel.add( MetaModelFile.metaModelDefinitions() ); + for ( final AspectModelFile file : inputFiles ) { + mergedModel.add( file.sourceModel() ); + } + + final List elements = new ArrayList<>(); + final List files = new ArrayList<>(); + final Map namespaceDefinitions = new HashMap<>(); + for ( final AspectModelFile file : inputFiles ) { + final DefaultAspectModelFile aspectModelFile = new DefaultAspectModelFile( file.sourceModel(), file.headerComment(), + file.sourceLocation() ); + files.add( aspectModelFile ); + final Model model = file.sourceModel(); + final ModelElementFactory modelElementFactory = new ModelElementFactory( mergedModel, Map.of(), element -> aspectModelFile ); + final List fileElements = model.listStatements( null, RDF.type, (RDFNode) null ).toList().stream() + .filter( statement -> !statement.getObject().isURIResource() || !statement.getResource().equals( SammNs.SAMM.Namespace() ) ) + .map( Statement::getSubject ) + .filter( RDFNode::isURIResource ) + .map( resource -> mergedModel.createResource( resource.getURI() ) ) + .map( resource -> modelElementFactory.create( ModelElement.class, resource ) ) + .toList(); + aspectModelFile.setElements( fileElements ); + elements.addAll( fileElements ); + } + + setNamespaces( files, elements ); + return new DefaultAspectModel( files, mergedModel, elements ); + } + + private static void setNamespaces( final Collection files, final Collection elements ) { + final Map> elementsGroupedByNamespaceUrn = elements.stream() + .filter( element -> !element.isAnonymous() ) + .collect( Collectors.groupingBy( element -> element.urn().getNamespaceIdentifier() ) ); + for ( final AspectModelFile file : files ) { + final Optional optionalNamespaceUrn = + Optional.ofNullable( file.sourceModel().getNsPrefixURI( "" ) ) + .map( urnPrefix -> urnPrefix.split( "#" )[0] ) + .or( () -> file.elements().stream() + .filter( element -> !element.isAnonymous() ) + .map( element -> element.urn().getNamespaceIdentifier() ) + .findAny() ); + if ( optionalNamespaceUrn.isEmpty() ) { + continue; + } + + final String namespaceUrn = optionalNamespaceUrn.get(); + MetaModelBaseAttributes namespaceDefinition = null; + AspectModelFile fileContainingNamespaceDefinition = null; + final List elementsForUrn = elementsGroupedByNamespaceUrn.get( namespaceUrn ); + if ( elementsForUrn != null ) { + for ( final ModelElement element : elementsForUrn ) { + final AspectModelFile elementFile = element.getSourceFile(); + if ( elementFile.sourceModel().contains( null, RDF.type, SammNs.SAMM.Namespace() ) ) { + final Model model = elementFile.sourceModel(); + final ModelElementFactory modelElementFactory = new ModelElementFactory( model, Map.of(), __ -> null ); + final Resource namespaceResource = model.listStatements( null, RDF.type, SammNs.SAMM.Namespace() ) + .mapWith( Statement::getSubject ) + .toList().iterator().next(); + namespaceDefinition = modelElementFactory.createBaseAttributes( namespaceResource ); + fileContainingNamespaceDefinition = elementFile; + break; + } + } + } + + final Namespace namespace = new DefaultNamespace( namespaceUrn, elementsGroupedByNamespaceUrn.get( namespaceUrn ), + Optional.ofNullable( fileContainingNamespaceDefinition ), Optional.ofNullable( namespaceDefinition ) ); + ( (DefaultAspectModelFile) file ).setNamespace( namespace ); + } + } + + public AspectModel merge( final AspectModel aspectModel1, final AspectModel aspectModel2 ) { + final List files = new ArrayList<>( aspectModel1.files() ); + final Set locations = aspectModel1.files().stream() + .flatMap( f -> f.sourceLocation().stream() ) + .collect( Collectors.toSet() ); + for ( final AspectModelFile file : aspectModel2.files() ) { + file.sourceLocation().filter( uri -> !locations.contains( uri ) ).ifPresent( uri -> files.add( file ) ); + } + return loadAspectModelFiles( files ); + } } diff --git a/core/esmf-aspect-meta-model-java/src/test/java/org/eclipse/esmf/aspectmodel/AspectModelBuilderTest.java b/core/esmf-aspect-meta-model-java/src/test/java/org/eclipse/esmf/aspectmodel/AspectModelBuilderTest.java deleted file mode 100644 index 5f5a00593..000000000 --- a/core/esmf-aspect-meta-model-java/src/test/java/org/eclipse/esmf/aspectmodel/AspectModelBuilderTest.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright (c) 2024 Robert Bosch Manufacturing Solutions GmbH - * - * See the AUTHORS file(s) distributed with this work for additional - * information regarding authorship. - * - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at https://mozilla.org/MPL/2.0/. - * - * SPDX-License-Identifier: MPL-2.0 - */ - -package org.eclipse.esmf.aspectmodel; - -import static org.assertj.core.api.Assertions.assertThat; - -import org.eclipse.esmf.metamodel.AspectModel; -import org.eclipse.esmf.test.TestAspect; -import org.eclipse.esmf.test.TestResources; - -import org.junit.jupiter.api.Test; - -public class AspectModelBuilderTest { - @Test - void testMergeAspectModels() { - final AspectModel a1 = TestResources.load( TestAspect.ASPECT ); - final AspectModel a2 = TestResources.load( TestAspect.ASPECT_WITH_PROPERTY ); - assertThat( a1.aspects() ).hasSize( 1 ); - assertThat( a2.aspects() ).hasSize( 1 ); - final AspectModel merged = AspectModelBuilder.merge( a1, a2 ); - assertThat( merged.aspects() ).hasSize( 2 ); - assertThat( merged.elements().size() ).isEqualTo( a1.elements().size() + a2.elements().size() ); - } -} diff --git a/core/esmf-aspect-meta-model-java/src/test/java/org/eclipse/esmf/aspectmodel/edit/AspectChangeContextTest.java b/core/esmf-aspect-meta-model-java/src/test/java/org/eclipse/esmf/aspectmodel/edit/AspectChangeContextTest.java index 5c2e77304..ad47a0747 100644 --- a/core/esmf-aspect-meta-model-java/src/test/java/org/eclipse/esmf/aspectmodel/edit/AspectChangeContextTest.java +++ b/core/esmf-aspect-meta-model-java/src/test/java/org/eclipse/esmf/aspectmodel/edit/AspectChangeContextTest.java @@ -20,7 +20,6 @@ import java.util.List; import java.util.Optional; -import org.eclipse.esmf.aspectmodel.AspectModelBuilder; import org.eclipse.esmf.aspectmodel.AspectModelFile; import org.eclipse.esmf.aspectmodel.edit.change.AddAspectModelFile; import org.eclipse.esmf.aspectmodel.edit.change.AddElementDefinition; @@ -31,6 +30,7 @@ import org.eclipse.esmf.aspectmodel.edit.change.MoveRenameAspectModelFile; import org.eclipse.esmf.aspectmodel.edit.change.RemoveAspectModelFile; import org.eclipse.esmf.aspectmodel.edit.change.RenameElement; +import org.eclipse.esmf.aspectmodel.loader.AspectModelLoader; import org.eclipse.esmf.aspectmodel.resolver.modelfile.RawAspectModelFileBuilder; import org.eclipse.esmf.aspectmodel.urn.AspectModelUrn; import org.eclipse.esmf.metamodel.Aspect; @@ -113,7 +113,7 @@ void testChangeGroups() { @Test void testCreateFile() { - final AspectModel aspectModel = AspectModelBuilder.buildEmptyModel(); + final AspectModel aspectModel = new AspectModelLoader().emptyModel(); assertThat( aspectModel.files() ).isEmpty(); assertThat( aspectModel.elements() ).isEmpty(); final AspectChangeContext ctx = new AspectChangeContext( aspectModel ); @@ -138,7 +138,7 @@ void testRemoveFile() { final AspectModelFile aspectModelFile = RawAspectModelFileBuilder.builder() .sourceLocation( Optional.of( URI.create( "file:///temp/test.ttl" ) ) ) .build(); - final AspectModel aspectModel = AspectModelBuilder.buildAspectModelFromFiles( List.of( aspectModelFile ) ); + final AspectModel aspectModel = new AspectModelLoader().loadAspectModelFiles( List.of( aspectModelFile ) ); assertThat( aspectModel.files() ).hasSize( 1 ); assertThat( aspectModel.elements() ).isEmpty(); final AspectChangeContext ctx = new AspectChangeContext( aspectModel ); @@ -155,7 +155,7 @@ void testRemoveFile() { @Test void testCreateFileWithElementDefinition() { - final AspectModel aspectModel = AspectModelBuilder.buildEmptyModel(); + final AspectModel aspectModel = new AspectModelLoader().emptyModel(); assertThat( aspectModel.files() ).isEmpty(); assertThat( aspectModel.elements() ).isEmpty(); final AspectChangeContext ctx = new AspectChangeContext( aspectModel ); @@ -189,7 +189,7 @@ void testCreateFileWithElementDefinition() { @Test void testCreateFileThenAddElementDefinition() { - final AspectModel aspectModel = AspectModelBuilder.buildEmptyModel(); + final AspectModel aspectModel = new AspectModelLoader().emptyModel(); assertThat( aspectModel.files() ).isEmpty(); assertThat( aspectModel.elements() ).isEmpty(); final AspectChangeContext ctx = new AspectChangeContext( aspectModel ); @@ -265,7 +265,7 @@ void testMoveElementToNewFile() { void testMoveElementToExistingFile() { final Optional file1Location = Optional.of( URI.create( "file:///file1.ttl" ) ); final Optional file2Location = Optional.of( URI.create( "file:///file2.ttl" ) ); - final AspectModel aspectModel = AspectModelBuilder.buildEmptyModel(); + final AspectModel aspectModel = new AspectModelLoader().emptyModel(); final AspectChangeContext ctx = new AspectChangeContext( aspectModel ); final AspectModelFile file1 = RawAspectModelFileBuilder.builder() .sourceLocation( file1Location ) @@ -346,7 +346,7 @@ void testMoveElementToOtherNamespaceNewFile() { void testMoveElementToOtherNamespaceExistingFile() { final Optional file1Location = Optional.of( URI.create( "file:///file1.ttl" ) ); final Optional file2Location = Optional.of( URI.create( "file:///file2.ttl" ) ); - final AspectModel aspectModel = AspectModelBuilder.buildEmptyModel(); + final AspectModel aspectModel = new AspectModelLoader().emptyModel(); final AspectChangeContext ctx = new AspectChangeContext( aspectModel ); final AspectModelFile file1 = RawAspectModelFileBuilder.builder() .sourceLocation( file1Location ) diff --git a/core/esmf-aspect-meta-model-java/src/test/java/org/eclipse/esmf/aspectmodel/loader/AspectModelLoaderTest.java b/core/esmf-aspect-meta-model-java/src/test/java/org/eclipse/esmf/aspectmodel/loader/AspectModelLoaderTest.java index b7423deec..439587c85 100644 --- a/core/esmf-aspect-meta-model-java/src/test/java/org/eclipse/esmf/aspectmodel/loader/AspectModelLoaderTest.java +++ b/core/esmf-aspect-meta-model-java/src/test/java/org/eclipse/esmf/aspectmodel/loader/AspectModelLoaderTest.java @@ -176,6 +176,17 @@ void testLoadAspectModelFromZipArchiveWithSharedProperty() throws FileNotFoundEx } ); } + @Test + void testMergeAspectModels() { + final AspectModel a1 = TestResources.load( TestAspect.ASPECT ); + final AspectModel a2 = TestResources.load( TestAspect.ASPECT_WITH_PROPERTY ); + assertThat( a1.aspects() ).hasSize( 1 ); + assertThat( a2.aspects() ).hasSize( 1 ); + final AspectModel merged = new AspectModelLoader().merge( a1, a2 ); + assertThat( merged.aspects() ).hasSize( 2 ); + assertThat( merged.elements().size() ).isEqualTo( a1.elements().size() + a2.elements().size() ); + } + /** * Returns the File object for a test model file */ diff --git a/core/esmf-aspect-model-serializer/src/main/java/org/eclipse/esmf/aspectmodel/serializer/AspectSerializer.java b/core/esmf-aspect-model-serializer/src/main/java/org/eclipse/esmf/aspectmodel/serializer/AspectSerializer.java index bf6fb4d2b..3eafa1a5f 100644 --- a/core/esmf-aspect-model-serializer/src/main/java/org/eclipse/esmf/aspectmodel/serializer/AspectSerializer.java +++ b/core/esmf-aspect-model-serializer/src/main/java/org/eclipse/esmf/aspectmodel/serializer/AspectSerializer.java @@ -29,9 +29,9 @@ import java.util.Map; import java.util.function.Function; -import org.eclipse.esmf.aspectmodel.AspectModelBuilder; import org.eclipse.esmf.aspectmodel.AspectModelFile; import org.eclipse.esmf.aspectmodel.RdfUtil; +import org.eclipse.esmf.aspectmodel.loader.AspectModelLoader; import org.eclipse.esmf.aspectmodel.resolver.modelfile.RawAspectModelFileBuilder; import org.eclipse.esmf.metamodel.Aspect; import org.eclipse.esmf.metamodel.AspectModel; @@ -145,7 +145,7 @@ public String aspectToString( final Aspect aspect ) { final RdfNamespace namespace = new SimpleRdfNamespace( "", aspect.urn().getUrnPrefix() ); final Model rdfModel = new RdfModelCreatorVisitor( namespace ).visitAspect( aspect, null ).model(); RdfUtil.cleanPrefixes( rdfModel ); - final AspectModel aspectModel = AspectModelBuilder.buildAspectModelFromFiles( + final AspectModel aspectModel = new AspectModelLoader().loadAspectModelFiles( List.of( RawAspectModelFileBuilder.builder().sourceModel( rdfModel ).build() ) ); final AspectModelFile newSourceFile = aspectModel.aspect().getSourceFile(); return aspectModelFileToString( newSourceFile ); diff --git a/core/esmf-aspect-model-serializer/src/test/java/org/eclipse/esmf/aspectmodel/serializer/AspectSerializerTest.java b/core/esmf-aspect-model-serializer/src/test/java/org/eclipse/esmf/aspectmodel/serializer/AspectSerializerTest.java index ad5981789..b4b316c0e 100644 --- a/core/esmf-aspect-model-serializer/src/test/java/org/eclipse/esmf/aspectmodel/serializer/AspectSerializerTest.java +++ b/core/esmf-aspect-model-serializer/src/test/java/org/eclipse/esmf/aspectmodel/serializer/AspectSerializerTest.java @@ -24,12 +24,12 @@ import java.util.Optional; import java.util.stream.Stream; -import org.eclipse.esmf.aspectmodel.AspectModelBuilder; import org.eclipse.esmf.aspectmodel.AspectModelFile; import org.eclipse.esmf.aspectmodel.edit.AspectChangeContext; import org.eclipse.esmf.aspectmodel.edit.ChangeGroup; import org.eclipse.esmf.aspectmodel.edit.change.AddAspectModelFile; import org.eclipse.esmf.aspectmodel.edit.change.MoveRenameAspectModelFile; +import org.eclipse.esmf.aspectmodel.loader.AspectModelLoader; import org.eclipse.esmf.aspectmodel.resolver.modelfile.RawAspectModelFileBuilder; import org.eclipse.esmf.metamodel.AspectModel; import org.eclipse.esmf.test.TestAspect; @@ -87,7 +87,7 @@ void testWriteAspectModelFileToFileSystem() { @Test void testWriteAspectModelToFileSystem() { // Construct Aspect Model with two files from scratch - final AspectModel aspectModel = AspectModelBuilder.buildEmptyModel(); + final AspectModel aspectModel = new AspectModelLoader().emptyModel(); final Path file1Path = outputDirectory.resolve( "Aspect1.ttl" ); final AspectModelFile file1 = RawAspectModelFileBuilder.builder() .sourceLocation( Optional.of( file1Path.toUri() ) ) diff --git a/tools/samm-cli/src/main/java/org/eclipse/esmf/aspect/edit/AspectEditMoveCommand.java b/tools/samm-cli/src/main/java/org/eclipse/esmf/aspect/edit/AspectEditMoveCommand.java index a02804cdf..22f9cbdec 100644 --- a/tools/samm-cli/src/main/java/org/eclipse/esmf/aspect/edit/AspectEditMoveCommand.java +++ b/tools/samm-cli/src/main/java/org/eclipse/esmf/aspect/edit/AspectEditMoveCommand.java @@ -207,7 +207,7 @@ private void moveElementToExistingFile( final File targetFileRelativeToInput ) { // Create a consistent in-memory representation of both the source and target models. // On this Aspect Model we can perform the refactoring operation - final AspectModel aspectModel = AspectModelLoader.merge( sourceAspectModel, targetAspectModel ); + final AspectModel aspectModel = new AspectModelLoader().merge( sourceAspectModel, targetAspectModel ); // Find the loaded AspectModelFile that corresponds to the input targetFile final ModelElement modelElement = determineModelElementToMove( aspectModel ); @@ -231,7 +231,7 @@ private void moveElementToOtherNamespaceExistingFile( final AspectModelUrn targe // Create a consistent in-memory representation of both the source and target models. // On this Aspect Model we can perform the refactoring operation - final AspectModel aspectModel = AspectModelLoader.merge( sourceAspectModel, targetAspectModel ); + final AspectModel aspectModel = new AspectModelLoader().merge( sourceAspectModel, targetAspectModel ); // Find the loaded AspectModelFile that corresponds to the input targetFile final Namespace namespace = new DefaultNamespace( targetNamespaceUrn, List.of(), Optional.empty() ); From 22d6af448d90d0248e7a17473c696412857ffac0 Mon Sep 17 00:00:00 2001 From: "Textor Andreas (BCI/ESW17)" Date: Tue, 13 Aug 2024 07:30:28 +0200 Subject: [PATCH 26/33] Rename AspectChangeContext to AspectChangeManager --- ...eContext.java => AspectChangeManager.java} | 30 ++- ...ig.java => AspectChangeManagerConfig.java} | 6 +- .../esmf/aspectmodel/edit/ChangeContext.java | 9 +- .../edit/ChangeReportFormatter.java | 10 +- ...Test.java => AspectChangeManagerTest.java} | 196 +++++++++--------- .../serializer/AspectSerializerTest.java | 6 +- .../aspect/edit/AspectEditMoveCommand.java | 52 ++--- 7 files changed, 149 insertions(+), 160 deletions(-) rename core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/{AspectChangeContext.java => AspectChangeManager.java} (87%) rename core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/{AspectChangeContextConfig.java => AspectChangeManagerConfig.java} (80%) rename core/esmf-aspect-meta-model-java/src/test/java/org/eclipse/esmf/aspectmodel/edit/{AspectChangeContextTest.java => AspectChangeManagerTest.java} (75%) diff --git a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/AspectChangeContext.java b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/AspectChangeManager.java similarity index 87% rename from core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/AspectChangeContext.java rename to core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/AspectChangeManager.java index 42ec97f88..7e38eb36e 100644 --- a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/AspectChangeContext.java +++ b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/AspectChangeManager.java @@ -16,7 +16,6 @@ import java.util.ArrayDeque; import java.util.Deque; import java.util.HashMap; -import java.util.List; import java.util.Map; import java.util.Optional; import java.util.stream.Stream; @@ -30,13 +29,13 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -public class AspectChangeContext implements ChangeContext { - private static final Logger LOG = LoggerFactory.getLogger( AspectChangeContext.class ); +public class AspectChangeManager implements ChangeContext { + private static final Logger LOG = LoggerFactory.getLogger( AspectChangeManager.class ); private final Deque undoStack = new ArrayDeque<>(); private final Deque redoStack = new ArrayDeque<>(); private final DefaultAspectModel aspectModel; - private final AspectChangeContextConfig config; + private final AspectChangeManagerConfig config; private final Map fileState = new HashMap<>(); private boolean isUndoOperation = false; @@ -44,7 +43,7 @@ private enum FileState { CREATED, CHANGED, REMOVED } - public AspectChangeContext( final AspectChangeContextConfig config, final AspectModel aspectModel ) { + public AspectChangeManager( final AspectChangeManagerConfig config, final AspectModel aspectModel ) { this.config = config; resetFileStates(); if ( aspectModel instanceof final DefaultAspectModel defaultAspectModel ) { @@ -54,8 +53,8 @@ public AspectChangeContext( final AspectChangeContextConfig config, final Aspect } } - public AspectChangeContext( final AspectModel aspectModel ) { - this( AspectChangeContextConfigBuilder.builder().build(), aspectModel ); + public AspectChangeManager( final AspectModel aspectModel ) { + this( AspectChangeManagerConfigBuilder.builder().build(), aspectModel ); } public synchronized ChangeReport applyChange( final Change change ) { @@ -125,32 +124,29 @@ public Stream aspectModelFiles() { } @Override - public AspectChangeContextConfig config() { + public AspectChangeManagerConfig config() { return config; } @Override - public List createdFiles() { + public Stream createdFiles() { return fileState.entrySet().stream() .filter( entry -> entry.getValue() == FileState.CREATED ) - .map( Map.Entry::getKey ) - .toList(); + .map( Map.Entry::getKey ); } @Override - public List modifiedFiles() { + public Stream modifiedFiles() { return fileState.entrySet().stream() .filter( entry -> entry.getValue() == FileState.CHANGED ) - .map( Map.Entry::getKey ) - .toList(); + .map( Map.Entry::getKey ); } @Override - public List removedFiles() { + public Stream removedFiles() { return fileState.entrySet().stream() .filter( entry -> entry.getValue() == FileState.REMOVED ) - .map( Map.Entry::getKey ) - .toList(); + .map( Map.Entry::getKey ); } @Override diff --git a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/AspectChangeContextConfig.java b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/AspectChangeManagerConfig.java similarity index 80% rename from core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/AspectChangeContextConfig.java rename to core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/AspectChangeManagerConfig.java index 9519ee739..9c5845830 100644 --- a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/AspectChangeContextConfig.java +++ b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/AspectChangeManagerConfig.java @@ -18,13 +18,13 @@ import io.soabase.recordbuilder.core.RecordBuilder; @RecordBuilder -public record AspectChangeContextConfig( +public record AspectChangeManagerConfig( List defaultFileHeader, boolean detailedChangeReport ) { - public static AspectChangeContextConfig DEFAULT = AspectChangeContextConfigBuilder.builder().build(); + public static AspectChangeManagerConfig DEFAULT = AspectChangeManagerConfigBuilder.builder().build(); - public AspectChangeContextConfig { + public AspectChangeManagerConfig { if ( defaultFileHeader == null ) { defaultFileHeader = List.of(); } diff --git a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/ChangeContext.java b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/ChangeContext.java index b6476a4b1..0579c9f92 100644 --- a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/ChangeContext.java +++ b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/ChangeContext.java @@ -13,7 +13,6 @@ package org.eclipse.esmf.aspectmodel.edit; -import java.util.List; import java.util.stream.Stream; import org.eclipse.esmf.aspectmodel.AspectModelFile; @@ -21,13 +20,13 @@ public interface ChangeContext { Stream aspectModelFiles(); - AspectChangeContextConfig config(); + AspectChangeManagerConfig config(); - List createdFiles(); + Stream createdFiles(); - List modifiedFiles(); + Stream modifiedFiles(); - List removedFiles(); + Stream removedFiles(); void indicateFileIsAdded( AspectModelFile file ); diff --git a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/ChangeReportFormatter.java b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/ChangeReportFormatter.java index 1467ab116..7b2bfcd00 100644 --- a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/ChangeReportFormatter.java +++ b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/ChangeReportFormatter.java @@ -23,7 +23,7 @@ import org.apache.jena.rdf.model.Model; import org.apache.jena.rdf.model.ModelFactory; -public class ChangeReportFormatter implements BiFunction { +public class ChangeReportFormatter implements BiFunction { public static final ChangeReportFormatter INSTANCE = new ChangeReportFormatter(); private ChangeReportFormatter() { @@ -37,7 +37,7 @@ private void handleSimpleEntry( final StringBuilder builder, final ChangeReport. } private void handleMultipleEntries( final StringBuilder builder, final ChangeReport.MultipleEntries multipleEntries, final String indent, - final int indentationLevel, final AspectChangeContextConfig config ) { + final int indentationLevel, final AspectChangeManagerConfig config ) { if ( multipleEntries.summary() != null ) { builder.append( indent ); builder.append( "- " ); @@ -58,7 +58,7 @@ private void handleMultipleEntries( final StringBuilder builder, final ChangeRep } private void handleEntryWithDetails( final StringBuilder builder, final ChangeReport.EntryWithDetails entryWithDetails, - final String indent, final AspectChangeContextConfig config ) { + final String indent, final AspectChangeManagerConfig config ) { builder.append( indent ); builder.append( "- " ); builder.append( entryWithDetails.summary() ); @@ -99,7 +99,7 @@ private void handleEntryWithDetails( final StringBuilder builder, final ChangeRe } } - private void append( final StringBuilder builder, final ChangeReport report, final AspectChangeContextConfig config, + private void append( final StringBuilder builder, final ChangeReport report, final AspectChangeManagerConfig config, final int indentationLevel ) { final String indent = " ".repeat( indentationLevel ); if ( report instanceof final ChangeReport.SimpleEntry simpleEntry ) { @@ -123,7 +123,7 @@ private String show( final Model model ) { } @Override - public String apply( final ChangeReport changeReport, final AspectChangeContextConfig config ) { + public String apply( final ChangeReport changeReport, final AspectChangeManagerConfig config ) { final StringBuilder builder = new StringBuilder(); append( builder, changeReport, config, 0 ); return builder.toString(); diff --git a/core/esmf-aspect-meta-model-java/src/test/java/org/eclipse/esmf/aspectmodel/edit/AspectChangeContextTest.java b/core/esmf-aspect-meta-model-java/src/test/java/org/eclipse/esmf/aspectmodel/edit/AspectChangeManagerTest.java similarity index 75% rename from core/esmf-aspect-meta-model-java/src/test/java/org/eclipse/esmf/aspectmodel/edit/AspectChangeContextTest.java rename to core/esmf-aspect-meta-model-java/src/test/java/org/eclipse/esmf/aspectmodel/edit/AspectChangeManagerTest.java index ad47a0747..c76fc1add 100644 --- a/core/esmf-aspect-meta-model-java/src/test/java/org/eclipse/esmf/aspectmodel/edit/AspectChangeContextTest.java +++ b/core/esmf-aspect-meta-model-java/src/test/java/org/eclipse/esmf/aspectmodel/edit/AspectChangeManagerTest.java @@ -45,7 +45,7 @@ import org.apache.jena.vocabulary.RDF; import org.junit.jupiter.api.Test; -public class AspectChangeContextTest { +public class AspectChangeManagerTest { @Test void testRenameElement() { final AspectModel aspectModel = TestResources.load( TestAspect.ASPECT ); @@ -55,19 +55,19 @@ void testRenameElement() { assertThat( aspectModel.aspect().urn().getName() ).isEqualTo( originalName ); final String newName = "RenamedAspect"; - final AspectChangeContext ctx = new AspectChangeContext( aspectModel ); + final AspectChangeManager changeManager = new AspectChangeManager( aspectModel ); final Change renameAspect = new RenameElement( aspect, newName ); - ctx.applyChange( renameAspect ); + changeManager.applyChange( renameAspect ); assertThat( aspectModel.aspect().getName() ).isEqualTo( newName ); assertThat( aspectModel.files().get( 0 ).sourceModel().listStatements( null, RDF.type, SammNs.SAMM.Aspect() ) .nextStatement().getSubject().getURI() ).endsWith( newName ); - assertThat( ctx.modifiedFiles() ).hasSize( 1 ); + assertThat( changeManager.modifiedFiles() ).hasSize( 1 ); - ctx.undoChange(); - assertThat( ctx.modifiedFiles() ).hasSize( 1 ); + changeManager.undoChange(); + assertThat( changeManager.modifiedFiles() ).hasSize( 1 ); assertThat( aspectModel.aspect().getName() ).isEqualTo( originalName ); - ctx.redoChange(); - assertThat( ctx.modifiedFiles() ).hasSize( 1 ); + changeManager.redoChange(); + assertThat( changeManager.modifiedFiles() ).hasSize( 1 ); assertThat( aspectModel.aspect().getName() ).isEqualTo( newName ); } @@ -81,13 +81,13 @@ void testUndoRedo() { assertThat( aspectModel.aspect().urn().getName() ).isEqualTo( originalName ); final String newName = "RenamedAspect"; - final AspectChangeContext ctx = new AspectChangeContext( aspectModel ); + final AspectChangeManager changeManager = new AspectChangeManager( aspectModel ); final Change renameAspect = new RenameElement( aspectUrn, newName ); - ctx.applyChange( renameAspect ); + changeManager.applyChange( renameAspect ); assertThat( aspectModel.aspect().getName() ).isEqualTo( newName ); - ctx.undoChange(); + changeManager.undoChange(); assertThat( aspectModel.aspect().getName() ).isEqualTo( originalName ); - ctx.redoChange(); + changeManager.redoChange(); assertThat( aspectModel.aspect().getName() ).isEqualTo( newName ); } @@ -101,12 +101,12 @@ void testChangeGroups() { final String newAspectName = "RenamedAspect"; final String newPropertyName = "renamedProperty"; - final AspectChangeContext ctx = new AspectChangeContext( aspectModel ); + final AspectChangeManager changeManager = new AspectChangeManager( aspectModel ); final Change renameAspect = new RenameElement( aspectUrn, newAspectName ); final Change renameProperty = new RenameElement( property.urn(), newPropertyName ); final Change group = new ChangeGroup( renameAspect, renameProperty ); - ctx.applyChange( group ); + changeManager.applyChange( group ); assertThat( aspectModel.aspect().urn().getName() ).isEqualTo( newAspectName ); assertThat( aspectModel.aspect().getProperties().get( 0 ).getName() ).isEqualTo( newPropertyName ); } @@ -116,20 +116,20 @@ void testCreateFile() { final AspectModel aspectModel = new AspectModelLoader().emptyModel(); assertThat( aspectModel.files() ).isEmpty(); assertThat( aspectModel.elements() ).isEmpty(); - final AspectChangeContext ctx = new AspectChangeContext( aspectModel ); + final AspectChangeManager changeManager = new AspectChangeManager( aspectModel ); final AspectModelFile aspectModelFile = RawAspectModelFileBuilder.builder() .sourceLocation( Optional.of( URI.create( "file:///temp/test.ttl" ) ) ) .build(); final Change addFile = new AddAspectModelFile( aspectModelFile ); - ctx.applyChange( addFile ); - assertThat( ctx.createdFiles() ).hasSize( 1 ); + changeManager.applyChange( addFile ); + assertThat( changeManager.createdFiles() ).hasSize( 1 ); assertThat( aspectModel.files() ).hasSize( 1 ); - ctx.undoChange(); - assertThat( ctx.createdFiles() ).isEmpty(); - assertThat( ctx.removedFiles() ).hasSize( 1 ); + changeManager.undoChange(); + assertThat( changeManager.createdFiles() ).isEmpty(); + assertThat( changeManager.removedFiles() ).hasSize( 1 ); assertThat( aspectModel.files() ).isEmpty(); - ctx.redoChange(); - assertThat( ctx.createdFiles() ).hasSize( 1 ); + changeManager.redoChange(); + assertThat( changeManager.createdFiles() ).hasSize( 1 ); assertThat( aspectModel.files() ).hasSize( 1 ); } @@ -141,15 +141,15 @@ void testRemoveFile() { final AspectModel aspectModel = new AspectModelLoader().loadAspectModelFiles( List.of( aspectModelFile ) ); assertThat( aspectModel.files() ).hasSize( 1 ); assertThat( aspectModel.elements() ).isEmpty(); - final AspectChangeContext ctx = new AspectChangeContext( aspectModel ); - ctx.applyChange( new RemoveAspectModelFile( aspectModel.files().get( 0 ) ) ); - assertThat( ctx.removedFiles() ).hasSize( 1 ); + final AspectChangeManager changeManager = new AspectChangeManager( aspectModel ); + changeManager.applyChange( new RemoveAspectModelFile( aspectModel.files().get( 0 ) ) ); + assertThat( changeManager.removedFiles() ).hasSize( 1 ); assertThat( aspectModel.files() ).isEmpty(); - ctx.undoChange(); - assertThat( ctx.removedFiles() ).isEmpty(); + changeManager.undoChange(); + assertThat( changeManager.removedFiles() ).isEmpty(); assertThat( aspectModel.files() ).hasSize( 1 ); - ctx.redoChange(); - assertThat( ctx.removedFiles() ).hasSize( 1 ); + changeManager.redoChange(); + assertThat( changeManager.removedFiles() ).hasSize( 1 ); assertThat( aspectModel.files() ).isEmpty(); } @@ -158,7 +158,7 @@ void testCreateFileWithElementDefinition() { final AspectModel aspectModel = new AspectModelLoader().emptyModel(); assertThat( aspectModel.files() ).isEmpty(); assertThat( aspectModel.elements() ).isEmpty(); - final AspectChangeContext ctx = new AspectChangeContext( aspectModel ); + final AspectChangeManager changeManager = new AspectChangeManager( aspectModel ); final AspectModelFile aspectModelFile = RawAspectModelFileBuilder.builder() .sourceLocation( Optional.of( URI.create( "file:///temp/test.ttl" ) ) ) .sourceModel( createModel( """ @@ -174,16 +174,16 @@ void testCreateFileWithElementDefinition() { ) ) .build(); final Change addFile = new AddAspectModelFile( aspectModelFile ); - ctx.applyChange( addFile ); - assertThat( ctx.createdFiles() ).hasSize( 1 ); + changeManager.applyChange( addFile ); + assertThat( changeManager.createdFiles() ).hasSize( 1 ); assertThat( aspectModel.files() ).hasSize( 1 ); assertThat( aspectModel.aspects() ).hasSize( 1 ); assertThat( aspectModel.aspect().getName() ).isEqualTo( "Aspect" ); - ctx.undoChange(); - assertThat( ctx.createdFiles() ).isEmpty(); + changeManager.undoChange(); + assertThat( changeManager.createdFiles() ).isEmpty(); assertThat( aspectModel.files() ).isEmpty(); - ctx.redoChange(); - assertThat( ctx.createdFiles() ).hasSize( 1 ); + changeManager.redoChange(); + assertThat( changeManager.createdFiles() ).hasSize( 1 ); assertThat( aspectModel.files() ).hasSize( 1 ); } @@ -192,7 +192,7 @@ void testCreateFileThenAddElementDefinition() { final AspectModel aspectModel = new AspectModelLoader().emptyModel(); assertThat( aspectModel.files() ).isEmpty(); assertThat( aspectModel.elements() ).isEmpty(); - final AspectChangeContext ctx = new AspectChangeContext( aspectModel ); + final AspectChangeManager changeManager = new AspectChangeManager( aspectModel ); final AspectModelFile aspectModelFile = RawAspectModelFileBuilder.builder() .sourceLocation( Optional.of( URI.create( "file:///temp/test.ttl" ) ) ) .build(); @@ -212,17 +212,17 @@ void testCreateFileThenAddElementDefinition() { """ ), aspectModelFile ) ); - ctx.applyChange( changes ); - assertThat( ctx.createdFiles() ).hasSize( 1 ); + changeManager.applyChange( changes ); + assertThat( changeManager.createdFiles() ).hasSize( 1 ); assertThat( aspectModel.files() ).hasSize( 1 ); assertThat( aspectModel.aspects() ).hasSize( 1 ); assertThat( aspectModel.aspect().getName() ).isEqualTo( "Aspect" ); - ctx.undoChange(); - assertThat( ctx.createdFiles() ).isEmpty(); - assertThat( ctx.removedFiles() ).hasSize( 1 ); + changeManager.undoChange(); + assertThat( changeManager.createdFiles() ).isEmpty(); + assertThat( changeManager.removedFiles() ).hasSize( 1 ); assertThat( aspectModel.files() ).isEmpty(); - ctx.redoChange(); - assertThat( ctx.createdFiles() ).hasSize( 1 ); + changeManager.redoChange(); + assertThat( changeManager.createdFiles() ).hasSize( 1 ); assertThat( aspectModel.files() ).hasSize( 1 ); } @@ -231,30 +231,30 @@ void testMoveElementToNewFile() { final AspectModel aspectModel = TestResources.load( TestAspect.ASPECT ); assertThat( aspectModel.files() ).hasSize( 1 ); final URI originalSourceLocation = aspectModel.aspect().getSourceFile().sourceLocation().get(); - final AspectChangeContext ctx = new AspectChangeContext( aspectModel ); + final AspectChangeManager changeManager = new AspectChangeManager( aspectModel ); final URI sourceLocation = URI.create( "file:///temp/test.ttl" ); final Change move = new MoveElementToNewFile( aspectModel.aspect(), Optional.of( sourceLocation ) ); - ctx.applyChange( move ); - assertThat( ctx.createdFiles() ).hasSize( 1 ); - assertThat( ctx.modifiedFiles() ).hasSize( 1 ); + changeManager.applyChange( move ); + assertThat( changeManager.createdFiles() ).hasSize( 1 ); + assertThat( changeManager.modifiedFiles() ).hasSize( 1 ); assertThat( aspectModel.files() ).hasSize( 2 ); assertThat( aspectModel.aspect().getSourceFile().sourceLocation() ).contains( sourceLocation ); assertThat( aspectModel.aspect().getSourceFile().sourceModel().listStatements( null, RDF.type, SammNs.SAMM.Aspect() ).nextStatement() .getSubject().getURI() ).isEqualTo( aspectModel.aspect().urn().toString() ); - ctx.undoChange(); - assertThat( ctx.createdFiles() ).isEmpty(); - assertThat( ctx.removedFiles() ).hasSize( 1 ); - assertThat( ctx.modifiedFiles() ).hasSize( 1 ); + changeManager.undoChange(); + assertThat( changeManager.createdFiles() ).isEmpty(); + assertThat( changeManager.removedFiles() ).hasSize( 1 ); + assertThat( changeManager.modifiedFiles() ).hasSize( 1 ); assertThat( aspectModel.files() ).hasSize( 1 ); assertThat( aspectModel.aspect().getSourceFile().sourceLocation() ).contains( originalSourceLocation ); assertThat( aspectModel.aspect().getSourceFile().sourceModel().listStatements( null, RDF.type, SammNs.SAMM.Aspect() ).nextStatement() .getSubject().getURI() ).isEqualTo( aspectModel.aspect().urn().toString() ); - ctx.redoChange(); - assertThat( ctx.createdFiles() ).hasSize( 1 ); - assertThat( ctx.modifiedFiles() ).hasSize( 1 ); + changeManager.redoChange(); + assertThat( changeManager.createdFiles() ).hasSize( 1 ); + assertThat( changeManager.modifiedFiles() ).hasSize( 1 ); assertThat( aspectModel.files() ).hasSize( 2 ); assertThat( aspectModel.aspect().getSourceFile().sourceLocation() ).contains( sourceLocation ); assertThat( aspectModel.aspect().getSourceFile().sourceModel().listStatements( null, RDF.type, SammNs.SAMM.Aspect() ).nextStatement() @@ -266,7 +266,7 @@ void testMoveElementToExistingFile() { final Optional file1Location = Optional.of( URI.create( "file:///file1.ttl" ) ); final Optional file2Location = Optional.of( URI.create( "file:///file2.ttl" ) ); final AspectModel aspectModel = new AspectModelLoader().emptyModel(); - final AspectChangeContext ctx = new AspectChangeContext( aspectModel ); + final AspectChangeManager changeManager = new AspectChangeManager( aspectModel ); final AspectModelFile file1 = RawAspectModelFileBuilder.builder() .sourceLocation( file1Location ) .sourceModel( createModel( """ @@ -283,23 +283,23 @@ void testMoveElementToExistingFile() { .build(); final AspectModelFile file2 = RawAspectModelFileBuilder.builder().sourceLocation( file2Location ).build(); - ctx.applyChange( new ChangeGroup( + changeManager.applyChange( new ChangeGroup( new AddAspectModelFile( file1 ), new AddAspectModelFile( file2 ) ) ); assertThat( aspectModel.aspect().getSourceFile().sourceLocation() ).isEqualTo( file1Location ); final Change move = new MoveElementToExistingFile( aspectModel.aspect(), file2 ); - ctx.applyChange( move ); - assertThat( ctx.modifiedFiles() ).hasSize( 2 ); - assertThat( ctx.createdFiles() ).isEmpty(); - assertThat( ctx.removedFiles() ).isEmpty(); + changeManager.applyChange( move ); + assertThat( changeManager.modifiedFiles() ).hasSize( 2 ); + assertThat( changeManager.createdFiles() ).isEmpty(); + assertThat( changeManager.removedFiles() ).isEmpty(); assertThat( aspectModel.aspect().getSourceFile().sourceLocation() ).isEqualTo( file2Location ); - ctx.undoChange(); - assertThat( ctx.modifiedFiles() ).hasSize( 2 ); - assertThat( ctx.createdFiles() ).isEmpty(); - assertThat( ctx.removedFiles() ).isEmpty(); + changeManager.undoChange(); + assertThat( changeManager.modifiedFiles() ).hasSize( 2 ); + assertThat( changeManager.createdFiles() ).isEmpty(); + assertThat( changeManager.removedFiles() ).isEmpty(); assertThat( aspectModel.aspect().getSourceFile().sourceLocation() ).isEqualTo( file1Location ); } @@ -308,34 +308,34 @@ void testMoveElementToOtherNamespaceNewFile() { final AspectModel aspectModel = TestResources.load( TestAspect.ASPECT ); assertThat( aspectModel.files() ).hasSize( 1 ); final URI originalSourceLocation = aspectModel.aspect().getSourceFile().sourceLocation().get(); - final AspectChangeContext ctx = new AspectChangeContext( aspectModel ); + final AspectChangeManager changeManager = new AspectChangeManager( aspectModel ); final URI sourceLocation = URI.create( "file:///temp/test.ttl" ); final AspectModelUrn targetUrn = AspectModelUrn.fromUrn( "urn:samm:org.eclipse.esmf.example.new:1.0.0#Aspect" ); final Namespace targetNamespace = new DefaultNamespace( targetUrn, List.of(), Optional.empty() ); final Change move = new MoveElementToOtherNamespaceNewFile( aspectModel.aspect(), targetNamespace, Optional.of( sourceLocation ) ); - ctx.applyChange( move ); - assertThat( ctx.modifiedFiles() ).hasSize( 1 ); - assertThat( ctx.createdFiles() ).hasSize( 1 ); + changeManager.applyChange( move ); + assertThat( changeManager.modifiedFiles() ).hasSize( 1 ); + assertThat( changeManager.createdFiles() ).hasSize( 1 ); assertThat( aspectModel.files() ).hasSize( 2 ); assertThat( aspectModel.aspect().getSourceFile().sourceLocation() ).contains( sourceLocation ); assertThat( aspectModel.aspect().getSourceFile().sourceModel().listStatements( null, RDF.type, SammNs.SAMM.Aspect() ).nextStatement() .getSubject().getURI() ).isEqualTo( aspectModel.aspect().urn().toString() ); assertThat( aspectModel.aspect().urn() ).isEqualTo( targetUrn ); - ctx.undoChange(); - assertThat( ctx.modifiedFiles() ).hasSize( 1 ); - assertThat( ctx.removedFiles() ).hasSize( 1 ); - assertThat( ctx.createdFiles() ).isEmpty(); + changeManager.undoChange(); + assertThat( changeManager.modifiedFiles() ).hasSize( 1 ); + assertThat( changeManager.removedFiles() ).hasSize( 1 ); + assertThat( changeManager.createdFiles() ).isEmpty(); assertThat( aspectModel.files() ).hasSize( 1 ); assertThat( aspectModel.aspect().getSourceFile().sourceLocation() ).contains( originalSourceLocation ); assertThat( aspectModel.aspect().getSourceFile().sourceModel().listStatements( null, RDF.type, SammNs.SAMM.Aspect() ).nextStatement() .getSubject().getURI() ).isEqualTo( aspectModel.aspect().urn().toString() ); - ctx.redoChange(); - assertThat( ctx.modifiedFiles() ).hasSize( 1 ); - assertThat( ctx.createdFiles() ).hasSize( 1 ); + changeManager.redoChange(); + assertThat( changeManager.modifiedFiles() ).hasSize( 1 ); + assertThat( changeManager.createdFiles() ).hasSize( 1 ); assertThat( aspectModel.files() ).hasSize( 2 ); assertThat( aspectModel.aspect().getSourceFile().sourceLocation() ).contains( sourceLocation ); assertThat( aspectModel.aspect().getSourceFile().sourceModel().listStatements( null, RDF.type, SammNs.SAMM.Aspect() ).nextStatement() @@ -347,7 +347,7 @@ void testMoveElementToOtherNamespaceExistingFile() { final Optional file1Location = Optional.of( URI.create( "file:///file1.ttl" ) ); final Optional file2Location = Optional.of( URI.create( "file:///file2.ttl" ) ); final AspectModel aspectModel = new AspectModelLoader().emptyModel(); - final AspectChangeContext ctx = new AspectChangeContext( aspectModel ); + final AspectChangeManager changeManager = new AspectChangeManager( aspectModel ); final AspectModelFile file1 = RawAspectModelFileBuilder.builder() .sourceLocation( file1Location ) .sourceModel( createModel( """ @@ -364,29 +364,29 @@ void testMoveElementToOtherNamespaceExistingFile() { .build(); final AspectModelFile file2 = RawAspectModelFileBuilder.builder().sourceLocation( file2Location ).build(); - ctx.applyChange( new ChangeGroup( + changeManager.applyChange( new ChangeGroup( new AddAspectModelFile( file1 ), new AddAspectModelFile( file2 ) ) ); - ctx.resetFileStates(); + changeManager.resetFileStates(); assertThat( aspectModel.aspect().getSourceFile().sourceLocation() ).isEqualTo( file1Location ); final AspectModelUrn targetUrn = AspectModelUrn.fromUrn( "urn:samm:org.eclipse.esmf.example.new:1.0.0#Aspect" ); final Namespace targetNamespace = new DefaultNamespace( targetUrn, List.of(), Optional.empty() ); final Change move = new MoveElementToOtherNamespaceExistingFile( aspectModel.aspect(), file2, targetNamespace ); - ctx.applyChange( move ); - assertThat( ctx.modifiedFiles() ).hasSize( 2 ); - assertThat( ctx.createdFiles() ).isEmpty(); + changeManager.applyChange( move ); + assertThat( changeManager.modifiedFiles() ).hasSize( 2 ); + assertThat( changeManager.createdFiles() ).isEmpty(); assertThat( aspectModel.aspect().getSourceFile().sourceLocation() ).isEqualTo( file2Location ); assertThat( aspectModel.aspect().getSourceFile().sourceModel().listStatements( null, RDF.type, SammNs.SAMM.Aspect() ).nextStatement() .getSubject().getURI() ).isEqualTo( aspectModel.aspect().urn().toString() ); assertThat( aspectModel.aspect().urn() ).isEqualTo( targetUrn ); - ctx.undoChange(); - assertThat( ctx.modifiedFiles() ).hasSize( 2 ); - assertThat( ctx.createdFiles() ).isEmpty(); + changeManager.undoChange(); + assertThat( changeManager.modifiedFiles() ).hasSize( 2 ); + assertThat( changeManager.createdFiles() ).isEmpty(); assertThat( aspectModel.aspect().getSourceFile().sourceLocation() ).isEqualTo( file1Location ); assertThat( aspectModel.aspect().getSourceFile().sourceModel().listStatements( null, RDF.type, SammNs.SAMM.Aspect() ).nextStatement() .getSubject().getURI() ).isEqualTo( aspectModel.aspect().urn().toString() ); @@ -400,28 +400,28 @@ void testMoveRenameFile() { assertThat( aspectModel.files() ).hasSize( 1 ); final URI originalLocation = aspect.getSourceFile().sourceLocation().get(); - final AspectChangeContext ctx = new AspectChangeContext( aspectModel ); + final AspectChangeManager changeManager = new AspectChangeManager( aspectModel ); final URI newLocation = URI.create( "file:///temp/test.ttl" ); final Change renameFile = new MoveRenameAspectModelFile( aspect.getSourceFile(), newLocation ); - ctx.applyChange( renameFile ); - assertThat( ctx.modifiedFiles() ).isEmpty(); - assertThat( ctx.createdFiles() ).hasSize( 1 ); - assertThat( ctx.removedFiles() ).hasSize( 1 ); + changeManager.applyChange( renameFile ); + assertThat( changeManager.modifiedFiles() ).isEmpty(); + assertThat( changeManager.createdFiles() ).hasSize( 1 ); + assertThat( changeManager.removedFiles() ).hasSize( 1 ); assertThat( aspectModel.files() ).hasSize( 1 ); assertThat( aspectModel.aspect().getSourceFile().sourceLocation() ).contains( newLocation ); - ctx.undoChange(); - assertThat( ctx.modifiedFiles() ).isEmpty(); - assertThat( ctx.createdFiles() ).hasSize( 1 ); - assertThat( ctx.removedFiles() ).hasSize( 1 ); + changeManager.undoChange(); + assertThat( changeManager.modifiedFiles() ).isEmpty(); + assertThat( changeManager.createdFiles() ).hasSize( 1 ); + assertThat( changeManager.removedFiles() ).hasSize( 1 ); assertThat( aspectModel.files() ).hasSize( 1 ); assertThat( aspectModel.aspect().getSourceFile().sourceLocation() ).contains( originalLocation ); - ctx.redoChange(); - assertThat( ctx.modifiedFiles() ).isEmpty(); - assertThat( ctx.createdFiles() ).hasSize( 1 ); - assertThat( ctx.removedFiles() ).hasSize( 1 ); + changeManager.redoChange(); + assertThat( changeManager.modifiedFiles() ).isEmpty(); + assertThat( changeManager.createdFiles() ).hasSize( 1 ); + assertThat( changeManager.removedFiles() ).hasSize( 1 ); assertThat( aspectModel.files() ).hasSize( 1 ); assertThat( aspectModel.aspect().getSourceFile().sourceLocation() ).contains( newLocation ); } diff --git a/core/esmf-aspect-model-serializer/src/test/java/org/eclipse/esmf/aspectmodel/serializer/AspectSerializerTest.java b/core/esmf-aspect-model-serializer/src/test/java/org/eclipse/esmf/aspectmodel/serializer/AspectSerializerTest.java index b4b316c0e..901146a15 100644 --- a/core/esmf-aspect-model-serializer/src/test/java/org/eclipse/esmf/aspectmodel/serializer/AspectSerializerTest.java +++ b/core/esmf-aspect-model-serializer/src/test/java/org/eclipse/esmf/aspectmodel/serializer/AspectSerializerTest.java @@ -25,7 +25,7 @@ import java.util.stream.Stream; import org.eclipse.esmf.aspectmodel.AspectModelFile; -import org.eclipse.esmf.aspectmodel.edit.AspectChangeContext; +import org.eclipse.esmf.aspectmodel.edit.AspectChangeManager; import org.eclipse.esmf.aspectmodel.edit.ChangeGroup; import org.eclipse.esmf.aspectmodel.edit.change.AddAspectModelFile; import org.eclipse.esmf.aspectmodel.edit.change.MoveRenameAspectModelFile; @@ -73,7 +73,7 @@ void testWriteAspectModelFileToFileSystem() { final AspectModel aspectModel = TestResources.load( TestAspect.ASPECT ); // Change the loaded test model's source location to a file system location - final AspectChangeContext changeContext = new AspectChangeContext( aspectModel ); + final AspectChangeManager changeContext = new AspectChangeManager( aspectModel ); final Path filePath = outputDirectory.resolve( "Aspect.ttl" ); changeContext.applyChange( new MoveRenameAspectModelFile( aspectModel.files().iterator().next(), filePath.toUri() ) ); @@ -118,7 +118,7 @@ void testWriteAspectModelToFileSystem() { """ ) ) .build(); - final AspectChangeContext changeContext = new AspectChangeContext( aspectModel ); + final AspectChangeManager changeContext = new AspectChangeManager( aspectModel ); changeContext.applyChange( new ChangeGroup( new AddAspectModelFile( file1 ), new AddAspectModelFile( file2 ) diff --git a/tools/samm-cli/src/main/java/org/eclipse/esmf/aspect/edit/AspectEditMoveCommand.java b/tools/samm-cli/src/main/java/org/eclipse/esmf/aspect/edit/AspectEditMoveCommand.java index 22f9cbdec..18b8a2e96 100644 --- a/tools/samm-cli/src/main/java/org/eclipse/esmf/aspect/edit/AspectEditMoveCommand.java +++ b/tools/samm-cli/src/main/java/org/eclipse/esmf/aspect/edit/AspectEditMoveCommand.java @@ -15,7 +15,6 @@ import java.io.File; import java.net.URI; -import java.net.URL; import java.nio.file.Paths; import java.util.ArrayList; import java.util.List; @@ -27,9 +26,9 @@ import org.eclipse.esmf.LoggingMixin; import org.eclipse.esmf.aspect.AspectEditCommand; import org.eclipse.esmf.aspectmodel.AspectModelFile; -import org.eclipse.esmf.aspectmodel.edit.AspectChangeContext; -import org.eclipse.esmf.aspectmodel.edit.AspectChangeContextConfig; -import org.eclipse.esmf.aspectmodel.edit.AspectChangeContextConfigBuilder; +import org.eclipse.esmf.aspectmodel.edit.AspectChangeManager; +import org.eclipse.esmf.aspectmodel.edit.AspectChangeManagerConfig; +import org.eclipse.esmf.aspectmodel.edit.AspectChangeManagerConfigBuilder; import org.eclipse.esmf.aspectmodel.edit.Change; import org.eclipse.esmf.aspectmodel.edit.ChangeReport; import org.eclipse.esmf.aspectmodel.edit.ChangeReportFormatter; @@ -275,11 +274,11 @@ private ModelElement determineModelElementToMove( final AspectModel aspectModel return potentialElements.get( 0 ); } - private Optional performRefactoring( final AspectModel aspectModel, final Change change ) { - final AspectChangeContextConfig config = AspectChangeContextConfigBuilder.builder() + private Optional performRefactoring( final AspectModel aspectModel, final Change change ) { + final AspectChangeManagerConfig config = AspectChangeManagerConfigBuilder.builder() .detailedChangeReport( details ) .build(); - final AspectChangeContext changeContext = new AspectChangeContext( config, aspectModel ); + final AspectChangeManager changeContext = new AspectChangeManager( config, aspectModel ); final ChangeReport changeReport = changeContext.applyChange( change ); if ( dryRun ) { System.out.println( "Changes to be performed" ); @@ -290,27 +289,24 @@ private Optional performRefactoring( final AspectModel aspe return Optional.of( changeContext ); } - private void performFileSystemWrite( final AspectChangeContext changeContext ) { - for ( final AspectModelFile fileToRemove : changeContext.removedFiles() ) { - final File file = Paths.get( fileToRemove.sourceLocation().orElseThrow() ).toFile(); - if ( !file.delete() ) { - throw new CommandException( "Could not delete file: " + file ); - } - } - for ( final AspectModelFile fileToCreate : changeContext.createdFiles() ) { + private void performFileSystemWrite( final AspectChangeManager changeContext ) { + changeContext.removedFiles() + .map( fileToRemove -> Paths.get( fileToRemove.sourceLocation().orElseThrow() ).toFile() ) + .filter( file -> !file.delete() ) + .forEach( file -> { + throw new CommandException( "Could not delete file: " + file ); + } ); + changeContext.createdFiles().forEach( fileToCreate -> { final File file = Paths.get( fileToCreate.sourceLocation().orElseThrow() ).toFile(); file.getParentFile().mkdirs(); AspectSerializer.INSTANCE.write( fileToCreate ); - } - for ( final AspectModelFile fileToModify : changeContext.modifiedFiles() ) { - AspectSerializer.INSTANCE.write( fileToModify ); - } + } ); + changeContext.modifiedFiles().forEach( AspectSerializer.INSTANCE::write ); } - private void checkFilesystemConsistency( final AspectChangeContext changeContext ) { + private void checkFilesystemConsistency( final AspectChangeManager changeContext ) { final List messages = new ArrayList<>(); - for ( final AspectModelFile fileToRemove : changeContext.removedFiles() ) { - final URL url = AspectSerializer.INSTANCE.aspectModelFileUrl( fileToRemove ); + changeContext.removedFiles().map( AspectSerializer.INSTANCE::aspectModelFileUrl ).forEach( url -> { if ( !url.getProtocol().equals( "file" ) ) { messages.add( "File should be removed, but it is not identified by a file: URL: " + url ); } @@ -318,10 +314,9 @@ private void checkFilesystemConsistency( final AspectChangeContext changeContext if ( !file.exists() ) { messages.add( "File should be removed, but it does not exist: " + file ); } - } + } ); - for ( final AspectModelFile fileToCreate : changeContext.createdFiles() ) { - final URL url = AspectSerializer.INSTANCE.aspectModelFileUrl( fileToCreate ); + changeContext.createdFiles().map( AspectSerializer.INSTANCE::aspectModelFileUrl ).forEach( url -> { if ( !url.getProtocol().equals( "file" ) ) { messages.add( "New file should be written, but it is not identified by a file: URL: " + url ); } @@ -333,10 +328,9 @@ private void checkFilesystemConsistency( final AspectChangeContext changeContext if ( file.exists() && force && !file.canWrite() ) { messages.add( "New file should be written, but it is not writable:" + file ); } - } + } ); - for ( final AspectModelFile fileToModify : changeContext.modifiedFiles() ) { - final URL url = AspectSerializer.INSTANCE.aspectModelFileUrl( fileToModify ); + changeContext.modifiedFiles().map( AspectSerializer.INSTANCE::aspectModelFileUrl ).forEach( url -> { if ( !url.getProtocol().equals( "file" ) ) { messages.add( "File should be modified, but it is not identified by a file: URL: " + url ); } @@ -350,7 +344,7 @@ private void checkFilesystemConsistency( final AspectChangeContext changeContext if ( !file.isFile() ) { messages.add( "File should be modified, but it is not a regular file: " + file ); } - } + } ); if ( !messages.isEmpty() ) { System.out.println( "Encountered problems, canceling writing." ); From 2d2fd3cf36259c2363fb28b7e2ab5dc3cfae50be Mon Sep 17 00:00:00 2001 From: "Textor Andreas (BCI/ESW17)" Date: Tue, 13 Aug 2024 08:35:29 +0200 Subject: [PATCH 27/33] Add JavaDoc to Change classes --- .../aspectmodel/edit/AspectChangeManager.java | 20 ++++++++++--- .../edit/AspectChangeManagerConfig.java | 2 -- .../eclipse/esmf/aspectmodel/edit/Change.java | 18 ++++++++++++ .../esmf/aspectmodel/edit/ChangeContext.java | 4 +++ .../esmf/aspectmodel/edit/ChangeGroup.java | 3 ++ .../esmf/aspectmodel/edit/ChangeReport.java | 3 ++ .../edit/ChangeReportFormatter.java | 3 ++ .../edit/change/AddAspectModelFile.java | 3 ++ .../edit/change/AddElementDefinition.java | 3 ++ .../edit/change/EditAspectModel.java | 17 ++++++++++- .../change/MoveElementToExistingFile.java | 3 ++ .../edit/change/MoveElementToNewFile.java | 3 ++ ...veElementToOtherNamespaceExistingFile.java | 3 ++ .../MoveElementToOtherNamespaceNewFile.java | 3 ++ .../change/MoveRenameAspectModelFile.java | 3 ++ .../edit/change/RemoveAspectModelFile.java | 3 ++ .../edit/change/RemoveElementDefinition.java | 3 ++ .../edit/change/RenameElement.java | 3 ++ .../aspectmodel/edit/change/RenameUrn.java | 4 +++ .../aspectmodel/loader/AspectModelLoader.java | 28 ++++++++++++++++++- 20 files changed, 124 insertions(+), 8 deletions(-) diff --git a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/AspectChangeManager.java b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/AspectChangeManager.java index 7e38eb36e..2fd66bc7d 100644 --- a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/AspectChangeManager.java +++ b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/AspectChangeManager.java @@ -29,6 +29,22 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +/** + * The AspectChangeManager is the central place to to changes/edits/refactorings of an {@link AspectModel}. The AspectChangeManager + * wraps an AspectModel and allows applying instances of the {@link Change} class using the {@link #applyChange(Change)} method. + * Calling this method returns a {@link ChangeReport} that describes the performed changes in a structured way. Use the + * {@link ChangeReportFormatter} to render the ChangeReport to a structured string representation. + *
+ * Note the following points: + *
    + *
  • Only one AspectChangeManager must wrap a given AspectModel at any time
  • + *
  • All changes are done in-memory. In order to write them to the file system, use the + * {@link org.eclipse.esmf.aspectmodel.serializer.AspectSerializer}
  • + *
  • After performing an {@link #applyChange(Change)}, {@link #undoChange()} or {@link #redoChange()} operation, and until the + * next call of one of them, the methods {@link #modifiedFiles()}, {@link #createdFiles()} and {@link #removedFiles()} indicate + * corresponding changes in the AspectModel's files. + *
+ */ public class AspectChangeManager implements ChangeContext { private static final Logger LOG = LoggerFactory.getLogger( AspectChangeManager.class ); @@ -37,7 +53,6 @@ public class AspectChangeManager implements ChangeContext { private final DefaultAspectModel aspectModel; private final AspectChangeManagerConfig config; private final Map fileState = new HashMap<>(); - private boolean isUndoOperation = false; private enum FileState { CREATED, CHANGED, REMOVED @@ -59,7 +74,6 @@ public AspectChangeManager( final AspectModel aspectModel ) { public synchronized ChangeReport applyChange( final Change change ) { resetFileStates(); - isUndoOperation = false; final ChangeReport result = change.fire( this ); updateAspectModelAfterChange(); undoStack.offerLast( change.reverse() ); @@ -70,7 +84,6 @@ public synchronized void undoChange() { if ( undoStack.isEmpty() ) { return; } - isUndoOperation = true; resetFileStates(); final Change change = undoStack.pollLast(); change.fire( this ); @@ -84,7 +97,6 @@ public synchronized void redoChange() { } resetFileStates(); final Change change = redoStack.pollLast(); - isUndoOperation = false; change.fire( this ); updateAspectModelAfterChange(); undoStack.offerLast( change.reverse() ); diff --git a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/AspectChangeManagerConfig.java b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/AspectChangeManagerConfig.java index 9c5845830..2af3383cd 100644 --- a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/AspectChangeManagerConfig.java +++ b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/AspectChangeManagerConfig.java @@ -22,8 +22,6 @@ public record AspectChangeManagerConfig( List defaultFileHeader, boolean detailedChangeReport ) { - public static AspectChangeManagerConfig DEFAULT = AspectChangeManagerConfigBuilder.builder().build(); - public AspectChangeManagerConfig { if ( defaultFileHeader == null ) { defaultFileHeader = List.of(); diff --git a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/Change.java b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/Change.java index 52eeec46c..c79f0d38f 100644 --- a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/Change.java +++ b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/Change.java @@ -13,8 +13,26 @@ package org.eclipse.esmf.aspectmodel.edit; +/** + * This interface represents a single modification to an Aspect Model. Instances of Change should be applied to an Aspect Model + * using {@link AspectChangeManager}; see the description about the change mechanism there. Instances of Change must not + * perform file system operations; synching "adding", "removing" and "modifying" files with the file system is a separate step. + */ public interface Change { + /** + * "Run" this change. This method should not be directly called on a Change object, but will be executed by the AspectChangeManager. + * The passed {@link ChangeContext} provides information to the Change implementation about the current state of the AspectModel. + * + * @param changeContext the change context + * @return the report describing what has been changed + */ ChangeReport fire( ChangeContext changeContext ); + /** + * Returns a Change that is the reverse operation of this one, i.e., running this change and the reverse change after another + * effectively cancels out any changes. + * + * @return the Change representing the reverse operation + */ Change reverse(); } diff --git a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/ChangeContext.java b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/ChangeContext.java index 0579c9f92..dcc190742 100644 --- a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/ChangeContext.java +++ b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/ChangeContext.java @@ -17,6 +17,10 @@ import org.eclipse.esmf.aspectmodel.AspectModelFile; +/** + * The ChangeContext encapsulates the functionality provided to {@link Change} implementations to access the current set of + * Aspect Model Files and indicate changes. + */ public interface ChangeContext { Stream aspectModelFiles(); diff --git a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/ChangeGroup.java b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/ChangeGroup.java index cbb6f3ba2..5f829aff8 100644 --- a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/ChangeGroup.java +++ b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/ChangeGroup.java @@ -17,6 +17,9 @@ import java.util.Arrays; import java.util.List; +/** + * A Change that groups other changes + */ public class ChangeGroup implements Change { private final String summary; private final List changes; diff --git a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/ChangeReport.java b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/ChangeReport.java index de4bdc9ac..2df2b6ad3 100644 --- a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/ChangeReport.java +++ b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/ChangeReport.java @@ -16,6 +16,9 @@ import java.util.List; import java.util.Map; +/** + * A structured representation of a number of {@link Change}s + */ public interface ChangeReport { ChangeReport NoChanges = new ChangeReport() { }; diff --git a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/ChangeReportFormatter.java b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/ChangeReportFormatter.java index 7b2bfcd00..6fec26c70 100644 --- a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/ChangeReportFormatter.java +++ b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/ChangeReportFormatter.java @@ -23,6 +23,9 @@ import org.apache.jena.rdf.model.Model; import org.apache.jena.rdf.model.ModelFactory; +/** + * Takes a {@link ChangeReport} as an input and renders it as a string + */ public class ChangeReportFormatter implements BiFunction { public static final ChangeReportFormatter INSTANCE = new ChangeReportFormatter(); diff --git a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/AddAspectModelFile.java b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/AddAspectModelFile.java index 9391c4cf6..e41ac836e 100644 --- a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/AddAspectModelFile.java +++ b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/AddAspectModelFile.java @@ -24,6 +24,9 @@ import org.apache.jena.rdf.model.Model; import org.apache.jena.rdf.model.ModelFactory; +/** + * Represents the operation of adding an {@link AspectModelFile} to an AspectModel + */ public class AddAspectModelFile extends AbstractChange { private final AspectModelFile newFile; diff --git a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/AddElementDefinition.java b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/AddElementDefinition.java index 6b19f42fb..a7bad278a 100644 --- a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/AddElementDefinition.java +++ b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/AddElementDefinition.java @@ -20,6 +20,9 @@ import org.apache.jena.rdf.model.Model; import org.apache.jena.rdf.model.ModelFactory; +/** + * Adds the definition of a model element to an AspectModelFile. The definition is given as a set of RDF statements (a {@link Model}). + */ public class AddElementDefinition extends EditAspectModel { private final AspectModelUrn elementUrn; private final Model definition; diff --git a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/EditAspectModel.java b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/EditAspectModel.java index ab89a6ce3..953759a36 100644 --- a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/EditAspectModel.java +++ b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/EditAspectModel.java @@ -13,7 +13,6 @@ package org.eclipse.esmf.aspectmodel.edit.change; -import java.util.AbstractMap; import java.util.Map; import java.util.stream.Collectors; @@ -23,11 +22,27 @@ import org.apache.jena.rdf.model.Model; +/** + * Abstract base class for all Changes that change the content (i.e., the underlying RDF) of an Aspect Model file. + */ public abstract class EditAspectModel extends AbstractChange { + /** + * Represents the changes to perform on the RDF model + * + * @param add the set of statements to add + * @param remove the set of statements to remove + * @param description the description of this change set (used in the {@link ChangeReport}) + */ protected record ModelChanges( Model add, Model remove, String description ) { public static final ModelChanges NONE = new ModelChanges( null, null, "" ); } + /** + * Each AspectModelFile is changed separately, so extending classes need to calculate changes for a given file. + * + * @param aspectModelFile the AspectModelFile to change + * @return the set of changes + */ abstract protected ModelChanges calculateChangesForFile( AspectModelFile aspectModelFile ); @Override diff --git a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/MoveElementToExistingFile.java b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/MoveElementToExistingFile.java index 83333a07e..128787a81 100644 --- a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/MoveElementToExistingFile.java +++ b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/MoveElementToExistingFile.java @@ -29,6 +29,9 @@ import org.apache.jena.rdf.model.Model; import org.apache.jena.rdf.model.Resource; +/** + * Refactoring operation: Moves a model element to another, existing file in the same namespace. + */ public class MoveElementToExistingFile extends StructuralChange { private final AspectModelUrn elementUrn; private final Optional targetFileLocation; diff --git a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/MoveElementToNewFile.java b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/MoveElementToNewFile.java index d25db5285..394f4037e 100644 --- a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/MoveElementToNewFile.java +++ b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/MoveElementToNewFile.java @@ -33,6 +33,9 @@ import org.apache.jena.rdf.model.Resource; import org.apache.jena.vocabulary.XSD; +/** + * Refactoring operation: Moves a model element to a new file in the same namespace. + */ public class MoveElementToNewFile extends StructuralChange { private final List headerComment; private final Optional sourceLocation; diff --git a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/MoveElementToOtherNamespaceExistingFile.java b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/MoveElementToOtherNamespaceExistingFile.java index db63f915e..3232d6111 100644 --- a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/MoveElementToOtherNamespaceExistingFile.java +++ b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/MoveElementToOtherNamespaceExistingFile.java @@ -23,6 +23,9 @@ import org.eclipse.esmf.metamodel.ModelElement; import org.eclipse.esmf.metamodel.Namespace; +/** + * Refactoring operation: Moves a model element to another, existing file in another namespace. + */ public class MoveElementToOtherNamespaceExistingFile extends StructuralChange { private final AspectModelUrn elementUrn; private final AspectModelFile targetFile; diff --git a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/MoveElementToOtherNamespaceNewFile.java b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/MoveElementToOtherNamespaceNewFile.java index c8ef55933..68d5db6b8 100644 --- a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/MoveElementToOtherNamespaceNewFile.java +++ b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/MoveElementToOtherNamespaceNewFile.java @@ -26,6 +26,9 @@ import org.eclipse.esmf.metamodel.ModelElement; import org.eclipse.esmf.metamodel.Namespace; +/** + * Refactoring operation: Moves a model element to a new file in another namespace. + */ public class MoveElementToOtherNamespaceNewFile extends StructuralChange { private final List headerComment; private final Optional sourceLocation; diff --git a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/MoveRenameAspectModelFile.java b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/MoveRenameAspectModelFile.java index ef1ed861f..d75cd2c86 100644 --- a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/MoveRenameAspectModelFile.java +++ b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/MoveRenameAspectModelFile.java @@ -24,6 +24,9 @@ import org.eclipse.esmf.aspectmodel.edit.ModelChangeException; import org.eclipse.esmf.aspectmodel.resolver.modelfile.RawAspectModelFileBuilder; +/** + * Refactoring operation: Renames/moves a file. This is done by changing its source location. + */ public class MoveRenameAspectModelFile extends StructuralChange { private final AspectModelFile file; private final Optional newLocation; diff --git a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/RemoveAspectModelFile.java b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/RemoveAspectModelFile.java index b80b29c10..93ba6d15e 100644 --- a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/RemoveAspectModelFile.java +++ b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/RemoveAspectModelFile.java @@ -20,6 +20,9 @@ import org.eclipse.esmf.aspectmodel.edit.ChangeContext; import org.eclipse.esmf.aspectmodel.edit.ChangeReport; +/** + * Refactoring operation: Removes an AspectModelFile from an Aspect Model + */ public class RemoveAspectModelFile extends AbstractChange { private final AspectModelFile fileToRemove; diff --git a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/RemoveElementDefinition.java b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/RemoveElementDefinition.java index 4858abd43..16ad6ea6f 100644 --- a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/RemoveElementDefinition.java +++ b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/RemoveElementDefinition.java @@ -24,6 +24,9 @@ import org.apache.jena.rdf.model.Resource; import org.apache.jena.vocabulary.RDF; +/** + * Removes the definition of a model element from an AspectModelFile. The definition is given as a set of RDF statements (a {@link Model}). + */ public class RemoveElementDefinition extends EditAspectModel { private final AspectModelUrn elementUrn; private AspectModelFile fileWithOriginalDefinition; diff --git a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/RenameElement.java b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/RenameElement.java index cce66da3f..3fcd92d40 100644 --- a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/RenameElement.java +++ b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/RenameElement.java @@ -17,6 +17,9 @@ import org.eclipse.esmf.aspectmodel.urn.AspectModelUrn; import org.eclipse.esmf.metamodel.ModelElement; +/** + * Refactoring operation: Renames a model element, i.e., its local name but not its namespace + */ public class RenameElement extends RenameUrn { public RenameElement( final ModelElement modelElement, final String newName ) { this( modelElement.urn(), newName ); diff --git a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/RenameUrn.java b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/RenameUrn.java index 007f08521..a909cd901 100644 --- a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/RenameUrn.java +++ b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/RenameUrn.java @@ -25,6 +25,10 @@ import org.apache.jena.rdf.model.Statement; import org.apache.jena.rdf.model.StmtIterator; +/** + * RDF-level refactoring operation: Renames all occurances of a URN to something else. For model-level refactoring, instead of this class, + * please use {@link RenameElement}, {@link MoveElementToOtherNamespaceExistingFile} or {@link MoveElementToOtherNamespaceNewFile}. + */ public class RenameUrn extends EditAspectModel { private final AspectModelUrn from; private final AspectModelUrn to; diff --git a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/loader/AspectModelLoader.java b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/loader/AspectModelLoader.java index f844ff253..cf7ecd7a8 100644 --- a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/loader/AspectModelLoader.java +++ b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/loader/AspectModelLoader.java @@ -48,6 +48,7 @@ import org.eclipse.esmf.aspectmodel.resolver.fs.FlatModelsRoot; import org.eclipse.esmf.aspectmodel.resolver.modelfile.DefaultAspectModelFile; import org.eclipse.esmf.aspectmodel.resolver.modelfile.MetaModelFile; +import org.eclipse.esmf.aspectmodel.resolver.modelfile.RawAspectModelFile; import org.eclipse.esmf.aspectmodel.urn.AspectModelUrn; import org.eclipse.esmf.aspectmodel.urn.ElementType; import org.eclipse.esmf.aspectmodel.urn.UrnSyntaxException; @@ -412,10 +413,22 @@ public boolean containsDefinition( final AspectModelFile aspectModelFile, final return result; } + /** + * Creates a new empty Aspect Model. + * + * @return A new empty Aspect Model + */ public AspectModel emptyModel() { return new DefaultAspectModel( new ArrayList<>(), ModelFactory.createDefaultModel(), new ArrayList<>() ); } + /** + * Creates a new Aspect Model from a collection of {@link AspectModelFile}s. The AspectModelFiles can be {@link RawAspectModelFile} + * (i.e., not contain {@link ModelElement} instances yet); this method takes care of instantiating the model elements. + * + * @param inputFiles the list of input files + * @return the Aspect Model + */ public AspectModel loadAspectModelFiles( final Collection inputFiles ) { final Model mergedModel = ModelFactory.createDefaultModel(); mergedModel.add( MetaModelFile.metaModelDefinitions() ); @@ -447,7 +460,13 @@ public AspectModel loadAspectModelFiles( final Collection input return new DefaultAspectModel( files, mergedModel, elements ); } - private static void setNamespaces( final Collection files, final Collection elements ) { + /** + * Sets up the namespace references in the collection of newly created AspectModelFiles + * + * @param files the files + * @param elements the collection of all model elements across all files + */ + private void setNamespaces( final Collection files, final Collection elements ) { final Map> elementsGroupedByNamespaceUrn = elements.stream() .filter( element -> !element.isAnonymous() ) .collect( Collectors.groupingBy( element -> element.urn().getNamespaceIdentifier() ) ); @@ -489,6 +508,13 @@ private static void setNamespaces( final Collection files, fina } } + /** + * Creates a new Aspect Model that contains the closure of two input Aspect Models + * + * @param aspectModel1 the first input Aspect Model + * @param aspectModel2 the second input Aspect Model + * @return the merged Aspect Model + */ public AspectModel merge( final AspectModel aspectModel1, final AspectModel aspectModel2 ) { final List files = new ArrayList<>( aspectModel1.files() ); final Set locations = aspectModel1.files().stream() From 51f6a9777c7b2310498853a75eae46a63851845f Mon Sep 17 00:00:00 2001 From: "Textor Andreas (BCI/ESW17)" Date: Tue, 13 Aug 2024 09:22:21 +0200 Subject: [PATCH 28/33] Fix code style --- .../java/org/eclipse/esmf/metamodel/vocabulary/SAMM.java | 1 + .../main/java/org/eclipse/esmf/aspectmodel/RdfUtil.java | 5 ++++- .../org/eclipse/esmf/aspectmodel/edit/ChangeReport.java | 2 +- .../esmf/aspectmodel/edit/change/EditAspectModel.java | 2 +- .../edit/change/MoveElementToExistingFile.java | 2 +- .../esmf/aspectmodel/edit/change/StructuralChange.java | 3 ++- .../esmf/aspectmodel/loader/AspectModelLoader.java | 2 +- .../org/eclipse/esmf/aspectmodel/edit/RdfUtilTest.java | 2 +- .../esmf/aspectmodel/java/AspectModelJavaUtilTest.java | 6 +++--- .../samm-cli/src/main/java/org/eclipse/esmf/SammCli.java | 8 ++++---- 10 files changed, 19 insertions(+), 14 deletions(-) diff --git a/core/esmf-aspect-meta-model-interface/src/main/java/org/eclipse/esmf/metamodel/vocabulary/SAMM.java b/core/esmf-aspect-meta-model-interface/src/main/java/org/eclipse/esmf/metamodel/vocabulary/SAMM.java index 8e452682d..39b4a4eb7 100644 --- a/core/esmf-aspect-meta-model-interface/src/main/java/org/eclipse/esmf/metamodel/vocabulary/SAMM.java +++ b/core/esmf-aspect-meta-model-interface/src/main/java/org/eclipse/esmf/metamodel/vocabulary/SAMM.java @@ -50,6 +50,7 @@ public String getNamespace() { return getUri() + "#"; } + @SuppressWarnings( "checkstyle:MethodName" ) public Resource Namespace() { return resource( "Namespace" ); } diff --git a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/RdfUtil.java b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/RdfUtil.java index 5544990b3..2ceabab5c 100644 --- a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/RdfUtil.java +++ b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/RdfUtil.java @@ -88,6 +88,9 @@ public static void cleanPrefixes( final Model model ) { case CHARACTERISTIC -> model.setNsPrefix( SammNs.SAMMC.getShortForm(), SammNs.SAMMC.getNamespace() ); case ENTITY -> model.setNsPrefix( SammNs.SAMME.getShortForm(), SammNs.SAMME.getNamespace() ); case UNIT -> model.setNsPrefix( SammNs.UNIT.getShortForm(), SammNs.UNIT.getNamespace() ); + default -> { + // nothing to do + } } } ); // XSD @@ -104,7 +107,7 @@ public static void cleanPrefixes( final Model model ) { .map( Resource::getURI ) .filter( type -> type.startsWith( XSD.NS ) ) ) .findAny() - .ifPresent( __ -> model.setNsPrefix( "xsd", XSD.NS ) ); + .ifPresent( resource -> model.setNsPrefix( "xsd", XSD.NS ) ); // Empty (namespace) prefix Streams.stream( model.listStatements( null, RDF.type, (RDFNode) null ) ) .map( Statement::getSubject ) diff --git a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/ChangeReport.java b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/ChangeReport.java index 2df2b6ad3..77600adc2 100644 --- a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/ChangeReport.java +++ b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/ChangeReport.java @@ -20,7 +20,7 @@ * A structured representation of a number of {@link Change}s */ public interface ChangeReport { - ChangeReport NoChanges = new ChangeReport() { + ChangeReport NO_CHANGES = new ChangeReport() { }; record SimpleEntry( String text ) implements ChangeReport { diff --git a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/EditAspectModel.java b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/EditAspectModel.java index 953759a36..ef0f88ce3 100644 --- a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/EditAspectModel.java +++ b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/EditAspectModel.java @@ -43,7 +43,7 @@ protected record ModelChanges( Model add, Model remove, String description ) { * @param aspectModelFile the AspectModelFile to change * @return the set of changes */ - abstract protected ModelChanges calculateChangesForFile( AspectModelFile aspectModelFile ); + protected abstract ModelChanges calculateChangesForFile( AspectModelFile aspectModelFile ); @Override public ChangeReport fire( final ChangeContext changeContext ) { diff --git a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/MoveElementToExistingFile.java b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/MoveElementToExistingFile.java index 128787a81..46f5a3c88 100644 --- a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/MoveElementToExistingFile.java +++ b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/MoveElementToExistingFile.java @@ -59,7 +59,7 @@ public ChangeReport fire( final ChangeContext changeContext ) { // Find source file with element definition final AspectModelFile sourceFile = sourceFile( changeContext, elementUrn ); if ( sourceFile == targetFile ) { - return ChangeReport.NoChanges; + return ChangeReport.NO_CHANGES; } final Resource elementResource = sourceFile.sourceModel().createResource( elementUrn.toString() ); final Model definition = RdfUtil.getModelElementDefinition( elementResource ); diff --git a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/StructuralChange.java b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/StructuralChange.java index a6af4bd28..aa568b8b5 100644 --- a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/StructuralChange.java +++ b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/edit/change/StructuralChange.java @@ -25,7 +25,8 @@ public abstract class StructuralChange extends AbstractChange { protected AspectModelFile sourceFile( final ChangeContext changeContext, final AspectModelUrn elementUrn ) { - return changeContext.aspectModelFiles().filter( aspectModelFile -> { + return changeContext.aspectModelFiles() + .filter( aspectModelFile -> { final Resource elementResource = aspectModelFile.sourceModel().createResource( elementUrn.toString() ); return Streams.stream( aspectModelFile.sourceModel().listStatements( elementResource, RDF.type, (RDFNode) null ) ) .count() == 1; diff --git a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/loader/AspectModelLoader.java b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/loader/AspectModelLoader.java index cf7ecd7a8..323202c06 100644 --- a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/loader/AspectModelLoader.java +++ b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/loader/AspectModelLoader.java @@ -491,7 +491,7 @@ private void setNamespaces( final Collection files, final Colle final AspectModelFile elementFile = element.getSourceFile(); if ( elementFile.sourceModel().contains( null, RDF.type, SammNs.SAMM.Namespace() ) ) { final Model model = elementFile.sourceModel(); - final ModelElementFactory modelElementFactory = new ModelElementFactory( model, Map.of(), __ -> null ); + final ModelElementFactory modelElementFactory = new ModelElementFactory( model, Map.of(), r -> null ); final Resource namespaceResource = model.listStatements( null, RDF.type, SammNs.SAMM.Namespace() ) .mapWith( Statement::getSubject ) .toList().iterator().next(); diff --git a/core/esmf-aspect-meta-model-java/src/test/java/org/eclipse/esmf/aspectmodel/edit/RdfUtilTest.java b/core/esmf-aspect-meta-model-java/src/test/java/org/eclipse/esmf/aspectmodel/edit/RdfUtilTest.java index 1382c3645..857ccbb11 100644 --- a/core/esmf-aspect-meta-model-java/src/test/java/org/eclipse/esmf/aspectmodel/edit/RdfUtilTest.java +++ b/core/esmf-aspect-meta-model-java/src/test/java/org/eclipse/esmf/aspectmodel/edit/RdfUtilTest.java @@ -35,7 +35,7 @@ public class RdfUtilTest { @ParameterizedTest @EnumSource( value = TestAspect.class ) - void testEveryElementDefinitionContainsATypeAssertion( final TestAspect aspect ) { + void testEveryElementDefinitionContainsTypeAssertion( final TestAspect aspect ) { final AspectModel aspectModel = TestResources.load( aspect ); aspectModel.elements().stream() .filter( element -> !element.isAnonymous() ) diff --git a/core/esmf-aspect-model-java-generator/src/test/java/org/eclipse/esmf/aspectmodel/java/AspectModelJavaUtilTest.java b/core/esmf-aspect-model-java-generator/src/test/java/org/eclipse/esmf/aspectmodel/java/AspectModelJavaUtilTest.java index e557ac3da..f3b87d58a 100644 --- a/core/esmf-aspect-model-java-generator/src/test/java/org/eclipse/esmf/aspectmodel/java/AspectModelJavaUtilTest.java +++ b/core/esmf-aspect-model-java-generator/src/test/java/org/eclipse/esmf/aspectmodel/java/AspectModelJavaUtilTest.java @@ -22,12 +22,12 @@ import org.eclipse.esmf.metamodel.Value; import org.eclipse.esmf.test.shared.arbitraries.PropertyBasedTest; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.Arguments; -import org.junit.jupiter.params.provider.MethodSource; import net.jqwik.api.ForAll; import net.jqwik.api.Property; import net.jqwik.api.Tuple; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; public class AspectModelJavaUtilTest extends PropertyBasedTest { private boolean isValidJavaIdentifier( final String value ) { diff --git a/tools/samm-cli/src/main/java/org/eclipse/esmf/SammCli.java b/tools/samm-cli/src/main/java/org/eclipse/esmf/SammCli.java index 02a16de03..b97d3f61b 100644 --- a/tools/samm-cli/src/main/java/org/eclipse/esmf/SammCli.java +++ b/tools/samm-cli/src/main/java/org/eclipse/esmf/SammCli.java @@ -212,8 +212,8 @@ private static String[] adjustCommandLineArguments( final String[] argv ) { List.of( "help", AasCommand.COMMAND_NAME, AasToCommand.COMMAND_NAME ) ); final int helpAspectEditIndex = Collections.indexOfSubList( argvList, List.of( "help", AspectCommand.COMMAND_NAME, AspectEditCommand.COMMAND_NAME ) ); - final int helpAspectXIndex = Collections.indexOfSubList( argvList, List.of( "help", AspectCommand.COMMAND_NAME ) ); - final int helpAasXIndex = Collections.indexOfSubList( argvList, List.of( "help", AasCommand.COMMAND_NAME ) ); + final int helpAspectSomeIndex = Collections.indexOfSubList( argvList, List.of( "help", AspectCommand.COMMAND_NAME ) ); + final int helpAasSomeIndex = Collections.indexOfSubList( argvList, List.of( "help", AasCommand.COMMAND_NAME ) ); if ( helpAspectToIndex != -1 || helpAasToIndex != -1 || helpAspectEditIndex != -1 ) { final int index = IntStream.of( helpAspectToIndex, helpAasToIndex, helpAspectEditIndex ).max().getAsInt(); final List customArgv = new ArrayList<>( argvList.subList( 0, index ) ); @@ -223,9 +223,9 @@ private static String[] adjustCommandLineArguments( final String[] argv ) { customArgv.add( "help" ); customArgv.addAll( argvList.subList( index + 3, argvList.size() ) ); adjustedArgv = customArgv.toArray( new String[] {} ); - } else if ( helpAspectXIndex != -1 || helpAasXIndex != -1 ) { + } else if ( helpAspectSomeIndex != -1 || helpAasSomeIndex != -1 ) { // "help aspect prettyprint" -> "aspect help prettyprint" - final int index = Integer.max( helpAspectXIndex, helpAasXIndex ); + final int index = Integer.max( helpAspectSomeIndex, helpAasSomeIndex ); final List customArgv = new ArrayList<>( argvList.subList( 0, index ) ); customArgv.add( argvList.get( index + 1 ) ); customArgv.add( "help" ); From 1f25f5b3aa04354464d12e1d0a86e7d943937f82 Mon Sep 17 00:00:00 2001 From: "Textor Andreas (BCI/ESW17)" Date: Tue, 13 Aug 2024 09:36:03 +0200 Subject: [PATCH 29/33] Fix non-deterministic tests --- .../loader/AspectModelLoaderTest.java | 21 ++++++++++--------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/core/esmf-aspect-meta-model-java/src/test/java/org/eclipse/esmf/aspectmodel/loader/AspectModelLoaderTest.java b/core/esmf-aspect-meta-model-java/src/test/java/org/eclipse/esmf/aspectmodel/loader/AspectModelLoaderTest.java index 439587c85..df81741a9 100644 --- a/core/esmf-aspect-meta-model-java/src/test/java/org/eclipse/esmf/aspectmodel/loader/AspectModelLoaderTest.java +++ b/core/esmf-aspect-meta-model-java/src/test/java/org/eclipse/esmf/aspectmodel/loader/AspectModelLoaderTest.java @@ -32,6 +32,7 @@ import org.eclipse.esmf.metamodel.AbstractEntity; import org.eclipse.esmf.metamodel.AspectModel; import org.eclipse.esmf.metamodel.ComplexType; +import org.eclipse.esmf.metamodel.HasDescription; import org.eclipse.esmf.samm.KnownVersion; import org.eclipse.esmf.test.TestAspect; import org.eclipse.esmf.test.TestResources; @@ -61,8 +62,8 @@ void testLoadAspectModelFromZipArchiveFile() { final AspectModel aspectModel = new AspectModelLoader().loadNamespacePackage( new File( archivePath.toString() ) ); assertThat( aspectModel.namespaces() ).hasSize( 2 ); - assertThat( aspectModel.namespaces().get( 0 ).getName() ).contains( "urn:samm:org.eclipse.examples:1.1.0" ); - assertThat( aspectModel.namespaces().get( 1 ).getName() ).contains( "urn:samm:org.eclipse.examples:1.0.0" ); + assertThat( aspectModel.namespaces() ).map( HasDescription::getName ).contains( "urn:samm:org.eclipse.examples:1.1.0" ); + assertThat( aspectModel.namespaces() ).map( HasDescription::getName ).contains( "urn:samm:org.eclipse.examples:1.0.0" ); final List aspectsNames = List.of( "Movement2", "Movement3", "Movement", "SimpleAspect" ); @@ -79,8 +80,8 @@ void testLoadAspectModelFromZipArchiveInputStream() throws FileNotFoundException final AspectModel aspectModel = new AspectModelLoader().loadNamespacePackage( new FileInputStream( archivePath.toString() ) ); assertThat( aspectModel.namespaces() ).hasSize( 2 ); - assertThat( aspectModel.namespaces().get( 0 ).getName() ).contains( "urn:samm:org.eclipse.examples:1.1.0" ); - assertThat( aspectModel.namespaces().get( 1 ).getName() ).contains( "urn:samm:org.eclipse.examples:1.0.0" ); + assertThat( aspectModel.namespaces() ).map( HasDescription::getName ).contains( "urn:samm:org.eclipse.examples:1.1.0" ); + assertThat( aspectModel.namespaces() ).map( HasDescription::getName ).contains( "urn:samm:org.eclipse.examples:1.0.0" ); final List aspectsNames = List.of( "Movement2", "Movement3", "Movement", "SimpleAspect" ); @@ -100,8 +101,8 @@ void testLoadAspectModelFromZipArchive2_0_0() throws FileNotFoundException { final AspectModel aspectModel = new AspectModelLoader().loadNamespacePackage( new FileInputStream( archivePath.toString() ) ); assertThat( aspectModel.namespaces() ).hasSize( 2 ); - assertThat( aspectModel.namespaces().get( 0 ).getName() ).contains( "urn:samm:org.eclipse.examples:1.1.0" ); - assertThat( aspectModel.namespaces().get( 1 ).getName() ).contains( "urn:samm:org.eclipse.examples:1.0.0" ); + assertThat( aspectModel.namespaces() ).map( HasDescription::getName ).contains( "urn:samm:org.eclipse.examples:1.1.0" ); + assertThat( aspectModel.namespaces() ).map( HasDescription::getName ).contains( "urn:samm:org.eclipse.examples:1.0.0" ); final List aspectsNames = List.of( "Movement2", "Movement3", "Movement4", "Movement", "SimpleAspect" ); @@ -118,8 +119,8 @@ void testLoadAspectModelFromZipArchiveAspectModelsRoot() throws FileNotFoundExce final AspectModel aspectModel = new AspectModelLoader().loadNamespacePackage( new FileInputStream( archivePath.toString() ) ); assertThat( aspectModel.namespaces() ).hasSize( 2 ); - assertThat( aspectModel.namespaces().get( 0 ).getName() ).contains( "urn:samm:org.eclipse.examples:1.1.0" ); - assertThat( aspectModel.namespaces().get( 1 ).getName() ).contains( "urn:samm:org.eclipse.examples:1.0.0" ); + assertThat( aspectModel.namespaces() ).map( HasDescription::getName ).contains( "urn:samm:org.eclipse.examples:1.1.0" ); + assertThat( aspectModel.namespaces() ).map( HasDescription::getName ).contains( "urn:samm:org.eclipse.examples:1.0.0" ); final List aspectsNames = List.of( "Movement2", "Movement3", "Movement", "SimpleAspect" ); @@ -136,8 +137,8 @@ void testLoadAspectModelFromZipArchiveAspectModelsSubfolder() throws FileNotFoun final AspectModel aspectModel = new AspectModelLoader().loadNamespacePackage( new FileInputStream( archivePath.toString() ) ); assertThat( aspectModel.namespaces() ).hasSize( 2 ); - assertThat( aspectModel.namespaces().get( 0 ).getName() ).contains( "urn:samm:org.eclipse.examples:1.1.0" ); - assertThat( aspectModel.namespaces().get( 1 ).getName() ).contains( "urn:samm:org.eclipse.examples:1.0.0" ); + assertThat( aspectModel.namespaces() ).map( HasDescription::getName ).contains( "urn:samm:org.eclipse.examples:1.1.0" ); + assertThat( aspectModel.namespaces() ).map( HasDescription::getName ).contains( "urn:samm:org.eclipse.examples:1.0.0" ); final List aspectsNames = List.of( "Movement2", "Movement3", "Movement", "SimpleAspect" ); From c949f41f66bab36d5be9a669f6a4cf64bf6124f0 Mon Sep 17 00:00:00 2001 From: "Textor Andreas (BCI/ESW17)" Date: Tue, 13 Aug 2024 11:50:45 +0200 Subject: [PATCH 30/33] Fix model serialization tests --- .../aspectmodel/serializer/PrettyPrinter.java | 12 ++++---- .../serializer/AspectSerializerTest.java | 28 +++++++++++++++++ .../serializer/PrettyPrinterTest.java | 1 - .../aspectmodel/serializer/RdfComparison.java | 30 +++++++++++++++++++ .../RdfModelCreatorVisitorTest.java | 29 ++---------------- .../ModelWithBlankAndAdditionalNodes.ttl | 2 +- 6 files changed, 67 insertions(+), 35 deletions(-) diff --git a/core/esmf-aspect-model-serializer/src/main/java/org/eclipse/esmf/aspectmodel/serializer/PrettyPrinter.java b/core/esmf-aspect-model-serializer/src/main/java/org/eclipse/esmf/aspectmodel/serializer/PrettyPrinter.java index 30a24efe7..dc037c568 100644 --- a/core/esmf-aspect-model-serializer/src/main/java/org/eclipse/esmf/aspectmodel/serializer/PrettyPrinter.java +++ b/core/esmf-aspect-model-serializer/src/main/java/org/eclipse/esmf/aspectmodel/serializer/PrettyPrinter.java @@ -77,8 +77,7 @@ public class PrettyPrinter { private final Set processedResources = new HashSet<>(); private final Queue resourceQueue = new ArrayDeque<>(); - private final List elementsToWrite; - private final List headerComment; + private final AspectModelFile modelFile; private final Model model; private final PrintWriter writer; @@ -91,8 +90,7 @@ public class PrettyPrinter { * @param writer the writer to write to */ public PrettyPrinter( final AspectModelFile modelFile, final PrintWriter writer ) { - elementsToWrite = modelFile.elements(); - headerComment = modelFile.headerComment(); + this.modelFile = modelFile; this.writer = writer; model = ModelFactory.createDefaultModel(); model.add( modelFile.sourceModel() ); @@ -211,10 +209,10 @@ private void showMilestoneBanner() { * Print to the PrintWriter given in the constructor. This method does not close the PrintWriter. */ public void print() { - for ( final String line : headerComment ) { + for ( final String line : modelFile.headerComment() ) { writer.println( "# " + line ); } - if ( headerComment.size() > 1 ) { + if ( modelFile.headerComment().size() > 1 ) { writer.println(); } @@ -224,7 +222,7 @@ public void print() { .forEach( entry -> writer.format( "@prefix %s: <%s> .%n", entry.getKey(), entry.getValue() ) ); writer.println(); - elementsToWrite.stream() + modelFile.elements().stream() .filter( element -> !element.isAnonymous() ) .sorted( elementDefinitionOrder ) .map( ModelElement::urn ) diff --git a/core/esmf-aspect-model-serializer/src/test/java/org/eclipse/esmf/aspectmodel/serializer/AspectSerializerTest.java b/core/esmf-aspect-model-serializer/src/test/java/org/eclipse/esmf/aspectmodel/serializer/AspectSerializerTest.java index 901146a15..8ae6f3a1f 100644 --- a/core/esmf-aspect-model-serializer/src/test/java/org/eclipse/esmf/aspectmodel/serializer/AspectSerializerTest.java +++ b/core/esmf-aspect-model-serializer/src/test/java/org/eclipse/esmf/aspectmodel/serializer/AspectSerializerTest.java @@ -15,6 +15,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.eclipse.esmf.aspectmodel.RdfUtil.createModel; +import static org.eclipse.esmf.aspectmodel.serializer.RdfComparison.modelToString; import java.io.File; import java.io.IOException; @@ -31,13 +32,18 @@ import org.eclipse.esmf.aspectmodel.edit.change.MoveRenameAspectModelFile; import org.eclipse.esmf.aspectmodel.loader.AspectModelLoader; import org.eclipse.esmf.aspectmodel.resolver.modelfile.RawAspectModelFileBuilder; +import org.eclipse.esmf.aspectmodel.resolver.services.TurtleLoader; import org.eclipse.esmf.metamodel.AspectModel; import org.eclipse.esmf.test.TestAspect; import org.eclipse.esmf.test.TestResources; +import io.vavr.control.Try; +import org.apache.jena.rdf.model.Model; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.EnumSource; public class AspectSerializerTest { Path outputDirectory = null; @@ -68,6 +74,28 @@ void afterEach() { } } + @ParameterizedTest + @EnumSource( value = TestAspect.class, + mode = EnumSource.Mode.EXCLUDE, + names = { "MODEL_WITH_BLANK_AND_ADDITIONAL_NODES" } ) + void testSerializeAspectModelFile( final TestAspect testAspect ) { + final AspectModel aspectModel = TestResources.load( testAspect ); + for ( final AspectModelFile file : aspectModel.files() ) { + final Model originalModel = file.sourceModel(); + + final String modelString = AspectSerializer.INSTANCE.aspectModelFileToString( file ); + final Try tryModel = TurtleLoader.loadTurtle( modelString ); + final Model serializedModel = tryModel.getOrElseThrow( () -> new RuntimeException( tryModel.getCause() ) ); + + serializedModel.clearNsPrefixMap(); + originalModel.getNsPrefixMap().forEach( serializedModel::setNsPrefix ); + + final String serializedModelString = modelToString( serializedModel ); + final String originalModelString = modelToString( originalModel ); + assertThat( serializedModelString ).isEqualToIgnoringWhitespace( originalModelString ); + } + } + @Test void testWriteAspectModelFileToFileSystem() { final AspectModel aspectModel = TestResources.load( TestAspect.ASPECT ); diff --git a/core/esmf-aspect-model-serializer/src/test/java/org/eclipse/esmf/aspectmodel/serializer/PrettyPrinterTest.java b/core/esmf-aspect-model-serializer/src/test/java/org/eclipse/esmf/aspectmodel/serializer/PrettyPrinterTest.java index d23b3b06f..4e8113561 100644 --- a/core/esmf-aspect-model-serializer/src/test/java/org/eclipse/esmf/aspectmodel/serializer/PrettyPrinterTest.java +++ b/core/esmf-aspect-model-serializer/src/test/java/org/eclipse/esmf/aspectmodel/serializer/PrettyPrinterTest.java @@ -50,7 +50,6 @@ void testPrettyPrinter( final TestModel testModel ) { final InputStream bufferInput = new ByteArrayInputStream( buffer.toByteArray() ); final String formattedModel = buffer.toString( StandardCharsets.UTF_8 ); - System.out.println( formattedModel ); final Model prettyPrintedModel = TurtleLoader.loadTurtle( formattedModel ).get(); assertThat( RdfComparison.hash( originalFile.sourceModel() ).equals( RdfComparison.hash( prettyPrintedModel ) ) ).isTrue(); diff --git a/core/esmf-aspect-model-serializer/src/test/java/org/eclipse/esmf/aspectmodel/serializer/RdfComparison.java b/core/esmf-aspect-model-serializer/src/test/java/org/eclipse/esmf/aspectmodel/serializer/RdfComparison.java index 75f004567..7e6586581 100644 --- a/core/esmf-aspect-model-serializer/src/test/java/org/eclipse/esmf/aspectmodel/serializer/RdfComparison.java +++ b/core/esmf-aspect-model-serializer/src/test/java/org/eclipse/esmf/aspectmodel/serializer/RdfComparison.java @@ -13,9 +13,12 @@ package org.eclipse.esmf.aspectmodel.serializer; +import java.util.Arrays; import java.util.List; import java.util.stream.Collectors; +import org.eclipse.esmf.test.TestModel; + import org.apache.jena.rdf.model.Model; import org.apache.jena.rdf.model.RDFList; import org.apache.jena.rdf.model.RDFNode; @@ -58,4 +61,31 @@ private static String hashAnonymousResource( final Resource resource ) { .sorted() .collect( Collectors.joining() ); } + + static String modelToString( final Model model ) { + return Arrays.stream( TestModel.modelToString( model ) + .replaceAll( ";", "" ) + .replaceAll( "\\.", "" ) + .replaceAll( " +", "" ) + .split( "\\n" ) ) + .filter( line -> !line.contains( "samm-c:values" ) ) + .filter( line -> !line.contains( "samm:see" ) ) + .map( RdfComparison::sortLineWithRdfListOrLangString ) + .sorted() + .collect( Collectors.joining() ) + .replaceAll( " +", " " ); + } + + /** + * In some test models, lines with RDF lists appear, e.g.: + * :property ( "foo" "bar" ) + * However, for some serialized models, the order of elements is non-deterministic since the underlying collection is a Set. + * In order to check that the line is present in the two models, we simply tokenize and sort both lines, so we can compare them. + */ + static String sortLineWithRdfListOrLangString( final String line ) { + if ( line.contains( " ( " ) || line.contains( "@" ) ) { + return Arrays.stream( line.split( "[ ,\"]" ) ).sorted().collect( Collectors.joining() ); + } + return line; + } } diff --git a/core/esmf-aspect-model-serializer/src/test/java/org/eclipse/esmf/aspectmodel/serializer/RdfModelCreatorVisitorTest.java b/core/esmf-aspect-model-serializer/src/test/java/org/eclipse/esmf/aspectmodel/serializer/RdfModelCreatorVisitorTest.java index 63ede1573..23446ec57 100644 --- a/core/esmf-aspect-model-serializer/src/test/java/org/eclipse/esmf/aspectmodel/serializer/RdfModelCreatorVisitorTest.java +++ b/core/esmf-aspect-model-serializer/src/test/java/org/eclipse/esmf/aspectmodel/serializer/RdfModelCreatorVisitorTest.java @@ -14,6 +14,7 @@ package org.eclipse.esmf.aspectmodel.serializer; import static org.assertj.core.api.Assertions.assertThat; +import static org.eclipse.esmf.aspectmodel.serializer.RdfComparison.modelToString; import java.util.Arrays; import java.util.HashMap; @@ -59,7 +60,8 @@ public class RdfModelCreatorVisitorTest { "ASPECT_WITH_UMLAUT_DESCRIPTION", "MODEL_WITH_BROKEN_CYCLES", "MODEL_WITH_BLANK_AND_ADDITIONAL_NODES", - "ASPECT_WITH_TIME_SERIES" + "ASPECT_WITH_TIME_SERIES", + "ASPECT_WITH_NAMESPACE_DESCRIPTION" } ) public void testRdfModelCreatorVisitor( final TestAspect testAspect ) { final KnownVersion knownVersion = KnownVersion.getLatest(); @@ -90,30 +92,5 @@ public void testRdfModelCreatorVisitor( final TestAspect testAspect ) { assertThat( serializedModelString ).isEqualToIgnoringWhitespace( originalModelString ); } - private String modelToString( final Model model ) { - return Arrays.stream( TestModel.modelToString( model ) - .replaceAll( ";", "" ) - .replaceAll( "\\.", "" ) - .replaceAll( " +", "" ) - .split( "\\n" ) ) - .filter( line -> !line.contains( "samm-c:values" ) ) - .filter( line -> !line.contains( "samm:see" ) ) - .map( this::sortLineWithRdfListOrLangString ) - .sorted() - .collect( Collectors.joining() ) - .replaceAll( " +", " " ); - } - /** - * In some test models, lines with RDF lists appear, e.g.: - * :property ( "foo" "bar" ) - * However, for some serialized models, the order of elements is non-deterministic since the underlying collection is a Set. - * In order to check that the line is present in the two models, we simply tokenize and sort both lines, so we can compare them. - */ - private String sortLineWithRdfListOrLangString( final String line ) { - if ( line.contains( " ( " ) || line.contains( "@" ) ) { - return Arrays.stream( line.split( "[ ,\"]" ) ).sorted().collect( Collectors.joining() ); - } - return line; - } } diff --git a/core/esmf-test-aspect-models/src/main/resources/valid/org.eclipse.esmf.test/1.0.0/ModelWithBlankAndAdditionalNodes.ttl b/core/esmf-test-aspect-models/src/main/resources/valid/org.eclipse.esmf.test/1.0.0/ModelWithBlankAndAdditionalNodes.ttl index da222ce7c..512a914df 100644 --- a/core/esmf-test-aspect-models/src/main/resources/valid/org.eclipse.esmf.test/1.0.0/ModelWithBlankAndAdditionalNodes.ttl +++ b/core/esmf-test-aspect-models/src/main/resources/valid/org.eclipse.esmf.test/1.0.0/ModelWithBlankAndAdditionalNodes.ttl @@ -12,7 +12,7 @@ @prefix : . @prefix samm: . @prefix samm-c: . -@prefix aux: . +@prefix aux: . @prefix xsd: . [ From 82b3fbd054bd89173c88f2009d4ba11837adfc0c Mon Sep 17 00:00:00 2001 From: "Textor Andreas (BCI/ESW17)" Date: Tue, 13 Aug 2024 14:17:13 +0200 Subject: [PATCH 31/33] Fix Antora tags in example files --- .../tooling-guide/examples/LoadAspectModelRdf.java | 8 ++++---- .../tooling-guide/examples/SerializeAspectModel.java | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/documentation/developer-guide/modules/tooling-guide/examples/LoadAspectModelRdf.java b/documentation/developer-guide/modules/tooling-guide/examples/LoadAspectModelRdf.java index b66f75138..d3b79b34d 100644 --- a/documentation/developer-guide/modules/tooling-guide/examples/LoadAspectModelRdf.java +++ b/documentation/developer-guide/modules/tooling-guide/examples/LoadAspectModelRdf.java @@ -33,19 +33,19 @@ public class LoadAspectModelRdf { @Test public void loadAndResolveFromFile() { - // tag::loadModel[] + // tag::loadAndResolveFromFile[] final AspectModel aspectModel = new AspectModelLoader().load( // a File, InputStream or AspectModelUrn - // end::generate[] + // end::loadAndResolveFromFile[] new File( "aspect-models/org.eclipse.esmf.examples.movement/1.0.0/Movement.ttl" ) - // tag::loadModel[] + // tag::loadAndResolveFromFile[] ); // Do something with the Aspect Model on the RDF level. // Example: List the URNs of all samm:Entitys aspectModel.mergedModel().listStatements( null, RDF.type, SammNs.SAMM.Entity() ) .forEachRemaining( statement -> System.out.println( statement.getSubject().getURI() ) ); - // end::loadModel[] + // end::loadAndResolveFromFile[] } @Test diff --git a/documentation/developer-guide/modules/tooling-guide/examples/SerializeAspectModel.java b/documentation/developer-guide/modules/tooling-guide/examples/SerializeAspectModel.java index 6bf0d7534..812a3584c 100644 --- a/documentation/developer-guide/modules/tooling-guide/examples/SerializeAspectModel.java +++ b/documentation/developer-guide/modules/tooling-guide/examples/SerializeAspectModel.java @@ -31,7 +31,7 @@ public void serializeAspectModel() { // tag::serialize[] // AspectModel as returned by the AspectModelLoader final AspectModel aspectModel = // ... - // end::generate[] + // end::serialize[] new AspectModelLoader().load( new File( "aspect-models/org.eclipse.esmf.examples.movement/1.0.0/Movement.ttl" ) ); // tag::serialize[] From 8b0bb4846b7b7b3d7741f55c6e8443a35819194f Mon Sep 17 00:00:00 2001 From: "Textor Andreas (BCI/ESW17)" Date: Tue, 13 Aug 2024 14:17:28 +0200 Subject: [PATCH 32/33] Add Java API documentation section on modifying Aspect Models --- .../examples/EditAspectModel.java | 100 ++++++++++++++++++ .../pages/java-aspect-tooling.adoc | 26 +++++ 2 files changed, 126 insertions(+) create mode 100644 documentation/developer-guide/modules/tooling-guide/examples/EditAspectModel.java diff --git a/documentation/developer-guide/modules/tooling-guide/examples/EditAspectModel.java b/documentation/developer-guide/modules/tooling-guide/examples/EditAspectModel.java new file mode 100644 index 000000000..3f9e625f0 --- /dev/null +++ b/documentation/developer-guide/modules/tooling-guide/examples/EditAspectModel.java @@ -0,0 +1,100 @@ +/* + * Copyright (c) 2024 Robert Bosch Manufacturing Solutions GmbH + * + * See the AUTHORS file(s) distributed with this work for additional + * information regarding authorship. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * SPDX-License-Identifier: MPL-2.0 + */ + +package examples; + +// tag::imports[] +import java.io.File; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.List; +import java.util.Optional; + +import org.eclipse.esmf.aspectmodel.edit.AspectChangeManager; +import org.eclipse.esmf.aspectmodel.edit.AspectChangeManagerConfig; +import org.eclipse.esmf.aspectmodel.edit.AspectChangeManagerConfigBuilder; +import org.eclipse.esmf.aspectmodel.edit.Change; +import org.eclipse.esmf.aspectmodel.edit.ChangeGroup; +import org.eclipse.esmf.aspectmodel.edit.ChangeReport; +import org.eclipse.esmf.aspectmodel.edit.ChangeReportFormatter; +import org.eclipse.esmf.aspectmodel.edit.change.MoveElementToNewFile; +import org.eclipse.esmf.aspectmodel.edit.change.RenameElement; +import org.eclipse.esmf.aspectmodel.loader.AspectModelLoader; +import org.eclipse.esmf.metamodel.AspectModel; +// end::imports[] + +import org.junit.jupiter.api.Test; + +public class EditAspectModel { + @Test + void testEditAspectModel() { + // tag::editModel[] + final AspectModel aspectModel = new AspectModelLoader().load( + // a File, InputStream or AspectModelUrn + // end::editModel[] + new File( "aspect-models/org.eclipse.esmf.examples.movement/1.0.0/Movement.ttl" ) + // tag::editModel[] + ); + + // All changes to an Aspect Model are done using an AspectChangeManager. + // Optionally, you can pass a config object as first constructor argument: + final AspectChangeManagerConfig config = AspectChangeManagerConfigBuilder.builder() + .detailedChangeReport( true ) + .defaultFileHeader( List.of( "Generated on " + + new SimpleDateFormat( "dd-MM-yyyy" ).format( new Date() ) ) ) + .build(); + final AspectChangeManager changeManager = new AspectChangeManager( config, aspectModel ); + + // You can create a single change, or you can combine multiple changes into a group. + // For all possible refactoring operations, see classes implementing the Change interface. + final Change refactorModel = new ChangeGroup( List.of( + // Rename an element. This works with with samm:Aspect and all other model elements. + new RenameElement( aspectModel.aspect(), "MyAspect" ), + // Move an element to a new Aspect Model file in the same namespace. + new MoveElementToNewFile( + // The element to move. + aspectModel.aspect().getProperties().get( 0 ), + // If you intend writing the model to the file system, set the location + // for the newly created file here. + Optional.empty() ) + ) ); + + // Apply the changes and get a report that summerizes the changes. + final ChangeReport changeReport = changeManager.applyChange( refactorModel ); + + // If you want to display the change report, you can serialize it to a string: + ChangeReportFormatter.INSTANCE.apply( changeReport, config ); + + // Alternatively, you can also get views on collections containing modified/ + // added/removed files for the last applied change. + changeManager.createdFiles().forEach( file -> System.out.println( "Created: " + file ) ); + changeManager.modifiedFiles().forEach( file -> System.out.println( "Modified: " + file ) ); + changeManager.removedFiles().forEach( file -> System.out.println( "Removed: " + file ) ); + + // At this point, you could use the AspectChangeManager's undo() method to revert + // the last change (refactorModel in this case); afterwards you can also redo(). + // This functionality is mainly interesting when refactoring the Aspect Model + // interactively. + + // If you want to write the model changes to the file system, use the AspectSerializer. + // Each AspectModelFile will be written to its respective source location. + // Alternatively, the AspectSerializer also provides a method to write an AspectModelFile + // into a String. + // end::editModel[] + /* + // tag::editModel[] + AspectSerializer.INSTANCE.write( aspectModel ); + // end::editModel[] + */ + } +} diff --git a/documentation/developer-guide/modules/tooling-guide/pages/java-aspect-tooling.adoc b/documentation/developer-guide/modules/tooling-guide/pages/java-aspect-tooling.adoc index f2f192fd4..9adede2a4 100644 --- a/documentation/developer-guide/modules/tooling-guide/pages/java-aspect-tooling.adoc +++ b/documentation/developer-guide/modules/tooling-guide/pages/java-aspect-tooling.adoc @@ -712,6 +712,32 @@ Example: include::example$sample-file-header.vm[] ---- +[[modifying-and-creating-aspect-models]] +== Modifying and creating Aspect Models + +You can use the `AspectChangeManager` to modify an Aspect Model. Each modifying operation performed +on an Aspect Model is called a _change_. Instances of classes that implement the `Change` interface +can be passed to the `AspectChangeManager` `applyChange()` method. Available `Change`​s +include renaming Aspect Model files or Model elements, adding and removing Aspect Model files and +moving Aspect Model elements to other or new files in the same or another namespace. + +++++ +
+Show used imports +++++ +[source,java,indent=0,subs="+macros,+quotes"] +---- +include::example$EditAspectModel.java[tags=imports] +---- +++++ +
+++++ + +[source,java,indent=0,subs="+macros,+quotes"] +---- +include::example$EditAspectModel.java[tags=editModel] +---- + [[accessing-samm-programmatically]] == Accessing the SAMM programmatically From a6f60c234f62a662b78639eb06a6d21ce770d622 Mon Sep 17 00:00:00 2001 From: "Textor Andreas (BCI/ESW17)" Date: Tue, 13 Aug 2024 15:03:11 +0200 Subject: [PATCH 33/33] Update documentation for samm-cli aspect edit move command --- .../modules/tooling-guide/pages/samm-cli.adoc | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/documentation/developer-guide/modules/tooling-guide/pages/samm-cli.adoc b/documentation/developer-guide/modules/tooling-guide/pages/samm-cli.adoc index 76fed04aa..18bf4bfa5 100644 --- a/documentation/developer-guide/modules/tooling-guide/pages/samm-cli.adoc +++ b/documentation/developer-guide/modules/tooling-guide/pages/samm-cli.adoc @@ -171,6 +171,20 @@ The available options and their meaning can also be seen in the help text of the | _--custom-resolver_ : use an external resolver for the resolution of the model elements | | _--aspect-data, -a_ : path to a JSON file containing aspect data corresponding to the Aspect Model | +.5+| [[aspect-edit-move]] aspect edit move [] | Move a model element definition from its + current place to another existing or new file in the same or another namespace. | `samm aspect AspectModel.ttl edit move + MyAspect otherFile.ttl` or `samm aspect + AspectModel.ttl edit move MyAspect + someFileInOtherNamespace.ttl + urn:samm:org.eclipse.example.myns:1.0.0` + | _--dry-run_ : Don't write changes to the file system, but print a report of changes + that would be performed. | + | _--details_ : When used with `--dry-run`, include details about model content + changes in the report . | + | _--copy-file-header_ : When a model element is moved to a new file, copy the file + header from the source file to the new file | + | _--force_ : When a new file is to be created but it already exists in the file system, + the operation will be cancelled, unless `--force` is used. | .3+| [[aas-to-aspect]] aas to aspect | Translate Asset Administration Shell (AAS) Submodel Templates to Aspect Models | `samm aas AssetAdminShell.aasx to aspect` | _--output-directory, -d_ : output directory to write files to (default: