diff --git a/.github/workflows/main.yml b/.github/workflows/4.x-release.yml
similarity index 97%
rename from .github/workflows/main.yml
rename to .github/workflows/4.x-release.yml
index f40adc9e..95820f3d 100644
--- a/.github/workflows/main.yml
+++ b/.github/workflows/4.x-release.yml
@@ -8,13 +8,13 @@
# GPG_PASSPHRASE - Passphrase for the GPG key
# PAT - Personal access token with repo scope (for pushing commits/tags)
#
-name: IABGPP-Java Release
+name: IABGPP-Java 4.X Release
on:
workflow_dispatch:
inputs:
version:
- description: 'The release version (e.g., 3.x.x)'
+ description: 'The release version (e.g., 4.x.x)'
required: true
default: ''
@@ -66,7 +66,7 @@ jobs:
# Pull latest changes from master
- name: Pull latest changes
- run: git pull origin master
+ run: git pull origin 4.X
# Set the release version in pom.xml
- name: Set release version
diff --git a/.github/workflows/main-workflow.yml b/.github/workflows/main-workflow.yml
new file mode 100644
index 00000000..95820f3d
--- /dev/null
+++ b/.github/workflows/main-workflow.yml
@@ -0,0 +1,109 @@
+# Release to Maven Central via Central Publisher Portal
+# https://central.sonatype.org/publish/publish-portal-guide/
+#
+# Required GitHub secrets (Settings → Secrets and variables → Actions):
+# CENTRAL_TOKEN_USERNAME - Portal token username (from https://central.sonatype.com/usertoken)
+# CENTRAL_TOKEN_PASSWORD - Portal token password (from same page; save on first view, cannot be retrieved later)
+# GPG_SECRET_KEY - Armored GPG private key for signing
+# GPG_PASSPHRASE - Passphrase for the GPG key
+# PAT - Personal access token with repo scope (for pushing commits/tags)
+#
+name: IABGPP-Java 4.X Release
+
+on:
+ workflow_dispatch:
+ inputs:
+ version:
+ description: 'The release version (e.g., 4.x.x)'
+ required: true
+ default: ''
+
+jobs:
+ release:
+ runs-on: ubuntu-latest
+ steps:
+ # Checkout the repository with full history for tagging
+ - name: Checkout repository
+ uses: actions/checkout@v4
+ with:
+ fetch-depth: 0
+
+ # Set up Java
+ - name: Set up Java
+ uses: actions/setup-java@v4
+ with:
+ distribution: 'temurin'
+ java-version: '21'
+
+ # Import GPG secret key for signing
+ - name: Import GPG key
+ run: |
+ echo "${{ secrets.GPG_SECRET_KEY }}" > secret_key.asc
+ gpg --import --no-tty --batch secret_key.asc || { echo "GPG import failed"; cat secret_key.asc; exit 1; }
+ rm -f secret_key.asc
+
+ # Generate settings.xml with Central Publisher Portal token credentials
+ # Token from: https://central.sonatype.com/usertoken
+ - name: Create settings.xml
+ env:
+ CENTRAL_TOKEN_USERNAME: ${{ secrets.CENTRAL_TOKEN_USERNAME }}
+ CENTRAL_TOKEN_PASSWORD: ${{ secrets.CENTRAL_TOKEN_PASSWORD }}
+ run: |
+ mkdir -p ~/.m2
+ cat > ~/.m2/settings.xml << EOF
+
+
+
+ central
+ ${CENTRAL_TOKEN_USERNAME}
+ ${CENTRAL_TOKEN_PASSWORD}
+
+
+
+ EOF
+
+ # Pull latest changes from master
+ - name: Pull latest changes
+ run: git pull origin 4.X
+
+ # Set the release version in pom.xml
+ - name: Set release version
+ run: mvn versions:set -DnewVersion=${{ github.event.inputs.version }} -DgenerateBackupPoms=false
+
+ # Build and deploy to Central Publisher Portal (mvn deploy uploads bundle and publishes)
+ - name: Deploy release
+ run: |
+ echo "pinentry-mode loopback" > ~/.gnupg/gpg.conf
+ echo "use-agent" >> ~/.gnupg/gpg.conf
+ export GPG_TTY=$(tty || echo /dev/tty)
+ mvn clean deploy --settings ~/.m2/settings.xml -Dgpg.passphrase="${{ secrets.GPG_PASSPHRASE }}" -Prelease
+
+ # Commit the release version and create a tag
+ - name: Commit and tag release
+ run: |
+ git config user.email "mayank@iabtechlab.com"
+ git config user.name "Mayank Mishra"
+ git add .
+ git commit -m "${{ github.event.inputs.version }}"
+ git tag "${{ github.event.inputs.version }}"
+
+ # Set the next snapshot version
+ - name: Set next snapshot version
+ run: mvn versions:set -DnextSnapshot=true -DgenerateBackupPoms=false
+
+ # Commit the snapshot version
+ - name: Commit snapshot version
+ run: |
+ NEW_VERSION=$(mvn help:evaluate -Dexpression=project.version -q -DforceStdout)
+ git add .
+ git commit -m "$NEW_VERSION"
+
+ # Push commits and tags to GitHub
+ - name: Push changes
+ run: |
+ git status
+ git push; git push --tags
+ env:
+ GITHUB_TOKEN: ${{ secrets.PAT }}
diff --git a/README.md b/README.md
index fa9345d3..ff5e524b 100644
--- a/README.md
+++ b/README.md
@@ -10,7 +10,7 @@ The official iabgpp java library is distributed through maven central. Please [s
#### Decoding
-```
+```xml
com.iabgpp
iabgpp-encoder
@@ -37,7 +37,7 @@ Integer uspV1Notice = uspV1Section.getNotice();
#### Encoding
-```
+```xml
com.iabgpp
iabgpp-encoder
@@ -45,7 +45,7 @@ Integer uspV1Notice = uspV1Section.getNotice();
```
-```
+```java
import com.iab.gpp.encoder.GppModel;
import com.iab.gpp.encoder.section.TcfEuV2;
import com.iab.gpp.encoder.section.UspV1;
@@ -76,7 +76,7 @@ String gppString = gppModel.encode();
The `iabgpp-extras` and `iabgpp-extras-jackson` libraries provides an interface and ability to parse the GVL and CMP
List respectively. The `iabgpp-extras-jackson` library uses Jackson 2.10.3 to parse the GVL and CMP List JSON.
-```
+```xml
com.iabgpp
iabgpp-extras
@@ -93,7 +93,7 @@ List respectively. The `iabgpp-extras-jackson` library uses Jackson 2.10.3 to pa
Example of parsing the GVL,
-```
+```java
import com.iab.gpp.extras.jackson.Loader;
import com.iab.gpp.extras.gvl.Gvl;
@@ -104,7 +104,7 @@ Gvl gvl = loader.globalVendorList(gvlContent);
Example of parsing the CMP List,
-```
+```java
import com.iab.gpp.extras.jackson.Loader;
import com.iab.gpp.extras.cmp.CmpList;
@@ -113,314 +113,6 @@ Loader loader = new Loader();
CmpList cmpList = loader.cmpList(cmpListContent);
```
-### Fields
-
-| Section Name | Section ID | Field | Data Type/Value |
-| ------------ | ---------- | ----------------------------------- | -------------------------------------------------------------- |
-| tcfeuv2 | 2 | Version | 6 bit int. Value is 2. |
-| tcfeuv2 | 2 | Created | Datetime. Updated when fields are set |
-| tcfeuv2 | 2 | LastUpdated | Datetime. Updated when fields are set |
-| tcfeuv2 | 2 | CmpId | 12 bit int |
-| tcfeuv2 | 2 | CmpVersion | 12 bit int |
-| tcfeuv2 | 2 | ConsentScreen | 6 bit int |
-| tcfeuv2 | 2 | ConsentLanguage | 2 character country code |
-| tcfeuv2 | 2 | VendorListVersion | 12 bit int |
-| tcfeuv2 | 2 | PolicyVersion | 6 bit int. Value is 2 |
-| tcfeuv2 | 2 | IsServiceSpecific | Boolean |
-| tcfeuv2 | 2 | UseNonStandardStacks | Boolean |
-| tcfeuv2 | 2 | SpecialFeatureOptins | Boolean array of size 12 |
-| tcfeuv2 | 2 | PurposeConsents | Boolean array of size 24 |
-| tcfeuv2 | 2 | PurposeLegitimateInterests | Boolean array of size 24 |
-| tcfeuv2 | 2 | PurposeOneTreatment | Boolean |
-| tcfeuv2 | 2 | PublisherCountryCode | 2 character country code |
-| tcfeuv2 | 2 | VendorConsents | Integer array of variable size |
-| tcfeuv2 | 2 | VendorLegitimateInterests | Integer array of variable size |
-| tcfeuv2 | 2 | PublisherRestrictions | Integer array of variable size |
-| tcfeuv2 | 2 | PublisherPurposesSegmentType | 3 bit int. Value is 3 |
-| tcfeuv2 | 2 | PublisherConsents | Boolean array of size 24 |
-| tcfeuv2 | 2 | PublisherLegitimateInterests | Boolean array of size 24 |
-| tcfeuv2 | 2 | NumCustomPurposes | 6 bit int |
-| tcfeuv2 | 2 | PublisherCustomConsents | Boolean array where size is set by the NumCustomPurposes field |
-| tcfeuv2 | 2 | PublisherCustomLegitimateInterests | Boolean array where size is set by the NumCustomPurposes field |
-| tcfeuv2 | 2 | VendorsAllowedSegmentType | 3 bit int. Value is 2 |
-| tcfeuv2 | 2 | VendorsAllowed | Integer array of variable size |
-| tcfeuv2 | 2 | VendorsDisclosedSegmentType | 3 bit int. Value is 1 |
-| tcfeuv2 | 2 | VendorsDisclosed | Integer array of variable size |
-| tcfcav1 | 5 | Version | 6 bit int. Value is 2. |
-| tcfcav1 | 5 | Created | Datetime. Updated when any fields are set |
-| tcfcav1 | 5 | LastUpdated | Datetime. Updated when any fields are set |
-| tcfcav1 | 5 | CmpId | 12 bit int |
-| tcfcav1 | 5 | CmpVersion | 12 bit int |
-| tcfcav1 | 5 | ConsentScreen | 6 bit int |
-| tcfcav1 | 5 | ConsentLanguage | 2 character country code |
-| tcfcav1 | 5 | VendorListVersion | 12 bit int |
-| tcfcav1 | 5 | TcfPolicyVersion | 6 bit int. Value is 2. |
-| tcfcav1 | 5 | UseNonStandardStacks | Boolean |
-| tcfcav1 | 5 | SpecialFeatureExpressConsent | Boolean array of size 12 |
-| tcfcav1 | 5 | PurposesExpressConsent | Boolean array of size 24 |
-| tcfcav1 | 5 | PurposesImpliedConsent | Boolean array of size 24 |
-| tcfcav1 | 5 | VendorExpressConsent | Integer array of variable size |
-| tcfcav1 | 5 | VendorImpliedConsent | Integer array of variable size |
-| tcfcav1 | 5 | PubRestrictions | RangeEntry list of variable size |
-| tcfcav1 | 5 | PubPurposesSegmentType | 3 bit int. Value is 3 |
-| tcfcav1 | 5 | PubPurposesExpressConsent | Boolean array of size 24 |
-| tcfcav1 | 5 | PubPurposesImpliedConsent | Boolean array of size 24 |
-| tcfcav1 | 5 | NumCustomPurposes | 6 bit int |
-| tcfcav1 | 5 | CustomPurposesExpressConsent | Boolean array where size is set by the NumCustomPurposes field |
-| tcfcav1 | 5 | CustomPurposesImpliedConsent | Boolean array where size is set by the NumCustomPurposes field |
-| tcfcav1 | 5 | DisclosedVendorsSegmentType | 3 bit int. Value is 1 |
-| tcfcav1 | 5 | DisclosedVendors | Integer list of variable size |
-| uspv1 | 6 | Version | 6 bit int. Value is 1 |
-| uspv1 | 6 | Notice | 2 bit int |
-| uspv1 | 6 | OptOutSale | 2 bit int |
-| uspv1 | 6 | LspaCovered | 2 bit int |
-| usnat | 7 | Version | 6 bit int. Value is 1 |
-| usnat | 7 | SharingNotice | 2 bit int. 0=Not applicable, 1=Yes, 2=No |
-| usnat | 7 | SaleOptOutNotice | 2 bit int. 0=Not applicable, 1=Yes, 2=No |
-| usnat | 7 | SharingOptOutNotice | 2 bit int. 0=Not applicable, 1=Yes, 2=No |
-| usnat | 7 | TargetedAdvertisingOptOutNotice | 2 bit int. 0=Not applicable, 1=Yes, 2=No |
-| usnat | 7 | SensitiveDataProcessingOptOutNotice | 2 bit int. 0=Not applicable, 1=Yes, 2=No |
-| usnat | 7 | SensitiveDataLimitUseNotice | 2 bit int. 0=Not applicable, 1=Yes, 2=No |
-| usnat | 7 | SaleOptOut | 2 bit int. 0=Not applicable, 1=Yes, 2=No |
-| usnat | 7 | SharingOptOut | 2 bit int. 0=Not applicable, 1=Yes, 2=No |
-| usnat | 7 | TargetedAdvertisingOptOut | 2 bit int. 0=Not applicable, 1=Yes, 2=No |
-| usnat | 7 | SensitiveDataProcessing | 2 bit int array of size 16. 0=Not applicable, 1=Yes, 2=No |
-| usnat | 7 | KnownChildSensitiveDataConsents | 2 bit int array of size 3. 0=Not applicable, 1=Yes, 2=No |
-| usnat | 7 | PersonalDataConsents | 2 bit int. 0=Not applicable, 1=Yes, 2=No |
-| usnat | 7 | MspaCoveredTransaction | 2 bit int. 0=Not applicable, 1=Yes, 2=No |
-| usnat | 7 | MspaOptOutOptionMode | 2 bit int. 0=Not applicable, 1=Yes, 2=No |
-| usnat | 7 | MspaServiceProviderMode | 2 bit int. 0=Not applicable, 1=Yes, 2=No |
-| usnat | 7 | GpcSegmentType | 2 bit int. Value is 1 |
-| usnat | 7 | GpcSegmentIncluded | Boolean. Default is true |
-| usnat | 7 | Gpc | Boolean |
-| usca | 8 | Version | 6 bit int. Value is 1 |
-| usca | 8 | SaleOptOutNotice | 2 bit int. 0=Not applicable, 1=Yes, 2=No |
-| usca | 8 | SharingOptOutNotice | 2 bit int. 0=Not applicable, 1=Yes, 2=No |
-| usca | 8 | SensitiveDataLimitUseNotice | 2 bit int. 0=Not applicable, 1=Yes, 2=No |
-| usca | 8 | SaleOptOut | 2 bit int. 0=Not applicable, 1=Yes, 2=No |
-| usca | 8 | SharingOptOut | 2 bit int. 0=Not applicable, 1=Yes, 2=No |
-| usca | 8 | SensitiveDataProcessing | 2 bit int array of size 9. 0=Not applicable, 1=Yes, 2=No |
-| usca | 8 | KnownChildSensitiveDataConsents | 2 bit int array of size 2. 0=Not applicable, 1=Yes, 2=No |
-| usca | 8 | PersonalDataConsents | 2 bit int. 0=Not applicable, 1=Yes, 2=No |
-| usca | 8 | MspaCoveredTransaction | 2 bit int. 0=Not applicable, 1=Yes, 2=No |
-| usca | 8 | MspaOptOutOptionMode | 2 bit int. 0=Not applicable, 1=Yes, 2=No |
-| usca | 8 | MspaServiceProviderMode | 2 bit int. 0=Not applicable, 1=Yes, 2=No |
-| usca | 8 | GpcSegmentType | 2 bit int. Value is 1 |
-| usca | 8 | GpcSegmentIncluded | Boolean. Default is true |
-| usca | 8 | Gpc | Boolean |
-| usva | 9 | Version | 6 bit int. Value is 1 |
-| usva | 9 | SharingNotice | 2 bit int. 0=Not applicable, 1=Yes, 2=No |
-| usva | 9 | SaleOptOutNotice | 2 bit int. 0=Not applicable, 1=Yes, 2=No |
-| usva | 9 | TargetedAdvertisingOptOutNotice | 2 bit int. 0=Not applicable, 1=Yes, 2=No |
-| usva | 9 | SaleOptOut | 2 bit int. 0=Not applicable, 1=Yes, 2=No |
-| usva | 9 | TargetedAdvertisingOptOut | 2 bit int. 0=Not applicable, 1=Yes, 2=No |
-| usva | 9 | SensitiveDataProcessing | 2 bit int array of size 8. 0=Not applicable, 1=Yes, 2=No |
-| usva | 9 | KnownChildSensitiveDataConsents | 2 bit int. 0=Not applicable, 1=Yes, 2=No |
-| usva | 9 | MspaCoveredTransaction | 2 bit int. 0=Not applicable, 1=Yes, 2=No |
-| usva | 9 | MspaOptOutOptionMode | 2 bit int. 0=Not applicable, 1=Yes, 2=No |
-| usva | 9 | MspaServiceProviderMode | 2 bit int. 0=Not applicable, 1=Yes, 2=No |
-| usco | 10 | Version | 6 bit int. Value is 1 |
-| usco | 10 | SharingNotice | 2 bit int. 0=Not applicable, 1=Yes, 2=No |
-| usco | 10 | SaleOptOutNotice | 2 bit int. 0=Not applicable, 1=Yes, 2=No |
-| usco | 10 | TargetedAdvertisingOptOutNotice | 2 bit int. 0=Not applicable, 1=Yes, 2=No |
-| usco | 10 | SaleOptOut | 2 bit int. 0=Not applicable, 1=Yes, 2=No |
-| usco | 10 | TargetedAdvertisingOptOut | 2 bit int. 0=Not applicable, 1=Yes, 2=No |
-| usco | 10 | SensitiveDataProcessing | 2 bit int array of size 7. 0=Not applicable, 1=Yes, 2=No |
-| usco | 10 | KnownChildSensitiveDataConsents | 2 bit int. 0=Not applicable, 1=Yes, 2=No |
-| usco | 10 | MspaCoveredTransaction | 2 bit int. 0=Not applicable, 1=Yes, 2=No |
-| usco | 10 | MspaOptOutOptionMode | 2 bit int. 0=Not applicable, 1=Yes, 2=No |
-| usco | 10 | MspaServiceProviderMode | 2 bit int. 0=Not applicable, 1=Yes, 2=No |
-| usco | 10 | GpcSegmentType | 2 bit int. Value is 1 |
-| usco | 10 | GpcSegmentIncluded | Boolean. Deafult is true |
-| usco | 10 | Gpc | Boolean |
-| usut | 11 | Version | 6 bit int. Value is 1 |
-| usut | 11 | SharingNotice | 2 bit int. 0=Not applicable, 1=Yes, 2=No |
-| usut | 11 | SaleOptOutNotice | 2 bit int. 0=Not applicable, 1=Yes, 2=No |
-| usut | 11 | TargetedAdvertisingOptOutNotice | 2 bit int. 0=Not applicable, 1=Yes, 2=No |
-| usut | 11 | SensitiveDataProcessingOptOutNotice | 2 bit int. 0=Not applicable, 1=Yes, 2=No |
-| usut | 11 | SaleOptOut | 2 bit int. 0=Not applicable, 1=Yes, 2=No |
-| usut | 11 | TargetedAdvertisingOptOut | 2 bit int. 0=Not applicable, 1=Yes, 2=No |
-| usut | 11 | SensitiveDataProcessing | 2 bit int array of size 8. 0=Not applicable, 1=Yes, 2=No |
-| usut | 11 | KnownChildSensitiveDataConsents | 2 bit int. 0=Not applicable, 1=Yes, 2=No |
-| usut | 11 | MspaCoveredTransaction | 2 bit int. 0=Not applicable, 1=Yes, 2=No |
-| usut | 11 | MspaOptOutOptionMode | 2 bit int. 0=Not applicable, 1=Yes, 2=No |
-| usut | 11 | MspaServiceProviderMode | 2 bit int. 0=Not applicable, 1=Yes, 2=No |
-| usct | 12 | Version | 6 bit int. Value is 1 |
-| usct | 12 | SharingNotice | 2 bit int. 0=Not applicable, 1=Yes, 2=No |
-| usct | 12 | SaleOptOutNotice | 2 bit int. 0=Not applicable, 1=Yes, 2=No |
-| usct | 12 | TargetedAdvertisingOptOutNotice | 2 bit int. 0=Not applicable, 1=Yes, 2=No |
-| usct | 12 | SaleOptOut | 2 bit int. 0=Not applicable, 1=Yes, 2=No |
-| usct | 12 | TargetedAdvertisingOptOut | 2 bit int. 0=Not applicable, 1=Yes, 2=No |
-| usct | 12 | SensitiveDataProcessing | 2 bit int array of size 8. 0=Not applicable, 1=Yes, 2=No |
-| usct | 12 | KnownChildSensitiveDataConsents | 2 bit int array of size 3. 0=Not applicable, 1=Yes, 2=No |
-| usct | 12 | MspaCoveredTransaction | 2 bit int. 0=Not applicable, 1=Yes, 2=No |
-| usct | 12 | MspaOptOutOptionMode | 2 bit int. 0=Not applicable, 1=Yes, 2=No |
-| usct | 12 | MspaServiceProviderMode | 2 bit int. 0=Not applicable, 1=Yes, 2=No |
-| usct | 12 | GpcSegmentType | 2 bit int. Value is 1 |
-| usct | 12 | GpcSegmentIncluded | Boolean. Default is true |
-| usct | 12 | Gpc | Boolean |
-| usfl | 13 | Version | 6 bit int. Value is 1 |
-| usfl | 13 | ProcessingNotice | 2 bit int. 0=Not applicable, 1=Yes, 2=No |
-| usfl | 13 | SaleOptOutNotice | 2 bit int. 0=Not applicable, 1=Yes, 2=No |
-| usfl | 13 | TargetedAdvertisingOptOutNotice | 2 bit int. 0=Not applicable, 1=Yes, 2=No |
-| usfl | 13 | SaleOptOut | 2 bit int. 0=Not applicable, 1=Yes, 2=No |
-| usfl | 13 | TargetedAdvertisingOptOut | 2 bit int. 0=Not applicable, 1=Yes, 2=No |
-| usfl | 13 | SensitiveDataProcessing | 2 bit int array of size 8. 0=Not applicable, 1=Yes, 2=No |
-| usfl | 13 | KnownChildSensitiveDataConsents | 2 bit int array of size 3. 0=Not applicable, 1=Yes, 2=No |
-| usfl | 13 | AdditionalDataProcessingConsent | 2 bit int. 0=Not applicable, 1=Yes, 2=No |
-| usfl | 13 | MspaCoveredTransaction | 2 bit int. 0=Not applicable, 1=Yes, 2=No |
-| usfl | 13 | MspaOptOutOptionMode | 2 bit int. 0=Not applicable, 1=Yes, 2=No |
-| usfl | 13 | MspaServiceProviderMode | 2 bit int. 0=Not applicable, 1=Yes, 2=No |
-| usmt | 14 | Version | 6 bit int. Value is 1 |
-| usmt | 14 | SharingNotice | 2 bit int. 0=Not applicable, 1=Yes, 2=No |
-| usmt | 14 | SaleOptOutNotice | 2 bit int. 0=Not applicable, 1=Yes, 2=No |
-| usmt | 14 | TargetedAdvertisingOptOutNotice | 2 bit int. 0=Not applicable, 1=Yes, 2=No |
-| usmt | 14 | SaleOptOut | 2 bit int. 0=Not applicable, 1=Yes, 2=No |
-| usmt | 14 | TargetedAdvertisingOptOut | 2 bit int. 0=Not applicable, 1=Yes, 2=No |
-| usmt | 14 | SensitiveDataProcessing | 2 bit int array of size 8. 0=Not applicable, 1=Yes, 2=No |
-| usmt | 14 | KnownChildSensitiveDataConsents | 2 bit int array of size 3. 0=Not applicable, 1=Yes, 2=No |
-| usmt | 14 | AdditionalDataProcessingConsent | 2 bit int. 0=Not applicable, 1=Yes, 2=No |
-| usmt | 14 | MspaCoveredTransaction | 2 bit int. 0=Not applicable, 1=Yes, 2=No |
-| usmt | 14 | MspaOptOutOptionMode | 2 bit int. 0=Not applicable, 1=Yes, 2=No |
-| usmt | 14 | MspaServiceProviderMode | 2 bit int. 0=Not applicable, 1=Yes, 2=No |
-| usmt | 14 | GpcSegmentType | 2 bit int. Value is 1 |
-| usmt | 14 | GpcSegmentIncluded | Boolean. Default is true |
-| usmt | 14 | Gpc | Boolean |
-| usor | 15 | Version | 6 bit int. Value is 1 |
-| usor | 15 | ProcessingNotice | 2 bit int. 0=Not applicable, 1=Yes, 2=No |
-| usor | 15 | SaleOptOutNotice | 2 bit int. 0=Not applicable, 1=Yes, 2=No |
-| usor | 15 | TargetedAdvertisingOptOutNotice | 2 bit int. 0=Not applicable, 1=Yes, 2=No |
-| usor | 15 | SaleOptOut | 2 bit int. 0=Not applicable, 1=Yes, 2=No |
-| usor | 15 | TargetedAdvertisingOptOut | 2 bit int. 0=Not applicable, 1=Yes, 2=No |
-| usor | 15 | SensitiveDataProcessing | 2 bit int array of size 11. 0=Not applicable, 1=Yes, 2=No |
-| usor | 15 | KnownChildSensitiveDataConsents | 2 bit int array of size 3. 0=Not applicable, 1=Yes, 2=No |
-| usor | 15 | AdditionalDataProcessingConsent | 2 bit int. 0=Not applicable, 1=Yes, 2=No |
-| usor | 15 | MspaCoveredTransaction | 2 bit int. 0=Not applicable, 1=Yes, 2=No |
-| usor | 15 | MspaOptOutOptionMode | 2 bit int. 0=Not applicable, 1=Yes, 2=No |
-| usor | 15 | MspaServiceProviderMode | 2 bit int. 0=Not applicable, 1=Yes, 2=No |
-| usor | 15 | GpcSegmentType | 2 bit int. Value is 1 |
-| usor | 15 | GpcSegmentIncluded | Boolean. Default is true |
-| usor | 15 | Gpc | Boolean |
-| ustx | 16 | Version | 6 bit int. Value is 1 |
-| ustx | 16 | ProcessingNotice | 2 bit int. 0=Not applicable, 1=Yes, 2=No |
-| ustx | 16 | SaleOptOutNotice | 2 bit int. 0=Not applicable, 1=Yes, 2=No |
-| ustx | 16 | TargetedAdvertisingOptOutNotice | 2 bit int. 0=Not applicable, 1=Yes, 2=No |
-| ustx | 16 | SaleOptOut | 2 bit int. 0=Not applicable, 1=Yes, 2=No |
-| ustx | 16 | TargetedAdvertisingOptOut | 2 bit int. 0=Not applicable, 1=Yes, 2=No |
-| ustx | 16 | SensitiveDataProcessing | 2 bit int array of size 8. 0=Not applicable, 1=Yes, 2=No |
-| ustx | 16 | KnownChildSensitiveDataConsents | 2 bit int. 0=Not applicable, 1=Yes, 2=No |
-| ustx | 16 | AdditionalDataProcessingConsent | 2 bit int. 0=Not applicable, 1=Yes, 2=No |
-| ustx | 16 | MspaCoveredTransaction | 2 bit int. 0=Not applicable, 1=Yes, 2=No |
-| ustx | 16 | MspaOptOutOptionMode | 2 bit int. 0=Not applicable, 1=Yes, 2=No |
-| ustx | 16 | MspaServiceProviderMode | 2 bit int. 0=Not applicable, 1=Yes, 2=No |
-| ustx | 16 | GpcSegmentType | 2 bit int. Value is 1 |
-| ustx | 16 | GpcSegmentIncluded | Boolean. Default is true |
-| ustx | 16 | Gpc | Boolean |
-| usde | 17 | Version | 6 bit int. Value is 1 |
-| usde | 17 | ProcessingNotice | 2 bit int. 0=Not applicable, 1=Yes, 2=No |
-| usde | 17 | SaleOptOutNotice | 2 bit int. 0=Not applicable, 1=Yes, 2=No |
-| usde | 17 | TargetedAdvertisingOptOutNotice | 2 bit int. 0=Not applicable, 1=Yes, 2=No |
-| usde | 17 | SaleOptOut | 2 bit int. 0=Not applicable, 1=Yes, 2=No |
-| usde | 17 | TargetedAdvertisingOptOut | 2 bit int. 0=Not applicable, 1=Yes, 2=No |
-| usde | 17 | SensitiveDataProcessing | 2 bit int array of size 9. 0=Not applicable, 1=Yes, 2=No |
-| usde | 17 | KnownChildSensitiveDataConsents | 2 bit int array of size 5. 0=Not applicable, 1=Yes, 2=No |
-| usde | 17 | AdditionalDataProcessingConsent | 2 bit int. 0=Not applicable, 1=Yes, 2=No |
-| usde | 17 | MspaCoveredTransaction | 2 bit int. 0=Not applicable, 1=Yes, 2=No |
-| usde | 17 | MspaOptOutOptionMode | 2 bit int. 0=Not applicable, 1=Yes, 2=No |
-| usde | 17 | MspaServiceProviderMode | 2 bit int. 0=Not applicable, 1=Yes, 2=No |
-| usde | 17 | GpcSegmentType | 2 bit int. Value is 1 |
-| usde | 17 | GpcSegmentIncluded | Boolean. Default is true |
-| usde | 17 | Gpc | Boolean |
-| usia | 18 | Version | 6 bit int. Value is 1 |
-| usia | 18 | ProcessingNotice | 2 bit int. 0=Not applicable, 1=Yes, 2=No |
-| usia | 18 | SaleOptOutNotice | 2 bit int. 0=Not applicable, 1=Yes, 2=No |
-| usia | 18 | TargetedAdvertisingOptOutNotice | 2 bit int. 0=Not applicable, 1=Yes, 2=No |
-| usia | 18 | SensitiveDataOptOutNotice | 2 bit int. 0=Not applicable, 1=Yes, 2=No |
-| usia | 18 | SaleOptOut | 2 bit int. 0=Not applicable, 1=Yes, 2=No |
-| usia | 18 | TargetedAdvertisingOptOut | 2 bit int. 0=Not applicable, 1=Yes, 2=No |
-| usia | 18 | SensitiveDataProcessing | 2 bit int array of size 8. 0=Not applicable, 1=Yes, 2=No |
-| usia | 18 | KnownChildSensitiveDataConsents | 2 bit int. 0=Not applicable, 1=Yes, 2=No |
-| usia | 18 | MspaCoveredTransaction | 2 bit int. 0=Not applicable, 1=Yes, 2=No |
-| usia | 18 | MspaOptOutOptionMode | 2 bit int. 0=Not applicable, 1=Yes, 2=No |
-| usia | 18 | MspaServiceProviderMode | 2 bit int. 0=Not applicable, 1=Yes, 2=No |
-| usia | 18 | GpcSegmentType | 2 bit int. Value is 1 |
-| usia | 18 | GpcSegmentIncluded | Boolean. Default is true |
-| usia | 18 | Gpc | Boolean |
-| usne | 19 | Version | 6 bit int. Value is 1 |
-| usne | 19 | ProcessingNotice | 2 bit int. 0=Not applicable, 1=Yes, 2=No |
-| usne | 19 | SaleOptOutNotice | 2 bit int. 0=Not applicable, 1=Yes, 2=No |
-| usne | 19 | TargetedAdvertisingOptOutNotice | 2 bit int. 0=Not applicable, 1=Yes, 2=No |
-| usne | 19 | SaleOptOut | 2 bit int. 0=Not applicable, 1=Yes, 2=No |
-| usne | 19 | TargetedAdvertisingOptOut | 2 bit int. 0=Not applicable, 1=Yes, 2=No |
-| usne | 19 | SensitiveDataProcessing | 2 bit int array of size 8. 0=Not applicable, 1=Yes, 2=No |
-| usne | 19 | KnownChildSensitiveDataConsents | 2 bit int. 0=Not applicable, 1=Yes, 2=No |
-| usne | 19 | AdditionalDataProcessingConsent | 2 bit int. 0=Not applicable, 1=Yes, 2=No |
-| usne | 19 | MspaCoveredTransaction | 2 bit int. 0=Not applicable, 1=Yes, 2=No |
-| usne | 19 | MspaOptOutOptionMode | 2 bit int. 0=Not applicable, 1=Yes, 2=No |
-| usne | 19 | MspaServiceProviderMode | 2 bit int. 0=Not applicable, 1=Yes, 2=No |
-| usne | 19 | GpcSegmentType | 2 bit int. Value is 1 |
-| usne | 19 | GpcSegmentIncluded | Boolean. Default is true |
-| usne | 19 | Gpc | Boolean |
-| usnh | 20 | Version | 6 bit int. Value is 1 |
-| usnh | 20 | ProcessingNotice | 2 bit int. 0=Not applicable, 1=Yes, 2=No |
-| usnh | 20 | SaleOptOutNotice | 2 bit int. 0=Not applicable, 1=Yes, 2=No |
-| usnh | 20 | TargetedAdvertisingOptOutNotice | 2 bit int. 0=Not applicable, 1=Yes, 2=No |
-| usnh | 20 | SaleOptOut | 2 bit int. 0=Not applicable, 1=Yes, 2=No |
-| usnh | 20 | TargetedAdvertisingOptOut | 2 bit int. 0=Not applicable, 1=Yes, 2=No |
-| usnh | 20 | SensitiveDataProcessing | 2 bit int array of size 8. 0=Not applicable, 1=Yes, 2=No |
-| usnh | 20 | KnownChildSensitiveDataConsents | 2 bit int array of size 3. 0=Not applicable, 1=Yes, 2=No |
-| usnh | 20 | AdditionalDataProcessingConsent | 2 bit int. 0=Not applicable, 1=Yes, 2=No |
-| usnh | 20 | MspaCoveredTransaction | 2 bit int. 0=Not applicable, 1=Yes, 2=No |
-| usnh | 20 | MspaOptOutOptionMode | 2 bit int. 0=Not applicable, 1=Yes, 2=No |
-| usnh | 20 | MspaServiceProviderMode | 2 bit int. 0=Not applicable, 1=Yes, 2=No |
-| usnh | 20 | GpcSegmentType | 2 bit int. Value is 1 |
-| usnh | 20 | GpcSegmentIncluded | Boolean. Default is true |
-| usnh | 20 | Gpc | Boolean |
-| usnj | 21 | Version | 6 bit int. Value is 1 |
-| usnj | 21 | ProcessingNotice | 2 bit int. 0=Not applicable, 1=Yes, 2=No |
-| usnj | 21 | SaleOptOutNotice | 2 bit int. 0=Not applicable, 1=Yes, 2=No |
-| usnj | 21 | TargetedAdvertisingOptOutNotice | 2 bit int. 0=Not applicable, 1=Yes, 2=No |
-| usnj | 21 | SaleOptOut | 2 bit int. 0=Not applicable, 1=Yes, 2=No |
-| usnj | 21 | TargetedAdvertisingOptOut | 2 bit int. 0=Not applicable, 1=Yes, 2=No |
-| usnj | 21 | SensitiveDataProcessing | 2 bit int array of size 10. 0=Not applicable, 1=Yes, 2=No |
-| usnj | 21 | KnownChildSensitiveDataConsents | 2 bit int array of size 5. 0=Not applicable, 1=Yes, 2=No |
-| usnj | 21 | AdditionalDataProcessingConsent | 2 bit int. 0=Not applicable, 1=Yes, 2=No |
-| usnj | 21 | MspaCoveredTransaction | 2 bit int. 0=Not applicable, 1=Yes, 2=No |
-| usnj | 21 | MspaOptOutOptionMode | 2 bit int. 0=Not applicable, 1=Yes, 2=No |
-| usnj | 21 | MspaServiceProviderMode | 2 bit int. 0=Not applicable, 1=Yes, 2=No |
-| usnj | 21 | GpcSegmentType | 2 bit int. Value is 1 |
-| usnj | 21 | GpcSegmentIncluded | Boolean. Default is true |
-| usnj | 21 | Gpc | Boolean |
-| ustn | 22 | Version | 6 bit int. Value is 1 |
-| ustn | 22 | ProcessingNotice | 2 bit int. 0=Not applicable, 1=Yes, 2=No |
-| ustn | 22 | SaleOptOutNotice | 2 bit int. 0=Not applicable, 1=Yes, 2=No |
-| ustn | 22 | TargetedAdvertisingOptOutNotice | 2 bit int. 0=Not applicable, 1=Yes, 2=No |
-| ustn | 22 | SaleOptOut | 2 bit int. 0=Not applicable, 1=Yes, 2=No |
-| ustn | 22 | TargetedAdvertisingOptOut | 2 bit int. 0=Not applicable, 1=Yes, 2=No |
-| ustn | 22 | SensitiveDataProcessing | 2 bit int array of size 8. 0=Not applicable, 1=Yes, 2=No |
-| ustn | 22 | KnownChildSensitiveDataConsents | 2 bit int. 0=Not applicable, 1=Yes, 2=No |
-| ustn | 22 | AdditionalDataProcessingConsent | 2 bit int. 0=Not applicable, 1=Yes, 2=No |
-| ustn | 22 | MspaCoveredTransaction | 2 bit int. 0=Not applicable, 1=Yes, 2=No |
-| ustn | 22 | MspaOptOutOptionMode | 2 bit int. 0=Not applicable, 1=Yes, 2=No |
-| ustn | 22 | MspaServiceProviderMode | 2 bit int. 0=Not applicable, 1=Yes, 2=No |
-| ustn | 22 | GpcSegmentType | 2 bit int. Value is 1 |
-| ustn | 22 | GpcSegmentIncluded | Boolean. Default is true |
-| ustn | 22 | Gpc | Boolean |
-| usmn | 23 | Version | 6 bit int. Value is 1 |
-| usmn | 23 | ProcessingNotice | 2 bit int. 0=Not applicable, 1=Yes, 2=No |
-| usmn | 23 | SaleOptOutNotice | 2 bit int. 0=Not applicable, 1=Yes, 2=No |
-| usmn | 23 | TargetedAdvertisingOptOutNotice | 2 bit int. 0=Not applicable, 1=Yes, 2=No |
-| usmn | 23 | SaleOptOut | 2 bit int. 0=Not applicable, 1=Yes, 2=No |
-| usmn | 23 | TargetedAdvertisingOptOut | 2 bit int. 0=Not applicable, 1=Yes, 2=No |
-| usmn | 23 | SensitiveDataProcessing | 2 bit int array of size 8. 0=Not applicable, 1=Yes, 2=No |
-| usmn | 23 | KnownChildSensitiveDataConsents | 2 bit int. 0=Not applicable, 1=Yes, 2=No |
-| usmn | 23 | AdditionalDataProcessingConsent | 2 bit int. 0=Not applicable, 1=Yes, 2=No |
-| usmn | 23 | MspaCoveredTransaction | 2 bit int. 0=Not applicable, 1=Yes, 2=No |
-| usmn | 23 | MspaOptOutOptionMode | 2 bit int. 0=Not applicable, 1=Yes, 2=No |
-| usmn | 23 | MspaServiceProviderMode | 2 bit int. 0=Not applicable, 1=Yes, 2=No |
-| usmn | 23 | GpcSegmentType | 2 bit int. Value is 1 |
-| usmn | 23 | GpcSegmentIncluded | Boolean. Default is true |
-| usmn | 23 | Gpc | Boolean |
## Contributing
Here you can find the [contributing guide](CONTRIBUTING.md) to help maintain and update the library. This library is managed by the Code Libraries Subgroup of the Global Privacy Working Group at the IAB Tech Lab. To join the group, please reach out to support@iabtechlab.com.
diff --git a/iabgpp-encoder/pom.xml b/iabgpp-encoder/pom.xml
index bf84bbbc..fbae54d6 100644
--- a/iabgpp-encoder/pom.xml
+++ b/iabgpp-encoder/pom.xml
@@ -7,7 +7,7 @@
com.iabgpp
iabgpp-core
- 3.2.6-SNAPSHOT
+ 4.0.0-RC1
iabgpp-encoder
@@ -27,7 +27,22 @@
maven-surefire-plugin
- 2.22.2
+
+
+ org.jacoco
+ jacoco-maven-plugin
+
+
+ org.apache.maven.plugins
+ maven-javadoc-plugin
+
+
+ org.apache.maven.plugins
+ maven-source-plugin
+
+
+ com.diffplug.spotless
+ spotless-maven-plugin
diff --git a/iabgpp-encoder/src/main/java/com/iab/gpp/encoder/GppModel.java b/iabgpp-encoder/src/main/java/com/iab/gpp/encoder/GppModel.java
index 2eef8aa5..22da774b 100644
--- a/iabgpp-encoder/src/main/java/com/iab/gpp/encoder/GppModel.java
+++ b/iabgpp-encoder/src/main/java/com/iab/gpp/encoder/GppModel.java
@@ -1,560 +1,380 @@
package com.iab.gpp.encoder;
+import com.iab.gpp.encoder.datatype.IntegerSet;
+import com.iab.gpp.encoder.error.DecodingException;
+import com.iab.gpp.encoder.error.InvalidFieldException;
+import com.iab.gpp.encoder.field.FieldKey;
+import com.iab.gpp.encoder.section.AbstractEncodable;
+import com.iab.gpp.encoder.section.EncodableSection;
+import com.iab.gpp.encoder.section.HeaderV1;
+import com.iab.gpp.encoder.section.SlicedCharSequence;
+import com.iab.gpp.encoder.section.TcfCaV1;
+import com.iab.gpp.encoder.section.TcfEuV2;
+import com.iab.gpp.encoder.section.UsCa;
+import com.iab.gpp.encoder.section.UsCo;
+import com.iab.gpp.encoder.section.UsCt;
+import com.iab.gpp.encoder.section.UsDe;
+import com.iab.gpp.encoder.section.UsFl;
+import com.iab.gpp.encoder.section.UsIa;
+import com.iab.gpp.encoder.section.UsMn;
+import com.iab.gpp.encoder.section.UsMt;
+import com.iab.gpp.encoder.section.UsNat;
+import com.iab.gpp.encoder.section.UsNe;
+import com.iab.gpp.encoder.section.UsNh;
+import com.iab.gpp.encoder.section.UsNj;
+import com.iab.gpp.encoder.section.UsOr;
+import com.iab.gpp.encoder.section.UsTn;
+import com.iab.gpp.encoder.section.UsTx;
+import com.iab.gpp.encoder.section.UsUt;
+import com.iab.gpp.encoder.section.UsVa;
+import com.iab.gpp.encoder.section.UspV1;
import java.util.ArrayList;
-import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
-import java.util.Map;
-import java.util.stream.Collectors;
-import com.iab.gpp.encoder.error.DecodingException;
-import com.iab.gpp.encoder.error.EncodingException;
-import com.iab.gpp.encoder.error.InvalidFieldException;
-import com.iab.gpp.encoder.field.HeaderV1Field;
-import com.iab.gpp.encoder.section.*;
-
-public class GppModel {
- private Map sections = new HashMap<>();
-
- private String encodedString;
+import java.util.PrimitiveIterator;
+import java.util.function.Supplier;
+
+public class GppModel extends AbstractEncodable {
+
+ // NOTE: we generally use concrete types to avoid the cost of interface calls
+ private static final HashMap>> SECTION_ID_TO_CONSTRUCTOR =
+ new HashMap<>();
+ private static final HashMap SECTION_NAME_TO_ID = new HashMap<>();
+
+ static {
+ List>> constructors = new ArrayList<>();
+
+ // ADDING A NEW SECTION:
+ // register section constructors here and add get*Section() method below
+ constructors.add(TcfEuV2::new);
+ constructors.add(TcfCaV1::new);
+ constructors.add(UspV1::new);
+ constructors.add(UsNat::new);
+ constructors.add(UsCa::new);
+ constructors.add(UsVa::new);
+ constructors.add(UsCo::new);
+ constructors.add(UsUt::new);
+ constructors.add(UsCt::new);
+ constructors.add(UsFl::new);
+ constructors.add(UsMt::new);
+ constructors.add(UsOr::new);
+ constructors.add(UsTx::new);
+ constructors.add(UsDe::new);
+ constructors.add(UsIa::new);
+ constructors.add(UsNe::new);
+ constructors.add(UsNh::new);
+ constructors.add(UsNj::new);
+ constructors.add(UsTn::new);
+ constructors.add(UsMn::new);
+
+ for (Supplier> constructor : constructors) {
+ EncodableSection> prototype = constructor.get();
+ Integer id = prototype.getId();
+ SECTION_ID_TO_CONSTRUCTOR.put(id, constructor);
+ SECTION_NAME_TO_ID.put(prototype.getName(), id);
+ }
+ }
- private boolean dirty = false;
- private boolean decoded = true;
+ private final HashMap> sections;
+ private final HeaderV1 header;
public GppModel() {
-
+ // empirically, most gpp strings have around 2 sections, so pad for more
+ this.sections = new HashMap<>(4);
+ this.header = new HeaderV1();
}
public GppModel(String encodedString) {
+ this();
decode(encodedString);
}
- public void setFieldValue(int sectionId, String fieldName, Object value) {
- setFieldValue(Sections.SECTION_ID_NAME_MAP.get(sectionId), fieldName, value);
+ public void setFieldValue(String sectionName, FieldKey fieldName, Object value) {
+ setFieldValue(SECTION_NAME_TO_ID.get(sectionName), fieldName, value);
}
- public void setFieldValue(String sectionName, String fieldName, Object value) {
- if (!this.decoded) {
- this.sections = this.decodeModel(this.encodedString);
- this.dirty = false;
- this.decoded = true;
- }
-
- EncodableSection section = null;
- if (!this.sections.containsKey(sectionName)) {
- if (sectionName.equals(TcfCaV1.NAME)) {
- section = new TcfCaV1();
- this.sections.put(TcfCaV1.NAME, section);
- } else if (sectionName.equals(TcfEuV2.NAME)) {
- section = new TcfEuV2();
- this.sections.put(TcfEuV2.NAME, section);
- } else if (sectionName.equals(UspV1.NAME)) {
- section = new UspV1();
- this.sections.put(UspV1.NAME, section);
- } else if (sectionName.equals(UsNat.NAME)) {
- section = new UsNat();
- this.sections.put(UsNat.NAME, section);
- } else if (sectionName.equals(UsCa.NAME)) {
- section = new UsCa();
- this.sections.put(UsCa.NAME, section);
- } else if (sectionName.equals(UsVa.NAME)) {
- section = new UsVa();
- this.sections.put(UsVa.NAME, section);
- } else if (sectionName.equals(UsCo.NAME)) {
- section = new UsCo();
- this.sections.put(UsCo.NAME, section);
- } else if (sectionName.equals(UsUt.NAME)) {
- section = new UsUt();
- this.sections.put(UsUt.NAME, section);
- } else if (sectionName.equals(UsCt.NAME)) {
- section = new UsCt();
- this.sections.put(UsCt.NAME, section);
- } else if (sectionName.equals(UsFl.NAME)) {
- section = new UsFl();
- this.sections.put(UsFl.NAME, section);
- } else if (sectionName.equals(UsMt.NAME)) {
- section = new UsMt();
- this.sections.put(UsMt.NAME, section);
- } else if (sectionName.equals(UsOr.NAME)) {
- section = new UsOr();
- this.sections.put(UsOr.NAME, section);
- } else if (sectionName.equals(UsTx.NAME)) {
- section = new UsTx();
- this.sections.put(UsTx.NAME, section);
- } else if (sectionName.equals(UsDe.NAME)) {
- section = new UsDe();
- this.sections.put(UsDe.NAME, section);
- } else if (sectionName.equals(UsIa.NAME)) {
- section = new UsIa();
- this.sections.put(UsIa.NAME, section);
- } else if (sectionName.equals(UsNe.NAME)) {
- section = new UsNe();
- this.sections.put(UsNe.NAME, section);
- } else if (sectionName.equals(UsNh.NAME)) {
- section = new UsNh();
- this.sections.put(UsNh.NAME, section);
- } else if (sectionName.equals(UsNj.NAME)) {
- section = new UsNj();
- this.sections.put(UsNj.NAME, section);
- } else if (sectionName.equals(UsTn.NAME)) {
- section = new UsTn();
- this.sections.put(UsTn.NAME, section);
- } else if (sectionName.equals(UsMn.NAME)) {
- section = new UsMn();
- this.sections.put(UsMn.NAME, section);
+ private EncodableSection> getOrCreateSection(Integer sectionId) {
+ EncodableSection> section = this.sections.get(sectionId);
+ if (section == null) {
+ Supplier> constructor = SECTION_ID_TO_CONSTRUCTOR.get(sectionId);
+ if (constructor != null) {
+ section = constructor.get();
+ this.sections.put(sectionId, section);
+ this.header.getSectionsIds().addInt(section.getId());
}
- } else {
- section = this.sections.get(sectionName);
}
+ return section;
+ }
+ public void setFieldValue(int sectionId, FieldKey fieldName, Object value) {
+ ensureDecode();
+ EncodableSection> section = getOrCreateSection(sectionId);
if (section != null) {
section.setFieldValue(fieldName, value);
- this.dirty = true;
} else {
- throw new InvalidFieldException(sectionName + "." + fieldName + " not found");
+ throw new InvalidFieldException(sectionId + "." + fieldName + " not found");
}
}
- public Object getFieldValue(int sectionId, String fieldName) {
- return getFieldValue(Sections.SECTION_ID_NAME_MAP.get(sectionId), fieldName);
+ public Object getFieldValue(String sectionName, FieldKey fieldName) {
+ return getFieldValue(SECTION_NAME_TO_ID.get(sectionName), fieldName);
}
- public Object getFieldValue(String sectionName, String fieldName) {
- if (!this.decoded) {
- this.sections = this.decodeModel(this.encodedString);
- this.dirty = false;
- this.decoded = true;
- }
-
- if (this.sections.containsKey(sectionName)) {
- return this.sections.get(sectionName).getFieldValue(fieldName);
+ public Object getFieldValue(int sectionId, FieldKey fieldName) {
+ EncodableSection> field = getSection(sectionId);
+ if (field != null) {
+ return field.getFieldValue(fieldName);
} else {
return null;
}
}
- public boolean hasField(int sectionId, String fieldName) {
- return hasField(Sections.SECTION_ID_NAME_MAP.get(sectionId), fieldName);
+ public boolean hasField(String sectionName, FieldKey fieldName) {
+ return hasField(SECTION_NAME_TO_ID.get(sectionName), fieldName);
}
- public boolean hasField(String sectionName, String fieldName) {
- if (!this.decoded) {
- this.sections = this.decodeModel(this.encodedString);
- this.dirty = false;
- this.decoded = true;
- }
-
- if (this.sections.containsKey(sectionName)) {
- return this.sections.get(sectionName).hasField(fieldName);
+ public boolean hasField(int sectionId, FieldKey fieldName) {
+ EncodableSection> field = getSection(sectionId);
+ if (field != null) {
+ return field.hasField(fieldName);
} else {
return false;
}
}
- public boolean hasSection(int sectionId) {
- return hasSection(Sections.SECTION_ID_NAME_MAP.get(sectionId));
- }
-
public boolean hasSection(String sectionName) {
- if (!this.decoded) {
- this.sections = this.decodeModel(this.encodedString);
- this.dirty = false;
- this.decoded = true;
- }
+ return hasSection(SECTION_NAME_TO_ID.get(sectionName));
+ }
- return this.sections.containsKey(sectionName);
+ public boolean hasSection(int sectionId) {
+ ensureDecode();
+ return this.sections.containsKey(sectionId);
}
public HeaderV1 getHeader() {
- if (!this.decoded) {
- this.sections = this.decodeModel(this.encodedString);
- this.dirty = false;
- this.decoded = true;
- }
-
- HeaderV1 header = new HeaderV1();
- try {
- header.setFieldValue("SectionIds", this.getSectionIds());
- } catch (InvalidFieldException e) {
-
- }
+ ensureDecode();
return header;
}
- public EncodableSection getSection(int sectionId) {
- return getSection(Sections.SECTION_ID_NAME_MAP.get(sectionId));
+ public EncodableSection> getSection(int sectionId) {
+ ensureDecode();
+ return this.sections.get(sectionId);
}
- public EncodableSection getSection(String sectionName) {
- if (!this.decoded) {
- this.sections = this.decodeModel(this.encodedString);
- this.dirty = false;
- this.decoded = true;
- }
-
- if (this.sections.containsKey(sectionName)) {
- return this.sections.get(sectionName);
- } else {
- return null;
- }
- }
-
- public void deleteSection(int sectionId) {
- deleteSection(Sections.SECTION_ID_NAME_MAP.get(sectionId));
+ public EncodableSection> getSection(String sectionName) {
+ return getSection(SECTION_NAME_TO_ID.get(sectionName));
}
public void deleteSection(String sectionName) {
- if (!this.decoded) {
- this.sections = this.decodeModel(this.encodedString);
- this.dirty = false;
- this.decoded = true;
- }
+ deleteSection(SECTION_NAME_TO_ID.get(sectionName));
+ }
- if (this.sections.containsKey(sectionName)) {
- this.sections.remove(sectionName);
- this.dirty = true;
+ public void deleteSection(int sectionId) {
+ ensureDecode();
+ EncodableSection> removed = this.sections.remove(sectionId);
+ if (removed != null) {
+ this.header.getSectionsIds().removeInt(removed.getId());
}
}
public void clear() {
- this.sections.clear();
- this.encodedString = null;
- this.dirty = false;
- this.decoded = true;
+ ensureDecode();
+ if (!this.sections.isEmpty()) {
+ this.sections.clear();
+ this.header.getSectionsIds().clear();
+ }
}
public TcfCaV1 getTcfCaV1Section() {
- return (TcfCaV1) getSection(TcfCaV1.NAME);
+ return (TcfCaV1) getSection(TcfCaV1.ID);
}
public TcfEuV2 getTcfEuV2Section() {
- return (TcfEuV2) getSection(TcfEuV2.NAME);
+ return (TcfEuV2) getSection(TcfEuV2.ID);
}
public UspV1 getUspV1Section() {
- return (UspV1) getSection(UspV1.NAME);
+ return (UspV1) getSection(UspV1.ID);
}
public UsNat getUsNatSection() {
- return (UsNat) getSection(UsNat.NAME);
+ return (UsNat) getSection(UsNat.ID);
}
public UsCa getUsCaSection() {
- return (UsCa) getSection(UsCa.NAME);
+ return (UsCa) getSection(UsCa.ID);
}
public UsVa getUsVaSection() {
- return (UsVa) getSection(UsVa.NAME);
+ return (UsVa) getSection(UsVa.ID);
}
public UsCo getUsCoSection() {
- return (UsCo) getSection(UsCo.NAME);
+ return (UsCo) getSection(UsCo.ID);
}
public UsUt getUsUtSection() {
- return (UsUt) getSection(UsUt.NAME);
+ return (UsUt) getSection(UsUt.ID);
}
public UsCt getUsCtSection() {
- return (UsCt) getSection(UsCt.NAME);
+ return (UsCt) getSection(UsCt.ID);
}
public UsFl getUsFlSection() {
- return (UsFl) getSection(UsFl.NAME);
+ return (UsFl) getSection(UsFl.ID);
}
public UsMt getUsMtSection() {
- return (UsMt) getSection(UsMt.NAME);
+ return (UsMt) getSection(UsMt.ID);
}
public UsOr getUsOrSection() {
- return (UsOr) getSection(UsOr.NAME);
+ return (UsOr) getSection(UsOr.ID);
}
public UsTx getUsTxSection() {
- return (UsTx) getSection(UsTx.NAME);
+ return (UsTx) getSection(UsTx.ID);
}
public UsDe getUsDeSection() {
- return (UsDe) getSection(UsDe.NAME);
+ return (UsDe) getSection(UsDe.ID);
}
public UsIa getUsIaSection() {
- return (UsIa) getSection(UsIa.NAME);
+ return (UsIa) getSection(UsIa.ID);
}
public UsNe getUsNeSection() {
- return (UsNe) getSection(UsNe.NAME);
+ return (UsNe) getSection(UsNe.ID);
}
public UsNh getUsNhSection() {
- return (UsNh) getSection(UsNh.NAME);
+ return (UsNh) getSection(UsNh.ID);
}
public UsNj getUsNjSection() {
- return (UsNj) getSection(UsNj.NAME);
+ return (UsNj) getSection(UsNj.ID);
}
public UsTn getUsTnSection() {
- return (UsTn) getSection(UsTn.NAME);
+ return (UsTn) getSection(UsTn.ID);
}
public UsMn getUsMnSection() {
- return (UsMn) getSection(UsMn.NAME);
+ return (UsMn) getSection(UsMn.ID);
}
- public List getSectionIds() {
- if (!this.decoded) {
- this.sections = this.decodeModel(this.encodedString);
- this.dirty = false;
- this.decoded = true;
- }
-
- List sectionIds = new ArrayList<>();
- for (int i = 0; i < Sections.SECTION_ORDER.size(); i++) {
- String sectionName = Sections.SECTION_ORDER.get(i);
- if (this.sections.containsKey(sectionName)) {
- EncodableSection section = this.sections.get(sectionName);
- sectionIds.add(section.getId());
- }
- }
- return sectionIds;
- }
-
- protected String encodeModel(Map sections) {
- List encodedSections = new ArrayList<>();
- List sectionIds = new ArrayList<>();
- for (int i = 0; i < Sections.SECTION_ORDER.size(); i++) {
- String sectionName = Sections.SECTION_ORDER.get(i);
- if (sections.containsKey(sectionName)) {
- EncodableSection section = sections.get(sectionName);
- encodedSections.add(section.encode());
- sectionIds.add(section.getId());
- }
- }
+ public IntegerSet getSectionIds() {
+ ensureDecode();
+ return header.getSectionsIds();
+ }
- HeaderV1 header = new HeaderV1();
- try {
- header.setFieldValue("SectionIds", getSectionIds());
- } catch (InvalidFieldException e) {
- throw new EncodingException(e);
+ @Override
+ protected CharSequence doEncode() {
+ List encodedSections = new ArrayList<>();
+ encodedSections.add(header.encodeCharSequence());
+ for (Integer sectionId : header.getSectionsIds()) {
+ EncodableSection> section = sections.get(sectionId);
+ encodedSections.add(section.encodeCharSequence());
}
- encodedSections.add(0, header.encode());
-
- String encodedString = encodedSections.stream().collect(Collectors.joining("~"));
- return encodedString;
+ return SlicedCharSequence.join('~', encodedSections);
}
- protected Map decodeModel(String str) {
- if (str == null || str.isEmpty() || str.startsWith("DB")) {
- Map sections = new HashMap<>();
+ @Override
+ protected void doDecode(CharSequence str) {
+ if (str == null || str.isEmpty() || (str.charAt(0) == 'D' && str.charAt(1) == 'B')) {
+ if (!sections.isEmpty()) {
+ sections.clear();
+ header.getSectionsIds().clear();
+ }
if (str != null && !str.isEmpty()) {
- String[] encodedSections = str.split("~");
- HeaderV1 header = new HeaderV1(encodedSections[0]);
- sections.put(HeaderV1.NAME, header);
-
- @SuppressWarnings("unchecked")
- List sectionIds = (List) header.getFieldValue("SectionIds");
- for (int i = 0; i < sectionIds.size(); i++) {
- if (sectionIds.get(i).equals(TcfEuV2.ID)) {
- TcfEuV2 section = new TcfEuV2(encodedSections[i + 1]);
- sections.put(TcfEuV2.NAME, section);
- } else if (sectionIds.get(i).equals(TcfCaV1.ID)) {
- TcfCaV1 section = new TcfCaV1(encodedSections[i + 1]);
- sections.put(TcfCaV1.NAME, section);
- } else if (sectionIds.get(i).equals(UspV1.ID)) {
- UspV1 section = new UspV1(encodedSections[i + 1]);
- sections.put(UspV1.NAME, section);
- } else if (sectionIds.get(i).equals(UsCa.ID)) {
- UsCa section = new UsCa(encodedSections[i + 1]);
- sections.put(UsCa.NAME, section);
- } else if (sectionIds.get(i).equals(UsNat.ID)) {
- UsNat section = new UsNat(encodedSections[i + 1]);
- sections.put(UsNat.NAME, section);
- } else if (sectionIds.get(i).equals(UsVa.ID)) {
- UsVa section = new UsVa(encodedSections[i + 1]);
- sections.put(UsVa.NAME, section);
- } else if (sectionIds.get(i).equals(UsCo.ID)) {
- UsCo section = new UsCo(encodedSections[i + 1]);
- sections.put(UsCo.NAME, section);
- } else if (sectionIds.get(i).equals(UsUt.ID)) {
- UsUt section = new UsUt(encodedSections[i + 1]);
- sections.put(UsUt.NAME, section);
- } else if (sectionIds.get(i).equals(UsCt.ID)) {
- UsCt section = new UsCt(encodedSections[i + 1]);
- sections.put(UsCt.NAME, section);
- } else if (sectionIds.get(i).equals(UsFl.ID)) {
- UsFl section = new UsFl(encodedSections[i + 1]);
- sections.put(UsFl.NAME, section);
- } else if (sectionIds.get(i).equals(UsMt.ID)) {
- UsMt section = new UsMt(encodedSections[i + 1]);
- sections.put(UsMt.NAME, section);
- } else if (sectionIds.get(i).equals(UsOr.ID)) {
- UsOr section = new UsOr(encodedSections[i + 1]);
- sections.put(UsOr.NAME, section);
- } else if (sectionIds.get(i).equals(UsTx.ID)) {
- UsTx section = new UsTx(encodedSections[i + 1]);
- sections.put(UsTx.NAME, section);
- } else if (sectionIds.get(i).equals(UsDe.ID)) {
- UsDe section = new UsDe(encodedSections[i + 1]);
- sections.put(UsDe.NAME, section);
- } else if (sectionIds.get(i).equals(UsIa.ID)) {
- UsIa section = new UsIa(encodedSections[i + 1]);
- sections.put(UsIa.NAME, section);
- } else if (sectionIds.get(i).equals(UsNe.ID)) {
- UsNe section = new UsNe(encodedSections[i + 1]);
- sections.put(UsNe.NAME, section);
- } else if (sectionIds.get(i).equals(UsNh.ID)) {
- UsNh section = new UsNh(encodedSections[i + 1]);
- sections.put(UsNh.NAME, section);
- } else if (sectionIds.get(i).equals(UsNj.ID)) {
- UsNj section = new UsNj(encodedSections[i + 1]);
- sections.put(UsNj.NAME, section);
- } else if (sectionIds.get(i).equals(UsTn.ID)) {
- UsTn section = new UsTn(encodedSections[i + 1]);
- sections.put(UsTn.NAME, section);
- } else if (sectionIds.get(i).equals(UsMn.ID)) {
- UsMn section = new UsMn(encodedSections[i + 1]);
- sections.put(UsMn.NAME, section);
+ List encodedSections = SlicedCharSequence.split(str, '~');
+ header.decode(encodedSections.get(0));
+
+ PrimitiveIterator.OfInt it = header.getSectionsIds().iterator();
+ int i = 1;
+ while (it.hasNext()) {
+ CharSequence encodedSection = encodedSections.get(i++);
+ int sectionId = it.nextInt();
+ EncodableSection> section = getOrCreateSection(sectionId);
+ if (section != null) {
+ section.decode(encodedSection);
+ } else {
+ // we do not support re-encoding this section
+ header.getSectionsIds().removeInt(sectionId);
}
}
}
-
- return sections;
- } else if (str.startsWith("C")) {
+ } else if (str.charAt(0) == 'C') {
// old tcfeu only string
- Map sections = new HashMap<>();
-
- TcfEuV2 section = new TcfEuV2(str);
- sections.put(TcfEuV2.NAME, section);
-
- HeaderV1 header = new HeaderV1();
- header.setFieldValue(HeaderV1Field.SECTION_IDS, Arrays.asList(2));
- sections.put(HeaderV1.NAME, section);
-
- return sections;
+ EncodableSection> section = getOrCreateSection(TcfEuV2.ID);
+ section.decode(str);
} else {
throw new DecodingException("Unable to decode '" + str + "'");
}
}
- public String encodeSection(int sectionId) {
- return encodeSection(Sections.SECTION_ID_NAME_MAP.get(sectionId));
- }
-
public String encodeSection(String sectionName) {
- if (!this.decoded) {
- this.sections = this.decodeModel(this.encodedString);
- this.dirty = false;
- this.decoded = true;
- }
+ return encodeSection(SECTION_NAME_TO_ID.get(sectionName));
+ }
- if (this.sections.containsKey(sectionName)) {
- return this.sections.get(sectionName).encode();
+ public String encodeSection(int sectionId) {
+ EncodableSection> section = getSection(sectionId);
+ if (section != null) {
+ return section.encode();
} else {
return null;
}
}
- public void decodeSection(int sectionId, String encodedString) {
- decodeSection(Sections.SECTION_ID_NAME_MAP.get(sectionId), encodedString);
- }
-
public void decodeSection(String sectionName, String encodedString) {
- if (!this.decoded) {
- this.sections = this.decodeModel(this.encodedString);
- this.dirty = false;
- this.decoded = true;
- }
-
- EncodableSection section = null;
- if (!this.sections.containsKey(sectionName)) {
- if (sectionName.equals(TcfEuV2.NAME)) {
- section = new TcfEuV2();
- this.sections.put(TcfEuV2.NAME, section);
- } else if (sectionName.equals(TcfCaV1.NAME)) {
- section = new TcfCaV1();
- this.sections.put(TcfCaV1.NAME, section);
- } else if (sectionName.equals(UspV1.NAME)) {
- section = new UspV1();
- this.sections.put(UspV1.NAME, section);
- } else if (sectionName.equals(UsNat.NAME)) {
- section = new UsNat();
- this.sections.put(UsNat.NAME, section);
- } else if (sectionName.equals(UsCa.NAME)) {
- section = new UsCa();
- this.sections.put(UsCa.NAME, section);
- } else if (sectionName.equals(UsVa.NAME)) {
- section = new UsVa();
- this.sections.put(UsVa.NAME, section);
- } else if (sectionName.equals(UsCo.NAME)) {
- section = new UsCo();
- this.sections.put(UsCo.NAME, section);
- } else if (sectionName.equals(UsUt.NAME)) {
- section = new UsUt();
- this.sections.put(UsUt.NAME, section);
- } else if (sectionName.equals(UsCt.NAME)) {
- section = new UsCt();
- this.sections.put(UsCt.NAME, section);
- } else if (sectionName.equals(UsFl.NAME)) {
- section = new UsFl();
- this.sections.put(UsFl.NAME, section);
- } else if (sectionName.equals(UsMt.NAME)) {
- section = new UsMt();
- this.sections.put(UsMt.NAME, section);
- } else if (sectionName.equals(UsOr.NAME)) {
- section = new UsOr();
- this.sections.put(UsOr.NAME, section);
- } else if (sectionName.equals(UsTx.NAME)) {
- section = new UsTx();
- this.sections.put(UsTx.NAME, section);
- }else if (sectionName.equals(UsDe.NAME)) {
- section = new UsDe();
- this.sections.put(UsDe.NAME, section);
- }else if (sectionName.equals(UsIa.NAME)) {
- section = new UsIa();
- this.sections.put(UsIa.NAME, section);
- }else if (sectionName.equals(UsNe.NAME)) {
- section = new UsNe();
- this.sections.put(UsNe.NAME, section);
- }else if (sectionName.equals(UsNh.NAME)) {
- section = new UsNh();
- this.sections.put(UsNh.NAME, section);
- }else if (sectionName.equals(UsNj.NAME)) {
- section = new UsNj();
- this.sections.put(UsNj.NAME, section);
- }else if (sectionName.equals(UsTn.NAME)) {
- section = new UsTn();
- this.sections.put(UsTn.NAME, section);
- }else if (sectionName.equals(UsMn.NAME)) {
- section = new UsMn();
- this.sections.put(UsMn.NAME, section);
- }
- } else {
- section = this.sections.get(sectionName);
- }
+ decodeSection(SECTION_NAME_TO_ID.get(sectionName), encodedString);
+ }
+ public void decodeSection(int sectionId, String encodedString) {
+ ensureDecode();
+ EncodableSection> section = getOrCreateSection(sectionId);
if (section != null) {
section.decode(encodedString);
- this.dirty = true;
}
}
- public String encode() {
- if (this.encodedString == null || this.encodedString.isEmpty() || this.dirty) {
- this.encodedString = encodeModel(this.sections);
- this.dirty = false;
- this.decoded = true;
+ public String toString() {
+ ensureDecode();
+ StringBuilder sb = new StringBuilder();
+ sb.append('[').append(header);
+ for (Integer sectionId : header.getSectionsIds()) {
+ EncodableSection> section = sections.get(sectionId);
+ sb.append(", ").append(section);
}
-
- return this.encodedString;
+ sb.append(']');
+ return sb.toString();
}
- public void decode(String encodedString) {
- this.encodedString = encodedString;
- this.dirty = false;
- this.decoded = false;
+ @Override
+ public boolean isDirty() {
+ if (header.isDirty()) {
+ return true;
+ }
+ for (Integer sectionId : header.getSectionsIds()) {
+ EncodableSection> section = sections.get(sectionId);
+ if (section != null && section.isDirty()) {
+ return true;
+ }
+ }
+ return false;
}
-
+ @Override
+ public void setDirty(boolean dirty) {
+ header.setDirty(dirty);
+ for (Integer sectionId : header.getSectionsIds()) {
+ EncodableSection> section = sections.get(sectionId);
+ if (section != null) {
+ section.setDirty(dirty);
+ }
+ }
+ }
}
diff --git a/iabgpp-encoder/src/main/java/com/iab/gpp/encoder/base64/AbstractBase64UrlEncoder.java b/iabgpp-encoder/src/main/java/com/iab/gpp/encoder/base64/AbstractBase64UrlEncoder.java
index a5434073..7e2f413f 100644
--- a/iabgpp-encoder/src/main/java/com/iab/gpp/encoder/base64/AbstractBase64UrlEncoder.java
+++ b/iabgpp-encoder/src/main/java/com/iab/gpp/encoder/base64/AbstractBase64UrlEncoder.java
@@ -1,79 +1,97 @@
package com.iab.gpp.encoder.base64;
-import java.util.Map;
-import java.util.regex.Pattern;
-import java.util.stream.Collectors;
-import java.util.stream.Stream;
-import com.iab.gpp.encoder.datatype.encoder.FixedIntegerEncoder;
+import com.iab.gpp.encoder.bitstring.BitSet;
+import com.iab.gpp.encoder.bitstring.BitString;
import com.iab.gpp.encoder.error.DecodingException;
import com.iab.gpp.encoder.error.EncodingException;
+import java.util.Arrays;
public abstract class AbstractBase64UrlEncoder {
- abstract protected String pad(String bitString);
+ protected abstract void pad(BitString bitString);
+
+ private static final int BASE64_BITS = 6;
+ private static final int NO_SYMBOL = -1;
/**
* Base 64 URL character set. Different from standard Base64 char set in that '+' and '/' are
* replaced with '-' and '_'.
*/
- private static String DICT = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
- // prettier-ignore
- private static Map REVERSE_DICT = Stream
- .of(new Object[][] {{'A', 0}, {'B', 1}, {'C', 2}, {'D', 3}, {'E', 4}, {'F', 5}, {'G', 6}, {'H', 7}, {'I', 8},
- {'J', 9}, {'K', 10}, {'L', 11}, {'M', 12}, {'N', 13}, {'O', 14}, {'P', 15}, {'Q', 16}, {'R', 17}, {'S', 18},
- {'T', 19}, {'U', 20}, {'V', 21}, {'W', 22}, {'X', 23}, {'Y', 24}, {'Z', 25}, {'a', 26}, {'b', 27}, {'c', 28},
- {'d', 29}, {'e', 30}, {'f', 31}, {'g', 32}, {'h', 33}, {'i', 34}, {'j', 35}, {'k', 36}, {'l', 37}, {'m', 38},
- {'n', 39}, {'o', 40}, {'p', 41}, {'q', 42}, {'r', 43}, {'s', 44}, {'t', 45}, {'u', 46}, {'v', 47}, {'w', 48},
- {'x', 49}, {'y', 50}, {'z', 51}, {'0', 52}, {'1', 53}, {'2', 54}, {'3', 55}, {'4', 56}, {'5', 57}, {'6', 58},
- {'7', 59}, {'8', 60}, {'9', 61}, {'-', 62}, {'_', 63}})
- .collect(Collectors.toMap(data -> (Character) data[0], data -> (Integer) data[1]));
+ private static final String DICT =
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
- private static Pattern BITSTRING_VERIFICATION_PATTERN = Pattern.compile("^[0-1]*$", Pattern.CASE_INSENSITIVE);
- private static Pattern BASE64URL_VERIFICATION_PATTERN =
- Pattern.compile("^[A-Za-z0-9\\-_]*$", Pattern.CASE_INSENSITIVE);
+ private static final int REVERSE_DICT_SIZE = 128;
+ private static final int[] REVERSE_DICT = new int[REVERSE_DICT_SIZE];
- public String encode(String bitString) {
- // should only be 0 or 1
- if (!BITSTRING_VERIFICATION_PATTERN.matcher(bitString).matches()) {
- throw new EncodingException("Unencodable Base64Url '" + bitString + "'");
+ static {
+ Arrays.fill(REVERSE_DICT, NO_SYMBOL);
+ for (int i = 0; i < DICT.length(); i++) {
+ REVERSE_DICT[DICT.charAt(i)] = i;
}
+ }
- bitString = pad(bitString);
-
- String str = "";
-
- int index = 0;
- while (index <= bitString.length() - 6) {
- String s = bitString.substring(index, index + 6);
-
+ public StringBuilder encode(BitString bitString) {
+ pad(bitString);
+ int length = bitString.length();
+ StringBuilder str = new StringBuilder(length / BASE64_BITS);
+ while (bitString.hasRemaining()) {
try {
- int n = FixedIntegerEncoder.decode(s);
- Character c = AbstractBase64UrlEncoder.DICT.charAt(n);
- str += c;
- index += 6;
+ int n = bitString.readInt(BASE64_BITS);
+ str.append(DICT.charAt(n));
} catch (DecodingException e) {
throw new EncodingException("Unencodable Base64Url '" + bitString + "'");
}
}
-
return str;
}
- public String decode(String str) {
- // should contain only characters from the base64url set
- if (!BASE64URL_VERIFICATION_PATTERN.matcher(str).matches()) {
+ public BitString decode(CharSequence str) {
+ try {
+ int length = str.length();
+ int bitLength = length * BASE64_BITS;
+ int numBlocks = length >> 2;
+ byte[] words = new byte[(numBlocks + 1) * 3];
+ int limit = numBlocks << 2;
+ int dst = 0;
+ int src = 0;
+ while (src < limit) {
+ int b1 = REVERSE_DICT[str.charAt(src++)];
+ int b2 = REVERSE_DICT[str.charAt(src++)];
+ int b3 = REVERSE_DICT[str.charAt(src++)];
+ int b4 = REVERSE_DICT[str.charAt(src++)];
+ if ((b1 | b2 | b3 | b4) < 0) {
+ throw new DecodingException("Undecodable Base64URL string");
+ }
+ int bits0 = b1 << 18 | b2 << 12 | b3 << 6 | b4;
+ words[dst++] = (byte) (bits0 >> 16);
+ words[dst++] = (byte) (bits0 >> 8);
+ words[dst++] = (byte) (bits0);
+ }
+ if (length > limit) {
+ remainder(str, words, length, src, dst);
+ }
+ return new BitString(new BitSet(words), bitLength);
+ } catch (ArrayIndexOutOfBoundsException e) {
throw new DecodingException("Undecodable Base64URL string");
}
+ }
- String bitString = "";
-
- for (int i = 0; i < str.length(); i++) {
- char c = str.charAt(i);
- Integer n = AbstractBase64UrlEncoder.REVERSE_DICT.get(c);
- String s = FixedIntegerEncoder.encode(n, 6);
- bitString += s;
+ private static final void remainder(
+ CharSequence str, byte[] words, int length, int src, int dst) {
+ int b1 = src < length ? REVERSE_DICT[str.charAt(src)] : 0;
+ src++;
+ int b2 = src < length ? REVERSE_DICT[str.charAt(src)] : 0;
+ src++;
+ int b3 = src < length ? REVERSE_DICT[str.charAt(src)] : 0;
+ src++;
+ int b4 = src < length ? REVERSE_DICT[str.charAt(src)] : 0;
+ src++;
+ if ((b1 | b2 | b3 | b4) < 0) {
+ throw new DecodingException("Undecodable Base64URL string");
}
-
- return bitString;
+ int bits0 = b1 << 18 | b2 << 12 | b3 << 6 | b4;
+ words[dst++] = (byte) (bits0 >> 16);
+ words[dst++] = (byte) (bits0 >> 8);
+ words[dst++] = (byte) (bits0);
}
}
diff --git a/iabgpp-encoder/src/main/java/com/iab/gpp/encoder/base64/CompressedBase64UrlEncoder.java b/iabgpp-encoder/src/main/java/com/iab/gpp/encoder/base64/CompressedBase64UrlEncoder.java
index 4ebd7be7..cdeff122 100644
--- a/iabgpp-encoder/src/main/java/com/iab/gpp/encoder/base64/CompressedBase64UrlEncoder.java
+++ b/iabgpp-encoder/src/main/java/com/iab/gpp/encoder/base64/CompressedBase64UrlEncoder.java
@@ -1,38 +1,33 @@
package com.iab.gpp.encoder.base64;
-import java.util.Arrays;
+import com.iab.gpp.encoder.bitstring.BitString;
-public class CompressedBase64UrlEncoder extends AbstractBase64UrlEncoder {
+public final class CompressedBase64UrlEncoder extends AbstractBase64UrlEncoder {
+
+ private static final CompressedBase64UrlEncoder instance = new CompressedBase64UrlEncoder();
+
+ private CompressedBase64UrlEncoder() {}
- private static CompressedBase64UrlEncoder instance = new CompressedBase64UrlEncoder();
-
- private CompressedBase64UrlEncoder() {
-
- }
-
public static CompressedBase64UrlEncoder getInstance() {
return instance;
}
-
+
@Override
- protected String pad(String bitString) {
- char[] chars1 = null;
- if(bitString.length() % 8 > 0) {
- chars1 = new char[8 - (bitString.length() % 8)];
- } else {
- chars1 = new char[0];
+ protected void pad(BitString bitString) {
+ int remainder = bitString.length() % 8;
+ if (remainder > 0) {
+ int padding = 8 - remainder;
+ for (int i = 0; i < padding; i++) {
+ bitString.writeBoolean(false);
+ }
}
- Arrays.fill(chars1, '0');
-
- char[] chars2 = null;
- if((bitString.length() + chars1.length) % 6 > 0) {
- chars2 = new char[6 - ((bitString.length() + chars1.length) % 6)];
- } else {
- chars2 = new char[0];
+
+ remainder = bitString.length() % 6;
+ if (remainder > 0) {
+ int padding = 6 - remainder;
+ for (int i = 0; i < padding; i++) {
+ bitString.writeBoolean(false);
+ }
}
- Arrays.fill(chars2, '0');
-
- return bitString + new String(chars1) + new String(chars2);
}
-
}
diff --git a/iabgpp-encoder/src/main/java/com/iab/gpp/encoder/base64/TraditionalBase64UrlEncoder.java b/iabgpp-encoder/src/main/java/com/iab/gpp/encoder/base64/TraditionalBase64UrlEncoder.java
index 78ffe719..a234b2b4 100644
--- a/iabgpp-encoder/src/main/java/com/iab/gpp/encoder/base64/TraditionalBase64UrlEncoder.java
+++ b/iabgpp-encoder/src/main/java/com/iab/gpp/encoder/base64/TraditionalBase64UrlEncoder.java
@@ -1,28 +1,25 @@
package com.iab.gpp.encoder.base64;
-import java.util.Arrays;
+import com.iab.gpp.encoder.bitstring.BitString;
-public class TraditionalBase64UrlEncoder extends AbstractBase64UrlEncoder {
+public final class TraditionalBase64UrlEncoder extends AbstractBase64UrlEncoder {
+
+ private static final TraditionalBase64UrlEncoder instance = new TraditionalBase64UrlEncoder();
+
+ private TraditionalBase64UrlEncoder() {}
- private static TraditionalBase64UrlEncoder instance = new TraditionalBase64UrlEncoder();
-
- private TraditionalBase64UrlEncoder() {
-
- }
-
public static TraditionalBase64UrlEncoder getInstance() {
return instance;
}
-
+
@Override
- protected String pad(String bitString) {
- if(bitString.length() % 24 > 0) {
- char[] chars = new char[24 - (bitString.length() % 24)];
- Arrays.fill(chars, '0');
- return bitString + new String(chars);
- } else {
- return bitString;
+ protected void pad(BitString bitString) {
+ int remainder = bitString.length() % 24;
+ if (remainder > 0) {
+ int padding = 24 - remainder;
+ for (int i = 0; i < padding; i++) {
+ bitString.writeBoolean(false);
+ }
}
}
-
}
diff --git a/iabgpp-encoder/src/main/java/com/iab/gpp/encoder/bitstring/BitSet.java b/iabgpp-encoder/src/main/java/com/iab/gpp/encoder/bitstring/BitSet.java
new file mode 100644
index 00000000..4f3b6b63
--- /dev/null
+++ b/iabgpp-encoder/src/main/java/com/iab/gpp/encoder/bitstring/BitSet.java
@@ -0,0 +1,171 @@
+package com.iab.gpp.encoder.bitstring;
+
+import com.iab.gpp.encoder.error.DecodingException;
+import java.util.Arrays;
+
+// a thin version of java.util.BitSet
+public final class BitSet {
+
+ private static final byte[] EMPTY = new byte[0];
+ private static final int ADDRESS_BITS_PER_WORD = 3;
+ private static final int BITS_PER_WORD = 1 << ADDRESS_BITS_PER_WORD;
+ private static final int MASK = 1 << (BITS_PER_WORD - 1);
+ private static final int MODULO = BITS_PER_WORD - 1;
+ private static final int CORRECTION = Integer.SIZE - BITS_PER_WORD;
+ private static final int WORD_MASK = 0xff;
+
+ private byte[] words;
+
+ public BitSet(byte[] words) {
+ this.words = words;
+ }
+
+ public BitSet(int initialCapacity) {
+ this(new byte[wordIndex(initialCapacity) + 1]);
+ }
+
+ public BitSet() {
+ this(EMPTY);
+ }
+
+ private static int wordIndex(int index) {
+ if (index < 0) {
+ throw new DecodingException("got negative word index");
+ }
+ return index >> ADDRESS_BITS_PER_WORD;
+ }
+
+ private static int wordMask(int index) {
+ int bit = index & MODULO;
+ return MASK >> bit;
+ }
+
+ private byte[] ensureIndex(int wordIndex) {
+ byte[] words = this.words;
+ int wordsUsed = words.length;
+ if (wordIndex >= wordsUsed) {
+ int request = Math.max(2 * wordsUsed, wordIndex + 1);
+ words = Arrays.copyOf(words, request);
+ this.words = words;
+ }
+ return words;
+ }
+
+ public boolean get(int bitIndex) {
+ int wordIndex = wordIndex(bitIndex);
+ byte[] words = this.words;
+ return (wordIndex < words.length) && (words[wordIndex] & wordMask(bitIndex)) != 0;
+ }
+
+ public void clear(int from, int to) {
+ for (int i = from; i < to; i++) {
+ clear(i);
+ }
+ }
+
+ public void clear(int bitIndex) {
+ int wordIndex = wordIndex(bitIndex);
+ byte[] words = this.words;
+ if (wordIndex < words.length) {
+ words[wordIndex] &= ~wordMask(bitIndex);
+ }
+ }
+
+ public int nextSetBit(int fromIndex) {
+ byte[] words = this.words;
+ int wordsInUse = words.length;
+ int u = wordIndex(fromIndex);
+ if (u >= wordsInUse) {
+ return -1;
+ }
+
+ int bit = fromIndex & MODULO;
+ int word = words[u] & (WORD_MASK >>> bit);
+
+ while (true) {
+ word &= WORD_MASK;
+ if (word != 0) {
+ return (u * BITS_PER_WORD) + Integer.numberOfLeadingZeros(word) - CORRECTION;
+ }
+ if (++u == wordsInUse) {
+ return -1;
+ }
+ word = words[u];
+ }
+ }
+
+ public void set(int from, int to) {
+ for (int i = from; i < to; i++) {
+ set(i);
+ }
+ }
+
+ public void set(int bitIndex) {
+ int wordIndex = wordIndex(bitIndex);
+ byte[] words = ensureIndex(wordIndex);
+ words[wordIndex] |= wordMask(bitIndex);
+ }
+
+ public boolean set(int bitIndex, boolean value) {
+ int wordIndex = wordIndex(bitIndex);
+ int wordMask = wordMask(bitIndex);
+ byte[] words = ensureIndex(wordIndex);
+ boolean prior = (words[wordIndex] & wordMask) != 0;
+ if (prior != value) {
+ if (value) {
+ words[wordIndex] |= wordMask;
+ } else {
+ words[wordIndex] &= ~wordMask;
+ }
+ }
+ return prior;
+ }
+
+ public int readInt(int from, int to) {
+ int startWordIndex = wordIndex(from);
+ int startBit = from & MODULO;
+ int endWordIndex = wordIndex(to);
+ int endBit = to & MODULO;
+ // TODO: is this needed if the caller checks range?
+ byte[] words = ensureIndex(endWordIndex);
+ int out = 0;
+ for (int wordIndex = startWordIndex; wordIndex <= endWordIndex; wordIndex++) {
+ int word = words[wordIndex] & WORD_MASK;
+ if (wordIndex == startWordIndex) {
+ word &= WORD_MASK >>> startBit;
+ }
+ if (wordIndex == endWordIndex) {
+ word &= ~(WORD_MASK >> endBit);
+ out |= word >>> (BITS_PER_WORD - endBit);
+ break;
+ }
+ int remaining = to - ((wordIndex + 1) << ADDRESS_BITS_PER_WORD);
+ out |= word << remaining;
+ }
+ return out;
+ }
+
+ public long readLong(int from, int to) {
+ int startWordIndex = wordIndex(from);
+ int startBit = from & MODULO;
+ int endWordIndex = wordIndex(to);
+ int endBit = to & MODULO;
+ // TODO: is this needed if the caller checks range?
+ byte[] words = ensureIndex(endWordIndex);
+ long out = 0;
+ for (int wordIndex = startWordIndex; wordIndex <= endWordIndex; wordIndex++) {
+ long word = words[wordIndex] & WORD_MASK;
+ if (wordIndex == startWordIndex) {
+ word &= (WORD_MASK >>> startBit);
+ }
+ if (wordIndex == endWordIndex) {
+ word &= ~(WORD_MASK >> endBit);
+ out |= word >>> (BITS_PER_WORD - endBit);
+ break;
+ }
+ int remaining = to - ((wordIndex + 1) << ADDRESS_BITS_PER_WORD);
+ out |= word << remaining;
+ }
+ return out;
+ }
+}
diff --git a/iabgpp-encoder/src/main/java/com/iab/gpp/encoder/bitstring/BitString.java b/iabgpp-encoder/src/main/java/com/iab/gpp/encoder/bitstring/BitString.java
new file mode 100644
index 00000000..f876bd04
--- /dev/null
+++ b/iabgpp-encoder/src/main/java/com/iab/gpp/encoder/bitstring/BitString.java
@@ -0,0 +1,222 @@
+package com.iab.gpp.encoder.bitstring;
+
+import com.iab.gpp.encoder.datatype.FixedIntegerList;
+import com.iab.gpp.encoder.datatype.IntegerSet;
+import com.iab.gpp.encoder.datatype.encoder.FibonacciIntegerEncoder;
+import com.iab.gpp.encoder.error.DecodingException;
+import com.iab.gpp.encoder.error.EncodingException;
+
+public final class BitString {
+ public static final char TRUE = '1';
+ public static final char FALSE = '0';
+ public static final String TRUE_STRING = new String(new char[] {TRUE});
+ public static final String FALSE_STRING = new String(new char[] {FALSE});
+
+ private final BitSet bitSet;
+ private int readIndex;
+ private int writeIndex;
+
+ public BitString(BitSet bitSet, int length) {
+ this.bitSet = bitSet;
+ this.writeIndex = length;
+ }
+
+ public BitString() {
+ this(new BitSet(), 0);
+ }
+
+ public static final BitString of(String str) {
+ int length = str.length();
+ BitString out = new BitString(new BitSet(length), 0);
+ for (int i = 0; i < length; i++) {
+ char c = str.charAt(i);
+ if (c == TRUE) {
+ out.writeBoolean(true);
+ } else if (c == FALSE) {
+ out.writeBoolean(false);
+ } else {
+ throw new DecodingException("Invalid bit string");
+ }
+ }
+ return out;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder(length());
+ for (int i = 0; i < writeIndex; i++) {
+ sb.append(bitSet.get(i) ? TRUE : FALSE);
+ }
+ return sb.toString();
+ }
+
+ private boolean getValue(int i) {
+ if (i >= writeIndex) {
+ throw new DecodingException("Bit string access out of range");
+ }
+ return bitSet.get(i);
+ }
+
+ public int length() {
+ return writeIndex;
+ }
+
+ public boolean hasRemaining() {
+ return readIndex < writeIndex;
+ }
+
+ public void writeEmpty(int length) {
+ this.writeIndex += length;
+ }
+
+ public void writeInt(int value, int length) {
+ int mask = 1 << length;
+ if (value >= mask) {
+ throw new EncodingException(
+ "Numeric value '" + value + "' is too large for a bit string length of '" + length + "'");
+ }
+ for (int i = 0; i < length; i++) {
+ mask >>= 1;
+ writeBoolean((mask & value) != 0);
+ }
+ }
+
+ public void writeLong(long value, int length) {
+ long mask = 1L << length;
+ if (value >= mask) {
+ throw new EncodingException(
+ "Numeric value '" + value + "' is too large for a bit string length of '" + length + "'");
+ }
+ for (int i = 0; i < length; i++) {
+ mask >>= 1;
+ writeBoolean((mask & value) != 0);
+ }
+ }
+
+ public void writeBoolean(boolean value) {
+ int idx = writeIndex++;
+ if (value) {
+ bitSet.set(idx);
+ }
+ }
+
+ public void writeFibonacci(int value) {
+ int largestIndex = 0;
+ for (int i = 0; i < FibonacciIntegerEncoder.FIBONACCI_LIMIT; i++) {
+ if (value >= FibonacciIntegerEncoder.FIBONACCI_NUMBERS[i]) {
+ largestIndex++;
+ } else {
+ break;
+ }
+ }
+ if (largestIndex == FibonacciIntegerEncoder.FIBONACCI_LIMIT) {
+ throw new EncodingException("Unencodable FibonacciInteger " + value);
+ }
+
+ int out = 1;
+ int mask = 1;
+ for (int i = largestIndex - 1; i >= 0; i--) {
+ mask <<= 1;
+ int f = FibonacciIntegerEncoder.FIBONACCI_NUMBERS[i];
+ if (value >= f) {
+ out |= mask;
+ value -= f;
+ }
+ }
+ writeInt(out, largestIndex + 1);
+ }
+
+ public void write(BitString other, int from, int to) {
+ for (int i = from; i < to; i++) {
+ writeBoolean(other.getValue(i));
+ }
+ }
+
+ public void write(BitString other) {
+ int otherLength = other.length();
+ for (int i = 0; i < otherLength; i++) {
+ writeBoolean(other.bitSet.get(i));
+ }
+ }
+
+ public int readInt(int length) {
+ int from = readIndex;
+ int to = from + length;
+ if (from >= writeIndex) {
+ throw new DecodingException("Bit string access out of range");
+ }
+ readIndex = to;
+ return bitSet.readInt(from, to);
+ }
+
+ // used for usnat v1 to v2 conversion, see note in UsNatCoreSegment
+ public int peekInt(int length) {
+ return bitSet.readInt(0, length);
+ }
+
+ // used for usnat v1 to v2 conversion, see note in UsNatCoreSegment
+ public void putInt(int value, int length) {
+ int mask = 1 << length;
+ if (value >= mask) {
+ throw new EncodingException(
+ "Numeric value '" + value + "' is too large for a bit string length of '" + length + "'");
+ }
+ for (int i = 0; i < length; i++) {
+ mask >>= 1;
+ bitSet.set(i, (mask & value) != 0);
+ }
+ }
+
+ public long readLong(int length) {
+ int from = readIndex;
+ int to = from + length;
+ if (from >= writeIndex) {
+ throw new DecodingException("Bit string access out of range");
+ }
+ readIndex = to;
+ return bitSet.readLong(from, to);
+ }
+
+ public boolean readBoolean() {
+ return getValue(readIndex++);
+ }
+
+ public int readFibonacci() {
+ int value = 0;
+ int readIndex = this.readIndex;
+ for (int i = 0; i < FibonacciIntegerEncoder.FIBONACCI_LIMIT; i++) {
+ if (getValue(readIndex + i)) {
+ // 1X
+ value += FibonacciIntegerEncoder.FIBONACCI_NUMBERS[i];
+ i++;
+ int next = readIndex + i;
+ if (getValue(next)) {
+ // 11
+ this.readIndex = next + 1;
+ return value;
+ }
+ }
+ }
+ throw new DecodingException("Invalid FibonacciInteger");
+ }
+
+ public IntegerSet readIntegerSet(int length) {
+ int newReadIndex = readIndex + length;
+ if (newReadIndex > writeIndex) {
+ throw new DecodingException("Bit string access out of range");
+ }
+ IntegerSet out = new IntegerSet(bitSet, readIndex, newReadIndex, 1);
+ readIndex = newReadIndex;
+ return out;
+ }
+
+ public FixedIntegerList readFixedIntegerList(int elementLength, int length) {
+ int newReadIndex = readIndex + elementLength * length;
+ if (newReadIndex > writeIndex) {
+ throw new DecodingException("Bit string access out of range");
+ }
+ FixedIntegerList out = new FixedIntegerList(bitSet, readIndex, elementLength, length);
+ readIndex = newReadIndex;
+ return out;
+ }
+}
diff --git a/iabgpp-encoder/src/main/java/com/iab/gpp/encoder/bitstring/BitStringEncoder.java b/iabgpp-encoder/src/main/java/com/iab/gpp/encoder/bitstring/BitStringEncoder.java
deleted file mode 100644
index 12bcb182..00000000
--- a/iabgpp-encoder/src/main/java/com/iab/gpp/encoder/bitstring/BitStringEncoder.java
+++ /dev/null
@@ -1,61 +0,0 @@
-package com.iab.gpp.encoder.bitstring;
-
-import java.util.List;
-import com.iab.gpp.encoder.datatype.AbstractEncodableBitStringDataType;
-import com.iab.gpp.encoder.datatype.SubstringException;
-import com.iab.gpp.encoder.error.DecodingException;
-import com.iab.gpp.encoder.error.EncodingException;
-import com.iab.gpp.encoder.field.EncodableBitStringFields;
-
-public class BitStringEncoder {
-
- private static BitStringEncoder instance = new BitStringEncoder();
-
- private BitStringEncoder() {
-
- }
-
- public static BitStringEncoder getInstance() {
- return instance;
- }
-
- public String encode(EncodableBitStringFields fields, List fieldNames) {
- String bitString = "";
- for (int i = 0; i < fieldNames.size(); i++) {
- String fieldName = fieldNames.get(i);
- if (fields.containsKey(fieldName)) {
- AbstractEncodableBitStringDataType> field = fields.get(fieldName);
- bitString += field.encode();
- } else {
- throw new EncodingException("Field not found: '" + fieldName + "'");
- }
- }
-
- return bitString;
- }
-
- public void decode(String bitString, List fieldNames, EncodableBitStringFields fields) {
- int index = 0;
- for (int i = 0; i < fieldNames.size(); i++) {
- String fieldName = fieldNames.get(i);
- if (fields.containsKey(fieldName)) {
- AbstractEncodableBitStringDataType> field = fields.get(fieldName);
- try {
- String substring = field.substring(bitString, index);
- field.decode(substring);
- index += substring.length();
- } catch (SubstringException e) {
- if(field.getHardFailIfMissing()) {
- throw new DecodingException("Unable to decode " + fieldName, e);
- } else {
- return;
- }
- } catch (Exception e) {
- throw new DecodingException("Unable to decode " + fieldName, e);
- }
- } else {
- throw new DecodingException("Field not found: '" + fieldName + "'");
- }
- }
- }
-}
diff --git a/iabgpp-encoder/src/main/java/com/iab/gpp/encoder/datatype/AbstractDirtyableBitStringDataType.java b/iabgpp-encoder/src/main/java/com/iab/gpp/encoder/datatype/AbstractDirtyableBitStringDataType.java
new file mode 100644
index 00000000..bbfbfea4
--- /dev/null
+++ b/iabgpp-encoder/src/main/java/com/iab/gpp/encoder/datatype/AbstractDirtyableBitStringDataType.java
@@ -0,0 +1,33 @@
+package com.iab.gpp.encoder.datatype;
+
+import com.iab.gpp.encoder.field.FieldKey;
+import java.util.function.Predicate;
+
+// This class is used to handle collection types.
+// It is important that we monitor the collections we return for changes.
+public abstract class AbstractDirtyableBitStringDataType<
+ E extends Enum & FieldKey, T extends Dirtyable>
+ extends AbstractEncodableBitStringDataType {
+
+ protected AbstractDirtyableBitStringDataType(String name, Predicate validator) {
+ super(name, validator);
+ }
+
+ @Override
+ public boolean isDirty(Object[] values, int index) {
+ T value = get(values, index);
+ return (value != null && value.isDirty());
+ }
+
+ @Override
+ protected abstract boolean isPresent(T value);
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public void setDirty(Object[] values, int index, boolean dirty) {
+ T value = (T) values[index];
+ if (value != null) {
+ value.setDirty(dirty);
+ }
+ }
+}
diff --git a/iabgpp-encoder/src/main/java/com/iab/gpp/encoder/datatype/AbstractEncodableBitStringDataType.java b/iabgpp-encoder/src/main/java/com/iab/gpp/encoder/datatype/AbstractEncodableBitStringDataType.java
index 07d9588c..1a734630 100644
--- a/iabgpp-encoder/src/main/java/com/iab/gpp/encoder/datatype/AbstractEncodableBitStringDataType.java
+++ b/iabgpp-encoder/src/main/java/com/iab/gpp/encoder/datatype/AbstractEncodableBitStringDataType.java
@@ -1,57 +1,30 @@
package com.iab.gpp.encoder.datatype;
-import java.util.Collection;
+import com.iab.gpp.encoder.bitstring.BitString;
+import com.iab.gpp.encoder.field.FieldKey;
+import com.iab.gpp.encoder.segment.EncodableSegment;
import java.util.function.Predicate;
-import java.util.stream.Collectors;
-import com.iab.gpp.encoder.error.ValidationException;
-public abstract class AbstractEncodableBitStringDataType implements EncodableDataType {
- //this if for backwards compatibility with the newer fields
- protected boolean hardFailIfMissing = true;
- protected Predicate validator = null;
- protected T value;
+public abstract class AbstractEncodableBitStringDataType & FieldKey, T>
+ extends DataType {
- protected AbstractEncodableBitStringDataType(boolean hardFailIfMissing) {
- this.hardFailIfMissing = hardFailIfMissing;
- }
-
- public AbstractEncodableBitStringDataType withValidator(Predicate validator) {
- this.validator = validator;
- return this;
- }
-
- public boolean hasValue() {
- return this.value != null;
+ protected AbstractEncodableBitStringDataType(String name, Predicate validator) {
+ super(name, validator);
}
- public T getValue() {
- return this.value;
+ @Override
+ public final void encode(
+ BitString writer, Object[] values, int index, EncodableSegment segment) {
+ encode(writer, get(values, index), segment);
}
- @SuppressWarnings("unchecked")
- public void setValue(Object value) {
- T v = (T) value;
- if (validator == null || validator.test(v)) {
- this.value = v;
- } else {
- if (v instanceof Collection) {
- throw new ValidationException("Invalid value '"
- + ((Collection>) v).stream().map(i -> i.toString()).collect(Collectors.joining(",")) + "'");
- } else {
- throw new ValidationException("Invalid value '" + v + "'");
- }
- }
-
- }
+ protected abstract void encode(BitString writer, T value, EncodableSegment segment);
- public boolean getHardFailIfMissing() {
- return this.hardFailIfMissing;
+ @Override
+ public final void decode(
+ BitString reader, Object[] values, int index, EncodableSegment segment) {
+ values[index] = decode(reader, segment);
}
- public abstract String encode();
-
- public abstract void decode(String bitString);
-
- public abstract String substring(String bitString, int fromIndex) throws SubstringException;
-
+ protected abstract T decode(BitString reader, EncodableSegment segment);
}
diff --git a/iabgpp-encoder/src/main/java/com/iab/gpp/encoder/datatype/DataType.java b/iabgpp-encoder/src/main/java/com/iab/gpp/encoder/datatype/DataType.java
index 8a0d2c07..e93944ee 100644
--- a/iabgpp-encoder/src/main/java/com/iab/gpp/encoder/datatype/DataType.java
+++ b/iabgpp-encoder/src/main/java/com/iab/gpp/encoder/datatype/DataType.java
@@ -1,7 +1,95 @@
package com.iab.gpp.encoder.datatype;
-public interface DataType {
- boolean hasValue();
- T getValue();
- void setValue(Object value);
+import com.iab.gpp.encoder.bitstring.BitString;
+import com.iab.gpp.encoder.error.ValidationException;
+import com.iab.gpp.encoder.field.FieldKey;
+import com.iab.gpp.encoder.segment.EncodableSegment;
+import java.util.Collection;
+import java.util.function.Predicate;
+import java.util.stream.Collectors;
+
+public abstract class DataType & FieldKey, T> {
+
+ protected final String name;
+ private final Predicate validator;
+
+ protected DataType(String name, Predicate validator) {
+ this.name = name;
+ this.validator = validator;
+ }
+
+ public final String getName() {
+ return name;
+ }
+
+ @Override
+ public String toString() {
+ return name + "=" + this.getClass().getSimpleName();
+ }
+
+ protected final void validate(T v) {
+ if (validator == null || validator.test(v)) {
+ return;
+ } else {
+ if (v instanceof Collection) {
+ throw new ValidationException(
+ "Invalid value '"
+ + ((Collection>) v)
+ .stream().map(Object::toString).collect(Collectors.joining(","))
+ + "'");
+ } else {
+ throw new ValidationException("Invalid value '" + v + "'");
+ }
+ }
+ }
+
+ public boolean isDirty(Object[] values, int index) {
+ return false;
+ }
+
+ public void setDirty(Object[] values, int index, boolean dirty) {
+ // pass
+ }
+
+ public void encode(BitString writer, Object[] values, int index, EncodableSegment segment) {
+ throw new UnsupportedOperationException("type does not permit bit string encoding");
+ }
+
+ public void decode(BitString reader, Object[] values, int index, EncodableSegment segment) {
+ throw new UnsupportedOperationException("type does not permit bit string decoding");
+ }
+
+ @SuppressWarnings("unchecked")
+ public final T get(Object[] values, int index) {
+ T value = (T) values[index];
+ if (value == null) {
+ value = initialize();
+ values[index] = value;
+ }
+ return value;
+ }
+
+ @SuppressWarnings("unchecked")
+ public final boolean isPresent(Object[] values, int index) {
+ T value = (T) values[index];
+ return value != null && isPresent(value);
+ }
+
+ protected boolean isPresent(T value) {
+ return true;
+ }
+
+ public final void set(Object[] values, int index, Object newValue) {
+ T oldValue = get(values, index);
+ T effectiveValue = processValue(oldValue, newValue);
+ validate(effectiveValue);
+ values[index] = effectiveValue;
+ }
+
+ protected abstract T initialize();
+
+ @SuppressWarnings("unchecked")
+ protected T processValue(T oldValue, Object newValue) {
+ return (T) newValue;
+ }
}
diff --git a/iabgpp-encoder/src/main/java/com/iab/gpp/encoder/datatype/Dirtyable.java b/iabgpp-encoder/src/main/java/com/iab/gpp/encoder/datatype/Dirtyable.java
new file mode 100644
index 00000000..ef4e5495
--- /dev/null
+++ b/iabgpp-encoder/src/main/java/com/iab/gpp/encoder/datatype/Dirtyable.java
@@ -0,0 +1,7 @@
+package com.iab.gpp.encoder.datatype;
+
+public interface Dirtyable {
+ boolean isDirty();
+
+ void setDirty(boolean dirty);
+}
diff --git a/iabgpp-encoder/src/main/java/com/iab/gpp/encoder/datatype/DirtyableList.java b/iabgpp-encoder/src/main/java/com/iab/gpp/encoder/datatype/DirtyableList.java
new file mode 100644
index 00000000..93ca3841
--- /dev/null
+++ b/iabgpp-encoder/src/main/java/com/iab/gpp/encoder/datatype/DirtyableList.java
@@ -0,0 +1,102 @@
+package com.iab.gpp.encoder.datatype;
+
+import java.util.AbstractList;
+import java.util.ArrayList;
+import java.util.Collection;
+
+// This class tracks whether a list has been modified.
+final class DirtyableList extends AbstractList implements Dirtyable {
+
+ private boolean dirty;
+ private final ArrayList delegate;
+
+ DirtyableList() {
+ this.delegate = new ArrayList<>();
+ }
+
+ @Override
+ public int size() {
+ return delegate.size();
+ }
+
+ @Override
+ public boolean add(T value) {
+ boolean result = delegate.add(value);
+ dirty = true;
+ return result;
+ }
+
+ @Override
+ public T get(int index) {
+ return delegate.get(index);
+ }
+
+ @Override
+ public T set(int index, T value) {
+ T prior = delegate.set(index, value);
+ dirty = true;
+ return prior;
+ }
+
+ @Override
+ public void add(int index, T element) {
+ delegate.add(index, element);
+ dirty = true;
+ }
+
+ @Override
+ public T remove(int index) {
+ T old = delegate.remove(index);
+ dirty = true;
+ return old;
+ }
+
+ @Override
+ public int indexOf(Object o) {
+ return delegate.indexOf(o);
+ }
+
+ @Override
+ public int lastIndexOf(Object o) {
+ return delegate.lastIndexOf(o);
+ }
+
+ @Override
+ public void clear() {
+ delegate.clear();
+ dirty = true;
+ }
+
+ @Override
+ public boolean addAll(Collection extends T> c) {
+ boolean result = delegate.addAll(c);
+ dirty = true;
+ return result;
+ }
+
+ @Override
+ public String toString() {
+ return delegate.toString();
+ }
+
+ @Override
+ public boolean isDirty() {
+ int size = delegate.size();
+ for (int i = 0; i < size; i++) {
+ T value = delegate.get(i);
+ if (value.isDirty()) {
+ return true;
+ }
+ }
+ return dirty;
+ }
+
+ @Override
+ public void setDirty(boolean dirty) {
+ int size = delegate.size();
+ for (int i = 0; i < size; i++) {
+ delegate.get(i).setDirty(dirty);
+ }
+ this.dirty = dirty;
+ }
+}
diff --git a/iabgpp-encoder/src/main/java/com/iab/gpp/encoder/datatype/EncodableArrayOfFixedIntegerRanges.java b/iabgpp-encoder/src/main/java/com/iab/gpp/encoder/datatype/EncodableArrayOfFixedIntegerRanges.java
index 699e0d26..f01470e5 100644
--- a/iabgpp-encoder/src/main/java/com/iab/gpp/encoder/datatype/EncodableArrayOfFixedIntegerRanges.java
+++ b/iabgpp-encoder/src/main/java/com/iab/gpp/encoder/datatype/EncodableArrayOfFixedIntegerRanges.java
@@ -1,110 +1,70 @@
package com.iab.gpp.encoder.datatype;
-import java.util.ArrayList;
-import java.util.List;
-import com.iab.gpp.encoder.datatype.encoder.FixedIntegerEncoder;
+import com.iab.gpp.encoder.bitstring.BitString;
import com.iab.gpp.encoder.datatype.encoder.FixedIntegerRangeEncoder;
-import com.iab.gpp.encoder.error.DecodingException;
-import com.iab.gpp.encoder.error.EncodingException;
+import com.iab.gpp.encoder.field.FieldKey;
+import com.iab.gpp.encoder.segment.EncodableSegment;
+import java.util.List;
-public class EncodableArrayOfFixedIntegerRanges extends AbstractEncodableBitStringDataType> {
+public final class EncodableArrayOfFixedIntegerRanges & FieldKey>
+ extends AbstractDirtyableBitStringDataType> {
- private int keyBitStringLength;
- private int typeBitStringLength;
+ private final int keyBitStringLength;
+ private final int typeBitStringLength;
- protected EncodableArrayOfFixedIntegerRanges(int keyBitStringLength, int typeBitStringLength) {
- super(true);
+ public EncodableArrayOfFixedIntegerRanges(
+ String name, int keyBitStringLength, int typeBitStringLength) {
+ super(name, null);
this.keyBitStringLength = keyBitStringLength;
this.typeBitStringLength = typeBitStringLength;
}
- public EncodableArrayOfFixedIntegerRanges(int keyBitStringLength, int typeBitStringLength, List value) {
- super(true);
- this.keyBitStringLength = keyBitStringLength;
- this.typeBitStringLength = typeBitStringLength;
- setValue(value);
+ @Override
+ public String toString() {
+ return name + "=N-ArrayOfRanges(" + keyBitStringLength + "," + typeBitStringLength + ")";
}
-
- public EncodableArrayOfFixedIntegerRanges(int keyBitStringLength, int typeBitStringLength, List value, boolean hardFailIfMissing) {
- super(hardFailIfMissing);
- this.keyBitStringLength = keyBitStringLength;
- this.typeBitStringLength = typeBitStringLength;
- setValue(value);
+
+ @Override
+ protected DirtyableList initialize() {
+ return new DirtyableList<>();
}
@Override
- public String encode() {
- try {
- List entries = this.value;
-
- StringBuilder sb = new StringBuilder();
- sb.append(FixedIntegerEncoder.encode(entries.size(), 12));
- for (RangeEntry entry : entries) {
- sb.append(FixedIntegerEncoder.encode(entry.getKey(), keyBitStringLength))
- .append(FixedIntegerEncoder.encode(entry.getType(), typeBitStringLength))
- .append(FixedIntegerRangeEncoder.encode(entry.getIds()));
- }
-
- return sb.toString();
- } catch (Exception e) {
- throw new EncodingException(e);
- }
+ protected boolean isPresent(DirtyableList value) {
+ return !value.isEmpty();
}
@Override
- public void decode(String bitString) {
- try {
- List entries = new ArrayList<>();
-
- int size = FixedIntegerEncoder.decode(bitString.substring(0, 12));
- int index = 12;
- for (int i = 0; i < size; i++) {
- int key = FixedIntegerEncoder.decode(bitString.substring(index, index + keyBitStringLength));
- index += keyBitStringLength;
-
- int type = FixedIntegerEncoder.decode(bitString.substring(index, index + typeBitStringLength));
- index += typeBitStringLength;
-
- String substring = new EncodableFixedIntegerRange().substring(bitString, index);
- List ids = FixedIntegerRangeEncoder.decode(substring);
- index += substring.length();
-
- entries.add(new RangeEntry(key, type, ids));
- }
-
- this.value = entries;
- } catch (Exception e) {
- throw new DecodingException(e);
+ protected void encode(
+ BitString sb, DirtyableList entries, EncodableSegment segment) {
+ sb.writeInt(entries.size(), 12);
+ for (RangeEntry entry : entries) {
+ sb.writeInt(entry.getKey(), keyBitStringLength);
+ sb.writeInt(entry.getType(), typeBitStringLength);
+ FixedIntegerRangeEncoder.encode(sb, entry.getIds());
}
}
@Override
- public String substring(String bitString, int fromIndex) throws SubstringException {
- try {
- StringBuilder sb = new StringBuilder();
- sb.append(bitString.substring(fromIndex, fromIndex + 12));
-
- int size = FixedIntegerEncoder.decode(sb.toString());
-
- int index = fromIndex + sb.length();
- for (int i = 0; i < size; i++) {
- String keySubstring = bitString.substring(index, index + keyBitStringLength);
- index += keySubstring.length();
- sb.append(keySubstring);
-
- String typeSubstring = bitString.substring(index, index + typeBitStringLength);
- index += typeSubstring.length();
- sb.append(typeSubstring);
-
- String rangeSubstring = new EncodableFixedIntegerRange().substring(bitString, index);
- index += rangeSubstring.length();
- sb.append(rangeSubstring);
- }
-
- return sb.toString();
- } catch (Exception e) {
- throw new SubstringException(e);
+ protected DirtyableList decode(BitString reader, EncodableSegment segment) {
+ int size = reader.readInt(12);
+ DirtyableList value = initialize();
+ for (int i = 0; i < size; i++) {
+ int key = reader.readInt(keyBitStringLength);
+ int type = reader.readInt(typeBitStringLength);
+ IntegerSet ids = FixedIntegerRangeEncoder.decode(reader);
+ RangeEntry entry = new RangeEntry(key, type, ids);
+ value.add(entry);
}
+ return value;
}
+ @SuppressWarnings("unchecked")
+ @Override
+ protected DirtyableList processValue(
+ DirtyableList oldValue, Object newValue) {
+ oldValue.clear();
+ oldValue.addAll((List) newValue);
+ return oldValue;
+ }
}
diff --git a/iabgpp-encoder/src/main/java/com/iab/gpp/encoder/datatype/EncodableBoolean.java b/iabgpp-encoder/src/main/java/com/iab/gpp/encoder/datatype/EncodableBoolean.java
index 340be3ab..0969538a 100644
--- a/iabgpp-encoder/src/main/java/com/iab/gpp/encoder/datatype/EncodableBoolean.java
+++ b/iabgpp-encoder/src/main/java/com/iab/gpp/encoder/datatype/EncodableBoolean.java
@@ -1,46 +1,31 @@
package com.iab.gpp.encoder.datatype;
-import com.iab.gpp.encoder.datatype.encoder.BooleanEncoder;
-import com.iab.gpp.encoder.error.DecodingException;
-import com.iab.gpp.encoder.error.EncodingException;
+import com.iab.gpp.encoder.bitstring.BitString;
+import com.iab.gpp.encoder.field.FieldKey;
+import com.iab.gpp.encoder.segment.EncodableSegment;
-public class EncodableBoolean extends AbstractEncodableBitStringDataType {
+public final class EncodableBoolean & FieldKey>
+ extends AbstractEncodableBitStringDataType {
- protected EncodableBoolean() {
- super(true);
- }
-
- public EncodableBoolean(Boolean value) {
- super(true);
- setValue(value);
- }
+ private final Boolean initial;
- public EncodableBoolean(Boolean value, boolean hardFailIfMissing) {
- super(hardFailIfMissing);
- setValue(value);
+ public EncodableBoolean(String name, Boolean initial) {
+ super(name, null);
+ this.initial = initial;
}
- public String encode() {
- try {
- return BooleanEncoder.encode(this.value);
- } catch (Exception e) {
- throw new EncodingException(e);
- }
+ @Override
+ protected Boolean initialize() {
+ return initial;
}
- public void decode(String bitString) {
- try {
- this.value = BooleanEncoder.decode(bitString);
- } catch (Exception e) {
- throw new DecodingException(e);
- }
+ @Override
+ protected void encode(BitString builder, Boolean value, EncodableSegment segment) {
+ builder.writeBoolean(value);
}
- public String substring(String bitString, int fromIndex) throws SubstringException {
- try {
- return bitString.substring(fromIndex, fromIndex + 1);
- } catch (Exception e) {
- throw new SubstringException(e);
- }
+ @Override
+ protected Boolean decode(BitString reader, EncodableSegment segment) {
+ return reader.readBoolean();
}
}
diff --git a/iabgpp-encoder/src/main/java/com/iab/gpp/encoder/datatype/EncodableDataType.java b/iabgpp-encoder/src/main/java/com/iab/gpp/encoder/datatype/EncodableDataType.java
deleted file mode 100644
index 94d416a6..00000000
--- a/iabgpp-encoder/src/main/java/com/iab/gpp/encoder/datatype/EncodableDataType.java
+++ /dev/null
@@ -1,7 +0,0 @@
-package com.iab.gpp.encoder.datatype;
-
-public interface EncodableDataType extends DataType {
- String encode();
-
- void decode(String str);
-}
diff --git a/iabgpp-encoder/src/main/java/com/iab/gpp/encoder/datatype/EncodableDatetime.java b/iabgpp-encoder/src/main/java/com/iab/gpp/encoder/datatype/EncodableDatetime.java
index a96c46f4..f9333f57 100644
--- a/iabgpp-encoder/src/main/java/com/iab/gpp/encoder/datatype/EncodableDatetime.java
+++ b/iabgpp-encoder/src/main/java/com/iab/gpp/encoder/datatype/EncodableDatetime.java
@@ -1,47 +1,30 @@
package com.iab.gpp.encoder.datatype;
-import java.time.ZonedDateTime;
+import com.iab.gpp.encoder.bitstring.BitString;
import com.iab.gpp.encoder.datatype.encoder.DatetimeEncoder;
-import com.iab.gpp.encoder.error.DecodingException;
-import com.iab.gpp.encoder.error.EncodingException;
+import com.iab.gpp.encoder.field.FieldKey;
+import com.iab.gpp.encoder.segment.EncodableSegment;
+import java.time.Instant;
-public class EncodableDatetime extends AbstractEncodableBitStringDataType {
+public final class EncodableDatetime & FieldKey>
+ extends AbstractEncodableBitStringDataType {
- protected EncodableDatetime() {
- super(true);
+ public EncodableDatetime(String name) {
+ super(name, null);
}
- public EncodableDatetime(ZonedDateTime value) {
- super(true);
- setValue(value);
+ @Override
+ protected Instant initialize() {
+ return Instant.EPOCH;
}
- public EncodableDatetime(ZonedDateTime value, boolean hardFailIfMissing) {
- super(hardFailIfMissing);
- setValue(value);
+ @Override
+ protected void encode(BitString builder, Instant value, EncodableSegment segment) {
+ DatetimeEncoder.encode(builder, value);
}
- public String encode() {
- try {
- return DatetimeEncoder.encode(this.value);
- } catch (Exception e) {
- throw new EncodingException(e);
- }
- }
-
- public void decode(String bitString) {
- try {
- this.value = DatetimeEncoder.decode(bitString);
- } catch (Exception e) {
- throw new DecodingException(e);
- }
- }
-
- public String substring(String bitString, int fromIndex) throws SubstringException {
- try {
- return bitString.substring(fromIndex, fromIndex + 36);
- } catch (Exception e) {
- throw new SubstringException(e);
- }
+ @Override
+ protected Instant decode(BitString reader, EncodableSegment segment) {
+ return DatetimeEncoder.decode(reader);
}
}
diff --git a/iabgpp-encoder/src/main/java/com/iab/gpp/encoder/datatype/EncodableFibonacciInteger.java b/iabgpp-encoder/src/main/java/com/iab/gpp/encoder/datatype/EncodableFibonacciInteger.java
index 3da64a15..3ada0870 100644
--- a/iabgpp-encoder/src/main/java/com/iab/gpp/encoder/datatype/EncodableFibonacciInteger.java
+++ b/iabgpp-encoder/src/main/java/com/iab/gpp/encoder/datatype/EncodableFibonacciInteger.java
@@ -1,51 +1,31 @@
package com.iab.gpp.encoder.datatype;
-import com.iab.gpp.encoder.datatype.encoder.FibonacciIntegerEncoder;
-import com.iab.gpp.encoder.error.DecodingException;
-import com.iab.gpp.encoder.error.EncodingException;
+import com.iab.gpp.encoder.bitstring.BitString;
+import com.iab.gpp.encoder.field.FieldKey;
+import com.iab.gpp.encoder.segment.EncodableSegment;
-public class EncodableFibonacciInteger extends AbstractEncodableBitStringDataType {
+public final class EncodableFibonacciInteger & FieldKey>
+ extends AbstractEncodableBitStringDataType {
- protected EncodableFibonacciInteger() {
- super(true);
- }
-
- public EncodableFibonacciInteger(Integer value) {
- super(true);
- setValue(value);
- }
+ private final Integer initial;
- public EncodableFibonacciInteger(Integer value, boolean hardFailIfMissing) {
- super(hardFailIfMissing);
- setValue(value);
+ public EncodableFibonacciInteger(String name, Integer initial) {
+ super(name, null);
+ this.initial = initial;
}
- public String encode() {
- try {
- return FibonacciIntegerEncoder.encode(this.value);
- } catch (Exception e) {
- throw new EncodingException(e);
- }
+ @Override
+ protected Integer initialize() {
+ return initial;
}
- public void decode(String bitString) {
- try {
- this.value = FibonacciIntegerEncoder.decode(bitString);
- } catch (Exception e) {
- throw new DecodingException(e);
- }
+ @Override
+ protected void encode(BitString builder, Integer value, EncodableSegment segment) {
+ builder.writeFibonacci(value);
}
- public String substring(String bitString, int fromIndex) throws SubstringException {
- try {
- int index = bitString.indexOf("11", fromIndex);
- if (index > 0) {
- return bitString.substring(fromIndex, index + 2);
- } else {
- return bitString;
- }
- } catch (Exception e) {
- throw new SubstringException(e);
- }
+ @Override
+ protected Integer decode(BitString reader, EncodableSegment segment) {
+ return reader.readFibonacci();
}
}
diff --git a/iabgpp-encoder/src/main/java/com/iab/gpp/encoder/datatype/EncodableFibonacciIntegerRange.java b/iabgpp-encoder/src/main/java/com/iab/gpp/encoder/datatype/EncodableFibonacciIntegerRange.java
index 53ab2eb2..7ac2ffd2 100644
--- a/iabgpp-encoder/src/main/java/com/iab/gpp/encoder/datatype/EncodableFibonacciIntegerRange.java
+++ b/iabgpp-encoder/src/main/java/com/iab/gpp/encoder/datatype/EncodableFibonacciIntegerRange.java
@@ -1,70 +1,43 @@
package com.iab.gpp.encoder.datatype;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.TreeSet;
+import com.iab.gpp.encoder.bitstring.BitString;
import com.iab.gpp.encoder.datatype.encoder.FibonacciIntegerRangeEncoder;
-import com.iab.gpp.encoder.datatype.encoder.FixedIntegerEncoder;
-import com.iab.gpp.encoder.error.DecodingException;
-import com.iab.gpp.encoder.error.EncodingException;
+import com.iab.gpp.encoder.field.FieldKey;
+import com.iab.gpp.encoder.segment.EncodableSegment;
+import java.util.Collection;
-public class EncodableFibonacciIntegerRange extends AbstractEncodableBitStringDataType> {
+public final class EncodableFibonacciIntegerRange & FieldKey>
+ extends AbstractDirtyableBitStringDataType {
- protected EncodableFibonacciIntegerRange() {
- super(true);
+ public EncodableFibonacciIntegerRange(String name) {
+ super(name, null);
}
- public EncodableFibonacciIntegerRange(List value) {
- super(true);
- setValue(value);
- }
-
- public EncodableFibonacciIntegerRange(List value, boolean hardFailIfMissing) {
- super(hardFailIfMissing);
- setValue(value);
- }
-
- public String encode() {
- try {
- return FibonacciIntegerRangeEncoder.encode(this.value);
- } catch (Exception e) {
- throw new EncodingException(e);
- }
+ @Override
+ public IntegerSet initialize() {
+ return new IntegerSet();
}
- public void decode(String bitString) {
- try {
- this.value = FibonacciIntegerRangeEncoder.decode(bitString);
- } catch (Exception e) {
- throw new DecodingException(e);
- }
+ @Override
+ protected boolean isPresent(IntegerSet value) {
+ return !value.isEmpty();
}
- public String substring(String bitString, int fromIndex) throws SubstringException {
- try {
- int count = FixedIntegerEncoder.decode(bitString.substring(fromIndex, fromIndex + 12));
- int index = fromIndex + 12;
- for (int i = 0; i < count; i++) {
- if (bitString.charAt(index) == '1') {
- index = bitString.indexOf("11", bitString.indexOf("11", index + 1) + 2) + 2;
- } else {
- index = bitString.indexOf("11", index + 1) + 2;
- }
- }
- return bitString.substring(fromIndex, index);
- } catch (Exception e) {
- throw new SubstringException(e);
- }
+ @Override
+ protected void encode(BitString builder, IntegerSet value, EncodableSegment segment) {
+ FibonacciIntegerRangeEncoder.encode(builder, value);
}
- @SuppressWarnings("unchecked")
@Override
- public void setValue(Object value) {
- super.setValue(new ArrayList<>(new TreeSet<>((List) value)));
+ protected IntegerSet decode(BitString reader, EncodableSegment segment) {
+ return FibonacciIntegerRangeEncoder.decode(reader);
}
+ @SuppressWarnings("unchecked")
@Override
- public List getValue() {
- return new ArrayList<>(super.getValue());
+ protected IntegerSet processValue(IntegerSet oldValue, Object newValue) {
+ oldValue.clear();
+ oldValue.addAll((Collection) newValue);
+ return oldValue;
}
}
diff --git a/iabgpp-encoder/src/main/java/com/iab/gpp/encoder/datatype/EncodableFixedBitfield.java b/iabgpp-encoder/src/main/java/com/iab/gpp/encoder/datatype/EncodableFixedBitfield.java
index 5e6890d9..a22eb31b 100644
--- a/iabgpp-encoder/src/main/java/com/iab/gpp/encoder/datatype/EncodableFixedBitfield.java
+++ b/iabgpp-encoder/src/main/java/com/iab/gpp/encoder/datatype/EncodableFixedBitfield.java
@@ -1,76 +1,51 @@
package com.iab.gpp.encoder.datatype;
-import java.util.ArrayList;
-import java.util.List;
+import com.iab.gpp.encoder.bitstring.BitString;
import com.iab.gpp.encoder.datatype.encoder.FixedBitfieldEncoder;
-import com.iab.gpp.encoder.error.DecodingException;
-import com.iab.gpp.encoder.error.EncodingException;
+import com.iab.gpp.encoder.field.FieldKey;
+import com.iab.gpp.encoder.segment.EncodableSegment;
+import java.util.Collection;
-public class EncodableFixedBitfield extends AbstractEncodableBitStringDataType> {
+public final class EncodableFixedBitfield & FieldKey>
+ extends AbstractDirtyableBitStringDataType {
- private int numElements;
+ private final int numElements;
- protected EncodableFixedBitfield(int numElements) {
- super(true);
+ public EncodableFixedBitfield(String name, int numElements) {
+ super(name, null);
this.numElements = numElements;
}
- protected EncodableFixedBitfield(int numElements, boolean hardFailIfMissing) {
- super(hardFailIfMissing);
- this.numElements = numElements;
- }
-
- public EncodableFixedBitfield(List value) {
- super(true);
- this.numElements = value.size();
- setValue(value);
- }
-
- public EncodableFixedBitfield(List value, boolean hardFailIfMissing) {
- super(hardFailIfMissing);
- this.numElements = value.size();
- setValue(value);
+ @Override
+ public String toString() {
+ return name + "=Bitfield(" + numElements + ")";
}
- public String encode() {
- try {
- return FixedBitfieldEncoder.encode(this.value, this.numElements);
- } catch (Exception e) {
- throw new EncodingException(e);
- }
+ @Override
+ protected IntegerSet initialize() {
+ return new IntegerSet(numElements);
}
- public void decode(String bitString) {
- try {
- this.value = FixedBitfieldEncoder.decode(bitString);
- } catch (Exception e) {
- throw new DecodingException(e);
- }
+ @Override
+ protected boolean isPresent(IntegerSet value) {
+ return !value.isEmpty();
}
- public String substring(String bitString, int fromIndex) throws SubstringException {
- try {
- return bitString.substring(fromIndex, fromIndex + this.numElements);
- } catch (Exception e) {
- throw new SubstringException(e);
- }
+ @Override
+ protected void encode(BitString builder, IntegerSet value, EncodableSegment segment) {
+ FixedBitfieldEncoder.encode(builder, value, this.numElements);
}
- @SuppressWarnings("unchecked")
@Override
- public void setValue(Object value) {
- List v = new ArrayList<>((List) value);
- for (int i = v.size(); i < numElements; i++) {
- v.add(false);
- }
- if (v.size() > numElements) {
- v = v.subList(0, numElements);
- }
- super.setValue(v);
+ protected IntegerSet decode(BitString reader, EncodableSegment segment) {
+ return reader.readIntegerSet(this.numElements);
}
+ @SuppressWarnings("unchecked")
@Override
- public List getValue() {
- return new ArrayList<>(super.getValue());
+ protected IntegerSet processValue(IntegerSet oldValue, Object newValue) {
+ oldValue.clear();
+ oldValue.addAll((Collection) newValue);
+ return oldValue;
}
}
diff --git a/iabgpp-encoder/src/main/java/com/iab/gpp/encoder/datatype/EncodableFixedInteger.java b/iabgpp-encoder/src/main/java/com/iab/gpp/encoder/datatype/EncodableFixedInteger.java
index 9fd25fb0..6d6fc90d 100644
--- a/iabgpp-encoder/src/main/java/com/iab/gpp/encoder/datatype/EncodableFixedInteger.java
+++ b/iabgpp-encoder/src/main/java/com/iab/gpp/encoder/datatype/EncodableFixedInteger.java
@@ -1,51 +1,44 @@
package com.iab.gpp.encoder.datatype;
-import com.iab.gpp.encoder.datatype.encoder.FixedIntegerEncoder;
-import com.iab.gpp.encoder.error.DecodingException;
-import com.iab.gpp.encoder.error.EncodingException;
+import com.iab.gpp.encoder.bitstring.BitString;
+import com.iab.gpp.encoder.field.FieldKey;
+import com.iab.gpp.encoder.segment.EncodableSegment;
+import java.util.function.Predicate;
-public class EncodableFixedInteger extends AbstractEncodableBitStringDataType {
+public final class EncodableFixedInteger & FieldKey>
+ extends AbstractEncodableBitStringDataType {
- private int bitStringLength;
+ private final int bitStringLength;
+ private final Integer initial;
- protected EncodableFixedInteger(int bitStringLength) {
- super(true);
+ public EncodableFixedInteger(
+ String name, int bitStringLength, Integer initial, Predicate validator) {
+ super(name, validator);
this.bitStringLength = bitStringLength;
+ this.initial = initial;
}
- public EncodableFixedInteger(int bitStringLength, Integer value) {
- super(true);
- this.bitStringLength = bitStringLength;
- setValue(value);
+ public EncodableFixedInteger(String name, int bitStringLength, Integer initial) {
+ this(name, bitStringLength, initial, null);
}
- public EncodableFixedInteger(int bitStringLength, Integer value, boolean hardFailIfMissing) {
- super(hardFailIfMissing);
- this.bitStringLength = bitStringLength;
- setValue(value);
+ @Override
+ public String toString() {
+ return name + "=Int(" + bitStringLength + ")";
}
- public String encode() {
- try {
- return FixedIntegerEncoder.encode(this.value, this.bitStringLength);
- } catch (Exception e) {
- throw new EncodingException(e);
- }
+ @Override
+ protected Integer initialize() {
+ return initial;
}
- public void decode(String bitString) {
- try {
- this.value = FixedIntegerEncoder.decode(bitString);
- } catch (Exception e) {
- throw new DecodingException(e);
- }
+ @Override
+ protected void encode(BitString builder, Integer value, EncodableSegment segment) {
+ builder.writeInt(value, this.bitStringLength);
}
- public String substring(String bitString, int fromIndex) throws SubstringException {
- try {
- return bitString.substring(fromIndex, fromIndex + this.bitStringLength);
- } catch (Exception e) {
- throw new SubstringException(e);
- }
+ @Override
+ protected Integer decode(BitString reader, EncodableSegment segment) {
+ return IntegerCache.valueOf(reader.readInt(bitStringLength));
}
}
diff --git a/iabgpp-encoder/src/main/java/com/iab/gpp/encoder/datatype/EncodableFixedIntegerList.java b/iabgpp-encoder/src/main/java/com/iab/gpp/encoder/datatype/EncodableFixedIntegerList.java
index f491e5ee..a0ced76d 100644
--- a/iabgpp-encoder/src/main/java/com/iab/gpp/encoder/datatype/EncodableFixedIntegerList.java
+++ b/iabgpp-encoder/src/main/java/com/iab/gpp/encoder/datatype/EncodableFixedIntegerList.java
@@ -1,75 +1,61 @@
package com.iab.gpp.encoder.datatype;
-import java.util.ArrayList;
-import java.util.List;
+import com.iab.gpp.encoder.bitstring.BitString;
import com.iab.gpp.encoder.datatype.encoder.FixedIntegerListEncoder;
-import com.iab.gpp.encoder.error.DecodingException;
-import com.iab.gpp.encoder.error.EncodingException;
+import com.iab.gpp.encoder.field.FieldKey;
+import com.iab.gpp.encoder.segment.EncodableSegment;
+import java.util.List;
+import java.util.function.Predicate;
-public class EncodableFixedIntegerList extends AbstractEncodableBitStringDataType> {
+public final class EncodableFixedIntegerList & FieldKey>
+ extends AbstractDirtyableBitStringDataType {
- private int elementBitStringLength;
- private int numElements;
+ private final int elementBitStringLength;
+ private final int numElements;
- protected EncodableFixedIntegerList(int elementBitStringLength, int numElements) {
- super(true);
+ public EncodableFixedIntegerList(
+ String name,
+ int elementBitStringLength,
+ int numElements,
+ Predicate validator) {
+ super(name, validator);
this.elementBitStringLength = elementBitStringLength;
this.numElements = numElements;
}
- public EncodableFixedIntegerList(int elementBitStringLength, List value) {
- super(true);
- this.elementBitStringLength = elementBitStringLength;
- this.numElements = value.size();
- setValue(value);
+ @Override
+ public String toString() {
+ return name + "=Int(" + elementBitStringLength + "," + numElements + ")";
}
- public EncodableFixedIntegerList(int elementBitStringLength, List value, boolean hardFailIfMissing) {
- super(hardFailIfMissing);
- this.elementBitStringLength = elementBitStringLength;
- this.numElements = value.size();
- setValue(value);
+ @Override
+ protected FixedIntegerList initialize() {
+ return new FixedIntegerList(elementBitStringLength, numElements);
}
- public String encode() {
- try {
- return FixedIntegerListEncoder.encode(this.value, this.elementBitStringLength, this.numElements);
- } catch (Exception e) {
- throw new EncodingException(e);
- }
+ @Override
+ protected boolean isPresent(FixedIntegerList value) {
+ return value.isPresent();
}
- public void decode(String bitString) {
- try {
- this.value = FixedIntegerListEncoder.decode(bitString, this.elementBitStringLength, this.numElements);
- } catch (Exception e) {
- throw new DecodingException(e);
- }
+ @Override
+ protected void encode(BitString builder, FixedIntegerList value, EncodableSegment segment) {
+ FixedIntegerListEncoder.encode(builder, value, this.elementBitStringLength, this.numElements);
}
- public String substring(String bitString, int fromIndex) throws SubstringException {
- try {
- return bitString.substring(fromIndex, fromIndex + (this.elementBitStringLength * numElements));
- } catch (Exception e) {
- throw new SubstringException(e);
- }
+ @Override
+ protected FixedIntegerList decode(BitString reader, EncodableSegment segment) {
+ return reader.readFixedIntegerList(elementBitStringLength, numElements);
}
@SuppressWarnings("unchecked")
@Override
- public void setValue(Object value) {
- List v = new ArrayList<>((List) value);
- for (int i = v.size(); i < numElements; i++) {
- v.add(0);
+ protected FixedIntegerList processValue(FixedIntegerList oldValue, Object newValue) {
+ List list = (List) newValue;
+ int size = list.size();
+ for (int i = 0; i < numElements; i++) {
+ oldValue.set(i, i < size ? list.get(i) : 0);
}
- if (v.size() > numElements) {
- v = v.subList(0, numElements);
- }
- super.setValue(v);
- }
-
- @Override
- public List getValue() {
- return new ArrayList<>(super.getValue());
+ return oldValue;
}
}
diff --git a/iabgpp-encoder/src/main/java/com/iab/gpp/encoder/datatype/EncodableFixedIntegerRange.java b/iabgpp-encoder/src/main/java/com/iab/gpp/encoder/datatype/EncodableFixedIntegerRange.java
index 7fdbd5a8..51f37ffd 100644
--- a/iabgpp-encoder/src/main/java/com/iab/gpp/encoder/datatype/EncodableFixedIntegerRange.java
+++ b/iabgpp-encoder/src/main/java/com/iab/gpp/encoder/datatype/EncodableFixedIntegerRange.java
@@ -1,70 +1,43 @@
package com.iab.gpp.encoder.datatype;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.TreeSet;
-import com.iab.gpp.encoder.datatype.encoder.FixedIntegerEncoder;
+import com.iab.gpp.encoder.bitstring.BitString;
import com.iab.gpp.encoder.datatype.encoder.FixedIntegerRangeEncoder;
-import com.iab.gpp.encoder.error.DecodingException;
-import com.iab.gpp.encoder.error.EncodingException;
+import com.iab.gpp.encoder.field.FieldKey;
+import com.iab.gpp.encoder.segment.EncodableSegment;
+import java.util.Collection;
-public class EncodableFixedIntegerRange extends AbstractEncodableBitStringDataType> {
+public final class EncodableFixedIntegerRange & FieldKey>
+ extends AbstractDirtyableBitStringDataType {
- protected EncodableFixedIntegerRange() {
- super(true);
+ public EncodableFixedIntegerRange(String name) {
+ super(name, null);
}
- public EncodableFixedIntegerRange(List value) {
- super(true);
- setValue(value);
- }
-
- public EncodableFixedIntegerRange(List value, boolean hardFailIfMissing) {
- super(hardFailIfMissing);
- setValue(value);
- }
-
- public String encode() {
- try {
- return FixedIntegerRangeEncoder.encode(this.value);
- } catch (Exception e) {
- throw new EncodingException(e);
- }
+ @Override
+ protected IntegerSet initialize() {
+ return new IntegerSet();
}
- public void decode(String bitString) {
- try {
- this.value = FixedIntegerRangeEncoder.decode(bitString);
- } catch (Exception e) {
- throw new DecodingException(e);
- }
+ @Override
+ protected boolean isPresent(IntegerSet value) {
+ return !value.isEmpty();
}
- public String substring(String bitString, int fromIndex) throws SubstringException {
- try {
- int count = FixedIntegerEncoder.decode(bitString.substring(fromIndex, fromIndex + 12));
- int index = fromIndex + 12;
- for (int i = 0; i < count; i++) {
- if (bitString.charAt(index) == '1') {
- index += 33;
- } else {
- index += 17;
- }
- }
- return bitString.substring(fromIndex, index);
- } catch (Exception e) {
- throw new SubstringException(e);
- }
+ @Override
+ protected void encode(BitString builder, IntegerSet value, EncodableSegment segment) {
+ FixedIntegerRangeEncoder.encode(builder, value);
}
- @SuppressWarnings("unchecked")
@Override
- public void setValue(Object value) {
- super.setValue(new ArrayList<>(new TreeSet<>((List) value)));
+ protected IntegerSet decode(BitString reader, EncodableSegment segment) {
+ return FixedIntegerRangeEncoder.decode(reader);
}
+ @SuppressWarnings("unchecked")
@Override
- public List getValue() {
- return new ArrayList<>(super.getValue());
+ protected IntegerSet processValue(IntegerSet oldValue, Object newValue) {
+ oldValue.clear();
+ oldValue.addAll((Collection) newValue);
+ return oldValue;
}
}
diff --git a/iabgpp-encoder/src/main/java/com/iab/gpp/encoder/datatype/EncodableFixedString.java b/iabgpp-encoder/src/main/java/com/iab/gpp/encoder/datatype/EncodableFixedString.java
index 4cea9b48..82b4b56c 100644
--- a/iabgpp-encoder/src/main/java/com/iab/gpp/encoder/datatype/EncodableFixedString.java
+++ b/iabgpp-encoder/src/main/java/com/iab/gpp/encoder/datatype/EncodableFixedString.java
@@ -1,51 +1,39 @@
package com.iab.gpp.encoder.datatype;
+import com.iab.gpp.encoder.bitstring.BitString;
import com.iab.gpp.encoder.datatype.encoder.FixedStringEncoder;
-import com.iab.gpp.encoder.error.DecodingException;
-import com.iab.gpp.encoder.error.EncodingException;
+import com.iab.gpp.encoder.field.FieldKey;
+import com.iab.gpp.encoder.segment.EncodableSegment;
-public class EncodableFixedString extends AbstractEncodableBitStringDataType {
+public final class EncodableFixedString & FieldKey>
+ extends AbstractEncodableBitStringDataType {
- private int stringLength;
+ private final int stringLength;
+ private final String initial;
- protected EncodableFixedString(int stringLength) {
- super(true);
+ public EncodableFixedString(String name, int stringLength, String initial) {
+ super(name, null);
this.stringLength = stringLength;
+ this.initial = initial;
}
- public EncodableFixedString(int stringLength, String value) {
- super(true);
- this.stringLength = stringLength;
- setValue(value);
- }
-
- public EncodableFixedString(int stringLength, String value, boolean hardFailIfMissing) {
- super(hardFailIfMissing);
- this.stringLength = stringLength;
- setValue(value);
+ @Override
+ public String toString() {
+ return name + "=String(" + stringLength + ")";
}
- public String encode() {
- try {
- return FixedStringEncoder.encode(this.value, this.stringLength);
- } catch (Exception e) {
- throw new EncodingException(e);
- }
+ @Override
+ protected String initialize() {
+ return initial;
}
- public void decode(String bitString) {
- try {
- this.value = FixedStringEncoder.decode(bitString);
- } catch (Exception e) {
- throw new DecodingException(e);
- }
+ @Override
+ protected void encode(BitString builder, String value, EncodableSegment segment) {
+ FixedStringEncoder.encode(builder, value, this.stringLength);
}
- public String substring(String bitString, int fromIndex) throws SubstringException {
- try {
- return bitString.substring(fromIndex, fromIndex + this.stringLength * 6);
- } catch (Exception e) {
- throw new SubstringException(e);
- }
+ @Override
+ protected String decode(BitString reader, EncodableSegment segment) {
+ return FixedStringEncoder.decode(reader, this.stringLength);
}
}
diff --git a/iabgpp-encoder/src/main/java/com/iab/gpp/encoder/datatype/EncodableFlexibleBitfield.java b/iabgpp-encoder/src/main/java/com/iab/gpp/encoder/datatype/EncodableFlexibleBitfield.java
index cb1a0991..a0a05742 100644
--- a/iabgpp-encoder/src/main/java/com/iab/gpp/encoder/datatype/EncodableFlexibleBitfield.java
+++ b/iabgpp-encoder/src/main/java/com/iab/gpp/encoder/datatype/EncodableFlexibleBitfield.java
@@ -1,73 +1,47 @@
package com.iab.gpp.encoder.datatype;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.function.IntSupplier;
+import com.iab.gpp.encoder.bitstring.BitString;
import com.iab.gpp.encoder.datatype.encoder.FixedBitfieldEncoder;
-import com.iab.gpp.encoder.error.DecodingException;
-import com.iab.gpp.encoder.error.EncodingException;
+import com.iab.gpp.encoder.field.FieldKey;
+import com.iab.gpp.encoder.segment.EncodableSegment;
+import com.iab.gpp.encoder.segment.SegmentValueProvider;
+import java.util.Collection;
-public class EncodableFlexibleBitfield extends AbstractEncodableBitStringDataType> {
+public final class EncodableFlexibleBitfield & FieldKey>
+ extends AbstractDirtyableBitStringDataType {
- private IntSupplier getLengthSupplier;
+ private final SegmentValueProvider getLengthSupplier;
- protected EncodableFlexibleBitfield(IntSupplier getLengthSupplier) {
- super(true);
- this.getLengthSupplier = getLengthSupplier;
+ public EncodableFlexibleBitfield(String name, E key) {
+ super(name, null);
+ this.getLengthSupplier = new SegmentValueProvider<>(key);
}
- public EncodableFlexibleBitfield(IntSupplier getLengthSupplier, List value) {
- super(true);
- this.getLengthSupplier = getLengthSupplier;
- this.setValue(value);
- }
-
- public EncodableFlexibleBitfield(IntSupplier getLengthSupplier, List value, boolean hardFailIfMissing) {
- super(hardFailIfMissing);
- this.getLengthSupplier = getLengthSupplier;
- this.setValue(value);
- }
-
- public String encode() {
- try {
- return FixedBitfieldEncoder.encode(this.value, this.getLengthSupplier.getAsInt());
- } catch (Exception e) {
- throw new EncodingException(e);
- }
+ @Override
+ protected IntegerSet initialize() {
+ return new IntegerSet();
}
- public void decode(String bitString) {
- try {
- this.value = FixedBitfieldEncoder.decode(bitString);
- } catch (Exception e) {
- throw new DecodingException(e);
- }
+ @Override
+ protected boolean isPresent(IntegerSet value) {
+ return !value.isEmpty();
}
- public String substring(String bitString, int fromIndex) throws SubstringException {
- try {
- return bitString.substring(fromIndex, fromIndex + this.getLengthSupplier.getAsInt());
- } catch (Exception e) {
- throw new SubstringException(e);
- }
+ @Override
+ protected void encode(BitString builder, IntegerSet value, EncodableSegment segment) {
+ FixedBitfieldEncoder.encode(builder, value, this.getLengthSupplier.extract(segment));
}
- @SuppressWarnings("unchecked")
@Override
- public void setValue(Object value) {
- int numElements = this.getLengthSupplier.getAsInt();
- List v = new ArrayList<>((List) value);
- for (int i = v.size(); i < numElements; i++) {
- v.add(false);
- }
- if (v.size() > numElements) {
- v = v.subList(0, numElements);
- }
- super.setValue(v);
+ protected IntegerSet decode(BitString reader, EncodableSegment segment) {
+ return reader.readIntegerSet(getLengthSupplier.extract(segment));
}
+ @SuppressWarnings("unchecked")
@Override
- public List getValue() {
- return new ArrayList<>(super.getValue());
+ protected IntegerSet processValue(IntegerSet oldValue, Object newValue) {
+ oldValue.clear();
+ oldValue.addAll((Collection) newValue);
+ return oldValue;
}
}
diff --git a/iabgpp-encoder/src/main/java/com/iab/gpp/encoder/datatype/EncodableOptimizedFibonacciRange.java b/iabgpp-encoder/src/main/java/com/iab/gpp/encoder/datatype/EncodableOptimizedFibonacciRange.java
deleted file mode 100644
index 2e49855f..00000000
--- a/iabgpp-encoder/src/main/java/com/iab/gpp/encoder/datatype/EncodableOptimizedFibonacciRange.java
+++ /dev/null
@@ -1,67 +0,0 @@
-package com.iab.gpp.encoder.datatype;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.TreeSet;
-import com.iab.gpp.encoder.datatype.encoder.FixedIntegerEncoder;
-import com.iab.gpp.encoder.datatype.encoder.OptimizedFibonacciRangeEncoder;
-import com.iab.gpp.encoder.error.DecodingException;
-import com.iab.gpp.encoder.error.EncodingException;
-
-public class EncodableOptimizedFibonacciRange extends AbstractEncodableBitStringDataType> {
-
- protected EncodableOptimizedFibonacciRange() {
- super(true);
- }
-
- public EncodableOptimizedFibonacciRange(List value) {
- super(true);
- setValue(value);
- }
-
- public EncodableOptimizedFibonacciRange(List value, boolean hardFailIfMissing) {
- super(hardFailIfMissing);
- setValue(value);
- }
-
- public String encode() {
- try {
- return OptimizedFibonacciRangeEncoder.encode(this.value);
- } catch (Exception e) {
- throw new EncodingException(e);
- }
- }
-
- public void decode(String bitString) {
- try {
- this.value = OptimizedFibonacciRangeEncoder.decode(bitString);
- } catch (Exception e) {
- throw new DecodingException(e);
- }
- }
-
- public String substring(String bitString, int fromIndex) throws SubstringException {
- try {
- int max = FixedIntegerEncoder.decode(bitString.substring(fromIndex, fromIndex + 16));
- if (bitString.charAt(fromIndex + 16) == '1') {
- return (bitString.substring(fromIndex, fromIndex + 17)
- + new EncodableFibonacciIntegerRange().substring(bitString, fromIndex + 17));
- } else {
- return bitString.substring(fromIndex, fromIndex + 17 + max);
- }
- } catch (Exception e) {
- throw new SubstringException(e);
- }
- }
-
- @SuppressWarnings("unchecked")
- @Override
- public void setValue(Object value) {
- super.setValue(new ArrayList<>(new TreeSet<>((List) value)));
- }
-
- @Override
- public List getValue() {
- return new ArrayList<>(super.getValue());
- }
-}
diff --git a/iabgpp-encoder/src/main/java/com/iab/gpp/encoder/datatype/EncodableOptimizedFixedRange.java b/iabgpp-encoder/src/main/java/com/iab/gpp/encoder/datatype/EncodableOptimizedFixedRange.java
index 57f4b8e9..b333b244 100644
--- a/iabgpp-encoder/src/main/java/com/iab/gpp/encoder/datatype/EncodableOptimizedFixedRange.java
+++ b/iabgpp-encoder/src/main/java/com/iab/gpp/encoder/datatype/EncodableOptimizedFixedRange.java
@@ -1,68 +1,43 @@
package com.iab.gpp.encoder.datatype;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.TreeSet;
-import com.iab.gpp.encoder.datatype.encoder.FixedIntegerEncoder;
+import com.iab.gpp.encoder.bitstring.BitString;
import com.iab.gpp.encoder.datatype.encoder.OptimizedFixedRangeEncoder;
-import com.iab.gpp.encoder.error.DecodingException;
-import com.iab.gpp.encoder.error.EncodingException;
+import com.iab.gpp.encoder.field.FieldKey;
+import com.iab.gpp.encoder.segment.EncodableSegment;
+import java.util.Collection;
+public final class EncodableOptimizedFixedRange & FieldKey>
+ extends AbstractDirtyableBitStringDataType {
-public class EncodableOptimizedFixedRange extends AbstractEncodableBitStringDataType> {
-
- protected EncodableOptimizedFixedRange() {
- super(true);
- }
-
- public EncodableOptimizedFixedRange(List value) {
- super(true);
- setValue(value);
- }
-
- public EncodableOptimizedFixedRange(List value, boolean hardFailIfMissing) {
- super(hardFailIfMissing);
- setValue(value);
+ public EncodableOptimizedFixedRange(String name) {
+ super(name, null);
}
- public String encode() {
- try {
- return OptimizedFixedRangeEncoder.encode(this.value);
- } catch (Exception e) {
- throw new EncodingException(e);
- }
+ @Override
+ protected IntegerSet initialize() {
+ return new IntegerSet();
}
- public void decode(String bitString) {
- try {
- this.value = OptimizedFixedRangeEncoder.decode(bitString);
- } catch (Exception e) {
- throw new DecodingException(e);
- }
+ @Override
+ protected boolean isPresent(IntegerSet value) {
+ return !value.isEmpty();
}
- public String substring(String bitString, int fromIndex) throws SubstringException {
- try {
- int max = FixedIntegerEncoder.decode(bitString.substring(fromIndex, fromIndex + 16));
- if (bitString.charAt(fromIndex + 16) == '1') {
- return bitString.substring(fromIndex, fromIndex + 17)
- + new EncodableFixedIntegerRange().substring(bitString, fromIndex + 17);
- } else {
- return bitString.substring(fromIndex, fromIndex + 17 + max);
- }
- } catch (Exception e) {
- throw new SubstringException(e);
- }
+ @Override
+ protected void encode(BitString builder, IntegerSet value, EncodableSegment segment) {
+ OptimizedFixedRangeEncoder.encode(builder, value);
}
- @SuppressWarnings("unchecked")
@Override
- public void setValue(Object value) {
- super.setValue(new ArrayList<>(new TreeSet<>((List) value)));
+ protected IntegerSet decode(BitString reader, EncodableSegment segment) {
+ return OptimizedFixedRangeEncoder.decode(reader);
}
+ @SuppressWarnings("unchecked")
@Override
- public List getValue() {
- return new ArrayList<>(super.getValue());
+ protected IntegerSet processValue(IntegerSet oldValue, Object newValue) {
+ oldValue.clear();
+ oldValue.addAll((Collection) newValue);
+ return oldValue;
}
}
diff --git a/iabgpp-encoder/src/main/java/com/iab/gpp/encoder/datatype/FixedIntegerList.java b/iabgpp-encoder/src/main/java/com/iab/gpp/encoder/datatype/FixedIntegerList.java
new file mode 100644
index 00000000..99778d41
--- /dev/null
+++ b/iabgpp-encoder/src/main/java/com/iab/gpp/encoder/datatype/FixedIntegerList.java
@@ -0,0 +1,102 @@
+package com.iab.gpp.encoder.datatype;
+
+import com.iab.gpp.encoder.bitstring.BitSet;
+import java.util.AbstractList;
+
+/**
+ * An optimized implementation of {@literal List} of fixed size. Use {@link #getInt(int)}
+ * and {@link #setInt(int, int)} for efficient access.
+ */
+public final class FixedIntegerList extends AbstractList implements Dirtyable {
+
+ private boolean dirty;
+ private final BitSet bitSet;
+ private final int offset;
+ private final int elementBitStringLength;
+ private final int numElements;
+
+ public FixedIntegerList(BitSet bitSet, int offset, int elementBitStringLength, int numElements) {
+ this.bitSet = bitSet;
+ this.offset = offset;
+ this.elementBitStringLength = elementBitStringLength;
+ this.numElements = numElements;
+ }
+
+ public FixedIntegerList(int elementBitStringLength, int numElements) {
+ this(new BitSet(elementBitStringLength * numElements), 0, elementBitStringLength, numElements);
+ }
+
+ public boolean isPresent() {
+ int start = offset;
+ int end = start + elementBitStringLength * numElements;
+ for (int i = start; i < end; i++) {
+ if (bitSet.get(i)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public int size() {
+ return numElements;
+ }
+
+ @Override
+ public Integer get(int index) {
+ return getInt(index);
+ }
+
+ public int getInt(int index) {
+ int length = elementBitStringLength;
+ int mask = 1 << length;
+ int from = offset + index * length;
+ int to = from + length;
+ int value = 0;
+ for (int i = from; i < to; i++) {
+ mask >>= 1;
+ if (bitSet.get(i)) {
+ value |= mask;
+ }
+ }
+ return value;
+ }
+
+ @Override
+ public Integer set(int index, Integer value) {
+ return setInt(index, value);
+ }
+
+ public int setInt(int index, int value) {
+ int length = elementBitStringLength;
+ int mask = 1 << length;
+ if (value < 0 || value >= mask) {
+ throw new IllegalArgumentException(
+ "Numeric value '"
+ + value
+ + "' is too large for a bit string length of '"
+ + elementBitStringLength
+ + "'");
+ }
+ int from = offset + index * length;
+ int to = from + length;
+ for (int i = from; i < to; i++) {
+ mask >>= 1;
+ if (bitSet.set(i, (value & mask) != 0)) {
+ value |= mask;
+ }
+ }
+ dirty = true;
+ return value;
+ }
+
+ @Override
+ public boolean isDirty() {
+ return dirty;
+ }
+
+ @Override
+ public void setDirty(boolean dirty) {
+ this.dirty = dirty;
+ }
+}
diff --git a/iabgpp-encoder/src/main/java/com/iab/gpp/encoder/datatype/IntegerCache.java b/iabgpp-encoder/src/main/java/com/iab/gpp/encoder/datatype/IntegerCache.java
new file mode 100644
index 00000000..79b9aa8b
--- /dev/null
+++ b/iabgpp-encoder/src/main/java/com/iab/gpp/encoder/datatype/IntegerCache.java
@@ -0,0 +1,22 @@
+package com.iab.gpp.encoder.datatype;
+
+import static com.iab.gpp.encoder.datatype.IntegerSet.MAX_COLLECTION_SIZE;
+
+final class IntegerCache {
+ private IntegerCache() {}
+
+ private static final Integer[] CACHE = new Integer[MAX_COLLECTION_SIZE];
+
+ static {
+ for (int i = 0; i < MAX_COLLECTION_SIZE; i++) {
+ CACHE[i] = i;
+ }
+ }
+
+ static Integer valueOf(int i) {
+ if (i >= 0 && i < MAX_COLLECTION_SIZE) {
+ return CACHE[i];
+ }
+ return i;
+ }
+}
diff --git a/iabgpp-encoder/src/main/java/com/iab/gpp/encoder/datatype/IntegerSet.java b/iabgpp-encoder/src/main/java/com/iab/gpp/encoder/datatype/IntegerSet.java
new file mode 100644
index 00000000..19c02996
--- /dev/null
+++ b/iabgpp-encoder/src/main/java/com/iab/gpp/encoder/datatype/IntegerSet.java
@@ -0,0 +1,222 @@
+package com.iab.gpp.encoder.datatype;
+
+import com.iab.gpp.encoder.bitstring.BitSet;
+import java.util.AbstractSet;
+import java.util.Collection;
+import java.util.NoSuchElementException;
+import java.util.PrimitiveIterator.OfInt;
+import java.util.Spliterator;
+import java.util.Spliterators;
+import java.util.logging.Logger;
+import java.util.stream.IntStream;
+import java.util.stream.StreamSupport;
+
+/**
+ * An optimized implementation of {@literal Set} backed by a {@link java.util.BitSet}. Use
+ * {@link #containsInt(int)}, {@link #addInt(int)}, and {@link #removeInt(int)} for efficient
+ * access.
+ */
+public final class IntegerSet extends AbstractSet implements Dirtyable {
+ private static final Logger LOGGER = Logger.getLogger(IntegerSet.class.getName());
+
+ static final int MAX_COLLECTION_SIZE = 8192;
+
+ private boolean dirty;
+ private final BitSet bitSet;
+ private final int from;
+ private final int to;
+ private final int adjustment;
+
+ public IntegerSet(BitSet bitSet, int from, int to, int adjustment) {
+ this.bitSet = bitSet;
+ this.from = from;
+ this.to = to;
+ this.adjustment = adjustment;
+ }
+
+ public IntegerSet(int limit) {
+ this(new BitSet(limit), 0, limit, 0);
+ }
+
+ public IntegerSet() {
+ this(new BitSet(), 0, MAX_COLLECTION_SIZE, 0);
+ }
+
+ @Override
+ public int size() {
+ OfInt it = iterator();
+ int count = 0;
+ while (it.hasNext()) {
+ it.next();
+ count++;
+ }
+ return count;
+ }
+
+ private int getOffset(int value) {
+ int offset = from - adjustment + value;
+ if (offset < from) {
+ throw new IndexOutOfBoundsException("Negative index provided");
+ }
+ return offset;
+ }
+
+ @Override
+ public void clear() {
+ dirty = true;
+ bitSet.clear(from, to);
+ }
+
+ @Override
+ public boolean isEmpty() {
+ return bitSet.nextSetBit(from) == -1;
+ }
+
+ public boolean containsInt(int value) {
+ if (value < adjustment) {
+ return false;
+ }
+ int offset = getOffset(value);
+ return offset < to && bitSet.get(offset);
+ }
+
+ @Override
+ public OfInt iterator() {
+ return new OfInt() {
+ int cursor = bitSet.nextSetBit(from);
+
+ @Override
+ public boolean hasNext() {
+ return cursor < to && cursor != -1;
+ }
+
+ @Override
+ public Integer next() {
+ return IntegerCache.valueOf(nextInt());
+ }
+
+ @Override
+ public int nextInt() {
+ if (!this.hasNext()) {
+ throw new NoSuchElementException();
+ }
+ int next = cursor;
+ cursor = bitSet.nextSetBit(cursor + 1);
+ return next - from + adjustment;
+ }
+ };
+ }
+
+ @Override
+ public Spliterator.OfInt spliterator() {
+ return Spliterators.spliteratorUnknownSize(
+ iterator(),
+ Spliterator.ORDERED | Spliterator.DISTINCT | Spliterator.IMMUTABLE | Spliterator.NONNULL);
+ }
+
+ public IntStream intStream() {
+ return StreamSupport.intStream(spliterator(), false);
+ }
+
+ private static final void logOutOfRange(int value) {
+ LOGGER.warning("Exceeding IntegerBitSet.MAX_COLLECTION_SIZE: " + value);
+ }
+
+ public void addRange(int start, int end) {
+ if (end < start) {
+ throw new IllegalArgumentException("Negative length range");
+ }
+ int realStart = getOffset(start);
+ int realEnd = getOffset(end);
+ if (realStart >= to) {
+ logOutOfRange(start);
+ return;
+ }
+ if (realEnd > to) {
+ logOutOfRange(end);
+ realEnd = to;
+ }
+ dirty = true;
+ bitSet.set(realStart, realEnd);
+ }
+
+ public boolean addInt(int value) {
+ int offset = getOffset(value);
+ if (offset >= to) {
+ logOutOfRange(value);
+ return false;
+ }
+ boolean present = !bitSet.set(offset, true);
+ dirty = true;
+ return present;
+ }
+
+ public boolean removeInt(int value) {
+ int offset = getOffset(value);
+ if (offset >= to) {
+ logOutOfRange(value);
+ return false;
+ }
+ boolean present = bitSet.set(offset, false);
+ dirty = true;
+ return present;
+ }
+
+ @Override
+ public final boolean contains(Object value) {
+ if (value instanceof Integer) {
+ return containsInt((Integer) value);
+ }
+ return false;
+ }
+
+ @Override
+ public final boolean add(Integer value) {
+ if (value == null) {
+ return false;
+ }
+ return addInt(value);
+ }
+
+ @Override
+ public final boolean remove(Object value) {
+ if (value instanceof Integer) {
+ return removeInt((Integer) value);
+ }
+ return false;
+ }
+
+ @Override
+ public boolean removeAll(Collection> c) {
+ boolean modified = false;
+ for (Integer i : this) {
+ if (c.contains(i)) {
+ remove(i);
+ modified = true;
+ }
+ }
+ return modified;
+ }
+
+ @Override
+ public boolean retainAll(Collection> c) {
+ boolean modified = false;
+ for (Integer i : this) {
+ if (!c.contains(i)) {
+ remove(i);
+ modified = true;
+ }
+ }
+ return modified;
+ }
+
+ @Override
+ public boolean isDirty() {
+ return dirty;
+ }
+
+ @Override
+ public void setDirty(boolean dirty) {
+ this.dirty = dirty;
+ }
+}
diff --git a/iabgpp-encoder/src/main/java/com/iab/gpp/encoder/datatype/RangeEntry.java b/iabgpp-encoder/src/main/java/com/iab/gpp/encoder/datatype/RangeEntry.java
index 0237ed18..1bfc6ce3 100644
--- a/iabgpp-encoder/src/main/java/com/iab/gpp/encoder/datatype/RangeEntry.java
+++ b/iabgpp-encoder/src/main/java/com/iab/gpp/encoder/datatype/RangeEntry.java
@@ -1,14 +1,23 @@
package com.iab.gpp.encoder.datatype;
-import java.util.List;
+import java.util.Collection;
-public class RangeEntry {
+public final class RangeEntry implements Dirtyable {
+ private boolean dirty;
private int key;
private int type;
- private List ids;
+ private final IntegerSet ids;
- public RangeEntry(int key, int type, List ids) {
+ public RangeEntry(int key, int type, Collection ids) {
+ super();
+ this.key = key;
+ this.type = type;
+ this.ids = new IntegerSet();
+ this.ids.addAll(ids);
+ }
+
+ RangeEntry(int key, int type, IntegerSet ids) {
super();
this.key = key;
this.type = type;
@@ -20,6 +29,7 @@ public int getKey() {
}
public void setKey(int key) {
+ this.dirty = true;
this.key = key;
}
@@ -28,15 +38,33 @@ public int getType() {
}
public void setType(int type) {
+ this.dirty = true;
this.type = type;
}
- public List getIds() {
+ public IntegerSet getIds() {
return ids;
}
- public void setIds(List ids) {
- this.ids = ids;
+ public void setIds(Collection ids) {
+ this.dirty = true;
+ this.ids.clear();
+ this.ids.addAll(ids);
}
+ @Override
+ public boolean isDirty() {
+ return dirty || ids.isDirty();
+ }
+
+ @Override
+ public void setDirty(boolean dirty) {
+ this.dirty = dirty;
+ ids.setDirty(dirty);
+ }
+
+ @Override
+ public String toString() {
+ return "{key=" + key + ", type=" + type + ", ids=" + ids + "}";
+ }
}
diff --git a/iabgpp-encoder/src/main/java/com/iab/gpp/encoder/datatype/SubstringException.java b/iabgpp-encoder/src/main/java/com/iab/gpp/encoder/datatype/SubstringException.java
deleted file mode 100644
index e9df8ca6..00000000
--- a/iabgpp-encoder/src/main/java/com/iab/gpp/encoder/datatype/SubstringException.java
+++ /dev/null
@@ -1,18 +0,0 @@
-package com.iab.gpp.encoder.datatype;
-
-public class SubstringException extends Exception {
-
- private static final long serialVersionUID = 1825100490468259890L;
-
- public SubstringException(String msg) {
- super(msg);
- }
-
- public SubstringException(Exception e) {
- super(e);
- }
-
- public SubstringException(String msg, Exception e) {
- super(msg, e);
- }
-}
diff --git a/iabgpp-encoder/src/main/java/com/iab/gpp/encoder/datatype/UnencodableBoolean.java b/iabgpp-encoder/src/main/java/com/iab/gpp/encoder/datatype/UnencodableBoolean.java
new file mode 100644
index 00000000..62d3af2f
--- /dev/null
+++ b/iabgpp-encoder/src/main/java/com/iab/gpp/encoder/datatype/UnencodableBoolean.java
@@ -0,0 +1,31 @@
+package com.iab.gpp.encoder.datatype;
+
+import com.iab.gpp.encoder.bitstring.BitString;
+import com.iab.gpp.encoder.field.FieldKey;
+import com.iab.gpp.encoder.segment.EncodableSegment;
+
+public final class UnencodableBoolean & FieldKey>
+ extends AbstractEncodableBitStringDataType {
+
+ private final Boolean initial;
+
+ public UnencodableBoolean(String name, Boolean initial) {
+ super(name, null);
+ this.initial = initial;
+ }
+
+ @Override
+ protected Boolean initialize() {
+ return initial;
+ }
+
+ @Override
+ protected void encode(BitString writer, Boolean value, EncodableSegment segment) {
+ // pass
+ }
+
+ @Override
+ protected Boolean decode(BitString reader, EncodableSegment segment) {
+ return Boolean.FALSE;
+ }
+}
diff --git a/iabgpp-encoder/src/main/java/com/iab/gpp/encoder/datatype/UnencodableCharacter.java b/iabgpp-encoder/src/main/java/com/iab/gpp/encoder/datatype/UnencodableCharacter.java
index 4ef9c256..d0379fc6 100644
--- a/iabgpp-encoder/src/main/java/com/iab/gpp/encoder/datatype/UnencodableCharacter.java
+++ b/iabgpp-encoder/src/main/java/com/iab/gpp/encoder/datatype/UnencodableCharacter.java
@@ -1,49 +1,20 @@
package com.iab.gpp.encoder.datatype;
+import com.iab.gpp.encoder.field.FieldKey;
import java.util.function.Predicate;
-import com.iab.gpp.encoder.error.ValidationException;
-public class UnencodableCharacter implements DataType {
+public final class UnencodableCharacter & FieldKey>
+ extends DataType {
- private Predicate validator;
- private Character value = null;
+ private final Character initial;
- public UnencodableCharacter() {
- this.validator = v -> true;
- }
-
- public UnencodableCharacter(Character value) {
- this.validator = v -> true;
- setValue(value);
- }
-
- public UnencodableCharacter(Character value, Predicate validator) {
- this.validator = validator;
- setValue(value);
+ public UnencodableCharacter(String name, Character initial, Predicate validator) {
+ super(name, validator);
+ this.initial = initial;
}
- public void setValidator(Predicate validator) {
- this.validator = validator;
- }
-
@Override
- public boolean hasValue() {
- return this.value != null;
+ protected Character initialize() {
+ return initial;
}
-
- @Override
- public Character getValue() {
- return this.value;
- }
-
- @Override
- public void setValue(Object value) {
- Character c = (Character)value.toString().charAt(0);
- if(validator.test(c)) {
- this.value = c;
- } else {
- throw new ValidationException("Invalid value '" + c + "'");
- }
- }
-
}
diff --git a/iabgpp-encoder/src/main/java/com/iab/gpp/encoder/datatype/UnencodableInteger.java b/iabgpp-encoder/src/main/java/com/iab/gpp/encoder/datatype/UnencodableInteger.java
index 60dcddfd..ac863667 100644
--- a/iabgpp-encoder/src/main/java/com/iab/gpp/encoder/datatype/UnencodableInteger.java
+++ b/iabgpp-encoder/src/main/java/com/iab/gpp/encoder/datatype/UnencodableInteger.java
@@ -1,49 +1,18 @@
package com.iab.gpp.encoder.datatype;
-import java.util.function.Predicate;
-import com.iab.gpp.encoder.error.ValidationException;
+import com.iab.gpp.encoder.field.FieldKey;
-public class UnencodableInteger implements DataType {
+public final class UnencodableInteger & FieldKey> extends DataType {
- private Predicate validator;
- private Integer value = null;
+ private final Integer initial;
- public UnencodableInteger() {
- this.validator = v -> true;
- }
-
- public UnencodableInteger(Integer value) {
- this.validator = v -> true;
- setValue(value);
- }
-
- public UnencodableInteger(Integer value, Predicate validator) {
- this.validator = validator;
- setValue(value);
+ public UnencodableInteger(String name, Integer initial) {
+ super(name, null);
+ this.initial = initial;
}
- public void setValidator(Predicate validator) {
- this.validator = validator;
- }
-
@Override
- public boolean hasValue() {
- return this.value != null;
+ protected Integer initialize() {
+ return initial;
}
-
- @Override
- public Integer getValue() {
- return this.value;
- }
-
- @Override
- public void setValue(Object value) {
- Integer i = (Integer)value;
- if(validator.test(i)) {
- this.value = i;
- } else {
- throw new ValidationException("Invalid value '" + i + "'");
- }
- }
-
}
diff --git a/iabgpp-encoder/src/main/java/com/iab/gpp/encoder/datatype/encoder/ArrayOfRangesEntryEncoder.java b/iabgpp-encoder/src/main/java/com/iab/gpp/encoder/datatype/encoder/ArrayOfRangesEntryEncoder.java
deleted file mode 100644
index de1449b3..00000000
--- a/iabgpp-encoder/src/main/java/com/iab/gpp/encoder/datatype/encoder/ArrayOfRangesEntryEncoder.java
+++ /dev/null
@@ -1,43 +0,0 @@
-package com.iab.gpp.encoder.datatype.encoder;
-
-import java.util.regex.Pattern;
-import com.iab.gpp.encoder.error.DecodingException;
-
-public class ArrayOfRangesEntryEncoder {
-
- private static Pattern BITSTRING_VERIFICATION_PATTERN = Pattern.compile("^[0-1]*$", Pattern.CASE_INSENSITIVE);
-
- public static String encode(long value, int bitStringLength) {
- String bitString = "";
- while (value > 0) {
- if ((value & 1) == 1) {
- bitString = "1" + bitString;
- } else {
- bitString = "0" + bitString;
- }
- value = value >> 1;
- }
-
- while (bitString.length() < bitStringLength) {
- bitString = "0" + bitString;
- }
-
- return bitString;
- }
-
- public static long decode(String bitString) throws DecodingException {
- if (!BITSTRING_VERIFICATION_PATTERN.matcher(bitString).matches()) {
- throw new DecodingException("Undecodable FixedLong '" + bitString + "'");
- }
-
- long value = 0;
-
- for (int i = 0; i < bitString.length(); i++) {
- if (bitString.charAt(bitString.length() - (i + 1)) == '1') {
- value += 1L << i;
- }
- }
-
- return value;
- }
-}
diff --git a/iabgpp-encoder/src/main/java/com/iab/gpp/encoder/datatype/encoder/BooleanEncoder.java b/iabgpp-encoder/src/main/java/com/iab/gpp/encoder/datatype/encoder/BooleanEncoder.java
deleted file mode 100644
index 20e6cbd0..00000000
--- a/iabgpp-encoder/src/main/java/com/iab/gpp/encoder/datatype/encoder/BooleanEncoder.java
+++ /dev/null
@@ -1,26 +0,0 @@
-package com.iab.gpp.encoder.datatype.encoder;
-
-import com.iab.gpp.encoder.error.DecodingException;
-import com.iab.gpp.encoder.error.EncodingException;
-
-public class BooleanEncoder {
- public static String encode(Boolean value) {
- if (value == true) {
- return "1";
- } else if (value == false) {
- return "0";
- } else {
- throw new EncodingException("Unencodable Boolean '" + value + "'");
- }
- }
-
- public static boolean decode(String bitString) {
- if (bitString.equals("1")) {
- return true;
- } else if (bitString.equals("0")) {
- return false;
- } else {
- throw new DecodingException("Undecodable Boolean '" + bitString + "'");
- }
- }
-}
diff --git a/iabgpp-encoder/src/main/java/com/iab/gpp/encoder/datatype/encoder/DatetimeEncoder.java b/iabgpp-encoder/src/main/java/com/iab/gpp/encoder/datatype/encoder/DatetimeEncoder.java
index 77f6f344..243aebbe 100644
--- a/iabgpp-encoder/src/main/java/com/iab/gpp/encoder/datatype/encoder/DatetimeEncoder.java
+++ b/iabgpp-encoder/src/main/java/com/iab/gpp/encoder/datatype/encoder/DatetimeEncoder.java
@@ -1,27 +1,22 @@
package com.iab.gpp.encoder.datatype.encoder;
-import java.time.Instant;
-import java.time.ZoneId;
-import java.time.ZonedDateTime;
-import java.util.regex.Pattern;
+import com.iab.gpp.encoder.bitstring.BitString;
import com.iab.gpp.encoder.error.DecodingException;
+import java.time.Instant;
public class DatetimeEncoder {
- private static Pattern BITSTRING_VERIFICATION_PATTERN = Pattern.compile("^[0-1]*$", Pattern.CASE_INSENSITIVE);
- public static String encode(ZonedDateTime value) {
+ private DatetimeEncoder() {}
+
+ public static void encode(BitString builder, Instant value) {
if (value != null) {
- return FixedLongEncoder.encode(value.toInstant().toEpochMilli() / 100, 36);
+ builder.writeLong(value.toEpochMilli() / 100, 36);
} else {
- return FixedLongEncoder.encode(0, 36);
+ builder.writeLong(0, 36);
}
}
- public static ZonedDateTime decode(String bitString) throws DecodingException {
- if (!BITSTRING_VERIFICATION_PATTERN.matcher(bitString).matches() || bitString.length() != 36) {
- throw new DecodingException("Undecodable Datetime '" + bitString + "'");
- }
-
- return ZonedDateTime.ofInstant(Instant.ofEpochMilli(FixedLongEncoder.decode(bitString) * 100L), ZoneId.of("UTC"));
+ public static Instant decode(BitString reader) throws DecodingException {
+ return Instant.ofEpochMilli(reader.readLong(36) * 100L);
}
}
diff --git a/iabgpp-encoder/src/main/java/com/iab/gpp/encoder/datatype/encoder/FibonacciIntegerEncoder.java b/iabgpp-encoder/src/main/java/com/iab/gpp/encoder/datatype/encoder/FibonacciIntegerEncoder.java
index 281b27da..f333c1c2 100644
--- a/iabgpp-encoder/src/main/java/com/iab/gpp/encoder/datatype/encoder/FibonacciIntegerEncoder.java
+++ b/iabgpp-encoder/src/main/java/com/iab/gpp/encoder/datatype/encoder/FibonacciIntegerEncoder.java
@@ -1,72 +1,22 @@
package com.iab.gpp.encoder.datatype.encoder;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.regex.Pattern;
-import com.iab.gpp.encoder.error.DecodingException;
-
public class FibonacciIntegerEncoder {
- private static Pattern BITSTRING_VERIFICATION_PATTERN = Pattern.compile("^[0-1]*$", Pattern.CASE_INSENSITIVE);
-
- public static String encode(int value) {
- List fib = new ArrayList();
- if (value >= 1) {
- fib.add(1);
-
- if (value >= 2) {
- fib.add(2);
-
- int i = 2;
- while (value >= fib.get(i - 1) + fib.get(i - 2)) {
- fib.add(fib.get(i - 1) + fib.get(i - 2));
- i++;
- }
- }
- }
-
- String bitString = "1";
- for (int i = fib.size() - 1; i >= 0; i--) {
- int f = fib.get(i);
- if (value >= f) {
- bitString = "1" + bitString;
- value -= f;
- } else {
- bitString = "0" + bitString;
- }
- }
-
- return bitString;
- }
+ private FibonacciIntegerEncoder() {}
- public static int decode(String bitString) throws DecodingException {
- // enforce a length restriction to avoid overflows
- // 2^16 has a bit string length of 24
- if (bitString.length() > 24) {
- throw new DecodingException("FibonacciInteger too long");
- }
- if (!BITSTRING_VERIFICATION_PATTERN.matcher(bitString).matches() || bitString.length() < 2
- || bitString.indexOf("11") != bitString.length() - 2) {
- throw new DecodingException("Undecodable FibonacciInteger '" + bitString + "'");
- }
+ // this is the length of the longest fibonacci encoded string of all 1's
+ // which does not overflow a 32-bit integer
+ public static final int FIBONACCI_LIMIT = 42;
+ public static final int[] FIBONACCI_NUMBERS = new int[FIBONACCI_LIMIT];
- int value = 0;
-
- List fib = new ArrayList<>();
- for (int i = 0; i < bitString.length() - 1; i++) {
+ static {
+ for (int i = 0; i < FIBONACCI_LIMIT; i++) {
if (i == 0) {
- fib.add(1);
+ FIBONACCI_NUMBERS[i] = 1;
} else if (i == 1) {
- fib.add(2);
+ FIBONACCI_NUMBERS[i] = 2;
} else {
- fib.add(fib.get(i - 1) + fib.get(i - 2));
- }
- }
-
- for (int i = 0; i < bitString.length() - 1; i++) {
- if (bitString.charAt(i) == '1') {
- value += fib.get(i);
+ FIBONACCI_NUMBERS[i] = FIBONACCI_NUMBERS[i - 1] + FIBONACCI_NUMBERS[i - 2];
}
}
- return value;
}
}
diff --git a/iabgpp-encoder/src/main/java/com/iab/gpp/encoder/datatype/encoder/FibonacciIntegerRangeEncoder.java b/iabgpp-encoder/src/main/java/com/iab/gpp/encoder/datatype/encoder/FibonacciIntegerRangeEncoder.java
index daafa795..d1888cae 100644
--- a/iabgpp-encoder/src/main/java/com/iab/gpp/encoder/datatype/encoder/FibonacciIntegerRangeEncoder.java
+++ b/iabgpp-encoder/src/main/java/com/iab/gpp/encoder/datatype/encoder/FibonacciIntegerRangeEncoder.java
@@ -1,100 +1,70 @@
package com.iab.gpp.encoder.datatype.encoder;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-import java.util.logging.Logger;
-import java.util.regex.Pattern;
+import com.iab.gpp.encoder.bitstring.BitString;
+import com.iab.gpp.encoder.datatype.IntegerSet;
import com.iab.gpp.encoder.error.DecodingException;
+import java.util.Collection;
public class FibonacciIntegerRangeEncoder {
+ private FibonacciIntegerRangeEncoder() {}
- private static final Logger LOGGER = Logger.getLogger(FibonacciIntegerRangeEncoder.class.getName());
- // NOTE: This is a value roughly the 2x the size of this list
- // https://tools.iabtechlab.com/transparencycenter/explorer/business/gpp
- static final int MAX_SIZE = 8192;
- private static Pattern BITSTRING_VERIFICATION_PATTERN = Pattern.compile("^[0-1]*$", Pattern.CASE_INSENSITIVE);
-
- public static String encode(List value) {
- Collections.sort(value);
-
- List> groups = new ArrayList<>();
-
+ public static int encode(BitString builder, Collection value) {
+ BitString rangeBuilder = new BitString();
+ int groupStart = -1;
+ int last = Integer.MIN_VALUE;
int offset = 0;
- int groupStartIndex = 0;
- while (groupStartIndex < value.size()) {
- int groupEndIndex = groupStartIndex;
- while (groupEndIndex < value.size() - 1 && value.get(groupEndIndex) + 1 == value.get(groupEndIndex + 1)) {
- groupEndIndex++;
+ int groupCount = 0;
+ for (Integer item : value) {
+ if (last != (item - 1)) {
+ if (groupStart > 0) {
+ groupCount++;
+ writeGroup(rangeBuilder, groupStart, last, offset);
+ offset = last;
+ }
+ groupStart = item;
}
-
- groups.add(value.subList(groupStartIndex, groupEndIndex + 1));
-
- groupStartIndex = groupEndIndex + 1;
+ last = item;
}
-
- String bitString = FixedIntegerEncoder.encode(groups.size(), 12);
- for (int i = 0; i < groups.size(); i++) {
- if (groups.get(i).size() == 1) {
- int v = groups.get(i).get(0) - offset;
- offset = groups.get(i).get(0);
- bitString += "0" + FibonacciIntegerEncoder.encode(v);
- } else {
- int startVal = groups.get(i).get(0) - offset;
- offset = groups.get(i).get(0);
- int endVal = groups.get(i).get(groups.get(i).size() - 1) - offset;
- offset = groups.get(i).get(groups.get(i).size() - 1);
- bitString += "1" + FibonacciIntegerEncoder.encode(startVal) + FibonacciIntegerEncoder.encode(endVal);
- }
+ if (groupStart > 0) {
+ groupCount++;
+ writeGroup(rangeBuilder, groupStart, last, offset);
}
- return bitString;
+ builder.writeInt(groupCount, 12);
+ builder.write(rangeBuilder);
+ return last;
}
- public static List decode(String bitString) throws DecodingException {
- if (!BITSTRING_VERIFICATION_PATTERN.matcher(bitString).matches() || bitString.length() < 12) {
- throw new DecodingException("Undecodable FibonacciIntegerRange '" + bitString + "'");
+ private static void writeGroup(BitString builder, int groupStart, int last, int offset) {
+ int base = groupStart - offset;
+ int span = last - groupStart;
+ if (span == 0) {
+ builder.writeBoolean(false);
+ builder.writeFibonacci(base);
+ } else {
+ builder.writeBoolean(true);
+ builder.writeFibonacci(base);
+ builder.writeFibonacci(span);
}
+ }
- List value = new ArrayList<>();
- int count = FixedIntegerEncoder.decode(bitString.substring(0, 12));
-
+ public static IntegerSet decode(BitString reader) throws DecodingException {
+ int count = reader.readInt(12);
+ IntegerSet value = new IntegerSet();
int offset = 0;
- int startIndex = 12;
for (int i = 0; i < count; i++) {
- boolean group = BooleanEncoder.decode(bitString.substring(startIndex, startIndex + 1));
- startIndex++;
-
- if (group == true) {
- int index = bitString.indexOf("11", startIndex);
- int start = FibonacciIntegerEncoder.decode(bitString.substring(startIndex, index + 2)) + offset;
+ boolean group = reader.readBoolean();
+ if (group) {
+ int start = reader.readFibonacci() + offset;
offset = start;
- startIndex = index + 2;
-
- index = bitString.indexOf("11", startIndex);
- int end = FibonacciIntegerEncoder.decode(bitString.substring(startIndex, index + 2)) + offset;
+ int end = reader.readFibonacci() + offset;
offset = end;
- startIndex = index + 2;
-
- if (value.size() + (end - start) > MAX_SIZE) {
- LOGGER.warning("FibonacciIntegerRange has too many values");
- break;
- }
- for (int j = start; j <= end; j++) {
- value.add(j);
- }
+ value.addRange(start, end + 1);
} else {
- int index = bitString.indexOf("11", startIndex);
- int val = FibonacciIntegerEncoder.decode(bitString.substring(startIndex, index + 2)) + offset;
+ int val = reader.readFibonacci() + offset;
offset = val;
- if (value.size() == MAX_SIZE) {
- LOGGER.warning("FibonacciIntegerRange has too many values");
- break;
- }
- value.add(val);
- startIndex = index + 2;
+ value.addInt(val);
}
}
-
return value;
}
}
diff --git a/iabgpp-encoder/src/main/java/com/iab/gpp/encoder/datatype/encoder/FixedBitfieldEncoder.java b/iabgpp-encoder/src/main/java/com/iab/gpp/encoder/datatype/encoder/FixedBitfieldEncoder.java
index 6c922263..cb1e14aa 100644
--- a/iabgpp-encoder/src/main/java/com/iab/gpp/encoder/datatype/encoder/FixedBitfieldEncoder.java
+++ b/iabgpp-encoder/src/main/java/com/iab/gpp/encoder/datatype/encoder/FixedBitfieldEncoder.java
@@ -1,41 +1,14 @@
package com.iab.gpp.encoder.datatype.encoder;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.regex.Pattern;
-import com.iab.gpp.encoder.error.DecodingException;
-import com.iab.gpp.encoder.error.EncodingException;
+import com.iab.gpp.encoder.bitstring.BitString;
+import com.iab.gpp.encoder.datatype.IntegerSet;
public class FixedBitfieldEncoder {
+ private FixedBitfieldEncoder() {}
- private static Pattern BITSTRING_VERIFICATION_PATTERN = Pattern.compile("^[0-1]*$", Pattern.CASE_INSENSITIVE);
-
- public static String encode(List value, int bitStringLength) {
- if (value.size() > bitStringLength) {
- throw new EncodingException("Too many values '" + value.size() + "'");
- }
-
- String bitString = "";
- for (int i = 0; i < value.size(); i++) {
- bitString += BooleanEncoder.encode(value.get(i));
- }
-
- while (bitString.length() < bitStringLength) {
- bitString += "0";
- }
-
- return bitString;
- }
-
- public static List decode(String bitString) {
- if (!BITSTRING_VERIFICATION_PATTERN.matcher(bitString).matches()) {
- throw new DecodingException("Undecodable FixedBitfield '" + bitString + "'");
- }
-
- List value = new ArrayList<>();
- for (int i = 0; i < bitString.length(); i++) {
- value.add(BooleanEncoder.decode(bitString.substring(i, i + 1)));
+ public static void encode(BitString builder, IntegerSet value, int bitStringLength) {
+ for (int i = 0; i < bitStringLength; i++) {
+ builder.writeBoolean(value.containsInt(i));
}
- return value;
}
}
diff --git a/iabgpp-encoder/src/main/java/com/iab/gpp/encoder/datatype/encoder/FixedIntegerEncoder.java b/iabgpp-encoder/src/main/java/com/iab/gpp/encoder/datatype/encoder/FixedIntegerEncoder.java
deleted file mode 100644
index dfc6802f..00000000
--- a/iabgpp-encoder/src/main/java/com/iab/gpp/encoder/datatype/encoder/FixedIntegerEncoder.java
+++ /dev/null
@@ -1,50 +0,0 @@
-package com.iab.gpp.encoder.datatype.encoder;
-
-import java.util.regex.Pattern;
-import com.iab.gpp.encoder.error.DecodingException;
-import com.iab.gpp.encoder.error.EncodingException;
-
-public class FixedIntegerEncoder {
-
- private static Pattern BITSTRING_VERIFICATION_PATTERN = Pattern.compile("^[0-1]*$", Pattern.CASE_INSENSITIVE);
-
- public static String encode(int value, int bitStringLength) {
- // let bitString = value.toString(2);
-
- String bitString = "";
- while (value > 0) {
- if ((value & 1) == 1) {
- bitString = "1" + bitString;
- } else {
- bitString = "0" + bitString;
- }
- value = value >> 1;
- }
-
- if (bitString.length() > bitStringLength) {
- throw new EncodingException(
- "Numeric value '" + value + "' is too large for a bit string length of '" + bitStringLength + "'");
- }
-
- while (bitString.length() < bitStringLength) {
- bitString = "0" + bitString;
- }
-
- return bitString;
- }
-
- public static int decode(String bitString) throws DecodingException {
- if (!BITSTRING_VERIFICATION_PATTERN.matcher(bitString).matches()) {
- throw new DecodingException("Undecodable FixedInteger '" + bitString + "'");
- }
- int value = 0;
-
- for (int i = 0; i < bitString.length(); i++) {
- if (bitString.charAt(bitString.length() - (i + 1)) == '1') {
- value += 1 << i;
- }
- }
-
- return value;
- }
-}
diff --git a/iabgpp-encoder/src/main/java/com/iab/gpp/encoder/datatype/encoder/FixedIntegerListEncoder.java b/iabgpp-encoder/src/main/java/com/iab/gpp/encoder/datatype/encoder/FixedIntegerListEncoder.java
index 044dfe26..bb886ed6 100644
--- a/iabgpp-encoder/src/main/java/com/iab/gpp/encoder/datatype/encoder/FixedIntegerListEncoder.java
+++ b/iabgpp-encoder/src/main/java/com/iab/gpp/encoder/datatype/encoder/FixedIntegerListEncoder.java
@@ -1,63 +1,27 @@
package com.iab.gpp.encoder.datatype.encoder;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.regex.Pattern;
-import com.iab.gpp.encoder.error.DecodingException;
+import com.iab.gpp.encoder.bitstring.BitString;
import com.iab.gpp.encoder.error.EncodingException;
+import java.util.List;
public class FixedIntegerListEncoder {
+ private FixedIntegerListEncoder() {}
- private static Pattern BITSTRING_VERIFICATION_PATTERN = Pattern.compile("^[0-1]*$", Pattern.CASE_INSENSITIVE);
-
- public static String encode(List value, int elementBitStringLength, int numElements) {
- if(value.size() > numElements) {
+ public static void encode(
+ BitString builder, List value, int elementBitStringLength, int numElements) {
+ int length = value.size();
+ if (length > numElements) {
throw new EncodingException("Too many values '" + value.size() + "'");
}
-
- String bitString = "";
- for (int i = 0; i < value.size(); i++) {
- bitString += FixedIntegerEncoder.encode(value.get(i), elementBitStringLength);
- }
- while (bitString.length() < elementBitStringLength * numElements) {
- bitString += "0";
+ for (int i = 0; i < numElements; i++) {
+ if (i < length) {
+ builder.writeInt(value.get(i), elementBitStringLength);
+ } else {
+ for (int j = 0; j < elementBitStringLength; j++) {
+ builder.writeBoolean(false);
+ }
+ }
}
-
- return bitString;
- }
-
- public static List decode(String bitString, int elementBitStringLength, int numElements)
- throws DecodingException {
- if (!BITSTRING_VERIFICATION_PATTERN.matcher(bitString).matches()) {
- throw new DecodingException("Undecodable FixedIntegerList '" + bitString + "'");
- }
-
- if (bitString.length() > elementBitStringLength * numElements) {
- throw new DecodingException("Undecodable FixedIntegerList '" + bitString + "'");
- }
-
- if (bitString.length() % elementBitStringLength != 0) {
- throw new DecodingException("Undecodable FixedIntegerList '" + bitString + "'");
- }
-
- while (bitString.length() < elementBitStringLength * numElements) {
- bitString += "0";
- }
-
- if (bitString.length() > elementBitStringLength * numElements) {
- bitString = bitString.substring(0, elementBitStringLength * numElements);
- }
-
- List value = new ArrayList<>();
- for (int i = 0; i < bitString.length(); i += elementBitStringLength) {
- value.add(FixedIntegerEncoder.decode(bitString.substring(i, i + elementBitStringLength)));
- }
-
- while (value.size() < numElements) {
- value.add(0);
- }
-
- return value;
}
}
diff --git a/iabgpp-encoder/src/main/java/com/iab/gpp/encoder/datatype/encoder/FixedIntegerRangeEncoder.java b/iabgpp-encoder/src/main/java/com/iab/gpp/encoder/datatype/encoder/FixedIntegerRangeEncoder.java
index 335952bf..85432eca 100644
--- a/iabgpp-encoder/src/main/java/com/iab/gpp/encoder/datatype/encoder/FixedIntegerRangeEncoder.java
+++ b/iabgpp-encoder/src/main/java/com/iab/gpp/encoder/datatype/encoder/FixedIntegerRangeEncoder.java
@@ -1,89 +1,61 @@
package com.iab.gpp.encoder.datatype.encoder;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-import java.util.logging.Logger;
-import java.util.regex.Pattern;
+import com.iab.gpp.encoder.bitstring.BitString;
+import com.iab.gpp.encoder.datatype.IntegerSet;
import com.iab.gpp.encoder.error.DecodingException;
+import java.util.Collection;
public class FixedIntegerRangeEncoder {
-
- private static final Logger LOGGER = Logger.getLogger(FixedIntegerRangeEncoder.class.getName());
- // NOTE: This is a value roughly the 2x the size of this list
- // https://tools.iabtechlab.com/transparencycenter/explorer/business/gpp
- private static final int MAX_SIZE = 8192;
- private static Pattern BITSTRING_VERIFICATION_PATTERN = Pattern.compile("^[0-1]*$", Pattern.CASE_INSENSITIVE);
-
- public static String encode(List value) {
- Collections.sort(value);
-
- List> groups = new ArrayList<>();
-
- int groupStartIndex = 0;
- while (groupStartIndex < value.size()) {
- int groupEndIndex = groupStartIndex;
- while (groupEndIndex < value.size() - 1 && value.get(groupEndIndex) + 1 == value.get(groupEndIndex + 1)) {
- groupEndIndex++;
+ private FixedIntegerRangeEncoder() {}
+
+ public static int encode(BitString builder, Collection value) {
+ BitString rangeBuilder = new BitString();
+ int groupStart = -1;
+ int last = Integer.MIN_VALUE;
+ int groupCount = 0;
+ for (Integer item : value) {
+ if (last != (item - 1)) {
+ if (groupStart > 0) {
+ groupCount++;
+ writeGroup(rangeBuilder, groupStart, last);
+ }
+ groupStart = item;
}
-
- groups.add(value.subList(groupStartIndex, groupEndIndex + 1));
-
- groupStartIndex = groupEndIndex + 1;
+ last = item;
}
-
- String bitString = FixedIntegerEncoder.encode(groups.size(), 12);
- for (int i = 0; i < groups.size(); i++) {
- if (groups.get(i).size() == 1) {
- bitString += "0" + FixedIntegerEncoder.encode(groups.get(i).get(0), 16);
- } else {
- bitString += "1" + FixedIntegerEncoder.encode(groups.get(i).get(0), 16)
- + FixedIntegerEncoder.encode(groups.get(i).get(groups.get(i).size() - 1), 16);
- }
+ if (groupStart > 0) {
+ groupCount++;
+ writeGroup(rangeBuilder, groupStart, last);
}
- return bitString;
+ builder.writeInt(groupCount, 12);
+ builder.write(rangeBuilder);
+ return last;
}
- public static List decode(String bitString) throws DecodingException {
- if (!BITSTRING_VERIFICATION_PATTERN.matcher(bitString).matches() || bitString.length() < 12) {
- throw new DecodingException("Undecodable FixedIntegerRange '" + bitString + "'");
+ private static void writeGroup(BitString builder, int groupStart, int last) {
+ if (groupStart == last) {
+ builder.writeBoolean(false);
+ builder.writeInt(groupStart, 16);
+ } else {
+ builder.writeBoolean(true);
+ builder.writeInt(groupStart, 16);
+ builder.writeInt(last, 16);
}
+ }
- List value = new ArrayList<>();
- int count = FixedIntegerEncoder.decode(bitString.substring(0, 12));
- int startIndex = 12;
+ public static IntegerSet decode(BitString reader) throws DecodingException {
+ int count = reader.readInt(12);
+ IntegerSet value = new IntegerSet();
for (int i = 0; i < count; i++) {
- boolean group = BooleanEncoder.decode(bitString.substring(startIndex, startIndex + 1));
- startIndex++;
-
- if (group == true) {
- int start = FixedIntegerEncoder.decode(bitString.substring(startIndex, startIndex + 16));
- startIndex += 16;
-
- int end = FixedIntegerEncoder.decode(bitString.substring(startIndex, startIndex + 16));
- startIndex += 16;
-
- if (end < start) {
- throw new DecodingException("FixedIntegerRange has invalid range");
- }
- if (value.size() + (end - start) > MAX_SIZE) {
- LOGGER.warning("FixedIntegerRange has too many values");
- break;
- }
- for (int j = start; j <= end; j++) {
- value.add(j);
- }
+ boolean group = reader.readBoolean();
+ if (group) {
+ int start = reader.readInt(16);
+ int end = reader.readInt(16);
+ value.addRange(start, end + 1);
} else {
- int val = FixedIntegerEncoder.decode(bitString.substring(startIndex, startIndex + 16));
- if (value.size() == MAX_SIZE) {
- LOGGER.warning("FixedIntegerRange has too many values");
- break;
- }
- value.add(val);
- startIndex += 16;
+ value.addInt(reader.readInt(16));
}
}
-
return value;
}
}
diff --git a/iabgpp-encoder/src/main/java/com/iab/gpp/encoder/datatype/encoder/FixedLongEncoder.java b/iabgpp-encoder/src/main/java/com/iab/gpp/encoder/datatype/encoder/FixedLongEncoder.java
deleted file mode 100644
index c244a601..00000000
--- a/iabgpp-encoder/src/main/java/com/iab/gpp/encoder/datatype/encoder/FixedLongEncoder.java
+++ /dev/null
@@ -1,49 +0,0 @@
-package com.iab.gpp.encoder.datatype.encoder;
-
-import java.util.regex.Pattern;
-import com.iab.gpp.encoder.error.DecodingException;
-import com.iab.gpp.encoder.error.EncodingException;
-
-public class FixedLongEncoder {
-
- private static Pattern BITSTRING_VERIFICATION_PATTERN = Pattern.compile("^[0-1]*$", Pattern.CASE_INSENSITIVE);
-
- public static String encode(long value, int bitStringLength) {
- String bitString = "";
- while (value > 0) {
- if ((value & 1) == 1) {
- bitString = "1" + bitString;
- } else {
- bitString = "0" + bitString;
- }
- value = value >> 1;
- }
-
- if (bitString.length() > bitStringLength) {
- throw new EncodingException(
- "Numeric value '" + value + "' is too large for a bit string length of '" + bitStringLength + "'");
- }
-
- while (bitString.length() < bitStringLength) {
- bitString = "0" + bitString;
- }
-
- return bitString;
- }
-
- public static long decode(String bitString) throws DecodingException {
- if (!BITSTRING_VERIFICATION_PATTERN.matcher(bitString).matches()) {
- throw new DecodingException("Undecodable FixedLong '" + bitString + "'");
- }
-
- long value = 0;
-
- for (int i = 0; i < bitString.length(); i++) {
- if (bitString.charAt(bitString.length() - (i + 1)) == '1') {
- value += 1L << i;
- }
- }
-
- return value;
- }
-}
diff --git a/iabgpp-encoder/src/main/java/com/iab/gpp/encoder/datatype/encoder/FixedStringEncoder.java b/iabgpp-encoder/src/main/java/com/iab/gpp/encoder/datatype/encoder/FixedStringEncoder.java
index 38db681e..ea2d970c 100644
--- a/iabgpp-encoder/src/main/java/com/iab/gpp/encoder/datatype/encoder/FixedStringEncoder.java
+++ b/iabgpp-encoder/src/main/java/com/iab/gpp/encoder/datatype/encoder/FixedStringEncoder.java
@@ -1,50 +1,41 @@
package com.iab.gpp.encoder.datatype.encoder;
-import java.util.regex.Pattern;
-import com.iab.gpp.encoder.error.DecodingException;
+import com.iab.gpp.encoder.bitstring.BitString;
import com.iab.gpp.encoder.error.EncodingException;
public class FixedStringEncoder {
+ private FixedStringEncoder() {}
- private static Pattern BITSTRING_VERIFICATION_PATTERN = Pattern.compile("^[0-1]*$", Pattern.CASE_INSENSITIVE);
+ private static final char SPACE = ' ';
- public static String encode(String value, int stringLength) {
- while (value.length() < stringLength) {
- value += " ";
- }
-
- String bitString = "";
- for (int i = 0; i < value.length(); i++) {
- int code = (int) value.charAt(i);
- if (code == 32) {
- // space
- bitString += FixedIntegerEncoder.encode(63, 6);
+ public static void encode(BitString builder, String value, int stringLength) {
+ int length = value.length();
+ for (int i = 0; i < stringLength; i++) {
+ int code = SPACE;
+ if (i < length) {
+ code = value.charAt(i);
+ }
+ if (code == SPACE) {
+ builder.writeInt(63, 6);
} else if (code >= 65) {
- bitString += FixedIntegerEncoder.encode(((int) value.charAt(i)) - 65, 6);
+ builder.writeInt(((int) value.charAt(i)) - 65, 6);
} else {
throw new EncodingException("Unencodable FixedString '" + value + "'");
}
}
-
- return bitString;
}
- public static String decode(String bitString) {
- if (!BITSTRING_VERIFICATION_PATTERN.matcher(bitString).matches() || bitString.length() % 6 != 0) {
- throw new DecodingException("Undecodable FixedString '" + bitString + "'");
- }
-
- String value = "";
-
- for (int i = 0; i < bitString.length(); i += 6) {
- int code = FixedIntegerEncoder.decode(bitString.substring(i, i + 6));
+ public static String decode(BitString reader, int length) {
+ StringBuilder value = new StringBuilder(length);
+ for (int i = 0; i < length; i++) {
+ int code = reader.readInt(6);
if (code == 63) {
- value += " ";
+ value.append(SPACE);
} else {
- value += (char) (code + 65);
+ value.append((char) (code + 65));
}
}
- return value.trim();
+ return value.toString().trim();
}
}
diff --git a/iabgpp-encoder/src/main/java/com/iab/gpp/encoder/datatype/encoder/OptimizedFibonacciRangeEncoder.java b/iabgpp-encoder/src/main/java/com/iab/gpp/encoder/datatype/encoder/OptimizedFibonacciRangeEncoder.java
deleted file mode 100644
index 3492fbcc..00000000
--- a/iabgpp-encoder/src/main/java/com/iab/gpp/encoder/datatype/encoder/OptimizedFibonacciRangeEncoder.java
+++ /dev/null
@@ -1,56 +0,0 @@
-package com.iab.gpp.encoder.datatype.encoder;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.regex.Pattern;
-import com.iab.gpp.encoder.error.DecodingException;
-import com.iab.gpp.encoder.error.EncodingException;
-
-public class OptimizedFibonacciRangeEncoder {
-
- private static Pattern BITSTRING_VERIFICATION_PATTERN = Pattern.compile("^[0-1]*$", Pattern.CASE_INSENSITIVE);
-
- public static String encode(List value) throws EncodingException {
- // TODO: encoding the range before choosing the shortest is inefficient. There is probably a way
- // to identify in advance which will be shorter based on the array length and values
- int max = value.size() > 0 ? value.get(value.size() - 1) : 0;
- String rangeBitString = FibonacciIntegerRangeEncoder.encode(value);
- int rangeLength = rangeBitString.length();
- int bitFieldLength = max;
-
- if (rangeLength <= bitFieldLength) {
- return FixedIntegerEncoder.encode(max, 16) + "1" + rangeBitString;
- } else {
- List bits = new ArrayList<>();
- int index = 0;
- for (int i = 0; i < max; i++) {
- if (i == value.get(index) - 1) {
- bits.add(true);
- index++;
- } else {
- bits.add(false);
- }
- }
- return FixedIntegerEncoder.encode(max, 16) + "0" + FixedBitfieldEncoder.encode(bits, bitFieldLength);
- }
- }
-
- public static List decode(String bitString) throws DecodingException {
- if (!BITSTRING_VERIFICATION_PATTERN.matcher(bitString).matches() || bitString.length() < 12) {
- throw new DecodingException("Undecodable FibonacciIntegerRange '" + bitString + "'");
- }
-
- if (bitString.charAt(16) == '1') {
- return FibonacciIntegerRangeEncoder.decode(bitString.substring(17));
- } else {
- List value = new ArrayList<>();
- List bits = FixedBitfieldEncoder.decode(bitString.substring(17));
- for (int i = 0; i < bits.size(); i++) {
- if (bits.get(i) == true) {
- value.add(i + 1);
- }
- }
- return value;
- }
- }
-}
diff --git a/iabgpp-encoder/src/main/java/com/iab/gpp/encoder/datatype/encoder/OptimizedFixedRangeEncoder.java b/iabgpp-encoder/src/main/java/com/iab/gpp/encoder/datatype/encoder/OptimizedFixedRangeEncoder.java
index 73fb5e68..a7656e0c 100644
--- a/iabgpp-encoder/src/main/java/com/iab/gpp/encoder/datatype/encoder/OptimizedFixedRangeEncoder.java
+++ b/iabgpp-encoder/src/main/java/com/iab/gpp/encoder/datatype/encoder/OptimizedFixedRangeEncoder.java
@@ -1,57 +1,39 @@
package com.iab.gpp.encoder.datatype.encoder;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.regex.Pattern;
+import com.iab.gpp.encoder.bitstring.BitString;
+import com.iab.gpp.encoder.datatype.IntegerSet;
import com.iab.gpp.encoder.error.DecodingException;
import com.iab.gpp.encoder.error.EncodingException;
public class OptimizedFixedRangeEncoder {
- private static Pattern BITSTRING_VERIFICATION_PATTERN = Pattern.compile("^[0-1]*$", Pattern.CASE_INSENSITIVE);
-
- public static String encode(List value) throws EncodingException {
+ public static void encode(BitString builder, IntegerSet value) throws EncodingException {
// TODO: encoding the range before choosing the shortest is inefficient. There is probably a way
// to identify in advance which will be shorter based on the array length and values
- int max = value.size() > 0 ? value.get(value.size() - 1) : 0;
- String rangeBitString = FixedIntegerRangeEncoder.encode(value);
+ BitString rangeBitString = new BitString();
+ int max = FixedIntegerRangeEncoder.encode(rangeBitString, value);
int rangeLength = rangeBitString.length();
int bitFieldLength = max;
if (rangeLength <= bitFieldLength) {
- return FixedIntegerEncoder.encode(max, 16) + "1" + rangeBitString;
+ builder.writeInt(max, 16);
+ builder.writeBoolean(true);
+ builder.write(rangeBitString);
} else {
- List bits = new ArrayList<>();
- int index = 0;
+ builder.writeInt(max, 16);
+ builder.writeBoolean(false);
for (int i = 0; i < max; i++) {
- if (i == value.get(index) - 1) {
- bits.add(true);
- index++;
- } else {
- bits.add(false);
- }
+ builder.writeBoolean(value.containsInt(i + 1));
}
-
- return FixedIntegerEncoder.encode(max, 16) + "0" + FixedBitfieldEncoder.encode(bits, bitFieldLength);
}
}
- public static List decode(String bitString) throws DecodingException {
- if (!BITSTRING_VERIFICATION_PATTERN.matcher(bitString).matches() || bitString.length() < 12) {
- throw new DecodingException("Undecodable FixedIntegerRange '" + bitString + "'");
- }
-
- if (bitString.charAt(16) == '1') {
- return FixedIntegerRangeEncoder.decode(bitString.substring(17));
+ public static IntegerSet decode(BitString reader) throws DecodingException {
+ int size = reader.readInt(16);
+ if (reader.readBoolean()) {
+ return FixedIntegerRangeEncoder.decode(reader);
} else {
- List value = new ArrayList<>();
- List bits = FixedBitfieldEncoder.decode(bitString.substring(17));
- for (int i = 0; i < bits.size(); i++) {
- if (bits.get(i) == true) {
- value.add(i + 1);
- }
- }
- return value;
+ return reader.readIntegerSet(size);
}
}
}
diff --git a/iabgpp-encoder/src/main/java/com/iab/gpp/encoder/field/EncodableBitStringFields.java b/iabgpp-encoder/src/main/java/com/iab/gpp/encoder/field/EncodableBitStringFields.java
deleted file mode 100644
index d2ac0f43..00000000
--- a/iabgpp-encoder/src/main/java/com/iab/gpp/encoder/field/EncodableBitStringFields.java
+++ /dev/null
@@ -1,31 +0,0 @@
-package com.iab.gpp.encoder.field;
-
-import java.util.HashMap;
-import java.util.Map;
-import com.iab.gpp.encoder.datatype.AbstractEncodableBitStringDataType;
-
-public class EncodableBitStringFields implements Fields> {
-
- private Map> fields = new HashMap<>();
-
- public boolean containsKey(String key) {
- return this.fields.containsKey(key);
- }
-
- public void put(String key, AbstractEncodableBitStringDataType> value) {
- this.fields.put(key, value);
- }
-
- public AbstractEncodableBitStringDataType> get(String key) {
- return this.fields.get(key);
- }
-
- public Map> getAll() {
- return new HashMap<>(this.fields);
- }
-
- public void reset(Fields> fields) {
- this.fields.clear();
- this.fields.putAll(fields.getAll());
- }
-}
diff --git a/iabgpp-encoder/src/main/java/com/iab/gpp/encoder/field/FieldKey.java b/iabgpp-encoder/src/main/java/com/iab/gpp/encoder/field/FieldKey.java
new file mode 100644
index 00000000..291be775
--- /dev/null
+++ b/iabgpp-encoder/src/main/java/com/iab/gpp/encoder/field/FieldKey.java
@@ -0,0 +1,23 @@
+package com.iab.gpp.encoder.field;
+
+import com.iab.gpp.encoder.datatype.DataType;
+import com.iab.gpp.encoder.datatype.FixedIntegerList;
+import java.util.function.Predicate;
+
+public interface FieldKey {
+ DataType, ?> getType();
+
+ public static final Predicate VALIDATOR_012 = (n -> n >= 0 && n <= 2);
+ public static final Predicate VALIDATOR_12 = (n -> n >= 1 && n <= 2);
+ public static final Predicate VALIDATOR_LIST_012 =
+ (l -> {
+ int size = l.size();
+ for (int i = 0; i < size; i++) {
+ int n = l.getInt(i);
+ if (n < 0 || n > 2) {
+ return false;
+ }
+ }
+ return true;
+ });
+}
diff --git a/iabgpp-encoder/src/main/java/com/iab/gpp/encoder/field/FieldNames.java b/iabgpp-encoder/src/main/java/com/iab/gpp/encoder/field/FieldNames.java
new file mode 100644
index 00000000..249c411d
--- /dev/null
+++ b/iabgpp-encoder/src/main/java/com/iab/gpp/encoder/field/FieldNames.java
@@ -0,0 +1,50 @@
+package com.iab.gpp.encoder.field;
+
+import com.iab.gpp.encoder.datatype.DataType;
+import java.util.Arrays;
+import java.util.LinkedHashMap;
+
+public final class FieldNames & FieldKey> {
+
+ private final LinkedHashMap map;
+ private final Integer[] indices;
+ private final DataType, ?>[] types;
+
+ @SafeVarargs
+ FieldNames(E... keys) {
+ this.map = new LinkedHashMap<>();
+ this.indices = new Integer[keys[0].getClass().getEnumConstants().length];
+ this.types = new DataType[keys.length];
+ for (int i = 0; i < keys.length; i++) {
+ E key = keys[i];
+ this.map.put(key, key);
+ this.indices[key.ordinal()] = i;
+ this.types[i] = key.getType();
+ }
+ }
+
+ public int size() {
+ return types.length;
+ }
+
+ @SuppressWarnings("unchecked")
+ public DataType get(int i) {
+ return (DataType) types[i];
+ }
+
+ public Integer getIndex(E key) {
+ if (key == null) {
+ return null;
+ }
+ return indices[key.ordinal()];
+ }
+
+ public E resolveKey(FieldKey key) {
+ return map.get(key);
+ }
+
+ @Override
+ public String toString() {
+ return Arrays.toString(types);
+ }
+}
diff --git a/iabgpp-encoder/src/main/java/com/iab/gpp/encoder/field/Fields.java b/iabgpp-encoder/src/main/java/com/iab/gpp/encoder/field/Fields.java
deleted file mode 100644
index ca091e7f..00000000
--- a/iabgpp-encoder/src/main/java/com/iab/gpp/encoder/field/Fields.java
+++ /dev/null
@@ -1,14 +0,0 @@
-package com.iab.gpp.encoder.field;
-
-import java.util.Map;
-import com.iab.gpp.encoder.datatype.DataType;
-
-public interface Fields> {
-
- boolean containsKey(String key);
- void put(String key, T value);
- T get(String key);
- Map getAll();
- void reset(Fields fields);
-
-}
diff --git a/iabgpp-encoder/src/main/java/com/iab/gpp/encoder/field/GenericFields.java b/iabgpp-encoder/src/main/java/com/iab/gpp/encoder/field/GenericFields.java
deleted file mode 100644
index 00263e55..00000000
--- a/iabgpp-encoder/src/main/java/com/iab/gpp/encoder/field/GenericFields.java
+++ /dev/null
@@ -1,31 +0,0 @@
-package com.iab.gpp.encoder.field;
-
-import java.util.HashMap;
-import java.util.Map;
-import com.iab.gpp.encoder.datatype.DataType;
-
-public class GenericFields implements Fields> {
-
- private Map> fields = new HashMap<>();
-
- public boolean containsKey(String key) {
- return this.fields.containsKey(key);
- }
-
- public void put(String key, DataType> value) {
- this.fields.put(key, value);
- }
-
- public DataType> get(String key) {
- return this.fields.get(key);
- }
-
- public Map> getAll() {
- return new HashMap<>(this.fields);
- }
-
- public void reset(Fields> fields) {
- this.fields.clear();
- this.fields.putAll(fields.getAll());
- }
-}
diff --git a/iabgpp-encoder/src/main/java/com/iab/gpp/encoder/field/HeaderV1Field.java b/iabgpp-encoder/src/main/java/com/iab/gpp/encoder/field/HeaderV1Field.java
index 17f1f5d9..ebe11eba 100644
--- a/iabgpp-encoder/src/main/java/com/iab/gpp/encoder/field/HeaderV1Field.java
+++ b/iabgpp-encoder/src/main/java/com/iab/gpp/encoder/field/HeaderV1Field.java
@@ -1,21 +1,26 @@
package com.iab.gpp.encoder.field;
-import java.util.Arrays;
-import java.util.List;
+import com.iab.gpp.encoder.datatype.DataType;
+import com.iab.gpp.encoder.datatype.EncodableFibonacciIntegerRange;
+import com.iab.gpp.encoder.datatype.EncodableFixedInteger;
+import com.iab.gpp.encoder.section.HeaderV1;
-public class HeaderV1Field {
+public enum HeaderV1Field implements FieldKey {
+ ID(new EncodableFixedInteger<>("Id", 6, HeaderV1.ID)),
+ VERSION(new EncodableFixedInteger<>("Version", 6, HeaderV1.VERSION)),
+ SECTION_IDS(new EncodableFibonacciIntegerRange<>("SectionIds"));
- public static String ID = "Id";
- public static String VERSION = "Version";
- public static String SECTION_IDS = "SectionIds";
+ private final DataType type;
- //@formatter:off
- public static List HEADER_CORE_SEGMENT_FIELD_NAMES = Arrays.asList(new String[] {
- HeaderV1Field.ID,
- HeaderV1Field.VERSION,
- HeaderV1Field.SECTION_IDS
- });
- //@formatter:on
+ HeaderV1Field(DataType type) {
+ this.type = type;
+ }
+ @Override
+ public DataType getType() {
+ return type;
+ }
+ public static final FieldNames HEADER_CORE_SEGMENT_FIELD_NAMES =
+ new FieldNames<>(HeaderV1Field.ID, HeaderV1Field.VERSION, HeaderV1Field.SECTION_IDS);
}
diff --git a/iabgpp-encoder/src/main/java/com/iab/gpp/encoder/field/TcfCaV1Field.java b/iabgpp-encoder/src/main/java/com/iab/gpp/encoder/field/TcfCaV1Field.java
index c87e886d..6f264b7d 100644
--- a/iabgpp-encoder/src/main/java/com/iab/gpp/encoder/field/TcfCaV1Field.java
+++ b/iabgpp-encoder/src/main/java/com/iab/gpp/encoder/field/TcfCaV1Field.java
@@ -1,73 +1,87 @@
package com.iab.gpp.encoder.field;
-import java.util.Arrays;
-import java.util.List;
+import com.iab.gpp.encoder.datatype.DataType;
+import com.iab.gpp.encoder.datatype.EncodableArrayOfFixedIntegerRanges;
+import com.iab.gpp.encoder.datatype.EncodableBoolean;
+import com.iab.gpp.encoder.datatype.EncodableDatetime;
+import com.iab.gpp.encoder.datatype.EncodableFixedBitfield;
+import com.iab.gpp.encoder.datatype.EncodableFixedInteger;
+import com.iab.gpp.encoder.datatype.EncodableFixedString;
+import com.iab.gpp.encoder.datatype.EncodableFlexibleBitfield;
+import com.iab.gpp.encoder.datatype.EncodableOptimizedFixedRange;
+import com.iab.gpp.encoder.section.TcfCaV1;
-public class TcfCaV1Field {
+public enum TcfCaV1Field implements FieldKey {
+ VERSION(new EncodableFixedInteger<>("Version", 6, TcfCaV1.VERSION)),
+ CREATED(new EncodableDatetime<>("Created")),
+ LAST_UPDATED(new EncodableDatetime<>("LastUpdated")),
+ CMP_ID(new EncodableFixedInteger<>("CmpId", 12, 0)),
+ CMP_VERSION(new EncodableFixedInteger<>("CmpVersion", 12, 0)),
+ CONSENT_SCREEN(new EncodableFixedInteger<>("ConsentScreen", 6, 0)),
+ CONSENT_LANGUAGE(new EncodableFixedString<>("ConsentLanguage", 2, "EN")),
+ VENDOR_LIST_VERSION(new EncodableFixedInteger<>("VendorListVersion", 12, 0)),
+ TCF_POLICY_VERSION(new EncodableFixedInteger<>("TcfPolicyVersion", 6, 2)),
+ USE_NON_STANDARD_STACKS(new EncodableBoolean<>("UseNonStandardStacks", false)),
+ SPECIAL_FEATURE_EXPRESS_CONSENT(new EncodableFixedBitfield<>("SpecialFeatureExpressConsent", 12)),
+ PURPOSES_EXPRESS_CONSENT(new EncodableFixedBitfield<>("PurposesExpressConsent", 24)),
+ PURPOSES_IMPLIED_CONSENT(new EncodableFixedBitfield<>("PurposesImpliedConsent", 24)),
+ VENDOR_EXPRESS_CONSENT(new EncodableOptimizedFixedRange<>("VendorExpressConsent")),
+ VENDOR_IMPLIED_CONSENT(new EncodableOptimizedFixedRange<>("VendorImpliedConsent")),
+ PUB_RESTRICTIONS(new EncodableArrayOfFixedIntegerRanges<>("PubRestrictions", 6, 2)),
- public static String VERSION = "Version";
- public static String CREATED = "Created";
- public static String LAST_UPDATED = "LastUpdated";
- public static String CMP_ID = "CmpId";
- public static String CMP_VERSION = "CmpVersion";
- public static String CONSENT_SCREEN = "ConsentScreen";
- public static String CONSENT_LANGUAGE = "ConsentLanguage";
- public static String VENDOR_LIST_VERSION = "VendorListVersion";
- public static String TCF_POLICY_VERSION = "TcfPolicyVersion";
- public static String USE_NON_STANDARD_STACKS = "UseNonStandardStacks";
- public static String SPECIAL_FEATURE_EXPRESS_CONSENT = "SpecialFeatureExpressConsent";
- public static String PURPOSES_EXPRESS_CONSENT = "PurposesExpressConsent";
- public static String PURPOSES_IMPLIED_CONSENT = "PurposesImpliedConsent";
- public static String VENDOR_EXPRESS_CONSENT = "VendorExpressConsent";
- public static String VENDOR_IMPLIED_CONSENT = "VendorImpliedConsent";
- public static String PUB_RESTRICTIONS = "PubRestrictions";
+ PUB_PURPOSES_SEGMENT_TYPE(new EncodableFixedInteger<>("PubPurposesSegmentType", 3, 3)),
+ PUB_PURPOSES_EXPRESS_CONSENT(new EncodableFixedBitfield<>("PubPurposesExpressConsent", 24)),
+ PUB_PURPOSES_IMPLIED_CONSENT(new EncodableFixedBitfield<>("PubPurposesImpliedConsent", 24)),
+ NUM_CUSTOM_PURPOSES(new EncodableFixedInteger<>("NumCustomPurposes", 6, 0)),
+ CUSTOM_PURPOSES_EXPRESS_CONSENT(
+ new EncodableFlexibleBitfield<>(
+ "CustomPurposesExpressConsent", TcfCaV1Field.NUM_CUSTOM_PURPOSES)),
+ CUSTOM_PURPOSES_IMPLIED_CONSENT(
+ new EncodableFlexibleBitfield<>(
+ "CustomPurposesImpliedConsent", TcfCaV1Field.NUM_CUSTOM_PURPOSES)),
- public static String PUB_PURPOSES_SEGMENT_TYPE = "PubPurposesSegmentType";
- public static String PUB_PURPOSES_EXPRESS_CONSENT = "PubPurposesExpressConsent";
- public static String PUB_PURPOSES_IMPLIED_CONSENT = "PubPurposesImpliedConsent";
- public static String NUM_CUSTOM_PURPOSES = "NumCustomPurposes";
- public static String CUSTOM_PURPOSES_EXPRESS_CONSENT = "CustomPurposesExpressConsent";
- public static String CUSTOM_PURPOSES_IMPLIED_CONSENT = "CustomPurposesImpliedConsent";
+ DISCLOSED_VENDORS_SEGMENT_TYPE(new EncodableFixedInteger<>("DisclosedVendorsSegmentType", 3, 1)),
+ DISCLOSED_VENDORS(new EncodableOptimizedFixedRange<>("DisclosedVendors"));
- public static String DISCLOSED_VENDORS_SEGMENT_TYPE = "DisclosedVendorsSegmentType";
- public static String DISCLOSED_VENDORS = "DisclosedVendors";
-
- //@formatter:off
- public static List TCFCAV1_CORE_SEGMENT_FIELD_NAMES = Arrays.asList(new String[] {
- TcfCaV1Field.VERSION,
- TcfCaV1Field.CREATED,
- TcfCaV1Field.LAST_UPDATED,
- TcfCaV1Field.CMP_ID,
- TcfCaV1Field.CMP_VERSION,
- TcfCaV1Field.CONSENT_SCREEN,
- TcfCaV1Field.CONSENT_LANGUAGE,
- TcfCaV1Field.VENDOR_LIST_VERSION,
- TcfCaV1Field.TCF_POLICY_VERSION,
- TcfCaV1Field.USE_NON_STANDARD_STACKS,
- TcfCaV1Field.SPECIAL_FEATURE_EXPRESS_CONSENT,
- TcfCaV1Field.PURPOSES_EXPRESS_CONSENT,
- TcfCaV1Field.PURPOSES_IMPLIED_CONSENT,
- TcfCaV1Field.VENDOR_EXPRESS_CONSENT,
- TcfCaV1Field.VENDOR_IMPLIED_CONSENT,
- TcfCaV1Field.PUB_RESTRICTIONS
- });
- //@formatter:on
+ private final DataType type;
- //@formatter:off
- public static List TCFCAV1_PUBLISHER_PURPOSES_SEGMENT_FIELD_NAMES = Arrays.asList(new String[] {
- TcfCaV1Field.PUB_PURPOSES_SEGMENT_TYPE,
- TcfCaV1Field.PUB_PURPOSES_EXPRESS_CONSENT,
- TcfCaV1Field.PUB_PURPOSES_IMPLIED_CONSENT,
- TcfCaV1Field.NUM_CUSTOM_PURPOSES,
- TcfCaV1Field.CUSTOM_PURPOSES_EXPRESS_CONSENT,
- TcfCaV1Field.CUSTOM_PURPOSES_IMPLIED_CONSENT,
- });
- //@formatter:on
-
- //@formatter:off
- public static List TCFCAV1_DISCLOSED_VENDORS_SEGMENT_FIELD_NAMES = Arrays.asList(new String[] {
- TcfCaV1Field.DISCLOSED_VENDORS_SEGMENT_TYPE,
- TcfCaV1Field.DISCLOSED_VENDORS,
- });
- //@formatter:on
+ TcfCaV1Field(DataType type) {
+ this.type = type;
+ }
+
+ @Override
+ public DataType getType() {
+ return type;
+ }
+
+ public static final FieldNames TCFCAV1_CORE_SEGMENT_FIELD_NAMES =
+ new FieldNames<>(
+ TcfCaV1Field.VERSION,
+ TcfCaV1Field.CREATED,
+ TcfCaV1Field.LAST_UPDATED,
+ TcfCaV1Field.CMP_ID,
+ TcfCaV1Field.CMP_VERSION,
+ TcfCaV1Field.CONSENT_SCREEN,
+ TcfCaV1Field.CONSENT_LANGUAGE,
+ TcfCaV1Field.VENDOR_LIST_VERSION,
+ TcfCaV1Field.TCF_POLICY_VERSION,
+ TcfCaV1Field.USE_NON_STANDARD_STACKS,
+ TcfCaV1Field.SPECIAL_FEATURE_EXPRESS_CONSENT,
+ TcfCaV1Field.PURPOSES_EXPRESS_CONSENT,
+ TcfCaV1Field.PURPOSES_IMPLIED_CONSENT,
+ TcfCaV1Field.VENDOR_EXPRESS_CONSENT,
+ TcfCaV1Field.VENDOR_IMPLIED_CONSENT,
+ TcfCaV1Field.PUB_RESTRICTIONS);
+
+ public static final FieldNames TCFCAV1_PUBLISHER_PURPOSES_SEGMENT_FIELD_NAMES =
+ new FieldNames<>(
+ TcfCaV1Field.PUB_PURPOSES_SEGMENT_TYPE,
+ TcfCaV1Field.PUB_PURPOSES_EXPRESS_CONSENT,
+ TcfCaV1Field.PUB_PURPOSES_IMPLIED_CONSENT,
+ TcfCaV1Field.NUM_CUSTOM_PURPOSES,
+ TcfCaV1Field.CUSTOM_PURPOSES_EXPRESS_CONSENT,
+ TcfCaV1Field.CUSTOM_PURPOSES_IMPLIED_CONSENT);
+
+ public static final FieldNames TCFCAV1_DISCLOSED_VENDORS_SEGMENT_FIELD_NAMES =
+ new FieldNames<>(TcfCaV1Field.DISCLOSED_VENDORS_SEGMENT_TYPE, TcfCaV1Field.DISCLOSED_VENDORS);
}
diff --git a/iabgpp-encoder/src/main/java/com/iab/gpp/encoder/field/TcfEuV2Field.java b/iabgpp-encoder/src/main/java/com/iab/gpp/encoder/field/TcfEuV2Field.java
index fedb510f..85abfca1 100644
--- a/iabgpp-encoder/src/main/java/com/iab/gpp/encoder/field/TcfEuV2Field.java
+++ b/iabgpp-encoder/src/main/java/com/iab/gpp/encoder/field/TcfEuV2Field.java
@@ -1,86 +1,96 @@
package com.iab.gpp.encoder.field;
-import java.util.Arrays;
-import java.util.List;
+import com.iab.gpp.encoder.datatype.DataType;
+import com.iab.gpp.encoder.datatype.EncodableArrayOfFixedIntegerRanges;
+import com.iab.gpp.encoder.datatype.EncodableBoolean;
+import com.iab.gpp.encoder.datatype.EncodableDatetime;
+import com.iab.gpp.encoder.datatype.EncodableFixedBitfield;
+import com.iab.gpp.encoder.datatype.EncodableFixedInteger;
+import com.iab.gpp.encoder.datatype.EncodableFixedString;
+import com.iab.gpp.encoder.datatype.EncodableFlexibleBitfield;
+import com.iab.gpp.encoder.datatype.EncodableOptimizedFixedRange;
+import com.iab.gpp.encoder.section.TcfEuV2;
-public class TcfEuV2Field {
+public enum TcfEuV2Field implements FieldKey {
+ VERSION(new EncodableFixedInteger<>("Version", 6, TcfEuV2.VERSION)),
+ CREATED(new EncodableDatetime<>("Created")),
+ LAST_UPDATED(new EncodableDatetime<>("LastUpdated")),
+ CMP_ID(new EncodableFixedInteger<>("CmpId", 12, 0)),
+ CMP_VERSION(new EncodableFixedInteger<>("CmpVersion", 12, 0)),
+ CONSENT_SCREEN(new EncodableFixedInteger<>("ConsentScreen", 6, 0)),
+ CONSENT_LANGUAGE(new EncodableFixedString<>("ConsentLanguage", 2, "EN")),
+ VENDOR_LIST_VERSION(new EncodableFixedInteger<>("VendorListVersion", 12, 0)),
+ POLICY_VERSION(new EncodableFixedInteger<>("PolicyVersion", 6, 5)),
+ IS_SERVICE_SPECIFIC(new EncodableBoolean<>("IsServiceSpecific", true)),
+ USE_NON_STANDARD_STACKS(new EncodableBoolean<>("UseNonStandardStacks", false)),
+ SPECIAL_FEATURE_OPTINS(new EncodableFixedBitfield<>("SpecialFeatureOptins", 12)),
+ PURPOSE_CONSENTS(new EncodableFixedBitfield<>("PurposeConsents", 24)),
+ PURPOSE_LEGITIMATE_INTERESTS(new EncodableFixedBitfield<>("PurposeLegitimateInterests", 24)),
+ PURPOSE_ONE_TREATMENT(new EncodableBoolean<>("PurposeOneTreatment", false)),
+ PUBLISHER_COUNTRY_CODE(new EncodableFixedString<>("PublisherCountryCode", 2, "AA")),
+ VENDOR_CONSENTS(new EncodableOptimizedFixedRange<>("VendorConsents")),
+ VENDOR_LEGITIMATE_INTERESTS(new EncodableOptimizedFixedRange<>("VendorLegitimateInterests")),
+ PUBLISHER_RESTRICTIONS(new EncodableArrayOfFixedIntegerRanges<>("PublisherRestrictions", 6, 2)),
+ PUBLISHER_PURPOSES_SEGMENT_TYPE(
+ new EncodableFixedInteger<>("PublisherPurposesSegmentType", 3, 3)),
+ PUBLISHER_CONSENTS(new EncodableFixedBitfield<>("PublisherConsents", 24)),
+ PUBLISHER_LEGITIMATE_INTERESTS(new EncodableFixedBitfield<>("PublisherLegitimateInterests", 24)),
+ NUM_CUSTOM_PURPOSES(new EncodableFixedInteger<>("NumCustomPurposes", 6, 0)),
+ PUBLISHER_CUSTOM_CONSENTS(
+ new EncodableFlexibleBitfield<>("PublisherCustomConsents", TcfEuV2Field.NUM_CUSTOM_PURPOSES)),
+ PUBLISHER_CUSTOM_LEGITIMATE_INTERESTS(
+ new EncodableFlexibleBitfield<>(
+ "PublisherCustomLegitimateInterests", TcfEuV2Field.NUM_CUSTOM_PURPOSES)),
+ VENDORS_ALLOWED_SEGMENT_TYPE(new EncodableFixedInteger<>("VendorsAllowedSegmentType", 3, 2)),
+ VENDORS_ALLOWED(new EncodableOptimizedFixedRange<>("VendorsAllowed")),
+ VENDORS_DISCLOSED_SEGMENT_TYPE(new EncodableFixedInteger<>("VendorsDisclosedSegmentType", 3, 1)),
+ VENDORS_DISCLOSED(new EncodableOptimizedFixedRange<>("VendorsDisclosed"));
- public static String VERSION = "Version";
- public static String CREATED = "Created";
- public static String LAST_UPDATED = "LastUpdated";
- public static String CMP_ID = "CmpId";
- public static String CMP_VERSION = "CmpVersion";
- public static String CONSENT_SCREEN = "ConsentScreen";
- public static String CONSENT_LANGUAGE = "ConsentLanguage";
- public static String VENDOR_LIST_VERSION = "VendorListVersion";
- public static String POLICY_VERSION = "PolicyVersion";
- public static String IS_SERVICE_SPECIFIC = "IsServiceSpecific";
- public static String USE_NON_STANDARD_STACKS = "UseNonStandardStacks";
- public static String SPECIAL_FEATURE_OPTINS = "SpecialFeatureOptins";
- public static String PURPOSE_CONSENTS = "PurposeConsents";
- public static String PURPOSE_LEGITIMATE_INTERESTS = "PurposeLegitimateInterests";
- public static String PURPOSE_ONE_TREATMENT = "PurposeOneTreatment";
- public static String PUBLISHER_COUNTRY_CODE = "PublisherCountryCode";
- public static String VENDOR_CONSENTS = "VendorConsents";
- public static String VENDOR_LEGITIMATE_INTERESTS = "VendorLegitimateInterests";
- public static String PUBLISHER_RESTRICTIONS = "PublisherRestrictions";
- public static String PUBLISHER_PURPOSES_SEGMENT_TYPE = "PublisherPurposesSegmentType";
- public static String PUBLISHER_CONSENTS = "PublisherConsents";
- public static String PUBLISHER_LEGITIMATE_INTERESTS = "PublisherLegitimateInterests";
- public static String NUM_CUSTOM_PURPOSES = "NumCustomPurposes";
- public static String PUBLISHER_CUSTOM_CONSENTS = "PublisherCustomConsents";
- public static String PUBLISHER_CUSTOM_LEGITIMATE_INTERESTS = "PublisherCustomLegitimateInterests";
- public static String VENDORS_ALLOWED_SEGMENT_TYPE = "VendorsAllowedSegmentType";
- public static String VENDORS_ALLOWED = "VendorsAllowed";
- public static String VENDORS_DISCLOSED_SEGMENT_TYPE = "VendorsDisclosedSegmentType";
- public static String VENDORS_DISCLOSED = "VendorsDisclosed";
+ private final DataType