Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,15 @@ This release does not contain security updates.

- `RootServicesHelper` was added to assist with processing OSLC Root Services documents. It can help with direct lookups (as long as your URI ends with `/rootservices` or `/rootservices.xml`), can look up a standard `/.well-known/oslc/rootservices.xml` location, or fall back to appending `/rootservices` for legacy systems.
- ⚡️Samples for IBM Jazz ERM (aka Doors NG), ETM, and EWM were migrated to .NET 10 and tested against Jazz.net. You can run them yourself using `OSLC4Net_SDK\Examples\scripts\test-jazz_net.ps1`.
- New `IBaseClause` interface with `IsError` and `ErrorReason` properties. `WhereClause` and `SortTerms` (and therefore `OrderByClause`) now implement it, so callers can inspect parse failures without `try`/`catch`. Ported from kuribara-hideaki/oslc4net commits `db49995`, `efcacd76`, `127e068a`.


### Changed

- `OSLC4Net.Core` requires .NET 10 to be able to use the `[Experimental]` annotation.
- `OSLC4Net.Client` requires .NET 10.
- ❗️ `SignedByteNode` (which corresponds to `xsd:byte`) is now parsed as C# `sbyte` (signed byte) instead of `byte`.
- ❗️ `QueryUtils.ParseWhere` and `QueryUtils.ParseOrderBy` no longer throw `ParseException` on invalid input. They return a clause whose `IsError` is `true` and `ErrorReason` holds the parser message. Existing call sites that rely on the throw will need to inspect `IsError` instead.

### Deprecated

Expand Down
31 changes: 31 additions & 0 deletions OSLC4Net_SDK/OSLC4Net.Core.Query/Grammars/OslcWhere.g
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
/******************************************************************************
* Copyright (c) 2013 IBM Corporation.
* Copyright (c) 2026 Andrii Berezovskyi and OSLC4Net contributors.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
Expand All @@ -15,6 +16,36 @@
*******************************************************************************/
grammar OslcWhere;

// =============================================================================
// OSLC Query v3.0 conformance notes
// spec: https://docs.oasis-open-projects.org/oslc-op/query/v3.0/oslc-query.html
//
// Wildcards (`*`):
// - The spec says implementations MAY support wildcards "in property names
// and nested properties". The `identifier_wc` / `wildcard` rules below
// implement that on the LEFT side of comparisons.
// - The spec does NOT permit a bare `*` as a comparison VALUE. Use a quoted
// string literal `"*"` if a literal asterisk is required. The
// kuribara-hideaki/oslc4net fork's `case ASTERISK` in
// ComparisonTermImpl.CreateValue was dead code because of this and was
// not ported.
//
// Whitespace:
// - The spec allows optional whitespace around the boolean and `in`
// operators. This grammar bakes one mandatory space into `boolean_op`
// (' and '!) and `in_op` (' in') for parsing simplicity. Strict
// spec-compliance would lift this restriction; see issue tracker before
// changing, as it requires a parser regeneration.
//
// Regeneration:
// The ANTLR 3 CSharp3 target is unmaintained and crashes StringTemplate
// (`null.attributes` template errors) on every modern Java/ST4 combo we
// have tried. Edits to this `.g` will NOT propagate to OslcWhereParser.cs
// without either an ANTLR 4 migration or hand-patching the generated file.
// See Impl/GeneratingParsers.txt. (eclipse-lyo regenerates fine because it
// targets Java; the C# target is the broken one.)
// =============================================================================

options {
output=AST;
language='CSharp3';
Expand Down
23 changes: 23 additions & 0 deletions OSLC4Net_SDK/OSLC4Net.Core.Query/IBaseClause.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/*******************************************************************************
* Copyright (c) 2026 Andrii Berezovskyi and OSLC4Net contributors.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* and Eclipse Distribution License v. 1.0 which accompanies this distribution.
*
* The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html
* and the Eclipse Distribution License is available at
* http://www.eclipse.org/org/documents/edl-v10.php.
*******************************************************************************/

namespace OSLC4Net.Core.Query;

/// <summary>
/// Common contract for parsed OSLC query clauses that can report a parse failure
/// without throwing. Ported from kuribara-hideaki/oslc4net commit db49995.
/// </summary>
public interface IBaseClause
{
bool IsError { get; }
string? ErrorReason { get; }
}
45 changes: 41 additions & 4 deletions OSLC4Net_SDK/OSLC4Net.Core.Query/Impl/GeneratingParsers.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,41 @@
cd into Impl directory
execute a command similar to this
c:\ANTLR3.5\bin\antlrgen.bat -o . ../Grammars/OslcOrderBy.g
ignoring all the warnings
Regenerating the parsers
========================

Historical instructions (from Steve Pitschke):

cd into Impl directory
execute a command similar to this
c:\ANTLR3.5\bin\antlrgen.bat -o . ../Grammars/OslcOrderBy.g
ignoring all the warnings

Current status (2026-05)
------------------------

The ANTLR 3 CSharp3 target is broken on every Java/StringTemplate combo
we have tried (Java 8 zulu, Java 17 temurin, Java 21 temurin, with the
antlr-3.5.2-complete.jar from both Maven Central and antlr3.org).
StringTemplate crashes inside the C# rule templates with hundreds of

warning(24): template error: ... no such property or can't access: null.attributes

emitted before the recogniser file is written. The `.tokens` file does
get regenerated but the `.cs` parser/lexer files are not.

This is a known unmaintained-tool situation: the CSharp3 target was
contributed by a third party, never updated for StringTemplate v4
changes, and ANTLR 3 itself has been unmaintained since 2014.

Workarounds, none of them clean:
1. Hand-patch the generated .cs file and document the hunk to re-apply.
The kuribara-hideaki/oslc4net fork went this route. Fragile.
2. Migrate the grammars to ANTLR 4. Significant effort: ANTLR 4
dropped `output=AST` and tree-rewrite rules, so all of the
consumer code (ComparisonTermImpl, CompoundTermImpl, SortTermsImpl,
etc.) needs to be rewritten against visitors/listeners. This is
the right long-term answer.
3. Track down a working CSharp3 toolchain (older StringTemplate
bundle, custom build of ANTLR 3). Not pursued.

Until (2) lands, treat the .g files as the spec and the generated .cs
files as the ground truth. Changes to a .g that affect output will not
propagate without one of the workarounds above.
17 changes: 17 additions & 0 deletions OSLC4Net_SDK/OSLC4Net.Core.Query/Impl/SortTermsImpl.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
/*******************************************************************************
* Copyright (c) 2013 IBM Corporation.
* Copyright (c) 2026 Andrii Berezovskyi and OSLC4Net contributors.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
Expand Down Expand Up @@ -36,10 +37,26 @@ IDictionary<string, string> prefixMap
this.prefixMap = prefixMap;
}

public SortTermsImpl(string errorReason)
{
tree = null!;
prefixMap = new Dictionary<string, string>(StringComparer.Ordinal);
IsError = true;
ErrorReason = errorReason;
}

public bool IsError { get; }
public string? ErrorReason { get; }

public IList<SortTerm> Children
{
get
{
if (IsError)
{
return Array.Empty<SortTerm>();
}

if (children == null)
{
var treeChildren = tree.Children;
Expand Down
14 changes: 12 additions & 2 deletions OSLC4Net_SDK/OSLC4Net.Core.Query/Impl/WhereClauseImpl.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
/*******************************************************************************
* Copyright (c) 2013 IBM Corporation.
* Copyright (c) 2026 Andrii Berezovskyi and OSLC4Net contributors.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
Expand All @@ -19,12 +20,21 @@ namespace OSLC4Net.Core.Query.Impl;

sealed class WhereClauseImpl : CompoundTermImpl, WhereClause
{
public
WhereClauseImpl(
public WhereClauseImpl(
CommonTree tree,
IDictionary<string, string> prefixMap
)
: base(tree, true, prefixMap)
{
}

public WhereClauseImpl(string errorReason)
: base(null, true, new Dictionary<string, string>(StringComparer.Ordinal))
{
IsError = true;
ErrorReason = errorReason;
}

public bool IsError { get; }
public string? ErrorReason { get; }
}
56 changes: 45 additions & 11 deletions OSLC4Net_SDK/OSLC4Net.Core.Query/QueryUtils.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
/*******************************************************************************
* Copyright (c) 2013 IBM Corporation.
* Copyright (c) 2026 Andrii Berezovskyi and OSLC4Net contributors.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
Expand Down Expand Up @@ -84,22 +85,53 @@ IDictionary<string, string> prefixMap
{
var parser = new OslcWhereParser(whereExpression);
var rawTree = (CommonTree)parser.Result;
var child = rawTree.GetChild(0);

if (child is CommonErrorNode)
// Reject if the lexer/parser did not consume the entire input
// (matches the kuribara fork's tolerance for trailing-garbage "in" clauses).
if (parser.input.ToString() != whereExpression)
{
throw new ParseException(child.ToString());
return new WhereClauseImpl("parse error: unconsumed input");
}

return (WhereClause)new WhereClauseImpl(rawTree, prefixMap);
if (TryFindError(rawTree, out var errorReason))
{
return new WhereClauseImpl(errorReason);
}

return (WhereClause)new WhereClauseImpl(rawTree, prefixMap);
}
catch (RecognitionException e)
{
throw new ParseException(e);
return new WhereClauseImpl(e.Message);
}
}

private static bool TryFindError(ITree? node, out string reason)
{
if (node is null)
{
reason = string.Empty;
return false;
}

if (node is CommonErrorNode err)
{
reason = err.ToString();
return true;
}

for (var i = 0; i < node.ChildCount; i++)
{
if (TryFindError(node.GetChild(i), out reason))
{
return true;
}
}

reason = string.Empty;
return false;
}

/// <summary>
/// Parse a oslc.select expression
/// </summary>
Expand Down Expand Up @@ -184,22 +216,24 @@ IDictionary<string, string> prefixMap
{
try
{

var parser = new OslcOrderByParser(orderByExpression);
var rawTree = (CommonTree)parser.Result;
var child = rawTree.GetChild(0);

if (child is CommonErrorNode)
if (parser.input.ToString() != orderByExpression)
{
throw new ParseException(child.ToString());
return new SortTermsImpl("parse error: unconsumed input");
}

return (OrderByClause)new SortTermsImpl(rawTree, prefixMap);
if (TryFindError(rawTree, out var errorReason))
{
return new SortTermsImpl(errorReason);
}

return (OrderByClause)new SortTermsImpl(rawTree, prefixMap);
}
catch (RecognitionException e)
{
throw new ParseException(e);
return new SortTermsImpl(e.Message);
}
}

Expand Down
2 changes: 1 addition & 1 deletion OSLC4Net_SDK/OSLC4Net.Core.Query/SortTerms.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@

namespace OSLC4Net.Core.Query;

public interface SortTerms
public interface SortTerms : IBaseClause
{
IList<SortTerm> Children { get; }
}
2 changes: 1 addition & 1 deletion OSLC4Net_SDK/OSLC4Net.Core.Query/WhereClause.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,6 @@ namespace OSLC4Net.Core.Query;
/// <summary>
/// Top-level CompoundTerm from olsc.where clause
/// </summary>
public interface WhereClause : CompoundTerm
public interface WhereClause : CompoundTerm, IBaseClause
{
}
Loading
Loading