Skip to content
Open
Show file tree
Hide file tree
Changes from 47 commits
Commits
Show all changes
50 commits
Select commit Hold shift + click to select a range
f0453da
Add initial version of tree-sitter-turtle
atextor Mar 20, 2026
0de08d5
Don't use list of hardcoded source files in Zig NativeCompile
atextor Mar 23, 2026
9683256
Merge branch 'main' into samm-lsp
atextor Mar 27, 2026
1bf835a
Add initial structure for Turtle language server
atextor Apr 21, 2026
2f0c6c1
Introduce Document abstraction and Treesitter-based parsing
atextor Apr 24, 2026
344112e
Base tree-sitter tree edits on previous document structure
atextor Apr 27, 2026
0f99721
Include tree-parser based syntax validation in Turtle service
atextor Apr 30, 2026
be2ac4a
Rename Turtle parser service
atextor May 6, 2026
6d2dae8
Add Turtle tokenizing
atextor May 6, 2026
5dae457
Start LSP server on local port
atextor May 6, 2026
792b7a0
Build semantic tokens from concrete syntax tree tree
atextor May 6, 2026
2542c6e
Unifiy diagnostics report building
atextor May 7, 2026
aa914e6
Refactor Download and ProcessLauncher into separate util module
atextor May 7, 2026
90fc6b1
Move util classes to reasonable packages
atextor May 7, 2026
7399a24
Push Aspect Model violations results as diagnostics
atextor May 12, 2026
4c541fd
Merge branch 'main' into samm-lsp
atextor May 18, 2026
a177a42
Fix code style
atextor May 18, 2026
b3f1d7a
Create build time cache directory if it doesn't exist yet
atextor May 18, 2026
4a815aa
Make sure xz is available at build time execution
atextor May 18, 2026
1930d84
Fix native lib compilation on Windows
atextor May 18, 2026
e512e7d
Fix checksum detection in build workflow
atextor May 18, 2026
c13a01e
Deduplicate utils and move TreeSitterUtil to esmf-tree-sitter
atextor May 18, 2026
16ec7fb
Fix broken RDF in test
atextor May 18, 2026
a14372e
Fix AspectModelOpenApiGeneratorTest when NOP Logger is configured
atextor May 19, 2026
de67330
Highlight the cause of an AspectModelLoadingException
atextor May 19, 2026
796b907
Update TokenRegistry with newly created resources in merged graph
atextor May 19, 2026
c15d976
Fix failing test
atextor May 19, 2026
86c1257
Fix slf4j setup in esmf-tree-sitter-turtle
atextor May 19, 2026
ab8d793
Merge branch 'main' into samm-lsp
atextor May 19, 2026
b3cf0b1
Download Turtle treesitter grammar at build instead of copying it
atextor May 20, 2026
4f089f3
Generate parser token types constants based on node-types.json
atextor May 20, 2026
9bf1575
Turn TurtleLanguageServer into the actual TurtleLanguageServer
atextor May 20, 2026
92c2f2a
Make launching LSP available as `samm lsp` subcommand
atextor May 20, 2026
3385efc
Merge branch 'main' into samm-lsp
atextor May 20, 2026
0ba2562
Shut logback up with its internal status
atextor May 21, 2026
24c80c9
Gracefully handle Jena parser problems
atextor May 21, 2026
c818ef1
Propagate Jena parser problems if they appear
atextor May 21, 2026
a86abcd
Merge branch 'main' into samm-lsp
atextor May 22, 2026
1320be1
Try different Zig mirrors
atextor May 22, 2026
e043894
Remove unused import
atextor May 22, 2026
4f49e6a
Change npm install to npm ci
andreas-wirth Jun 2, 2026
0b92fd2
Fix maven shade plugin to include required multi version classes
andreas-wirth Jun 2, 2026
5b1632c
Support multiple connections to lsp
andreas-wirth Jun 5, 2026
b297b40
Use npm instead of npx because of windows compatibility
andreas-wirth Jun 8, 2026
4d7a0a3
Merge branch 'main' into samm-lsp
andreas-wirth Jun 8, 2026
74e9a75
Download jni header files during maven build
andreas-wirth Jun 9, 2026
a7d23b8
Merge branch 'main' into samm-lsp
atextor Jun 9, 2026
08220b9
Merge branch 'main' into samm-lsp
atextor Jun 9, 2026
69fb3d2
Update copyright
atextor Jun 9, 2026
07dba6c
Merge branch 'main' into samm-lsp
atextor Jun 9, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .development/esmf-checkstyle.xml
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,7 @@
value="Local variable name ''{0}'' must match pattern ''{1}''."/>
</module>
<module name="PatternVariableName">
<property name="format" value="^[a-z]([a-z0-9][a-zA-Z0-9]*)?$"/>
<property name="format" value="^(_|[a-z]([a-z0-9][a-zA-Z0-9]*)?)$"/>
<message key="name.invalidPattern"
value="Pattern variable name ''{0}'' must match pattern ''{1}''."/>
</module>
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/pull-request-check.yml
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ jobs:
cp tools/samm-cli/scripts/windows/run.bat ./${bundle}/

curl -Lo warp-packer.exe https://github.com/dgiagio/warp/releases/download/v0.3.0/windows-x64.warp-packer.exe
if [ "$(sha256sum warp-packer | cut -d' ' -f1)" != "4f9a0f223f0e9f689fc718fdf86a147a357921ffa69c236deadc3274091070c1" ]; then
if [ "$(sha256sum warp-packer.exe | cut -d' ' -f1)" != "4f9a0f223f0e9f689fc718fdf86a147a357921ffa69c236deadc3274091070c1" ]; then
echo "Warp packer checksum does not match"
exit 1
fi
Expand All @@ -145,7 +145,7 @@ jobs:
chmod +x ./${bundle}/run.sh

curl -Lo warp-packer https://github.com/dgiagio/warp/releases/download/v0.3.0/macos-x64.warp-packer
if [ "$(sha256sum warp-packer | cut -d' ' -f1)" != "01d00038dbbe4e5a6e2ca19c1235f051617ac0e6e582d2407a06cec33125044b" ]; then
if [ "$(shasum -a 256 warp-packer | cut -d' ' -f1)" != "01d00038dbbe4e5a6e2ca19c1235f051617ac0e6e582d2407a06cec33125044b" ]; then
echo "Warp packer checksum does not match"
exit 1
fi
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,18 +13,35 @@

package org.eclipse.esmf.aspectmodel;

import org.apache.jena.rdf.model.RDFNode;
import org.jspecify.annotations.Nullable;

public class AspectLoadingException extends RuntimeException {
private static final long serialVersionUID = 7687644022103150329L;

private final @Nullable RDFNode highlightElement;

public AspectLoadingException( final Throwable cause ) {
super( cause );
highlightElement = null;
}

public AspectLoadingException( final String message ) {
super( message );
highlightElement = null;
}

public AspectLoadingException( final String message, final Throwable cause ) {
super( message, cause );
highlightElement = null;
}

public AspectLoadingException( final String message, final RDFNode highlightElement ) {
super( message );
this.highlightElement = highlightElement;
}

public @Nullable RDFNode highlightElement() {
return highlightElement;
}
}
8 changes: 8 additions & 0 deletions core/esmf-aspect-meta-model-java/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@
</properties>

<dependencies>
<dependency>
<groupId>org.eclipse.esmf</groupId>
<artifactId>esmf-util</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.esmf</groupId>
<artifactId>esmf-semantic-aspect-meta-model</artifactId>
Expand All @@ -42,6 +46,10 @@
<groupId>org.eclipse.esmf</groupId>
<artifactId>esmf-aspect-meta-model-interface</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.esmf</groupId>
<artifactId>esmf-tree-sitter-turtle</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-text</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,15 @@
import java.util.stream.Collectors;
import java.util.stream.Stream;

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.rdf.model.StmtIterator;
import org.apache.jena.vocabulary.RDF;
import org.apache.jena.vocabulary.XSD;

import org.eclipse.esmf.aspectmodel.AspectLoadingException;
import org.eclipse.esmf.aspectmodel.AspectModelFile;
import org.eclipse.esmf.aspectmodel.RdfUtil;
Expand Down Expand Up @@ -68,19 +77,13 @@
import org.eclipse.esmf.metamodel.impl.DefaultNamespace;
import org.eclipse.esmf.metamodel.vocabulary.SammNs;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.common.collect.Streams;

import io.vavr.control.Either;
import io.vavr.control.Try;
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.rdf.model.StmtIterator;
import org.apache.jena.vocabulary.RDF;
import org.apache.jena.vocabulary.XSD;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
* The core class to load an {@link AspectModel}. The AspectModelLoader is also a
Expand Down Expand Up @@ -339,6 +342,23 @@ public AspectModel loadUrns( final Collection<AspectModelUrn> urns ) {
return loadAspectModelFiles( loaderContext.loadedFiles() );
}

/**
* Load an Aspect Model from a String containing the RDF/Turtle represenatation and set the srouce
* location
* for this input
*
* @param turtleRepresentation the document as RDF/Turtle
* @param sourceLocation the source location for the model
* @return the Aspect Model
*/
public AspectModel load( final String turtleRepresentation, final URI sourceLocation ) {
final AspectModelFile rawFile = AspectModelFileLoader.load( turtleRepresentation, sourceLocation );
final AspectModelFile migratedModel = migrate( rawFile );
final LoaderContext loaderContext = new LoaderContext();
resolve( List.of( migratedModel ), loaderContext );
return loadAspectModelFiles( loaderContext.loadedFiles() );
}

/**
* Load an Aspect Model (.ttl) from an input stream and set the source location for this input. For
* loading an Aspect Model Namespace Package (.zip), use
Expand Down Expand Up @@ -640,7 +660,11 @@ public AspectModel loadAspectModelFiles( final Collection<AspectModelFile> input
.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 -> {
final Resource newResource = mergedModel.createResource( resource.getURI() );
TokenRegistry.updateNode( resource.asNode(), newResource.asNode() );
return newResource;
} )
.map( resource -> modelElementFactory.create( ModelElement.class, resource ) )
.toList();
aspectModelFile.setElements( fileElements );
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,14 @@
import java.util.stream.Collectors;
import java.util.stream.Stream;

import org.apache.jena.rdf.model.Literal;
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.rdf.model.Statement;
import org.apache.jena.vocabulary.RDF;

import org.eclipse.esmf.aspectmodel.AspectLoadingException;
import org.eclipse.esmf.aspectmodel.RdfUtil;
import org.eclipse.esmf.aspectmodel.urn.AspectModelUrn;
Expand All @@ -44,14 +52,6 @@
import org.eclipse.esmf.metamodel.vocabulary.SAMM;
import org.eclipse.esmf.metamodel.vocabulary.SammNs;

import org.apache.jena.rdf.model.Literal;
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.rdf.model.Statement;
import org.apache.jena.vocabulary.RDF;

public abstract class Instantiator<T extends ModelElement> extends AttributeValueRetriever implements Function<Resource, T> {
protected final ModelElementFactory modelElementFactory;
protected Class<T> targetClass;
Expand Down Expand Up @@ -128,7 +128,8 @@ private Statement getDataType( final Resource resource ) {
final Statement dataType = resource.getProperty( SammNs.SAMM.dataType() );
if ( dataType == null ) {
throw new AspectLoadingException(
String.format( "No datatype is defined on the Characteristic instance '%s: '.", resource.getLocalName() ) );
String.format( "No datatype is defined on the Characteristic instance '%s'.", resource.getLocalName() ),
resource );
}
return dataType;
} );
Expand Down Expand Up @@ -157,7 +158,7 @@ protected Value buildValue( final RDFNode node, final Optional<Resource> charact
if ( node.isLiteral() ) {
final Literal literal = node.asLiteral();
return valueInstantiator.buildScalarValue( literal.getLexicalForm(), literal.getLanguage(), literal.getDatatypeURI() )
.orElseThrow( () -> new AspectLoadingException( "Literal can not be parsed: " + literal ) );
.orElseThrow( () -> new AspectLoadingException( "Literal can not be parsed: " + literal, literal ) );
}

if ( node.isResource() ) {
Expand All @@ -166,7 +167,7 @@ protected Value buildValue( final RDFNode node, final Optional<Resource> charact
final Optional<String> valueOpt = optionalAttributeValue( resource, SammNs.SAMM.value() ).map( Statement::getString );

if ( valueOpt.isEmpty() ) {
throw new AspectLoadingException( "samm:Value must contain a samm:value property" );
throw new AspectLoadingException( "samm:Value must contain a samm:value property", resource );
}

return new DefaultScalarValue( buildBaseAttributes( resource ), valueOpt.get(), new DefaultScalar( type.getUrn() ) );
Expand Down Expand Up @@ -196,7 +197,7 @@ protected Value buildValue( final RDFNode node, final Optional<Resource> charact

// This could happen if an entity instance should be constructed for an AbstractEntity type
if ( !type.is( Entity.class ) ) {
throw new AspectLoadingException( "Expected type of value " + node + " to be samm:Entity, but it is not" );
throw new AspectLoadingException( "Expected type of value " + node + " to be samm:Entity, but it is not", node );
}

// Entities
Expand All @@ -221,11 +222,12 @@ protected EntityInstance buildEntityInstance( final Resource entityInstance, fin
if ( property.isOptional() ) {
return;
}
throw new AspectLoadingException( "Mandatory Property " + property + " not found in Entity instance " + entityInstance );
throw new AspectLoadingException( "Mandatory Property " + property + " not found in Entity instance " + entityInstance,
entityInstance );
}
final RDFNode rdfValue = entityInstance.getProperty( rdfProperty ).getObject();
final Type propertyType = property.getDataType()
.orElseThrow( () -> new AspectLoadingException( "Invalid Property without a dataType found" ) );
.orElseThrow( () -> new AspectLoadingException( "Invalid Property without a dataType found", entityInstance ) );
final Resource characteristic = attributeValue( rdfProperty, SammNs.SAMM.characteristic() ).getResource();
final Value value = buildValue( rdfValue, Optional.of( characteristic ), propertyType );
assertions.put( property, value );
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,15 @@
import java.util.stream.Collectors;
import java.util.stream.Stream;

import org.apache.commons.lang3.StringUtils;
import org.apache.jena.rdf.model.Model;
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;
import org.apache.jena.vocabulary.RDF;
import org.apache.jena.vocabulary.RDFS;

import org.eclipse.esmf.aspectmodel.AspectLoadingException;
import org.eclipse.esmf.aspectmodel.AspectModelFile;
import org.eclipse.esmf.aspectmodel.loader.instantiator.AbstractEntityInstantiator;
Expand Down Expand Up @@ -77,14 +86,6 @@
import org.eclipse.esmf.metamodel.vocabulary.SammNs;

import com.google.common.collect.Streams;
import org.apache.commons.lang3.StringUtils;
import org.apache.jena.rdf.model.Model;
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;
import org.apache.jena.vocabulary.RDF;
import org.apache.jena.vocabulary.RDFS;

/**
* Used as part of the loading process in the {@link AspectModelLoader}, it creates instance for the
Expand Down Expand Up @@ -170,11 +171,12 @@ public <T extends ModelElement> T create( final Class<T> clazz, final Resource m

// No generic instantiator could be found. This means the element is an entity instance
if ( !model.contains( targetType, RDF.type, (RDFNode) null ) ) {
throw new AspectLoadingException( "Could not load " + modelElement + ": Unknown type " + targetType );
throw new AspectLoadingException( "Could not load " + modelElement + ": Unknown type " + targetType, modelElement );
}
final Entity entity = create( Entity.class, targetType );
if ( entity == null ) {
throw new AspectLoadingException( "Could not load " + modelElement + ": Expected " + targetType + " to be an Entity" );
throw new AspectLoadingException( "Could not load " + modelElement + ": Expected " + targetType + " to be an Entity",
modelElement );
}
return (T) new EntityInstanceInstantiator( this, entity ).apply( modelElement );
}
Expand All @@ -190,7 +192,7 @@ public Unit findOrCreateUnit( final Resource unitResource ) {
if ( SammNs.UNIT.getNamespace().equals( unitResource.getNameSpace() ) ) {
final AspectModelUrn unitUrn = AspectModelUrn.fromUrn( unitResource.getURI() );
return Units.fromName( unitUrn.getName() )
.orElseThrow( () -> new AspectLoadingException( "Unit definition for " + unitUrn + " is invalid" ) );
.orElseThrow( () -> new AspectLoadingException( "Unit definition for " + unitUrn + " is invalid", unitResource ) );
}

final Set<QuantityKind> quantityKinds = Streams.stream(
Expand Down Expand Up @@ -220,7 +222,7 @@ private Resource resourceType( final Resource resource ) {
.filter( Optional::isPresent )
.map( Optional::get )
.findFirst()
.orElseThrow( () -> new AspectLoadingException( "Resource " + resource + " has no type" ) );
.orElseThrow( () -> new AspectLoadingException( "Resource " + resource + " has no type", resource ) );
}

protected Model getModel() {
Expand Down Expand Up @@ -301,7 +303,8 @@ private static List<String> getSeeValues( final Resource resource, final Attribu
private static String getSyntheticName( final Resource modelElement ) {
final Resource namedParent = getNamedParent( modelElement, modelElement.getModel() );
if ( namedParent == null ) {
throw new AspectLoadingException( "At least one anonymous node in the model does not have a parent with a regular name." );
throw new AspectLoadingException( "At least one anonymous node in the model does not have a parent with a regular name.",
modelElement );
}
final String parentModelElementUri = namedParent.getURI();
final String parentModelElementName = AspectModelUrn.from( parentModelElementUri )
Expand Down Expand Up @@ -365,7 +368,7 @@ private static Resource getModelElementType( final Resource modelElement ) {
// This model element has no type, but maybe it extends another element
final Statement extendsStatement = modelElement.getProperty( SammNs.SAMM._extends() );
if ( extendsStatement == null ) {
throw new AspectLoadingException( "Model element has no type and does not extend another type: " + modelElement );
throw new AspectLoadingException( "Model element has no type and does not extend another type: " + modelElement, modelElement );
}

final Resource superElement = extendsStatement.getObject().asResource();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ private ScalarValue buildScalarValue( final RDFNode node, final Type expectedTyp
if ( node.isLiteral() ) {
final Literal literal = node.asLiteral();
return valueInstantiator.buildScalarValue( literal.getLexicalForm(), literal.getLanguage(), literal.getDatatypeURI() )
.orElseThrow( () -> new AspectLoadingException( "Literal cannot be parsed: " + literal ) );
.orElseThrow( () -> new AspectLoadingException( "Literal cannot be parsed: " + literal, literal ) );
}

if ( node.isResource() ) {
Expand All @@ -108,7 +108,7 @@ private ScalarValue buildScalarValue( final RDFNode node, final Type expectedTyp
final Optional<String> valueOpt = optionalAttributeValue( resource, SammNs.SAMM.value() ).map( Statement::getString );

if ( valueOpt.isEmpty() ) {
throw new AspectLoadingException( "samm:Value must contain a samm:value property" );
throw new AspectLoadingException( "samm:Value must contain a samm:value property", resource );
}

return new DefaultScalarValue( buildBaseAttributes( resource ), valueOpt.get(), new DefaultScalar( expectedType.toString() ) );
Expand All @@ -117,6 +117,6 @@ private ScalarValue buildScalarValue( final RDFNode node, final Type expectedTyp
return new DefaultScalarValue( buildBaseAttributes( resource ), resource.getURI(), new DefaultScalar( expectedType.toString() ) );
}

throw new AspectLoadingException( "Unexpected RDF node type: " + node );
throw new AspectLoadingException( "Unexpected RDF node type: " + node, node );
}
}
Loading
Loading