@@ -215,58 +215,91 @@ private String getTranslatedScript() {
215215 return sb .toString ().trim ();
216216 }
217217
218- protected static String getFieldId (FieldReferenceContext ctx ) {
218+ private String getLinkedFieldId (String baseFieldId , LinkedFieldPropertyContext ctx ) {
219+ if (ctx .PublicationDate () != null )
220+ return this .symbols .getPrivacySettingOfField (baseFieldId , PrivacySetting .PUBLICATION_DATE_FIELD );
221+ else if (ctx .JustificationCode () != null )
222+ return this .symbols .getPrivacySettingOfField (baseFieldId , PrivacySetting .JUSTIFICATION_CODE_FIELD );
223+ else if (ctx .JustificationDescription () != null )
224+ return this .symbols .getPrivacySettingOfField (baseFieldId , PrivacySetting .JUSTIFICATION_DESCRIPTION_FIELD );
225+ else
226+ throw ConsistencyCheckException .unhandledLinkedFieldProperty (ctx .getText ());
227+ }
228+
229+ protected String getFieldId (LinkedFieldReferenceContext ctx ) {
230+ if (ctx == null ) {
231+ return null ;
232+ }
233+ String baseFieldId = ctx .simpleFieldReference ().fieldId .getText ();
234+ if (ctx .linkedFieldProperty () == null ) {
235+ return baseFieldId ;
236+ }
237+ return this .getLinkedFieldId (baseFieldId , ctx .linkedFieldProperty ());
238+ }
239+
240+ protected String getFieldId (EfxParser .FieldMentionContext ctx ) {
241+ if (ctx == null ) {
242+ return null ;
243+ }
244+ String baseFieldId = ctx .fieldId .getText ();
245+ if (ctx .linkedFieldProperty () == null ) {
246+ return baseFieldId ;
247+ }
248+ return this .getLinkedFieldId (baseFieldId , ctx .linkedFieldProperty ());
249+ }
250+
251+ protected String getFieldId (FieldReferenceContext ctx ) {
219252 if (ctx == null ) {
220253 return null ;
221254 }
222255
223256 if (ctx .absoluteFieldReference () != null ) {
224- return getFieldId (ctx .absoluteFieldReference ());
257+ return this . getFieldId (ctx .absoluteFieldReference ());
225258 }
226259
227260 if (ctx .fieldReferenceInOtherNotice () != null ) {
228- return getFieldId (ctx .fieldReferenceInOtherNotice ());
261+ return this . getFieldId (ctx .fieldReferenceInOtherNotice ());
229262 }
230263 assert false : "Unexpected context type for field reference: " + ctx .getClass ().getSimpleName ();
231264 return null ;
232265 }
233266
234- protected static String getFieldId (AbsoluteFieldReferenceContext ctx ) {
267+ protected String getFieldId (AbsoluteFieldReferenceContext ctx ) {
235268 if (ctx == null ) {
236269 return null ;
237270 }
238- return ctx .reference .reference .simpleFieldReference (). fieldId . getText ( );
271+ return this . getFieldId ( ctx .reference .reference .linkedFieldReference () );
239272 }
240273
241- protected static String getFieldId (FieldReferenceInOtherNoticeContext ctx ) {
274+ protected String getFieldId (FieldReferenceInOtherNoticeContext ctx ) {
242275 if (ctx == null ) {
243276 return null ;
244277 }
245- return ctx .reference .reference .reference .reference .reference .simpleFieldReference (). fieldId . getText ( );
278+ return this . getFieldId ( ctx .reference .reference .reference .reference .reference .linkedFieldReference () );
246279 }
247280
248- protected static String getFieldId (FieldContextContext ctx ) {
281+ protected String getFieldId (FieldContextContext ctx ) {
249282 if (ctx == null ) {
250283 return null ;
251284 }
252285
253286 if (ctx .absoluteFieldReference () != null ) {
254- return getFieldId (ctx .absoluteFieldReference ());
287+ return this . getFieldId (ctx .absoluteFieldReference ());
255288 }
256289
257290 if (ctx .fieldReferenceWithPredicate () != null ) {
258- return getFieldId (ctx .fieldReferenceWithPredicate ());
291+ return this . getFieldId (ctx .fieldReferenceWithPredicate ());
259292 }
260293
261294 assert false : "Unexpected context type for field reference: " + ctx .getClass ().getSimpleName ();
262295 return null ;
263296 }
264297
265- protected static String getFieldId (FieldReferenceWithPredicateContext ctx ) {
298+ protected String getFieldId (FieldReferenceWithPredicateContext ctx ) {
266299 if (ctx == null ) {
267300 return null ;
268301 }
269- return ctx .fieldReferenceWithAxis ().simpleFieldReference (). fieldId . getText ( );
302+ return this . getFieldId ( ctx .fieldReferenceWithAxis ().linkedFieldReference () );
270303 }
271304
272305 protected static String getNodeId (NodeReferenceContext ctx ) {
@@ -1102,6 +1135,16 @@ public void exitSimpleFieldReference(EfxParser.SimpleFieldReferenceContext ctx)
11021135 symbols .getRelativePathOfField (ctx .fieldId .getText (), this .efxContext .symbol ()));
11031136 }
11041137
1138+ @ Override
1139+ public void exitLinkedFieldReference (LinkedFieldReferenceContext ctx ) {
1140+ if (ctx .linkedFieldProperty () != null ) {
1141+ this .stack .pop (PathExpression .class ); // discard base field path
1142+ String companionFieldId = getFieldId (ctx );
1143+ this .stack .push (
1144+ symbols .getRelativePathOfField (companionFieldId , this .efxContext .symbol ()));
1145+ }
1146+ }
1147+
11051148 @ Override
11061149 public void enterAbsoluteFieldReference (AbsoluteFieldReferenceContext ctx ) {
11071150 if (ctx .Slash () != null ) {
@@ -1630,69 +1673,98 @@ public void exitEndsWithFunction(EndsWithFunctionContext ctx) {
16301673 // #region Privacy settings ------------------------------------------------
16311674
16321675 @ Override
1633- public void exitFieldIsWithholdableCondition (FieldIsWithholdableConditionContext ctx ) {
1634- final String fieldId = ctx .fieldMention ().getText ();
1676+ public void exitFieldWasWithheldProperty (FieldWasWithheldPropertyContext ctx ) {
1677+ final String fieldId = getFieldId (ctx .fieldMention ());
1678+ if (this .isFieldRepeatableFromContext (fieldId , this .efxContext .peek ())) {
1679+ throw TypeMismatchException .fieldMayRepeat (fieldId , this .efxContext .symbol ());
1680+ }
16351681
16361682 final String privacyCode = this .symbols .getPrivacyCodeOfField (fieldId );
1637- final boolean negated = ctx .modifier != null && ctx .modifier .getText ().equals (NOT_MODIFIER );
1638- final boolean isWithholdable = privacyCode != null && !privacyCode .isEmpty ();
1639- this .stack .push (this .script .getBooleanEquivalent (negated != isWithholdable ));
1683+ if (privacyCode == null || privacyCode .isEmpty ()) {
1684+ throw InvalidUsageException .fieldNotWithholdable (fieldId );
1685+ }
1686+
1687+ this .stack .push (this .composeWasWithheldCondition (fieldId , privacyCode ));
16401688 }
16411689
16421690 @ Override
1643- public void exitFieldWasWithheldCondition (FieldWasWithheldConditionContext ctx ) {
1644- final String fieldId = ctx .fieldMention ().getText ();
1691+ public void exitFieldIsWithheldProperty (FieldIsWithheldPropertyContext ctx ) {
1692+ final String fieldId = getFieldId (ctx .fieldMention ());
1693+ if (this .isFieldRepeatableFromContext (fieldId , this .efxContext .peek ())) {
1694+ throw TypeMismatchException .fieldMayRepeat (fieldId , this .efxContext .symbol ());
1695+ }
16451696
16461697 final String privacyCode = this .symbols .getPrivacyCodeOfField (fieldId );
16471698 if (privacyCode == null || privacyCode .isEmpty ()) {
16481699 throw InvalidUsageException .fieldNotWithholdable (fieldId );
16491700 }
16501701
1651- BooleanExpression result = this .composeWasWithheldCondition (fieldId , privacyCode );
1652- if (ctx .modifier != null && ctx .modifier .getText ().equals (NOT_MODIFIER )) {
1653- result = this .script .composeLogicalNot (result );
1654- }
1655- this .stack .push (result );
1702+ this .stack .push (this .script .composeLogicalAnd (
1703+ this .composeWasWithheldCondition (fieldId , privacyCode ),
1704+ this .composeStillWithheldCondition (fieldId )));
1705+ }
1706+
1707+ @ Override
1708+ public void exitFieldIsWithholdableProperty (FieldIsWithholdablePropertyContext ctx ) {
1709+ final String fieldId = getFieldId (ctx .fieldMention ());
1710+ final String privacyCode = this .symbols .getPrivacyCodeOfField (fieldId );
1711+ final boolean isWithholdable = privacyCode != null && !privacyCode .isEmpty ();
1712+ this .stack .push (this .script .getBooleanEquivalent (isWithholdable ));
16561713 }
16571714
16581715 @ Override
1659- public void exitFieldIsWithheldCondition (FieldIsWithheldConditionContext ctx ) {
1660- final String fieldId = ctx .fieldMention ().getText ();
1716+ public void exitFieldIsDisclosedProperty (FieldIsDisclosedPropertyContext ctx ) {
1717+ final String fieldId = getFieldId (ctx .fieldMention ());
1718+ if (this .isFieldRepeatableFromContext (fieldId , this .efxContext .peek ())) {
1719+ throw TypeMismatchException .fieldMayRepeat (fieldId , this .efxContext .symbol ());
1720+ }
16611721
16621722 final String privacyCode = this .symbols .getPrivacyCodeOfField (fieldId );
16631723 if (privacyCode == null || privacyCode .isEmpty ()) {
16641724 throw InvalidUsageException .fieldNotWithholdable (fieldId );
16651725 }
16661726
1667- // "is withheld" = "was withheld" AND "still withheld"
1668- BooleanExpression result = this .script .composeLogicalAnd (
1669- this .composeWasWithheldCondition (fieldId , privacyCode ),
1670- this .composeStillWithheldCondition (fieldId ));
1671- if (ctx .modifier != null && ctx .modifier .getText ().equals (NOT_MODIFIER )) {
1672- result = this .script .composeLogicalNot (result );
1673- }
1674- this .stack .push (result );
1727+ // "isDisclosed" = "was withheld" AND NOT "still withheld" AND NOT "masked"
1728+ this .stack .push (this .script .composeLogicalAnd (
1729+ this .script .composeLogicalAnd (
1730+ this .composeWasWithheldCondition (fieldId , privacyCode ),
1731+ this .script .composeLogicalNot (this .composeStillWithheldCondition (fieldId ))),
1732+ this .script .composeLogicalNot (this .composeIsMaskedCondition (fieldId ))));
16751733 }
16761734
16771735 @ Override
1678- public void exitFieldIsDisclosedCondition (FieldIsDisclosedConditionContext ctx ) {
1679- final String fieldId = ctx .fieldMention ().getText ();
1736+ public void exitFieldIsMaskedProperty (FieldIsMaskedPropertyContext ctx ) {
1737+ final String fieldId = getFieldId (ctx .fieldMention ());
1738+ if (this .isFieldRepeatableFromContext (fieldId , this .efxContext .peek ())) {
1739+ throw TypeMismatchException .fieldMayRepeat (fieldId , this .efxContext .symbol ());
1740+ }
16801741
16811742 final String privacyCode = this .symbols .getPrivacyCodeOfField (fieldId );
16821743 if (privacyCode == null || privacyCode .isEmpty ()) {
16831744 throw InvalidUsageException .fieldNotWithholdable (fieldId );
16841745 }
16851746
1686- // "is disclosed" = "was withheld" AND NOT "still withheld" AND NOT "masked"
1687- BooleanExpression result = this .script .composeLogicalAnd (
1688- this .script .composeLogicalAnd (
1689- this .composeWasWithheldCondition (fieldId , privacyCode ),
1690- this .script .composeLogicalNot (this .composeStillWithheldCondition (fieldId ))),
1691- this .composeNotMaskedCondition (fieldId ));
1692- if (ctx .modifier != null && ctx .modifier .getText ().equals (NOT_MODIFIER )) {
1693- result = this .script .composeLogicalNot (result );
1747+ // "isMasked" = was withheld AND field value equals the privacy mask
1748+ this .stack .push (this .script .composeLogicalAnd (
1749+ this .composeWasWithheldCondition (fieldId , privacyCode ),
1750+ this .composeIsMaskedCondition (fieldId )));
1751+ }
1752+
1753+ @ Override
1754+ public void exitFieldPrivacyCodeProperty (FieldPrivacyCodePropertyContext ctx ) {
1755+ final String fieldId = getFieldId (ctx .fieldMention ());
1756+ final String privacyCode = this .symbols .getPrivacyCodeOfField (fieldId );
1757+ if (privacyCode == null || privacyCode .isEmpty ()) {
1758+ throw InvalidUsageException .fieldNotWithholdable (fieldId );
16941759 }
1695- this .stack .push (result );
1760+ this .stack .push (this .script .getStringLiteralFromUnquotedString (privacyCode ));
1761+ }
1762+
1763+ private boolean isFieldRepeatableFromContext (String fieldId , Context context ) {
1764+ String contextNodeId = context .isFieldContext ()
1765+ ? this .symbols .getParentNodeOfField (context .symbol ())
1766+ : context .symbol ();
1767+ return this .symbols .isFieldRepeatableFromContext (fieldId , contextNodeId );
16961768 }
16971769
16981770 private BooleanExpression composeWasWithheldCondition (String fieldId , String privacyCode ) {
@@ -1726,13 +1798,31 @@ private BooleanExpression composeStillWithheldCondition(String fieldId) {
17261798 BooleanExpression .class );
17271799 }
17281800
1729- private BooleanExpression composeNotMaskedCondition (String fieldId ) {
1801+ private BooleanExpression composeIsMaskedCondition (String fieldId ) {
17301802 final String maskingValue = this .symbols .getPrivacyMask (fieldId );
1731- final PathExpression fieldPath = this .symbols .getRelativePathOfField (fieldId , this .efxContext .symbol ());
1803+ final PathExpression fieldValue = this .script .composeFieldValueReference (this .symbols .getRelativePathOfField (fieldId , this .efxContext .symbol ()));
1804+
1805+ if (!(fieldValue instanceof ScalarExpression )) {
1806+ throw TypeMismatchException .fieldMayRepeat (fieldId , this .efxContext .symbol ());
1807+ }
1808+
17321809 return this .script .composeComparisonOperation (
1733- new StringExpression (this .script .composeFieldValueReference (fieldPath ).getScript ()),
1734- "!=" ,
1735- this .script .getStringLiteralFromUnquotedString (maskingValue ));
1810+ TypedExpression .from (fieldValue , ScalarExpression .class ),
1811+ "==" ,
1812+ this .getTypedLiteralFromUnquotedString (maskingValue , fieldValue .getDataType ()));
1813+ }
1814+
1815+ private ScalarExpression getTypedLiteralFromUnquotedString (String value , Class <? extends EfxDataType > type ) {
1816+ if (EfxDataType .Number .class .isAssignableFrom (type )) {
1817+ return this .script .getNumericLiteralEquivalent (value );
1818+ }
1819+ if (EfxDataType .Date .class .isAssignableFrom (type )) {
1820+ return this .script .getDateLiteralEquivalent (value );
1821+ }
1822+ if (EfxDataType .Time .class .isAssignableFrom (type )) {
1823+ return this .script .getTimeLiteralEquivalent (value );
1824+ }
1825+ return this .script .getStringLiteralFromUnquotedString (value );
17361826 }
17371827
17381828 // #endregion Privacy settings ---------------------------------------------
0 commit comments