Skip to content

Commit 03576e5

Browse files
dmealingclaude
andcommitted
refactor(metadata): tighten WA2 commonAttrs additions — name the universal-tier key, drop ATTR_PREFIX alias, sharpen comments
Behavior-preserving simplifications across the WA2 commonAttrs surface: - MetaDataRegistry: extract UNIVERSAL_PARENT_KEY constant for the "*.*" globalRequirements tier (named over magic string; 3 usages). - MetaDataRegistry.isArrayShapedValue: switch to pattern-matching instanceof (Java 21) and add Javadoc clarifying that the empty-string branch handles the desugar-invariant case (post-`[]` token), not author input. - CanonicalJsonParser: drop the private @deprecated ATTR_PREFIX alias and use JSON_ATTR_PREFIX directly at its 3 sites; the alias was internal-only so no external consumer is affected (only the public JSON_ATTR_PREFIX is referenced from codegen-base). - BaseMetaDataParser: add a real import for CommonAttributeDef to drop the fully-qualified reference in the lookup body. - ConformanceTest: add a "maintenance caveat" comment on PROVIDER_ALIASES per code-review feedback — if any of the 8 backing Java provider ids is renamed or removed, the cross-language alias silently disappears. Tests: metadata 608/0/0, ConformanceTest 168/0/0. Downstream codegen-base compiles unchanged. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
1 parent 83d0b62 commit 03576e5

4 files changed

Lines changed: 34 additions & 18 deletions

File tree

server/java/metadata/src/main/java/com/metaobjects/loader/parser/BaseMetaDataParser.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import com.metaobjects.identity.MetaIdentity;
1111
import com.metaobjects.loader.MetaDataLoader;
1212
import com.metaobjects.object.MetaObject;
13+
import com.metaobjects.registry.CommonAttributeDef;
1314
import com.metaobjects.registry.MetaDataRegistry;
1415
import com.metaobjects.registry.TypeDefinition;
1516
import com.metaobjects.registry.ChildRequirement;
@@ -864,8 +865,7 @@ protected String getExpectedAttributeSubTypeFromRegistry(TypeDefinition typeDef,
864865
// honour its declared value subtype. Checked BEFORE the wildcard "attr,*"
865866
// fallback so a non-string common attr (e.g. a future boolean) is not
866867
// silently coerced to "string" by the open-policy default.
867-
com.metaobjects.registry.CommonAttributeDef commonDef =
868-
getTypeRegistry().getCommonAttribute(attrName);
868+
CommonAttributeDef commonDef = getTypeRegistry().getCommonAttribute(attrName);
869869
if (commonDef != null) {
870870
return commonDef.valueType();
871871
}

server/java/metadata/src/main/java/com/metaobjects/loader/parser/json/CanonicalJsonParser.java

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -102,10 +102,6 @@ public class CanonicalJsonParser extends BaseMetaDataParser implements MetaDataF
102102
*/
103103
public static final String JSON_ATTR_PREFIX = "@";
104104

105-
/** @deprecated use {@link #JSON_ATTR_PREFIX} */
106-
@Deprecated
107-
private static final String ATTR_PREFIX = JSON_ATTR_PREFIX;
108-
109105
/** All reserved body keys — keys handled structurally (not as @-attrs). */
110106
private static final List<String> RESERVED_KEYS = Arrays.asList(
111107
KEY_NAME, KEY_PACKAGE, KEY_EXTENDS, KEY_ABSTRACT, KEY_OVERLAY,
@@ -546,7 +542,7 @@ private void processAttributes(MetaData md, JsonObject body) {
546542
continue;
547543
}
548544

549-
if (!key.startsWith(ATTR_PREFIX)) {
545+
if (!key.startsWith(JSON_ATTR_PREFIX)) {
550546
// Non-reserved, non-@-prefixed key — warn and skip
551547
log.warn("Unknown key '{}' on node [{}:{}:{}] in file [{}] — " +
552548
"must be reserved or @-prefixed",
@@ -555,7 +551,7 @@ private void processAttributes(MetaData md, JsonObject body) {
555551
}
556552

557553
// Strip the @ prefix
558-
String attrName = key.substring(ATTR_PREFIX.length());
554+
String attrName = key.substring(JSON_ATTR_PREFIX.length());
559555

560556
// ADR-0006 §D1: reserved structural keywords must be written bare in canonical JSON.
561557
// Writing them as @-prefixed attributes (e.g. @isArray, @name) is unconditionally
@@ -564,7 +560,7 @@ private void processAttributes(MetaData md, JsonObject body) {
564560
throw new MetaDataException(
565561
ErrorMessageConstants.ERR_RESERVED_ATTR
566562
+ ": reserved structural key '" + attrName + "' must not be "
567-
+ ATTR_PREFIX + "-prefixed on " + md.getType() + "." + md.getSubType()
563+
+ JSON_ATTR_PREFIX + "-prefixed on " + md.getType() + "." + md.getSubType()
568564
+ " '" + md.getName() + "' in file [" + getFilename() + "] — write it bare",
569565
ErrorCode.ERR_RESERVED_ATTR);
570566
}

server/java/metadata/src/main/java/com/metaobjects/registry/MetaDataRegistry.java

Lines changed: 22 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,14 @@ public class MetaDataRegistry {
8686
*/
8787
private final Map<String, CommonAttributeDef> commonAttributes = new ConcurrentHashMap<>();
8888

89+
/**
90+
* Fully-global parent-key tier in {@link #globalRequirements} — matches any
91+
* (parentType, parentSubType). Used by {@link #registerCommonAttribute} so
92+
* common attrs surface on every node via {@link #acceptsChild} /
93+
* {@link #getChildRequirements}.
94+
*/
95+
private static final String UNIVERSAL_PARENT_KEY = "*.*";
96+
8997
private volatile boolean initialized = false;
9098

9199
/**
@@ -355,8 +363,8 @@ public boolean acceptsChild(String parentType, String parentSubType,
355363
}
356364
}
357365

358-
// Check fully-global "*.*" requirements (common attributes on any node)
359-
List<ChildRequirement> universalReqs = globalRequirements.get("*.*");
366+
// Check fully-global universal requirements (common attributes on any node)
367+
List<ChildRequirement> universalReqs = globalRequirements.get(UNIVERSAL_PARENT_KEY);
360368
if (universalReqs != null) {
361369
for (ChildRequirement req : universalReqs) {
362370
if (req.matches(childType, childSubType, childName)) {
@@ -398,8 +406,8 @@ public List<ChildRequirement> getChildRequirements(String parentType, String par
398406
requirements.addAll(wildcardReqs);
399407
}
400408

401-
// Add fully-global "*.*" requirements (common attributes on any node)
402-
List<ChildRequirement> universalReqs = globalRequirements.get("*.*");
409+
// Add fully-global universal requirements (common attributes on any node)
410+
List<ChildRequirement> universalReqs = globalRequirements.get(UNIVERSAL_PARENT_KEY);
403411
if (universalReqs != null) {
404412
requirements.addAll(universalReqs);
405413
}
@@ -493,8 +501,8 @@ public void registerCommonAttribute(String name, String valueType, boolean isArr
493501
CommonAttributeDef def = new CommonAttributeDef(name, valueType, isArray);
494502
commonAttributes.put(name, def);
495503

496-
// 1. Global ChildRequirement under "*.*" — makes acceptsChild() return true
497-
// for this attr on any (parentType, parentSubType) pair.
504+
// 1. Global ChildRequirement under the UNIVERSAL_PARENT_KEY tier — makes
505+
// acceptsChild() return true for this attr on any (parentType, parentSubType) pair.
498506
ChildRequirement req = new ChildRequirement(name, MetaAttribute.TYPE_ATTR, valueType, false);
499507
addGlobalChildRequirement("*", "*", req);
500508

@@ -554,14 +562,19 @@ public boolean hasCommonAttributes() {
554562
* {@code null} is permitted (optional), bracketed or comma-delimited string
555563
* forms are permitted. The actual structured list value (post-desugar) is a
556564
* {@code List<?>} which we also accept.
565+
*
566+
* <p>The empty-string acceptance is the desugar-invariant case (not author
567+
* input): the canonical bare-string desugar in CanonicalJsonParser may emit
568+
* an empty token through {@code convertJsonArrayToCommaDelimited} for an
569+
* empty JSON array {@code []}. Authored empty values arrive as {@code null}
570+
* via the optional-attr path.</p>
557571
*/
558572
private static boolean isArrayShapedValue(Object value) {
559573
if (value == null) return true; // optional
560574
if (value instanceof List<?>) return true;
561-
if (value instanceof String) {
562-
String s = (String) value;
575+
if (value instanceof String s) {
563576
return (s.startsWith("[") && s.endsWith("]")) || s.contains(",")
564-
|| s.isEmpty(); // empty post-desugar
577+
|| s.isEmpty(); // empty post-desugar (see Javadoc)
565578
}
566579
return false;
567580
}

server/java/metadata/src/test/java/com/metaobjects/conformance/ConformanceTest.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,13 @@ public class ConformanceTest {
9999
*
100100
* <p>Declared BEFORE {@link #AVAILABLE_PROVIDERS} so it is initialised first
101101
* (JLS §12.4 — class-init runs static fields top-to-bottom).</p>
102+
*
103+
* <p><strong>Maintenance caveat:</strong> if Java ever drops or renames one
104+
* of the 8 backing provider ids below, the {@code metaobjects-core-types}
105+
* alias will silently disappear from {@link #AVAILABLE_PROVIDERS} and any
106+
* fixture requiring it will fail honestly (as a missing-provider gap, not a
107+
* silent skip). Keep this list in sync with the actual provider IDs declared
108+
* in {@code META-INF/services/com.metaobjects.registry.MetaDataTypeProvider}.</p>
102109
*/
103110
private static final java.util.Map<String, List<String>> PROVIDER_ALIASES =
104111
java.util.Map.of(

0 commit comments

Comments
 (0)