├── NOTICE.txt ├── src ├── site │ ├── resources │ │ ├── download.html │ │ └── css │ │ │ └── site.css │ ├── markdown │ │ ├── enterprise.md │ │ └── index.md │ └── site.xml ├── main │ ├── assembly │ │ └── dist.xml │ ├── java │ │ ├── org │ │ │ └── joda │ │ │ │ └── collect │ │ │ │ └── grid │ │ │ │ ├── package-info.java │ │ │ │ ├── MutableCell.java │ │ │ │ ├── AbstractCell.java │ │ │ │ ├── EmptyGrid.java │ │ │ │ ├── SingletonGrid.java │ │ │ │ ├── ImmutableCell.java │ │ │ │ ├── SparseImmutableGrid.java │ │ │ │ ├── SparseGrid.java │ │ │ │ ├── AbstractGrid.java │ │ │ │ ├── ImmutableGrid.java │ │ │ │ ├── DenseImmutableGrid.java │ │ │ │ ├── Grid.java │ │ │ │ └── DenseGrid.java │ │ └── module-info.java │ └── checkstyle │ │ └── checkstyle.xml ├── test │ └── java │ │ └── org │ │ └── joda │ │ └── collect │ │ └── grid │ │ ├── TestSparseGrid.java │ │ ├── MockCell.java │ │ ├── MockSingletonGrid.java │ │ ├── AbstractTestImmutableGrid.java │ │ ├── TestDenseGrid.java │ │ ├── TestDenseImmutableGrid.java │ │ ├── TestSparseImmutableGrid.java │ │ ├── TestImmutableCell.java │ │ ├── TestEmptyGrid.java │ │ ├── TestSingletonGrid.java │ │ ├── TestImmutableGrid.java │ │ ├── AbstractTestGrid.java │ │ └── AbstractTestMutableGrid.java └── changes │ └── changes.xml ├── .gitignore ├── .github ├── dependabot.yml ├── FUNDING.yml ├── SECURITY.md ├── maven-settings.xml └── workflows │ ├── build.yml │ ├── release.yml │ └── website.yml ├── RELEASE-NOTES.txt ├── README.md └── LICENSE.txt /NOTICE.txt: -------------------------------------------------------------------------------- 1 | Joda Collect 2 | Copyright 2014-present Stephen Colebourne 3 | 4 | This product includes software developed by 5 | Joda.org (https://www.joda.org/). 6 | -------------------------------------------------------------------------------- /src/site/resources/download.html: -------------------------------------------------------------------------------- 1 | 2 | OpenGamma 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /bin/ 2 | /target/ 3 | *.log 4 | /tests/ 5 | /test-output/ 6 | .checkstyle 7 | .classpath 8 | .project 9 | /.settings/ 10 | .idea 11 | *.iml 12 | /pom.xml.releaseBackup 13 | /release.properties 14 | *.DS_Store 15 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # Dependabot config 2 | 3 | version: 2 4 | updates: 5 | - package-ecosystem: "maven" 6 | directory: "/" 7 | schedule: 8 | interval: weekly 9 | time: "02:30" 10 | open-pull-requests-limit: 20 11 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: jodastephen 2 | open_collective: joda 3 | tidelift: maven/org.joda:joda-collect 4 | 5 | # https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/displaying-a-sponsor-button-in-your-repository 6 | -------------------------------------------------------------------------------- /.github/SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Supported Versions 4 | 5 | If a security issue occurs, only the latest version is guaranteed to be patched. 6 | 7 | ## Reporting a Vulnerability 8 | 9 | To report a security vulnerability, please use the [Tidelift security contact](https://tidelift.com/security). 10 | Tidelift will coordinate the fix and disclosure. 11 | -------------------------------------------------------------------------------- /RELEASE-NOTES.txt: -------------------------------------------------------------------------------- 1 | 2 | Joda-Collect 3 | ================================================ 4 | Joda-Collect provides collections that are not present in the JDK or [Google Guava](https://github.com/google/guava). 5 | 6 | The release runs on JDK 21 or later. 7 | 8 | See https://www.joda.org/joda-collect/changes-report.html for changes 9 | 10 | 11 | Feedback 12 | -------- 13 | Feedback is best received using GitHub issues and Pull Requests. 14 | https://github.com/JodaOrg/joda-collect/ 15 | 16 | The Joda team 17 | -------------------------------------------------------------------------------- /.github/maven-settings.xml: -------------------------------------------------------------------------------- 1 | 5 | 6 | 7 | central-publish 8 | ${env.MAVEN_CENTRAL_USERNAME} 9 | ${env.MAVEN_CENTRAL_PASSWORD} 10 | 11 | 12 | github 13 | ${env.GITHUB_TOKEN} 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /src/main/assembly/dist.xml: -------------------------------------------------------------------------------- 1 | 2 | dist 3 | 4 | tar.gz 5 | zip 6 | 7 | ${artifactId}-${version} 8 | 9 | 10 | 11 | checkstyle.xml 12 | LICENSE.txt 13 | NOTICE.txt 14 | pom.xml 15 | RELEASE-NOTES.txt 16 | 17 | 18 | 19 | src 20 | 21 | 22 | target 23 | 24 | 25 | *.jar 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /src/main/java/org/joda/collect/grid/package-info.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014-present Stephen Colebourne 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | /** 18 | * A {@code Grid} data structure. 19 | *

20 | * The grid data structure provides a grid of values accessed by row and column. 21 | * It differs from Guava's {@code Table} in that the row and column are always {@code int}. 22 | */ 23 | package org.joda.collect.grid; 24 | -------------------------------------------------------------------------------- /src/main/java/module-info.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014-present Stephen Colebourne 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | /** 18 | * Joda-Collect provides a {@code Grid} data structure based on the {@code Table} data structure in Guava. 19 | */ 20 | module org.joda.collect { 21 | 22 | // dependency on Guava - should be transitive as Guava types are in Joda-Collect API 23 | // but Guava is an automatic module, so 'requires transitive' is unwise 24 | // the 'transitive' will be added if Guava becomes a fully defined module 25 | requires com.google.common; 26 | 27 | // export all packages 28 | exports org.joda.collect.grid; 29 | 30 | } 31 | -------------------------------------------------------------------------------- /src/test/java/org/joda/collect/grid/TestSparseGrid.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014-present Stephen Colebourne 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.joda.collect.grid; 17 | 18 | /** 19 | * Test SparseGrid. 20 | */ 21 | public class TestSparseGrid extends AbstractTestMutableGrid { 22 | 23 | @Override 24 | protected Grid create3x3() { 25 | return SparseGrid.create(3, 3); 26 | } 27 | 28 | @Override 29 | protected Grid create(int rowCount, int columnCount) { 30 | return SparseGrid.create(rowCount, columnCount); 31 | } 32 | 33 | @Override 34 | protected Grid create(Grid grid) { 35 | return SparseGrid.create(grid); 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /src/test/java/org/joda/collect/grid/MockCell.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014-present Stephen Colebourne 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.joda.collect.grid; 17 | 18 | /** 19 | * Mock cell. 20 | */ 21 | public class MockCell extends AbstractCell { 22 | 23 | private final int row; 24 | private final int column; 25 | private final String value; 26 | 27 | public MockCell(int row, int column, String value) { 28 | this.row = row; 29 | this.column = column; 30 | this.value = value; 31 | } 32 | 33 | @Override 34 | public int getRow() { 35 | return row; 36 | } 37 | 38 | @Override 39 | public int getColumn() { 40 | return column; 41 | } 42 | 43 | @Override 44 | public String getValue() { 45 | return value; 46 | } 47 | 48 | } 49 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | 3 | on: 4 | push: 5 | branches: 6 | - '*' 7 | pull_request: 8 | branches: 9 | - 'main' 10 | 11 | permissions: 12 | contents: write 13 | 14 | jobs: 15 | build: 16 | permissions: 17 | security-events: write # for github/codeql-action 18 | runs-on: ubuntu-latest 19 | steps: 20 | - name: Checkout 21 | uses: actions/checkout@v4 22 | with: 23 | token: ${{ secrets.PERSONAL_GITHUB_TOKEN }} 24 | fetch-tags: true 25 | 26 | - name: Setup git 27 | run: | 28 | git config --global user.name "Stephen Colebourne (CI)" 29 | git config --global user.email "scolebourne@joda.org" 30 | 31 | - name: Set up JDK 32 | uses: actions/setup-java@v4 33 | with: 34 | java-version: 21 35 | distribution: 'temurin' 36 | cache: 'maven' 37 | 38 | - name: Maven version 39 | run: | 40 | mkdir -p ./.mvn 41 | echo "-e" >> ./.mvn/maven.config 42 | echo "-B" >> ./.mvn/maven.config 43 | echo "-ntp" >> ./.mvn/maven.config 44 | echo "-DtrimStackTrace=false" >> ./.mvn/maven.config 45 | echo "--settings" >> ./.mvn/maven.config 46 | echo "$( pwd )/.github/maven-settings.xml" >> ./.mvn/maven.config 47 | mvn --version 48 | mkdir -p target 49 | 50 | #------------------------------------------------------------------------ 51 | - name: Initialize CodeQL 52 | uses: github/codeql-action/init@v3 53 | with: 54 | languages: java 55 | 56 | - name: Maven build 57 | run: | 58 | mvn install site 59 | 60 | - name: Perform CodeQL Analysis 61 | uses: github/codeql-action/analyze@v3 62 | -------------------------------------------------------------------------------- /src/site/resources/css/site.css: -------------------------------------------------------------------------------- 1 | /* Fix broken definition that causes hyperlinks to break */ 2 | h1[id]:before, 3 | h2[id]:before, 4 | h3[id]:before, 5 | h4[id]:before, 6 | h5[id]:before, 7 | h6[id]:before, 8 | a[name]:before { 9 | height:0px; 10 | margin:0px; 11 | } 12 | /* Blacker text */ 13 | body { 14 | color: #222; 15 | } 16 | code, pre { 17 | color: #444; 18 | } 19 | .main-body p b { 20 | font-weight: bold; 21 | color: #722; 22 | } 23 | .dropdown-menu>li>a { 24 | color: #666; 25 | } 26 | /* Sidebar had too much padding at the top */ 27 | .well { 28 | padding-top: 6px; 29 | padding-bottom: 36px; 30 | } 31 | /* Font Awesome icons by CSS as markdown class is stripped */ 32 | h2 i { 33 | display: inline-block; 34 | font: normal normal normal 14px/1 FontAwesome; 35 | font-size: inherit; 36 | text-rendering: auto; 37 | -webkit-font-smoothing: antialiased; 38 | -moz-osx-font-smoothing: grayscale; 39 | } 40 | h2#About i:before { 41 | content: "\f015"; 42 | } 43 | h2#Features i:before { 44 | content: "\f0d0"; 45 | } 46 | h2#Documentation i:before { 47 | content: "\f02d"; 48 | } 49 | h2#Releases i:before { 50 | content: "\f02c"; 51 | } 52 | 53 | /* Enterprise page */ 54 | h2#Joda-Money_for_Enterprise~div > p:last-child { 55 | text-align: center; 56 | margin-top: 1.1em; 57 | margin-bottom: 1.2em; 58 | } 59 | button.btn-learnmore, button.btn-requestdemo { 60 | width: 14em; 61 | font-size: 1.2em; 62 | text-align: center; 63 | padding-top: 0.3em; 64 | padding-bottom: 0.3em; 65 | border-radius: 3px; 66 | border-style: solid; 67 | } 68 | button.btn-learnmore { 69 | color: #f6914d; 70 | background-color: white; 71 | border-color: #f6914d; 72 | } 73 | button.btn-learnmore a { 74 | color: #f6914d; 75 | } 76 | button.btn-requestdemo { 77 | color: white; 78 | background-color: #f6914d; 79 | border-color: #f6914d; 80 | } 81 | button.btn-requestdemo a { 82 | color: white; 83 | } 84 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | push: 5 | tags: 6 | - 'release*' 7 | 8 | permissions: 9 | contents: write 10 | 11 | jobs: 12 | build: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - name: Checkout 16 | uses: actions/checkout@v4 17 | with: 18 | token: ${{ secrets.PERSONAL_GITHUB_TOKEN }} 19 | ref: "main" 20 | fetch-tags: true 21 | 22 | - name: Setup git 23 | run: | 24 | git config --global user.name "Stephen Colebourne (CI)" 25 | git config --global user.email "scolebourne@joda.org" 26 | 27 | - name: Set up JDK 28 | uses: actions/setup-java@v4 29 | with: 30 | java-version: 21 31 | distribution: 'temurin' 32 | cache: 'maven' 33 | 34 | - name: Maven version 35 | run: | 36 | mkdir -p ./.mvn 37 | echo "-e" >> ./.mvn/maven.config 38 | echo "-B" >> ./.mvn/maven.config 39 | echo "-ntp" >> ./.mvn/maven.config 40 | echo "-DtrimStackTrace=false" >> ./.mvn/maven.config 41 | echo "--settings" >> ./.mvn/maven.config 42 | echo "$( pwd )/.github/maven-settings.xml" >> ./.mvn/maven.config 43 | mvn --version 44 | mkdir -p target 45 | 46 | #------------------------------------------------------------------------ 47 | - name: Maven install 48 | run: | 49 | mvn clean install 50 | 51 | - name: Maven release 52 | env: 53 | MAVEN_CENTRAL_USERNAME: ${{ secrets.MAVEN_CENTRAL_USERNAME }} 54 | MAVEN_CENTRAL_PASSWORD: ${{ secrets.MAVEN_CENTRAL_PASSWORD }} 55 | MAVEN_GPG_PASSPHRASE: ${{ secrets.RELEASES_GPG_PASSPHRASE }} 56 | MAVEN_GPG_KEY: ${{ secrets.RELEASES_GPG_PRIVATE_KEY }} 57 | GITHUB_TOKEN: ${{ secrets.PERSONAL_GITHUB_TOKEN }} 58 | run: | 59 | mvn release:clean release:prepare release:perform 60 | 61 | - name: Update website 62 | run: | 63 | git tag websiterelease 64 | git push origin websiterelease 65 | 66 | - name: Delete release tag 67 | if: "always()" 68 | run: | 69 | git tag --delete "${GITHUB_REF_NAME}" || true 70 | git push --delete origin "${GITHUB_REF_NAME}" || true 71 | -------------------------------------------------------------------------------- /src/changes/changes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Changes 6 | Stephen Colebourne 7 | 8 | 9 | 10 | 11 | 12 | 13 | Major version based on Java SE 21. 14 | v2.x is compatible with v1.x with one exception - the module definition has changed slightly. 15 | Guava is no longer transitively included (because it is an automatic module), and as such 16 | end users may need to alter their module declaration to include Guava directly. 17 | 18 | 19 | Add Tidelift commercial support and security policy. 20 | 21 | 22 | Switch master to main. 23 | 24 | 25 | 26 | 27 | Update Guava. 28 | 29 | 30 | Fix OSGi information for Java 8. 31 | 32 | 33 | 34 | 35 | Add module-info for Java 9. 36 | 37 | 38 | Update and redesign build to support Java 9. 39 | Switch to require Java 8. 40 | 41 | 42 | Switch from TestNG to JUnit4. 43 | 44 | 45 | 46 | 47 | Add automatic module name for Java SE 9. 48 | 49 | 50 | 51 | 52 | Initial version. 53 | 54 | 55 | 56 | 57 | 58 | -------------------------------------------------------------------------------- /src/main/java/org/joda/collect/grid/MutableCell.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014-present Stephen Colebourne 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.joda.collect.grid; 17 | 18 | import java.io.Serial; 19 | import java.io.Serializable; 20 | 21 | /** 22 | * Immutable implementations of the {@code Grid.Cell} data structure. 23 | * 24 | * @param the type of the value 25 | * @author Stephen Colebourne 26 | */ 27 | final class MutableCell extends AbstractCell implements Serializable { 28 | 29 | /** Serialization version. */ 30 | @Serial 31 | private static final long serialVersionUID = 1L; 32 | 33 | /** 34 | * The row. 35 | */ 36 | private int row; 37 | /** 38 | * The column. 39 | */ 40 | private int column; 41 | /** 42 | * The value. 43 | */ 44 | private V value; 45 | 46 | /** 47 | * Creates an instance. 48 | */ 49 | MutableCell() { 50 | } 51 | 52 | 53 | //----------------------------------------------------------------------- 54 | @Override 55 | public int getRow() { 56 | return row; 57 | } 58 | 59 | @Override 60 | public int getColumn() { 61 | return column; 62 | } 63 | 64 | @Override 65 | public V getValue() { 66 | return value; 67 | } 68 | 69 | /** 70 | * Sets the content of the cell. 71 | * 72 | * @param row the row index 73 | * @param column the column index 74 | * @param value the value 75 | */ 76 | void set(int row, int column, V value) { 77 | this.row = row; 78 | this.column = column; 79 | this.value = value; 80 | } 81 | 82 | } 83 | -------------------------------------------------------------------------------- /.github/workflows/website.yml: -------------------------------------------------------------------------------- 1 | name: Website 2 | 3 | on: 4 | push: 5 | tags: 6 | - 'website*' 7 | 8 | permissions: 9 | contents: write 10 | 11 | jobs: 12 | build: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - name: Checkout 16 | uses: actions/checkout@v4 17 | with: 18 | token: ${{ secrets.PERSONAL_GITHUB_TOKEN }} 19 | ref: ${{ github.ref }} 20 | fetch-tags: true 21 | 22 | - name: Setup git 23 | run: | 24 | git config --global user.name "Stephen Colebourne (CI)" 25 | git config --global user.email "scolebourne@joda.org" 26 | 27 | - name: Set up JDK 28 | uses: actions/setup-java@v4 29 | with: 30 | java-version: 21 31 | distribution: 'temurin' 32 | cache: 'maven' 33 | 34 | - name: Maven version 35 | run: | 36 | mkdir -p ./.mvn 37 | echo "-e" >> ./.mvn/maven.config 38 | echo "-B" >> ./.mvn/maven.config 39 | echo "-ntp" >> ./.mvn/maven.config 40 | echo "-DtrimStackTrace=false" >> ./.mvn/maven.config 41 | echo "--settings" >> ./.mvn/maven.config 42 | echo "$( pwd )/.github/maven-settings.xml" >> ./.mvn/maven.config 43 | mvn --version 44 | mkdir -p target 45 | 46 | #------------------------------------------------------------------------ 47 | - name: Maven site 48 | run: | 49 | mvn install site 50 | 51 | - name: Checkout website 52 | uses: actions/checkout@v4 53 | with: 54 | token: ${{ secrets.PERSONAL_GITHUB_TOKEN }} 55 | repository: JodaOrg/jodaorg.github.io 56 | path: target/jodaorg.github.io 57 | ref: "main" 58 | 59 | - name: Update website 60 | run: | 61 | cd target/jodaorg.github.io 62 | git status 63 | 64 | rm -rf joda-collect/ 65 | cp -R ../site joda-collect/ 66 | 67 | git add -A 68 | git status 69 | git commit --message "Update joda-collect from CI: $GITHUB_RUN_ID" 70 | 71 | git push origin main 72 | 73 | - name: Delete website tag 74 | if: "always()" 75 | run: | 76 | git tag --delete "${GITHUB_REF_NAME}" || true 77 | git push --delete origin "${GITHUB_REF_NAME}" || true 78 | -------------------------------------------------------------------------------- /src/test/java/org/joda/collect/grid/MockSingletonGrid.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014-present Stephen Colebourne 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.joda.collect.grid; 17 | 18 | import java.util.SortedSet; 19 | import java.util.TreeSet; 20 | 21 | /** 22 | * Mock grid. 23 | */ 24 | public class MockSingletonGrid extends AbstractGrid { 25 | 26 | private final int rowCount; 27 | private final int columnCount; 28 | private final int row; 29 | private final int column; 30 | private final String value; 31 | 32 | public MockSingletonGrid(int rowCount, int columnCount, int row, int column, String value) { 33 | this.rowCount = rowCount; 34 | this.columnCount = columnCount; 35 | this.row = row; 36 | this.column = column; 37 | this.value = value; 38 | } 39 | 40 | @Override 41 | public int rowCount() { 42 | return rowCount; 43 | } 44 | 45 | @Override 46 | public int columnCount() { 47 | return columnCount; 48 | } 49 | 50 | @Override 51 | public SortedSet> cells() { 52 | TreeSet> set = new TreeSet>(AbstractCell.comparator()); 53 | set.add(new MockCell(row, column, value)); 54 | return set; 55 | } 56 | 57 | @Override 58 | public void clear() { 59 | throw new UnsupportedOperationException(); 60 | } 61 | 62 | @Override 63 | public void put(int row, int column, String value) { 64 | throw new UnsupportedOperationException(); 65 | } 66 | 67 | @Override 68 | public void putAll(Grid grid) { 69 | throw new UnsupportedOperationException(); 70 | } 71 | 72 | @Override 73 | public boolean remove(int row, int column) { 74 | throw new UnsupportedOperationException(); 75 | } 76 | 77 | } 78 | -------------------------------------------------------------------------------- /src/main/java/org/joda/collect/grid/AbstractCell.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014-present Stephen Colebourne 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.joda.collect.grid; 17 | 18 | import com.google.common.base.Objects; 19 | import java.util.Comparator; 20 | import org.joda.collect.grid.Grid.Cell; 21 | 22 | /** 23 | * Abstract implementation of the {@code Grid.Cell} data structure. 24 | * 25 | * @param the type of the value 26 | * @author Stephen Colebourne 27 | */ 28 | abstract class AbstractCell implements Cell { 29 | 30 | /** 31 | * Compare by row then column. 32 | * 33 | * @param the type of the value 34 | * @return the comparator, not null 35 | */ 36 | @SuppressWarnings({"unchecked", "rawtypes"}) 37 | static Comparator> comparator() { 38 | return (Comparator>) (Comparator) COMPARATOR; 39 | } 40 | /** 41 | * Compare by row then column. 42 | */ 43 | private static final Comparator> COMPARATOR = 44 | Comparator.comparingInt((Cell cell) -> cell.getRow()).thenComparingInt(Cell::getColumn); 45 | 46 | //----------------------------------------------------------------------- 47 | /** 48 | * Restricted constructor. 49 | */ 50 | AbstractCell() { 51 | } 52 | 53 | //----------------------------------------------------------------------- 54 | @Override 55 | public boolean equalRowColumn(int row, int column) { 56 | return row == getRow() && column == getColumn(); 57 | } 58 | 59 | @Override 60 | public boolean equalValue(Object value) { 61 | return Objects.equal(value, getValue()); 62 | } 63 | 64 | //----------------------------------------------------------------------- 65 | @Override 66 | public boolean equals(Object obj) { 67 | return obj == this || 68 | (obj instanceof Cell other && 69 | getRow() == other.getRow() && getColumn() == other.getColumn() && 70 | Objects.equal(getValue(), other.getValue())); 71 | } 72 | 73 | @Override 74 | public int hashCode() { 75 | return getRow() ^ Integer.rotateLeft(getColumn(), 16) ^ getValue().hashCode(); 76 | } 77 | 78 | @Override 79 | public String toString() { 80 | return "(" + getRow() + "," + getColumn() + ")=" + getValue(); 81 | } 82 | 83 | } 84 | -------------------------------------------------------------------------------- /src/test/java/org/joda/collect/grid/AbstractTestImmutableGrid.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014-present Stephen Colebourne 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.joda.collect.grid; 17 | 18 | import java.util.Iterator; 19 | 20 | import org.joda.collect.grid.Grid.Cell; 21 | import org.junit.Test; 22 | 23 | /** 24 | * Test abstract Grid. 25 | */ 26 | public abstract class AbstractTestImmutableGrid extends AbstractTestGrid { 27 | 28 | protected abstract ImmutableGrid createNonEmpty(); 29 | 30 | //----------------------------------------------------------------------- 31 | @SuppressWarnings("deprecation") 32 | @Test(expected = UnsupportedOperationException.class) 33 | public void test_immutable_clear() { 34 | createNonEmpty().clear(); 35 | } 36 | 37 | @SuppressWarnings("deprecation") 38 | @Test(expected = UnsupportedOperationException.class) 39 | public void test_immutable_put() { 40 | createNonEmpty().put(0, 0, "Hello"); 41 | } 42 | 43 | @SuppressWarnings("deprecation") 44 | @Test(expected = UnsupportedOperationException.class) 45 | public void test_immutable_putAll() { 46 | createNonEmpty().putAll(ImmutableGrid.of()); 47 | } 48 | 49 | @SuppressWarnings("deprecation") 50 | @Test(expected = UnsupportedOperationException.class) 51 | public void test_immutable_remove() { 52 | createNonEmpty().remove(0, 0); 53 | } 54 | 55 | //----------------------------------------------------------------------- 56 | @Test(expected = UnsupportedOperationException.class) 57 | public void test_cells_add() { 58 | ImmutableGrid test = createNonEmpty(); 59 | Iterator> it = test.cells().iterator(); 60 | it.next(); 61 | it.remove(); 62 | } 63 | 64 | @Test(expected = UnsupportedOperationException.class) 65 | public void test_cells_remove() { 66 | createNonEmpty().cells().add(ImmutableCell.of(0, 0, "Rejected")); 67 | } 68 | 69 | @Test(expected = UnsupportedOperationException.class) 70 | public void test_cells_clear() { 71 | createNonEmpty().cells().clear(); 72 | } 73 | 74 | //----------------------------------------------------------------------- 75 | @Test(expected = UnsupportedOperationException.class) 76 | public void test_rows_clear() { 77 | createNonEmpty().rows().clear(); 78 | } 79 | 80 | @Test(expected = UnsupportedOperationException.class) 81 | public void test_columns_clear() { 82 | createNonEmpty().rows().clear(); 83 | } 84 | 85 | } 86 | -------------------------------------------------------------------------------- /src/main/java/org/joda/collect/grid/EmptyGrid.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014-present Stephen Colebourne 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.joda.collect.grid; 17 | 18 | import com.google.common.collect.ImmutableList; 19 | import com.google.common.collect.ImmutableSortedSet; 20 | import java.io.Serial; 21 | import java.io.Serializable; 22 | 23 | /** 24 | * Immutable implementation of the {@code Grid} data structure storing no cells. 25 | * 26 | * @param the type of the value 27 | * @author Stephen Colebourne 28 | */ 29 | final class EmptyGrid extends ImmutableGrid implements Serializable { 30 | 31 | /** Serialization version. */ 32 | @Serial 33 | private static final long serialVersionUID = 1L; 34 | 35 | /** 36 | * The row count. 37 | */ 38 | private final int rowCount; 39 | /** 40 | * The column count. 41 | */ 42 | private final int columnCount; 43 | 44 | /** 45 | * Restricted constructor. 46 | */ 47 | EmptyGrid() { 48 | this.rowCount = 0; 49 | this.columnCount = 0; 50 | } 51 | 52 | /** 53 | * Restricted constructor. 54 | */ 55 | EmptyGrid(int rowCount, int columnCount) { 56 | validateCounts(rowCount, columnCount); 57 | this.rowCount = rowCount; 58 | this.columnCount = columnCount; 59 | } 60 | 61 | //----------------------------------------------------------------------- 62 | @Override 63 | public int rowCount() { 64 | return rowCount; 65 | } 66 | 67 | @Override 68 | public int columnCount() { 69 | return columnCount; 70 | } 71 | 72 | @Override 73 | public int size() { 74 | return 0; 75 | } 76 | 77 | @Override 78 | public boolean contains(int row, int column) { 79 | return false; 80 | } 81 | 82 | @Override 83 | public boolean containsValue(Object valueToFind) { 84 | return false; 85 | } 86 | 87 | @Override 88 | public V get(int row, int column) { 89 | return null; 90 | } 91 | 92 | @Override 93 | public Cell cell(int row, int column) { 94 | return null; 95 | } 96 | 97 | //----------------------------------------------------------------------- 98 | @Override 99 | public ImmutableSortedSet> cells() { 100 | return ImmutableSortedSet.of(); 101 | } 102 | 103 | @Override 104 | public ImmutableList values() { 105 | return ImmutableList.of(); 106 | } 107 | 108 | } 109 | -------------------------------------------------------------------------------- /src/site/markdown/enterprise.md: -------------------------------------------------------------------------------- 1 | ## Joda-Collect for Enterprise 2 | 3 | ### Available as part of the Tidelift Subscription 4 | 5 | **Tidelift** is working with the maintainers of **Joda-Collect** and thousands of other open source projects to deliver 6 | commercial support and maintenance for the open source dependencies you use to build your applications. 7 | Save time, reduce risk, and improve code health, while paying the maintainers of the exact dependencies you use. 8 | 9 | 10 | 11 | 12 | ### Enterprise-ready open source software - managed for you 13 | 14 | The Tidelift Subscription is a managed open source subscription for application dependencies covering millions 15 | of open source projects across JavaScript, Python, Java, PHP, Ruby, .NET, and more. 16 | 17 | Your subscription includes: 18 | 19 | * **Security updates**
20 | Tidelift’s security response team coordinates patches for new breaking security vulnerabilities and alerts 21 | immediately through a private channel, so your software supply chain is always secure. 22 | 23 | * **Licensing verification and indemnification**
24 | Tidelift verifies license information to enable easy policy enforcement and adds intellectual property 25 | indemnification to cover creators and users in case something goes wrong. You always have a 100% up-to-date 26 | bill of materials for your dependencies to share with your legal team, customers, or partners. 27 | 28 | * **Maintenance and code improvement**
29 | Tidelift ensures the software you rely on keeps working as long as you need it to work. 30 | Your managed dependencies are actively maintained and we recruit additional maintainers where required. 31 | 32 | * **Package selection and version guidance**
33 | We help you choose the best open source packages from the start—and then guide you through updates to stay on 34 | the best releases as new issues arise. 35 | 36 | * **Roadmap input**
37 | Take a seat at the table with the creators behind the software you use. Tidelift’s participating maintainers 38 | earn more income as their software is used by more subscribers, so they’re interested in knowing what you need. 39 | 40 | * **Tooling and cloud integration**
41 | Tidelift works with GitHub, GitLab, BitBucket, and more. 42 | We support every cloud platform (and other deployment targets, too). 43 | 44 | The end result? All of the capabilities you expect from commercial-grade software, for the full breadth 45 | of open source you use. That means less time grappling with esoteric open source trivia, and more 46 | time building your own applications—and your business. 47 | 48 | 49 | 50 | 51 | [1]: https://tidelift.com/subscription/pkg/maven-org-joda-joda-collect?utm_source=maven-org-joda-joda-collect&utm_medium=referral&utm_campaign=enterprise 52 | [2]: https://tidelift.com/subscription/request-a-demo?utm_source=maven-org-joda-joda-collect&utm_medium=referral&utm_campaign=enterprise 53 | -------------------------------------------------------------------------------- /src/site/markdown/index.md: -------------------------------------------------------------------------------- 1 | ## About 2 | 3 | **Joda-Collect** provides collections that are not present in the JDK or 4 | [Google Guava](https://github.com/google/guava). 5 | 6 | The JDK and Guava contain many collection classes, but occasionally there is something missing. 7 | This project provides a home for those collections. 8 | 9 | Joda-Collect is licensed under the business-friendly [Apache 2.0 licence](licenses.html). 10 | 11 | 12 | ## Features 13 | 14 | Included collection types: 15 | 16 | * Grid - A grid data structure, providing access to its values by row and column. 17 | This is similar to Guava's `Table`, but uses `int` for the row and column, allowing optimisations. 18 | Mutable and immutable implementations are provided in sparse and dense variations. 19 | 20 | 21 | ## Documentation 22 | 23 | Various documentation is available: 24 | 25 | * The [Javadoc](apidocs/index.html) 26 | * The [change notes](changes-report.html) for each release 27 | * The [GitHub](https://github.com/JodaOrg/joda-collect) source repository 28 | 29 | 30 | --- 31 | 32 | ## Releases 33 | 34 | The 2.x branch (v2.0.0) is compatible with Java SE 21 or later. 35 | 36 | The 1.x branch (v1.0.1) is compatible with Java SE 8 or later. 37 | 38 | v2.x releases are compatible with v1.x releases - except for the Java SE version and `module-info.class` file. 39 | Guava is a required module, but it cannot be declared as 'transitive' because it is an automatic module. 40 | 41 | Joda-Collect [depends](dependencies.html) on Google Guava. 42 | 43 | Available in [Maven Central](https://search.maven.org/search?q=g:org.joda%20AND%20a:joda-collect&core=gav). 44 | 45 | ```xml 46 | 47 | org.joda 48 | joda-collect 49 | 2.0.0 50 | 51 | ``` 52 | 53 | Java module name: `org.joda.collect`. 54 | 55 | --- 56 | 57 | ### For Enterprise 58 | 59 | [Available as part of the Tidelift Subscription](https://tidelift.com/subscription/pkg/maven-org-joda-joda-collect?utm_source=maven-org-joda-joda-collect&utm_medium=referral&utm_campaign=enterprise). 60 | 61 | Joda and the maintainers of thousands of other packages are working with Tidelift to deliver one 62 | enterprise subscription that covers all of the open source you use. 63 | 64 | If you want the flexibility of open source and the confidence of commercial-grade software, this is for you. 65 | [Learn more](https://tidelift.com/subscription/pkg/maven-org-joda-joda-collect?utm_source=maven-org-joda-joda-collect&utm_medium=referral&utm_campaign=enterprise). 66 | 67 | 68 | ### Support 69 | 70 | Please use [Stack Overflow](https://stackoverflow.com/search?q=joda-collect) for general usage questions. 71 | GitHub [issues](https://github.com/JodaOrg/joda-collect/issues) and [pull requests](https://github.com/JodaOrg/joda-collect/pulls) 72 | should be used when you want to help advance the project. 73 | 74 | Any donations to support the project are accepted via [OpenCollective](https://opencollective.com/joda). 75 | 76 | To report a security vulnerability, please use the [Tidelift security contact](https://tidelift.com/security). 77 | Tidelift will coordinate the fix and disclosure. 78 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Joda-Collect 2 | ------------ 3 | 4 | Joda-Collect provides collections that are not present in the JDK or [Google Guava](https://github.com/google/guava). 5 | 6 | The project is related to [Joda-Primitives](https://www.joda.org/joda-primitives/) which provides primitive versions of the collection interfaces. 7 | This project is separate, as the use cases are likely to be different. 8 | 9 | Joda-Collect is licensed under the business-friendly [Apache 2.0 licence](https://www.joda.org/joda-collect/licenses.html). 10 | 11 | 12 | ### Contents 13 | 14 | Joda-Collect contains the following collections: 15 | 16 | * Grid - a grid data structure, providing access to its values by row and column. 17 | This is similar to Guava's `Table`, but uses `int` for the row and column, allowing optimisations. 18 | Mutable and immutable implementations are provided in sparse and dense variations. 19 | 20 | 21 | ### Documentation 22 | Various documentation is available: 23 | 24 | * The [home page](https://www.joda.org/joda-collect/) 25 | * The [Javadoc](https://www.joda.org/joda-collect/apidocs/index.html) 26 | * The change notes for the [releases](https://www.joda.org/joda-collect/changes-report.html) 27 | 28 | 29 | ### Releases 30 | The 2.x branch is compatible with Java SE 21 or later. 31 | 32 | The 1.x branch is compatible with Java SE 8 or later. 33 | 34 | v2.x releases are compatible with v1.x releases - except for the Java SE version and `module-info.class` file. 35 | Guava is a required module, but it cannot be declared as 'transitive' because it is an automatic module. 36 | 37 | Joda-Collect depends on [Google-Guava](https://github.com/google/guava). 38 | 39 | Available in the [Maven Central repository](https://search.maven.org/search?q=g:org.joda%20AND%20a:joda-collect&core=gav) 40 | 41 | ![Tidelift dependency check](https://tidelift.com/badges/github/JodaOrg/joda-collect) 42 | 43 | 44 | ### For enterprise 45 | Available as part of the Tidelift Subscription. 46 | 47 | Joda and the maintainers of thousands of other packages are working with Tidelift to deliver one enterprise subscription that covers all of the open source you use. 48 | 49 | If you want the flexibility of open source and the confidence of commercial-grade software, this is for you. 50 | 51 | [Learn more](https://tidelift.com/subscription/pkg/maven-org-joda-joda-collect?utm_source=maven-org-joda-joda-collect&utm_medium=github) 52 | 53 | 54 | ### Support 55 | Please use [Stack Overflow](https://stackoverflow.com/search?q=joda-collect) for general usage questions. 56 | GitHub [issues](https://github.com/JodaOrg/joda-collect/issues) and [pull requests](https://github.com/JodaOrg/joda-collect/pulls) 57 | should be used when you want to help advance the project. 58 | 59 | Any donations to support the project are accepted via [OpenCollective](https://opencollective.com/joda). 60 | 61 | To report a security vulnerability, please use the [Tidelift security contact](https://tidelift.com/security). 62 | Tidelift will coordinate the fix and disclosure. 63 | 64 | 65 | ### Release process 66 | 67 | * Update version (index.md, changes.xml) 68 | * Commit and push 69 | * `git push origin HEAD:refs/tags/release` 70 | * Code and Website will be built and released by GitHub Actions 71 | 72 | Release from local: 73 | 74 | * Ensure `gpg-agent` is running 75 | * `mvn clean release:clean release:prepare release:perform` 76 | -------------------------------------------------------------------------------- /src/site/site.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | UA-1425975-4 6 | 7 | 8 | org.joda.external 9 | reflow-maven-skin 10 | 1.2 11 | 12 | 13 | 14 | 15 | false 16 | true 17 | github 18 | false 19 | bootswatch-cosmo 20 | 21 | Joda-Collect 22 | index.html 23 | 24 | Documentation|Releases|Development|Joda|For Enterprise 25 | 26 | Documentation 27 | Releases 28 | Development 29 | Reports 30 | 31 | 34 | false 35 | false 36 | false 37 | true 38 | 39 | 40 | 41 | 3 42 | 43 | 44 | 45 | Home 46 | false 47 | 48 | 49 | false 50 | 51 | 52 | false 53 | 54 | 55 | false 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 65 | ]]> 66 | 67 | 68 |

69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | -------------------------------------------------------------------------------- /src/test/java/org/joda/collect/grid/TestDenseGrid.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014-present Stephen Colebourne 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.joda.collect.grid; 17 | 18 | import static org.junit.Assert.assertEquals; 19 | 20 | import org.junit.Test; 21 | 22 | /** 23 | * Test DenseGrid. 24 | */ 25 | public class TestDenseGrid extends AbstractTestMutableGrid { 26 | 27 | @Override 28 | protected Grid create3x3() { 29 | return DenseGrid.create(3, 3); 30 | } 31 | 32 | @Override 33 | protected Grid create(int rowCount, int columnCount) { 34 | return DenseGrid.create(rowCount, columnCount); 35 | } 36 | 37 | @Override 38 | protected Grid create(Grid grid) { 39 | return DenseGrid.create(grid); 40 | } 41 | 42 | //----------------------------------------------------------------------- 43 | @Test 44 | public void test_create_Grid_fromDenseImutable() { 45 | SparseGrid base = SparseGrid.create(2, 2); 46 | base.put(0, 0, "Hello"); 47 | base.put(1, 0, "World"); 48 | ImmutableGrid imm = ImmutableGrid.copyOf(base); 49 | DenseGrid test = DenseGrid.create(imm); 50 | checkGrid(test, 0, 0, "Hello", 1, 0, "World"); 51 | } 52 | 53 | @Test 54 | public void test_create_array2D() { 55 | String[][] array = new String[][] { 56 | {"Hello", "World"}, 57 | {null}, 58 | }; 59 | DenseGrid test = DenseGrid.create(array); 60 | checkGrid(test, 0, 0, "Hello", 0, 1, "World"); 61 | } 62 | 63 | @Test 64 | public void test_create_array2D_empty() { 65 | String[][] array = new String[][] {}; 66 | DenseGrid test = DenseGrid.create(array); 67 | checkGrid(test); 68 | } 69 | 70 | @Test(expected = IllegalArgumentException.class) 71 | public void test_create_array2D_null() { 72 | DenseGrid.create((String[][]) null); 73 | } 74 | 75 | //----------------------------------------------------------------------- 76 | @Test 77 | public void test_rowCount_columnCount() { 78 | Grid test = create3x3(); 79 | test.put(0, 0, "Hello"); 80 | test.put(0, 1, "World"); 81 | assertEquals(3, test.rowCount()); 82 | assertEquals(3, test.columnCount()); 83 | } 84 | 85 | @Test 86 | public void test_rowCount_columnCount_empty() { 87 | Grid test = create3x3(); 88 | assertEquals(3, test.rowCount()); 89 | assertEquals(3, test.columnCount()); 90 | } 91 | 92 | //----------------------------------------------------------------------- 93 | @Test(expected = IndexOutOfBoundsException.class) 94 | public void test_put_rowTooBig() { 95 | DenseGrid test = DenseGrid.create(2, 2); 96 | test.put(3, 1, "Hello"); 97 | } 98 | 99 | @Test(expected = IndexOutOfBoundsException.class) 100 | public void test_put_columnTooBig() { 101 | DenseGrid test = DenseGrid.create(2, 2); 102 | test.put(1, 3, "Hello"); 103 | } 104 | 105 | } 106 | -------------------------------------------------------------------------------- /src/main/java/org/joda/collect/grid/SingletonGrid.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014-present Stephen Colebourne 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.joda.collect.grid; 17 | 18 | import com.google.common.collect.ImmutableList; 19 | import com.google.common.collect.ImmutableSet; 20 | import java.io.Serial; 21 | import java.io.Serializable; 22 | 23 | /** 24 | * Immutable implementation of the {@code Grid} data structure storing one cell. 25 | * 26 | * @param the type of the value 27 | * @author Stephen Colebourne 28 | */ 29 | final class SingletonGrid extends ImmutableGrid implements Serializable { 30 | 31 | /** Serialization version. */ 32 | @Serial 33 | private static final long serialVersionUID = 1L; 34 | 35 | /** 36 | * The row count. 37 | */ 38 | private final int rowCount; 39 | /** 40 | * The column count. 41 | */ 42 | private final int columnCount; 43 | /** 44 | * The cell. 45 | */ 46 | private final ImmutableCell cell; 47 | 48 | /** 49 | * Restricted constructor. 50 | */ 51 | SingletonGrid(int rowCount, int columnCount, Cell cell) { 52 | validateCounts(rowCount, columnCount); 53 | this.rowCount = rowCount; 54 | this.columnCount = columnCount; 55 | this.cell = ImmutableCell.copyOf(cell).validateCounts(rowCount, columnCount); 56 | } 57 | 58 | /** 59 | * Restricted constructor. 60 | */ 61 | SingletonGrid(int rowCount, int columnCount, int row, int column, V value) { 62 | validateCounts(rowCount, columnCount); 63 | this.rowCount = rowCount; 64 | this.columnCount = columnCount; 65 | this.cell = ImmutableCell.of(row, column, value).validateCounts(rowCount, columnCount); 66 | } 67 | 68 | //----------------------------------------------------------------------- 69 | @Override 70 | public int rowCount() { 71 | return rowCount; 72 | } 73 | 74 | @Override 75 | public int columnCount() { 76 | return columnCount; 77 | } 78 | 79 | //----------------------------------------------------------------------- 80 | @Override 81 | public int size() { 82 | return 1; 83 | } 84 | 85 | @Override 86 | public boolean contains(int row, int column) { 87 | return cell.equalRowColumn(row, column); 88 | } 89 | 90 | @Override 91 | public boolean containsValue(Object valueToFind) { 92 | return cell.equalValue(valueToFind); 93 | } 94 | 95 | @Override 96 | public V get(int row, int column) { 97 | return cell.equalRowColumn(row, column) ? cell.getValue() : null; 98 | } 99 | 100 | @Override 101 | public Cell cell(int row, int column) { 102 | return cell.equalRowColumn(row, column) ? cell : null; 103 | } 104 | 105 | //----------------------------------------------------------------------- 106 | @Override 107 | public ImmutableSet> cells() { 108 | return ImmutableSet.of(cell); 109 | } 110 | 111 | @Override 112 | public ImmutableList values() { 113 | return ImmutableList.of(cell.getValue()); 114 | } 115 | 116 | } 117 | -------------------------------------------------------------------------------- /src/test/java/org/joda/collect/grid/TestDenseImmutableGrid.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014-present Stephen Colebourne 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.joda.collect.grid; 17 | 18 | 19 | import static org.junit.Assert.assertEquals; 20 | 21 | import org.junit.Test; 22 | 23 | /** 24 | * Test DenseImmutableGrid. 25 | */ 26 | public class TestDenseImmutableGrid extends AbstractTestImmutableGrid { 27 | 28 | @Override 29 | protected ImmutableGrid createNonEmpty() { 30 | DenseGrid hash = DenseGrid.create(2, 2); 31 | hash.put(0, 0, "Hello"); 32 | hash.put(0, 1, "World"); 33 | return ImmutableGrid.copyOf(hash); 34 | } 35 | 36 | //----------------------------------------------------------------------- 37 | @Test 38 | public void test_factory_copyOf_Grid_fromDense() { 39 | DenseGrid hash = DenseGrid.create(2, 2); 40 | hash.put(0, 0, "Hello"); 41 | hash.put(1, 0, "World"); 42 | ImmutableGrid test = ImmutableGrid.copyOf(hash); 43 | assertEquals(2, test.rowCount()); 44 | assertEquals(2, test.columnCount()); 45 | checkGrid(test, 0, 0, "Hello", 1, 0, "World"); 46 | assertEquals("[2x2:(0,0)=Hello, (1,0)=World]", test.toString()); 47 | } 48 | 49 | @Test 50 | public void test_factory_copyOf_Grid_fromSparse() { 51 | SparseGrid hash = SparseGrid.create(2, 2); 52 | hash.put(0, 0, "Hello"); 53 | hash.put(0, 1, "World"); 54 | ImmutableGrid test = ImmutableGrid.copyOf(hash); 55 | assertEquals(2, test.rowCount()); 56 | assertEquals(2, test.columnCount()); 57 | checkGrid(test, 0, 0, "Hello", 0, 1, "World"); 58 | assertEquals("[2x2:(0,0)=Hello, (0,1)=World]", test.toString()); 59 | } 60 | 61 | //----------------------------------------------------------------------- 62 | @Test 63 | public void test_containsValue_Object() { 64 | SparseGrid hash = SparseGrid.create(2, 2); 65 | hash.put(0, 0, "Hello"); 66 | hash.put(0, 1, "World"); 67 | ImmutableGrid test = ImmutableGrid.copyOf(hash); 68 | assertEquals(true, test.containsValue("Hello")); 69 | assertEquals(true, test.containsValue("World")); 70 | assertEquals(false, test.containsValue("Spicy")); 71 | assertEquals(false, test.containsValue("")); 72 | assertEquals(false, test.containsValue(null)); 73 | assertEquals(false, test.containsValue(Integer.valueOf(6))); 74 | } 75 | 76 | @SuppressWarnings("unlikely-arg-type") 77 | @Test 78 | public void test_equalsHashCode() { 79 | SparseGrid hash = SparseGrid.create(2, 2); 80 | hash.put(0, 0, "Hello"); 81 | hash.put(0, 1, "World"); 82 | ImmutableGrid test = ImmutableGrid.copyOf(hash); 83 | assertEquals(true, test.equals(test)); 84 | assertEquals(true, test.equals(ImmutableGrid.copyOf(hash))); 85 | assertEquals(true, test.equals(hash)); 86 | assertEquals(false, test.equals(null)); 87 | assertEquals(false, test.equals("")); 88 | 89 | assertEquals(2 ^ Integer.rotateLeft(2, 16) ^ test.cells().hashCode(), test.hashCode()); 90 | } 91 | 92 | } 93 | -------------------------------------------------------------------------------- /src/test/java/org/joda/collect/grid/TestSparseImmutableGrid.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014-present Stephen Colebourne 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.joda.collect.grid; 17 | 18 | import static org.junit.Assert.assertEquals; 19 | 20 | import org.junit.Test; 21 | 22 | /** 23 | * Test SparseImmutableGrid. 24 | */ 25 | public class TestSparseImmutableGrid extends AbstractTestImmutableGrid { 26 | 27 | @Override 28 | protected ImmutableGrid createNonEmpty() { 29 | DenseGrid hash = DenseGrid.create(2, 3); 30 | hash.put(0, 0, "Hello"); 31 | hash.put(0, 1, "World"); 32 | return ImmutableGrid.copyOf(hash); 33 | } 34 | 35 | //----------------------------------------------------------------------- 36 | @Test 37 | public void test_factory_copyOf_Grid() { 38 | SparseGrid hash = SparseGrid.create(2, 3); 39 | hash.put(0, 0, "Hello"); 40 | hash.put(0, 1, "World"); 41 | ImmutableGrid test = ImmutableGrid.copyOf(hash); 42 | assertEquals(2, test.rowCount()); 43 | assertEquals(3, test.columnCount()); 44 | checkGrid(test, 0, 0, "Hello", 0, 1, "World"); 45 | assertEquals("[2x3:(0,0)=Hello, (0,1)=World]", test.toString()); 46 | } 47 | 48 | @Test 49 | public void test_factory_copyOfDeriveCounts() { 50 | SparseGrid hash = SparseGrid.create(2, 2); 51 | hash.put(0, 0, "Hello"); 52 | hash.put(0, 1, "World"); 53 | ImmutableGrid test = ImmutableGrid.copyOfDeriveCounts(hash.cells()); 54 | assertEquals(1, test.rowCount()); 55 | assertEquals(2, test.columnCount()); 56 | checkGrid(test, 0, 0, "Hello", 0, 1, "World"); 57 | assertEquals("[1x2:(0,0)=Hello, (0,1)=World]", test.toString()); 58 | } 59 | 60 | //----------------------------------------------------------------------- 61 | @Test 62 | public void test_containsValue_Object() { 63 | SparseGrid hash = SparseGrid.create(2, 3); 64 | hash.put(0, 0, "Hello"); 65 | hash.put(0, 1, "World"); 66 | ImmutableGrid test = ImmutableGrid.copyOf(hash); 67 | assertEquals(true, test.containsValue("Hello")); 68 | assertEquals(true, test.containsValue("World")); 69 | assertEquals(false, test.containsValue("Spicy")); 70 | assertEquals(false, test.containsValue("")); 71 | assertEquals(false, test.containsValue(null)); 72 | assertEquals(false, test.containsValue(Integer.valueOf(6))); 73 | } 74 | 75 | @SuppressWarnings("unlikely-arg-type") 76 | @Test 77 | public void test_equalsHashCode() { 78 | SparseGrid hash = SparseGrid.create(2, 3); 79 | hash.put(0, 0, "Hello"); 80 | hash.put(0, 1, "World"); 81 | ImmutableGrid test = ImmutableGrid.copyOf(hash); 82 | assertEquals(true, test.equals(test)); 83 | assertEquals(true, test.equals(ImmutableGrid.copyOf(hash))); 84 | assertEquals(true, test.equals(hash)); 85 | assertEquals(false, test.equals(null)); 86 | assertEquals(false, test.equals("")); 87 | 88 | assertEquals(2 ^ Integer.rotateLeft(3, 16) ^ test.cells().hashCode(), test.hashCode()); 89 | } 90 | 91 | } 92 | -------------------------------------------------------------------------------- /src/test/java/org/joda/collect/grid/TestImmutableCell.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014-present Stephen Colebourne 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.joda.collect.grid; 17 | 18 | import static org.junit.Assert.assertEquals; 19 | 20 | import org.joda.collect.grid.Grid.Cell; 21 | import org.junit.Test; 22 | 23 | /** 24 | * Test ImmutableCell. 25 | */ 26 | public class TestImmutableCell { 27 | 28 | @Test 29 | public void test_of() { 30 | Cell test = ImmutableCell.of(1, 2, "Hello"); 31 | assertEquals(1, test.getRow()); 32 | assertEquals(2, test.getColumn()); 33 | assertEquals("Hello", test.getValue()); 34 | assertEquals("(1,2)=Hello", test.toString()); 35 | } 36 | 37 | @Test(expected = IndexOutOfBoundsException.class) 38 | public void test_of_negativeRow() { 39 | ImmutableCell.of(-1, 2, "Hello"); 40 | } 41 | 42 | @Test(expected = IndexOutOfBoundsException.class) 43 | public void test_of_negativeColumn() { 44 | ImmutableCell.of(1, -2, "Hello"); 45 | } 46 | 47 | @Test(expected = IllegalArgumentException.class) 48 | public void test_of_nullValue() { 49 | ImmutableCell.of(1, 2, null); 50 | } 51 | 52 | //----------------------------------------------------------------------- 53 | @Test 54 | public void test_copyOf() { 55 | Cell base = new MockCell(1, 2, "Hello"); 56 | Cell test = ImmutableCell.copyOf(base); 57 | assertEquals(1, test.getRow()); 58 | assertEquals(2, test.getColumn()); 59 | assertEquals("Hello", test.getValue()); 60 | assertEquals("(1,2)=Hello", test.toString()); 61 | } 62 | 63 | @Test(expected = IllegalArgumentException.class) 64 | public void test_copyOf_nullCell() { 65 | ImmutableCell.copyOf(null); 66 | } 67 | 68 | @Test(expected = IndexOutOfBoundsException.class) 69 | public void test_copyOf_negativeRow() { 70 | ImmutableCell.copyOf(new MockCell(-1, 2, null)); 71 | } 72 | 73 | @Test(expected = IndexOutOfBoundsException.class) 74 | public void test_copyOf_negativeColumn() { 75 | ImmutableCell.copyOf(new MockCell(1, -2, null)); 76 | } 77 | 78 | @Test(expected = IllegalArgumentException.class) 79 | public void test_copyOf_nullValue() { 80 | ImmutableCell.copyOf(new MockCell(1, 2, null)); 81 | } 82 | 83 | //----------------------------------------------------------------------- 84 | @SuppressWarnings("unlikely-arg-type") 85 | @Test 86 | public void test_equalsHashCode() { 87 | Cell test = ImmutableCell.of(1, 2, "Hello"); 88 | assertEquals(true, test.equals(test)); 89 | assertEquals(true, test.equals(ImmutableCell.of(1, 2, "Hello"))); 90 | assertEquals(false, test.equals(ImmutableCell.of(0, 2, "Hello"))); 91 | assertEquals(false, test.equals(ImmutableCell.of(1, 3, "Hello"))); 92 | assertEquals(false, test.equals(ImmutableCell.of(1, 2, "Hell"))); 93 | assertEquals(false, test.equals(null)); 94 | assertEquals(false, test.equals("")); 95 | 96 | assertEquals("Hello".hashCode() ^ 1 ^ Integer.rotateLeft(2, 16), test.hashCode()); 97 | } 98 | 99 | } 100 | -------------------------------------------------------------------------------- /src/test/java/org/joda/collect/grid/TestEmptyGrid.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014-present Stephen Colebourne 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.joda.collect.grid; 17 | 18 | 19 | import static org.junit.Assert.assertEquals; 20 | 21 | import org.junit.Test; 22 | 23 | /** 24 | * Test EmptyGrid. 25 | */ 26 | public class TestEmptyGrid extends AbstractTestGrid { 27 | 28 | @Test 29 | public void test_factory() { 30 | ImmutableGrid test = ImmutableGrid.of(); 31 | assertEquals(0, test.rowCount()); 32 | assertEquals(0, test.columnCount()); 33 | checkGrid(test); 34 | assertEquals("[0x0:]", test.toString()); 35 | } 36 | 37 | //----------------------------------------------------------------------- 38 | @Test 39 | public void test_containsValue_Object() { 40 | ImmutableGrid test = ImmutableGrid.of(); 41 | assertEquals(false, test.containsValue("Hello")); 42 | assertEquals(false, test.containsValue("")); 43 | assertEquals(false, test.containsValue(null)); 44 | assertEquals(false, test.containsValue(Integer.valueOf(6))); 45 | } 46 | 47 | @SuppressWarnings("unlikely-arg-type") 48 | @Test 49 | public void test_equalsHashCode_0x0() { 50 | ImmutableGrid test = ImmutableGrid.of(); 51 | assertEquals(true, test.equals(test)); 52 | assertEquals(true, test.equals(ImmutableGrid.of())); 53 | assertEquals(true, test.equals(SparseGrid.create(0, 0))); 54 | assertEquals(false, test.equals(SparseGrid.create(1, 1))); 55 | assertEquals(true, test.equals(DenseGrid.create(0, 0))); 56 | assertEquals(false, test.equals(DenseGrid.create(1, 1))); 57 | assertEquals(false, test.equals(null)); 58 | assertEquals(false, test.equals("")); 59 | 60 | assertEquals(test.cells().hashCode(), test.hashCode()); 61 | } 62 | 63 | @SuppressWarnings("unlikely-arg-type") 64 | @Test 65 | public void test_equalsHashCode_1x2() { 66 | ImmutableGrid test = ImmutableGrid.of(1, 2); 67 | assertEquals(true, test.equals(test)); 68 | assertEquals(true, test.equals(ImmutableGrid.of(1, 2))); 69 | assertEquals(true, test.equals(SparseGrid.create(1, 2))); 70 | assertEquals(false, test.equals(SparseGrid.create(1, 1))); 71 | assertEquals(true, test.equals(DenseGrid.create(1, 2))); 72 | assertEquals(false, test.equals(DenseGrid.create(1, 1))); 73 | assertEquals(false, test.equals(null)); 74 | assertEquals(false, test.equals("")); 75 | 76 | assertEquals(1 ^ Integer.rotateLeft(2, 16) ^ test.cells().hashCode(), test.hashCode()); 77 | } 78 | 79 | //----------------------------------------------------------------------- 80 | @SuppressWarnings("deprecation") 81 | @Test(expected = UnsupportedOperationException.class) 82 | public void test_immutable_clear() { 83 | ImmutableGrid test = ImmutableGrid.of(); 84 | test.clear(); 85 | } 86 | 87 | @SuppressWarnings("deprecation") 88 | @Test(expected = UnsupportedOperationException.class) 89 | public void test_immutable_put() { 90 | ImmutableGrid test = ImmutableGrid.of(); 91 | test.put(0, 0, "Hello"); 92 | } 93 | 94 | @SuppressWarnings("deprecation") 95 | @Test(expected = UnsupportedOperationException.class) 96 | public void test_immutable_putAll() { 97 | ImmutableGrid test = ImmutableGrid.of(); 98 | test.putAll(ImmutableGrid.of()); 99 | } 100 | 101 | @SuppressWarnings("deprecation") 102 | @Test(expected = UnsupportedOperationException.class) 103 | public void test_immutable_remove() { 104 | ImmutableGrid test = ImmutableGrid.of(); 105 | test.remove(0, 0); 106 | } 107 | 108 | } 109 | -------------------------------------------------------------------------------- /src/main/java/org/joda/collect/grid/ImmutableCell.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014-present Stephen Colebourne 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.joda.collect.grid; 17 | 18 | import java.io.Serial; 19 | import java.io.Serializable; 20 | import org.joda.collect.grid.Grid.Cell; 21 | 22 | /** 23 | * Immutable implementations of the {@code Grid.Cell} data structure. 24 | * 25 | * @param the type of the value 26 | * @author Stephen Colebourne 27 | */ 28 | public final class ImmutableCell extends AbstractCell implements Serializable { 29 | 30 | /** Serialization version. */ 31 | @Serial 32 | private static final long serialVersionUID = 1L; 33 | 34 | /** 35 | * The row. 36 | */ 37 | private final int row; 38 | /** 39 | * The column. 40 | */ 41 | private final int column; 42 | /** 43 | * The value. 44 | */ 45 | private final V value; 46 | 47 | //----------------------------------------------------------------------- 48 | /** 49 | * Obtains an instance of {@code Cell}. 50 | * 51 | * @param the type of the value 52 | * @param row the row, zero or greater 53 | * @param column the column, zero or greater 54 | * @param value the value to put into the grid, not null 55 | * @return the immutable cell, not null 56 | * @throws IndexOutOfBoundsException if either index is less than zero 57 | */ 58 | public static ImmutableCell of(int row, int column, R value) { 59 | if (row < 0) { 60 | throw new IndexOutOfBoundsException("Row must not be negative: " + row + " < 0"); 61 | } 62 | if (column < 0) { 63 | throw new IndexOutOfBoundsException("Column must not be negative: " + column + " < 0"); 64 | } 65 | if (value == null) { 66 | throw new IllegalArgumentException("Value must not be null"); 67 | } 68 | return new ImmutableCell<>(row, column, value); 69 | } 70 | 71 | /** 72 | * Obtains an instance of {@code Cell}. 73 | * 74 | * @param the type of the value 75 | * @param cell the cell to copy, not null 76 | * @return the immutable cell, not null 77 | */ 78 | public static ImmutableCell copyOf(Cell cell) { 79 | if (cell == null) { 80 | throw new IllegalArgumentException("Cell must not be null"); 81 | } 82 | if (cell instanceof ImmutableCell) { 83 | @SuppressWarnings("unchecked") 84 | ImmutableCell result = (ImmutableCell) cell; 85 | return result; 86 | } 87 | return ImmutableCell.of(cell.getRow(), cell.getColumn(), cell.getValue()); 88 | } 89 | 90 | //----------------------------------------------------------------------- 91 | /** 92 | * Restricted constructor. 93 | */ 94 | ImmutableCell(int row, int column, V value) { 95 | this.row = row; 96 | this.column = column; 97 | this.value = value; 98 | } 99 | 100 | //----------------------------------------------------------------------- 101 | @Override 102 | public int getRow() { 103 | return row; 104 | } 105 | 106 | @Override 107 | public int getColumn() { 108 | return column; 109 | } 110 | 111 | @Override 112 | public V getValue() { 113 | return value; 114 | } 115 | 116 | /** 117 | * Validates this cell against the specified counts. 118 | */ 119 | ImmutableCell validateCounts(int rowCount, int columnCount) { 120 | if (row >= rowCount || column >= columnCount) { 121 | throw new IndexOutOfBoundsException( 122 | "Invalid row-column: " + row + "," + column + " for grid " + rowCount + "x" + columnCount); 123 | } 124 | return this; 125 | } 126 | 127 | } 128 | -------------------------------------------------------------------------------- /src/main/java/org/joda/collect/grid/SparseImmutableGrid.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014-present Stephen Colebourne 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.joda.collect.grid; 17 | 18 | import com.google.common.collect.ImmutableCollection; 19 | import com.google.common.collect.ImmutableSet; 20 | import com.google.common.collect.Iterables; 21 | import java.io.Serial; 22 | import java.io.Serializable; 23 | import java.util.ArrayList; 24 | import java.util.Arrays; 25 | import java.util.Collection; 26 | 27 | /** 28 | * Immutable implementation of the {@code Grid} data structure. 29 | * 30 | * @param the type of the value 31 | * @author Stephen Colebourne 32 | */ 33 | final class SparseImmutableGrid extends ImmutableGrid implements Serializable { 34 | 35 | /** Serialization version. */ 36 | @Serial 37 | private static final long serialVersionUID = 1L; 38 | 39 | /** 40 | * The row count. 41 | */ 42 | private final int rowCount; 43 | /** 44 | * The column count. 45 | */ 46 | private final int columnCount; 47 | /** 48 | * The keys. 49 | */ 50 | private final long[] keys; 51 | /** 52 | * The cells. 53 | */ 54 | private final Cell[] cells; 55 | /** 56 | * The cell set. 57 | */ 58 | private transient ImmutableSet> cellSet; 59 | /** 60 | * The values. 61 | */ 62 | private transient ImmutableCollection valueCollection; 63 | 64 | //----------------------------------------------------------------------- 65 | /** 66 | * Restricted constructor. 67 | */ 68 | @SuppressWarnings("unchecked") 69 | SparseImmutableGrid(Grid gridToCopy) { 70 | this.rowCount = gridToCopy.rowCount(); 71 | this.columnCount = gridToCopy.columnCount(); 72 | validateCounts(rowCount, columnCount); 73 | 74 | int size = gridToCopy.cells().size(); 75 | keys = new long[size]; 76 | cells = new Cell[size]; 77 | int i = 0; 78 | for (Cell cell : gridToCopy.cells()) { 79 | keys[i] = key(cell.getRow(), cell.getColumn()); 80 | cells[i] = ImmutableCell.copyOf(cell).validateCounts(rowCount, columnCount); 81 | i++; 82 | } 83 | } 84 | 85 | private long key(int row, int column) { 86 | return (((long) row) << 32) + column; 87 | } 88 | 89 | /** 90 | * Restricted constructor. 91 | */ 92 | @SuppressWarnings("unchecked") 93 | SparseImmutableGrid(int rowCount, int columnCount, Iterable> cells) { 94 | validateCounts(rowCount, columnCount); 95 | this.rowCount = rowCount; 96 | this.columnCount = columnCount; 97 | 98 | Collection> list; 99 | if (cells instanceof Collection) { 100 | list = (Collection>) cells; 101 | } else { 102 | list = new ArrayList<>(); 103 | Iterables.addAll(list, cells); 104 | } 105 | int size = list.size(); 106 | this.keys = new long[size]; 107 | this.cells = new Cell[size]; 108 | int i = 0; 109 | for (Cell cell : list) { 110 | this.keys[i] = key(cell.getRow(), cell.getColumn()); 111 | this.cells[i] = ImmutableCell.copyOf(cell).validateCounts(rowCount, columnCount); 112 | i++; 113 | } 114 | } 115 | 116 | //----------------------------------------------------------------------- 117 | @Override 118 | public int rowCount() { 119 | return rowCount; 120 | } 121 | 122 | @Override 123 | public int columnCount() { 124 | return columnCount; 125 | } 126 | 127 | //----------------------------------------------------------------------- 128 | @Override 129 | public int size() { 130 | return cells.length; 131 | } 132 | 133 | @Override 134 | public boolean contains(int row, int column) { 135 | if (exists(row, column)) { 136 | return Arrays.binarySearch(keys, key(row, column)) >= 0; 137 | } 138 | return false; 139 | } 140 | 141 | @Override 142 | public V get(int row, int column) { 143 | if (exists(row, column)) { 144 | int index = Arrays.binarySearch(keys, key(row, column)); 145 | if (index >= 0) { 146 | return cells[index].getValue(); 147 | } 148 | } 149 | return null; 150 | } 151 | 152 | @Override 153 | public Cell cell(int row, int column) { 154 | if (exists(row, column)) { 155 | int index = Arrays.binarySearch(keys, key(row, column)); 156 | if (index >= 0) { 157 | return cells[index]; 158 | } 159 | } 160 | return null; 161 | } 162 | 163 | //----------------------------------------------------------------------- 164 | @Override 165 | public ImmutableSet> cells() { 166 | ImmutableSet> c = cellSet; 167 | if (c == null) { 168 | c = ImmutableSet.copyOf(cells); 169 | cellSet = c; 170 | } 171 | return c; 172 | } 173 | 174 | @Override 175 | public ImmutableCollection values() { 176 | ImmutableCollection v = valueCollection; 177 | if (v == null) { 178 | v = super.values(); 179 | valueCollection = v; 180 | } 181 | return v; 182 | } 183 | 184 | } 185 | -------------------------------------------------------------------------------- /src/test/java/org/joda/collect/grid/TestSingletonGrid.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014-present Stephen Colebourne 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.joda.collect.grid; 17 | 18 | import static org.junit.Assert.assertEquals; 19 | 20 | import org.junit.Test; 21 | 22 | /** 23 | * Test SingletonGrid. 24 | */ 25 | public class TestSingletonGrid extends AbstractTestGrid { 26 | 27 | @Test 28 | public void test_factory_simple() { 29 | ImmutableGrid test = ImmutableGrid.of("Hello"); 30 | assertEquals(1, test.rowCount()); 31 | assertEquals(1, test.columnCount()); 32 | checkGrid(test, 0, 0, "Hello"); 33 | assertEquals("[1x1:(0,0)=Hello]", test.toString()); 34 | } 35 | 36 | @Test(expected = IllegalArgumentException.class) 37 | public void test_factory_simple_nullValue() { 38 | ImmutableGrid.of(null); 39 | } 40 | 41 | //----------------------------------------------------------------------- 42 | @Test 43 | public void test_factory_full() { 44 | ImmutableGrid test = ImmutableGrid.of(2, 2, 0, 1, "Hello"); 45 | assertEquals(2, test.rowCount()); 46 | assertEquals(2, test.columnCount()); 47 | checkGrid(test, 0, 1, "Hello"); 48 | assertEquals("[2x2:(0,1)=Hello]", test.toString()); 49 | } 50 | 51 | //----------------------------------------------------------------------- 52 | @Test(expected = IllegalArgumentException.class) 53 | public void test_factory_full_negativeRowCount() { 54 | ImmutableGrid.of(-2, 3, 0, 1, "World"); 55 | } 56 | 57 | @Test(expected = IllegalArgumentException.class) 58 | public void test_factory_full_negativeColumnCount() { 59 | ImmutableGrid.of(2, -3, 0, 1, "World"); 60 | } 61 | 62 | @Test(expected = IndexOutOfBoundsException.class) 63 | public void test_factory_full_negativeRow() { 64 | ImmutableGrid.of(2, 2, -1, 2, "Hello"); 65 | } 66 | 67 | @Test(expected = IndexOutOfBoundsException.class) 68 | public void test_factory_full_negativeColumn() { 69 | ImmutableGrid.of(2, 2, 1, -2, "Hello"); 70 | } 71 | 72 | @Test(expected = IndexOutOfBoundsException.class) 73 | public void test_factory_full_rowEqualRowCount() { 74 | ImmutableGrid.of(1, 1, 1, 0, "World"); 75 | } 76 | 77 | @Test(expected = IndexOutOfBoundsException.class) 78 | public void test_factory_full_columnEqualColumnCount() { 79 | ImmutableGrid.of(1, 1, 0, 1, "World"); 80 | } 81 | 82 | @Test(expected = IllegalArgumentException.class) 83 | public void test_factory_full_nullValue() { 84 | ImmutableGrid.of(2, 2, 1, 2, null); 85 | } 86 | 87 | //----------------------------------------------------------------------- 88 | @Test 89 | public void test_containsValue_Object() { 90 | ImmutableGrid test = create(); 91 | assertEquals(true, test.containsValue("Hello")); 92 | assertEquals(false, test.containsValue("Spicy")); 93 | assertEquals(false, test.containsValue("")); 94 | assertEquals(false, test.containsValue(null)); 95 | assertEquals(false, test.containsValue(Integer.valueOf(6))); 96 | } 97 | 98 | @SuppressWarnings("unlikely-arg-type") 99 | @Test 100 | public void test_equalsHashCode() { 101 | ImmutableGrid test = create(); 102 | assertEquals(true, test.equals(test)); 103 | assertEquals(true, test.equals(create())); 104 | 105 | SparseGrid hash = SparseGrid.create(2, 3); 106 | hash.put(0, 1, "Hello"); 107 | assertEquals(true, test.equals(hash)); 108 | 109 | SparseGrid colCountDiff = SparseGrid.create(2, 4); 110 | colCountDiff.put(0, 1, "Hello"); 111 | assertEquals(false, test.equals(colCountDiff)); 112 | 113 | SparseGrid valueDiff = SparseGrid.create(2, 3); 114 | valueDiff.put(0, 1, "World"); 115 | assertEquals(false, test.equals(valueDiff)); 116 | 117 | assertEquals(false, test.equals(null)); 118 | assertEquals(false, test.equals("")); 119 | 120 | assertEquals(2 ^ Integer.rotateLeft(3, 16) ^ test.cells().hashCode(), test.hashCode()); 121 | } 122 | 123 | //----------------------------------------------------------------------- 124 | @SuppressWarnings("deprecation") 125 | @Test(expected = UnsupportedOperationException.class) 126 | public void test_immutable_clear() { 127 | ImmutableGrid test = create(); 128 | test.clear(); 129 | } 130 | 131 | @SuppressWarnings("deprecation") 132 | @Test(expected = UnsupportedOperationException.class) 133 | public void test_immutable_put() { 134 | ImmutableGrid test = create(); 135 | test.put(0, 0, "Hello"); 136 | } 137 | 138 | @SuppressWarnings("deprecation") 139 | @Test(expected = UnsupportedOperationException.class) 140 | public void test_immutable_putAll() { 141 | ImmutableGrid test = create(); 142 | test.putAll(ImmutableGrid.of()); 143 | } 144 | 145 | @SuppressWarnings("deprecation") 146 | @Test(expected = UnsupportedOperationException.class) 147 | public void test_immutable_remove() { 148 | ImmutableGrid test = create(); 149 | test.remove(0, 0); 150 | } 151 | 152 | private ImmutableGrid create() { 153 | return ImmutableGrid.of(2, 3, 0, 1, "Hello"); 154 | } 155 | 156 | } 157 | -------------------------------------------------------------------------------- /src/main/checkstyle/checkstyle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | -------------------------------------------------------------------------------- /src/main/java/org/joda/collect/grid/SparseGrid.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014-present Stephen Colebourne 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.joda.collect.grid; 17 | 18 | import com.google.common.collect.ForwardingSortedSet; 19 | import java.io.Serial; 20 | import java.io.Serializable; 21 | import java.util.Collection; 22 | import java.util.Iterator; 23 | import java.util.Set; 24 | import java.util.SortedSet; 25 | import java.util.TreeSet; 26 | 27 | /** 28 | * Mutable implementation of the {@code Grid} data structure based on hashing. 29 | * 30 | * @param the type of the value 31 | * @author Stephen Colebourne 32 | */ 33 | public final class SparseGrid extends AbstractGrid implements Serializable { 34 | 35 | /** Serialization version. */ 36 | @Serial 37 | private static final long serialVersionUID = 1L; 38 | 39 | /** 40 | * The row count. 41 | */ 42 | private final int rowCount; 43 | /** 44 | * The column count. 45 | */ 46 | private final int columnCount; 47 | /** 48 | * The cells. 49 | */ 50 | private final SortedSet> cells; 51 | 52 | //----------------------------------------------------------------------- 53 | /** 54 | * Creates an empty {@code SparseGrid} of the specified row-column count. 55 | * 56 | * @param the type of the value 57 | * @param rowCount the number of rows, zero or greater 58 | * @param columnCount the number of columns, zero or greater 59 | * @return the mutable grid, not null 60 | */ 61 | public static SparseGrid create(int rowCount, int columnCount) { 62 | return new SparseGrid<>(rowCount, columnCount, new TreeSet<>(AbstractCell.comparator())); 63 | } 64 | 65 | /** 66 | * Creates a {@code SparseGrid} copying from another grid. 67 | * 68 | * @param the type of the value 69 | * @param grid the grid to copy, not null 70 | * @return the mutable grid, not null 71 | */ 72 | public static SparseGrid create(Grid grid) { 73 | if (grid == null) { 74 | throw new IllegalArgumentException("Grid must not be null"); 75 | } 76 | SparseGrid created = SparseGrid.create(grid.rowCount(), grid.columnCount()); 77 | created.putAll(grid); 78 | return created; 79 | } 80 | 81 | //----------------------------------------------------------------------- 82 | /** 83 | * Restricted constructor. 84 | */ 85 | SparseGrid(int rowCount, int columnCount, SortedSet> data) { 86 | validateCounts(rowCount, columnCount); 87 | this.rowCount = rowCount; 88 | this.columnCount = columnCount; 89 | this.cells = data; 90 | } 91 | 92 | //----------------------------------------------------------------------- 93 | @Override 94 | public int rowCount() { 95 | return rowCount; 96 | } 97 | 98 | @Override 99 | public int columnCount() { 100 | return columnCount; 101 | } 102 | 103 | //----------------------------------------------------------------------- 104 | @Override 105 | public int size() { 106 | return cells.size(); 107 | } 108 | 109 | @Override 110 | public Cell cell(int row, int column) { 111 | if (exists(row, column)) { 112 | SortedSet> tail = cells.tailSet(finder(row, column)); 113 | if (!tail.isEmpty()) { 114 | Cell cell = tail.first(); 115 | if (cell.getRow() == row && cell.getColumn() == column) { 116 | return cell; 117 | } 118 | } 119 | } 120 | return null; 121 | } 122 | 123 | //----------------------------------------------------------------------- 124 | @Override 125 | public SortedSet> cells() { 126 | return new ForwardingSortedSet<>() { 127 | @Override 128 | protected SortedSet> delegate() { 129 | return cells; 130 | } 131 | @Override 132 | public boolean add(Cell element) { 133 | return super.add(ImmutableCell.copyOf(element)); 134 | } 135 | @Override 136 | public boolean addAll(Collection> collection) { 137 | return super.standardAddAll(collection); 138 | } 139 | }; 140 | } 141 | 142 | //----------------------------------------------------------------------- 143 | @Override 144 | public void clear() { 145 | cells.clear(); 146 | } 147 | 148 | @Override 149 | public void put(int row, int column, V value) { 150 | if (!exists(row, column)) { 151 | throw new IndexOutOfBoundsException("Invalid row-column: " + row + "," + column); 152 | } 153 | Cell cell = ImmutableCell.of(row, column, value); 154 | cells.remove(cell); 155 | cells.add(cell); 156 | } 157 | 158 | @Override 159 | public void putAll(Grid grid) { 160 | if (grid == null) { 161 | throw new IllegalArgumentException("Grid must nor be null"); 162 | } 163 | for (Cell cell : grid.cells()) { 164 | cells.add(ImmutableCell.copyOf(cell)); 165 | } 166 | } 167 | 168 | @Override 169 | public boolean remove(int row, int column) { 170 | if (exists(row, column)) { 171 | Set> tail = cells.tailSet(finder(row, column)); 172 | if (!tail.isEmpty()) { 173 | Iterator> it = tail.iterator(); 174 | Cell cell = it.next(); 175 | if (cell.getRow() == row && cell.getColumn() == column) { 176 | it.remove(); 177 | return true; 178 | } 179 | } 180 | } 181 | return false; 182 | } 183 | 184 | } 185 | -------------------------------------------------------------------------------- /src/main/java/org/joda/collect/grid/AbstractGrid.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014-present Stephen Colebourne 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.joda.collect.grid; 17 | 18 | import com.google.common.base.Preconditions; 19 | import com.google.common.collect.ImmutableCollection; 20 | import com.google.common.collect.ImmutableList; 21 | import com.google.common.collect.ImmutableList.Builder; 22 | import java.util.AbstractList; 23 | import java.util.List; 24 | 25 | /** 26 | * Abstract implementation of the {@code Grid} data structure. 27 | * 28 | * @param the type of the value 29 | * @author Stephen Colebourne 30 | */ 31 | abstract class AbstractGrid implements Grid { 32 | 33 | /** 34 | * Validates the row and column counts. 35 | * 36 | * @param rowCount the row count 37 | * @param columnCount the column count 38 | */ 39 | static void validateCounts(int rowCount, int columnCount) { 40 | if (rowCount < 0) { 41 | throw new IllegalArgumentException("Row count must not be negative: " + rowCount + " < 0"); 42 | } 43 | if (columnCount < 0) { 44 | throw new IllegalArgumentException("Column count must not be negative: " + columnCount + " < 0"); 45 | } 46 | } 47 | 48 | //----------------------------------------------------------------------- 49 | /** 50 | * Restricted constructor. 51 | */ 52 | AbstractGrid() { 53 | } 54 | 55 | //----------------------------------------------------------------------- 56 | @Override 57 | public boolean exists(int row, int column) { 58 | return row >= 0 && row < rowCount() && column >= 0 && column < columnCount(); 59 | } 60 | 61 | //----------------------------------------------------------------------- 62 | @Override 63 | public boolean isFull() { 64 | return size() == rowCount() * columnCount(); 65 | } 66 | 67 | @Override 68 | public boolean isEmpty() { 69 | return size() == 0; 70 | } 71 | 72 | @Override 73 | public int size() { 74 | return cells().size(); 75 | } 76 | 77 | @Override 78 | public boolean contains(int row, int column) { 79 | return cell(row, column) != null; 80 | } 81 | 82 | @Override 83 | public boolean containsValue(Object valueToFind) { 84 | return (valueToFind != null && values().contains(valueToFind)); 85 | } 86 | 87 | @Override 88 | public V get(int row, int column) { 89 | Cell cell = cell(row, column); 90 | return (cell != null ? cell.getValue() : null); 91 | } 92 | 93 | @Override 94 | public Cell cell(int row, int column) { 95 | if (exists(row, column)) { 96 | for (Cell cell : cells()) { 97 | if (cell.getRow() == row && cell.getColumn() == column) { 98 | return cell; 99 | } 100 | } 101 | } 102 | return null; 103 | } 104 | 105 | //----------------------------------------------------------------------- 106 | @Override 107 | public ImmutableCollection values() { 108 | Builder builder = ImmutableList.builder(); 109 | for (Cell cell : cells()) { 110 | builder.add(cell.getValue()); 111 | } 112 | return builder.build(); 113 | } 114 | 115 | //----------------------------------------------------------------------- 116 | @Override 117 | public List row(int row) { 118 | Preconditions.checkElementIndex(row, rowCount(), "Row index"); 119 | return new Inner<>(this, columnCount(), row, true); 120 | } 121 | 122 | @Override 123 | public List> rows() { 124 | return new Outer<>(this, rowCount(), columnCount(), true); 125 | } 126 | 127 | @Override 128 | public List column(int column) { 129 | Preconditions.checkElementIndex(column, columnCount(), "Column index"); 130 | return new Inner<>(this, rowCount(), column, false); 131 | } 132 | 133 | @Override 134 | public List> columns() { 135 | return new Outer<>(this, columnCount(), rowCount(), false); 136 | } 137 | 138 | static class Outer extends AbstractList> { 139 | private final Grid grid; 140 | private final int size; 141 | private final int innerSize; 142 | private final boolean rows; 143 | 144 | Outer(Grid grid, int size, int innerSize, boolean rows) { 145 | this.grid = grid; 146 | this.size = size; 147 | this.innerSize = innerSize; 148 | this.rows = rows; 149 | } 150 | 151 | @Override 152 | public int size() { 153 | return size; 154 | } 155 | 156 | @Override 157 | public List get(int index) { 158 | Preconditions.checkElementIndex(index, size); 159 | return new Inner<>(grid, innerSize, index, rows); 160 | } 161 | } 162 | 163 | static class Inner extends AbstractList { 164 | private final Grid grid; 165 | private final int size; 166 | private final int outerIndex; 167 | private final boolean rows; 168 | 169 | Inner(Grid grid, int size, int outerIndex, boolean rows) { 170 | this.grid = grid; 171 | this.size = size; 172 | this.outerIndex = outerIndex; 173 | this.rows = rows; 174 | } 175 | 176 | @Override 177 | public int size() { 178 | return size; 179 | } 180 | 181 | @Override 182 | public V get(int index) { 183 | Preconditions.checkElementIndex(index, size); 184 | if (rows) { 185 | return grid.get(outerIndex, index); 186 | } else { 187 | return grid.get(index, outerIndex); 188 | } 189 | } 190 | 191 | @Override 192 | public V set(int index, V newValue) { 193 | V old = get(index); 194 | if (rows) { 195 | if (newValue == null) { 196 | grid.remove(outerIndex, index); 197 | } else { 198 | grid.put(outerIndex, index, newValue); 199 | } 200 | } else { 201 | if (newValue == null) { 202 | grid.remove(index, outerIndex); 203 | } else { 204 | grid.put(index, outerIndex, newValue); 205 | } 206 | } 207 | return old; 208 | } 209 | } 210 | 211 | //----------------------------------------------------------------------- 212 | Cell finder(int row, int column) { 213 | @SuppressWarnings({"unchecked", "rawtypes"}) 214 | Cell finder = new ImmutableCell(row, column, ""); 215 | return finder; 216 | } 217 | 218 | //----------------------------------------------------------------------- 219 | @Override 220 | public boolean equals(Object obj) { 221 | return obj == this || 222 | (obj instanceof Grid other && 223 | rowCount() == other.rowCount() && 224 | columnCount() == other.columnCount() && 225 | cells().equals(other.cells())); 226 | } 227 | 228 | @Override 229 | public int hashCode() { 230 | return rowCount() ^ Integer.rotateLeft(columnCount(), 16) ^ cells().hashCode(); 231 | } 232 | 233 | @Override 234 | public String toString() { 235 | StringBuilder buf = new StringBuilder(size() * 16); 236 | buf.append('[').append(rowCount()).append('x').append(columnCount()).append(':'); 237 | if (size() > 0) { 238 | for (Cell cell : cells()) { 239 | buf.append(cell).append(',').append(' '); 240 | } 241 | buf.setLength(buf.length() - 2); 242 | } 243 | buf.append(']'); 244 | return buf.toString(); 245 | } 246 | 247 | } 248 | -------------------------------------------------------------------------------- /src/main/java/org/joda/collect/grid/ImmutableGrid.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014-present Stephen Colebourne 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.joda.collect.grid; 17 | 18 | /** 19 | * Immutable implementation of the {@code Grid} data structure. 20 | * 21 | * @param the type of the value 22 | * @author Stephen Colebourne 23 | */ 24 | public abstract class ImmutableGrid extends AbstractGrid { 25 | 26 | /** 27 | * Obtains an empty immutable grid with zero row-column count. 28 | * 29 | * @param the type of the value 30 | * @return the empty immutable grid, not null 31 | */ 32 | public static ImmutableGrid of() { 33 | return new EmptyGrid<>(); 34 | } 35 | 36 | /** 37 | * Obtains an empty immutable grid of the specified row-column count. 38 | * 39 | * @param the type of the value 40 | * @param rowCount the number of rows, zero or greater 41 | * @param columnCount the number of columns, zero or greater 42 | * @return the empty immutable grid, not null 43 | */ 44 | public static ImmutableGrid of(int rowCount, int columnCount) { 45 | return new EmptyGrid<>(rowCount, columnCount); 46 | } 47 | 48 | /** 49 | * Obtains an immutable grid with row-column count 1x1 and a single cell. 50 | *

51 | * The single cell is at row zero column zero. 52 | * 53 | * @param the type of the value 54 | * @param value the value of the single cell, not null 55 | * @return the empty immutable grid, not null 56 | */ 57 | public static ImmutableGrid of(R value) { 58 | return new SingletonGrid<>(1, 1, 0, 0, value); 59 | } 60 | 61 | /** 62 | * Obtains an immutable grid of the specified row-column count with a single cell. 63 | * 64 | * @param the type of the value 65 | * @param rowCount the number of rows, zero or greater 66 | * @param columnCount the number of columns, zero or greater 67 | * @param row the row of the single cell, zero or greater 68 | * @param column the column of the single cell, zero or greater 69 | * @param value the value of the single cell, not null 70 | * @return the empty immutable grid, not null 71 | */ 72 | public static ImmutableGrid of(int rowCount, int columnCount, int row, int column, R value) { 73 | return new SingletonGrid<>(rowCount, columnCount, row, column, value); 74 | } 75 | 76 | //----------------------------------------------------------------------- 77 | /** 78 | * Obtains an immutable grid with one cell. 79 | * 80 | * @param the type of the value 81 | * @param rowCount the number of rows, zero or greater 82 | * @param columnCount the number of columns, zero or greater 83 | * @param cell the cell that the grid should contain, not null 84 | * @return the immutable grid, not null 85 | * @throws IndexOutOfBoundsException if either index is less than zero 86 | */ 87 | public static ImmutableGrid copyOf(int rowCount, int columnCount, Cell cell) { 88 | if (cell == null) { 89 | throw new IllegalArgumentException("Cell must not be null"); 90 | } 91 | return new SingletonGrid<>(rowCount, columnCount, cell); 92 | } 93 | 94 | /** 95 | * Obtains an immutable grid by copying a set of cells. 96 | * 97 | * @param the type of the value 98 | * @param rowCount the number of rows, zero or greater 99 | * @param columnCount the number of columns, zero or greater 100 | * @param cells the cells to copy, not null 101 | * @return the immutable grid, not null 102 | * @throws IndexOutOfBoundsException if either index is less than zero 103 | */ 104 | public static ImmutableGrid copyOf(int rowCount, int columnCount, Iterable> cells) { 105 | if (cells == null) { 106 | throw new IllegalArgumentException("Cells must not be null"); 107 | } 108 | if (!cells.iterator().hasNext()) { 109 | return new EmptyGrid<>(rowCount, columnCount); 110 | } 111 | return new SparseImmutableGrid<>(rowCount, columnCount, cells); 112 | } 113 | 114 | /** 115 | * Obtains an immutable grid by copying a set of cells, deriving the row and column count. 116 | *

117 | * The row and column counts are calculated as the maximum row and column specified. 118 | * 119 | * @param the type of the value 120 | * @param cells the cells to copy, not null 121 | * @return the immutable grid, not null 122 | * @throws IndexOutOfBoundsException if either index is less than zero 123 | */ 124 | public static ImmutableGrid copyOfDeriveCounts(Iterable> cells) { 125 | if (cells == null) { 126 | throw new IllegalArgumentException("Cells must not be null"); 127 | } 128 | if (!cells.iterator().hasNext()) { 129 | return new EmptyGrid<>(); 130 | } 131 | int rowCount = 0; 132 | int columnCount = 0; 133 | for (Cell cell : cells) { 134 | rowCount = Math.max(rowCount, cell.getRow()); 135 | columnCount = Math.max(columnCount, cell.getColumn()); 136 | } 137 | return new SparseImmutableGrid<>(rowCount + 1, columnCount + 1, cells); 138 | } 139 | 140 | //----------------------------------------------------------------------- 141 | /** 142 | * Obtains an immutable grid by copying another grid. 143 | *

144 | * If you need to change the row-column count, use {@link #copyOf(int, int, Iterable)} 145 | * passing in the set of cells from the grid. 146 | * 147 | * @param the type of the value 148 | * @param grid the grid to copy, not null 149 | * @return the immutable grid, not null 150 | * @throws IndexOutOfBoundsException if either index is less than zero 151 | */ 152 | public static ImmutableGrid copyOf(Grid grid) { 153 | if (grid == null) { 154 | throw new IllegalArgumentException("Grid must not be null"); 155 | } 156 | if (grid instanceof ImmutableGrid) { 157 | return (ImmutableGrid) grid; 158 | } 159 | validateCounts(grid.rowCount(), grid.columnCount()); 160 | if (grid.isEmpty()) { 161 | return new EmptyGrid<>(grid.rowCount(), grid.columnCount()); 162 | } 163 | if (grid.size() == 1) { 164 | Cell cell = grid.cells().iterator().next(); 165 | return new SingletonGrid<>(grid.rowCount(), grid.columnCount(), cell); 166 | } 167 | if (grid.size() >= (grid.rowCount() * grid.columnCount() / 2)) { 168 | return DenseImmutableGrid.create(grid); 169 | } 170 | return new SparseImmutableGrid<>(grid); 171 | } 172 | 173 | //----------------------------------------------------------------------- 174 | /** 175 | * Restricted constructor. 176 | */ 177 | ImmutableGrid() { 178 | } 179 | 180 | //----------------------------------------------------------------------- 181 | /** 182 | * {@inheritDoc} 183 | * @deprecated Grid is read-only 184 | */ 185 | @Deprecated 186 | @Override 187 | public void clear() { 188 | throw new UnsupportedOperationException("Grid is read-only"); 189 | } 190 | 191 | /** 192 | * {@inheritDoc} 193 | * @deprecated Grid is read-only 194 | */ 195 | @Deprecated 196 | @Override 197 | public void put(int row, int column, V value) { 198 | throw new UnsupportedOperationException("Grid is read-only"); 199 | } 200 | 201 | /** 202 | * {@inheritDoc} 203 | * @deprecated Grid is read-only 204 | */ 205 | @Deprecated 206 | @Override 207 | public void putAll(Grid grid) { 208 | throw new UnsupportedOperationException("Grid is read-only"); 209 | } 210 | 211 | /** 212 | * {@inheritDoc} 213 | * @deprecated Grid is read-only 214 | */ 215 | @Deprecated 216 | @Override 217 | public boolean remove(int row, int column) { 218 | throw new UnsupportedOperationException("Grid is read-only"); 219 | } 220 | 221 | } 222 | -------------------------------------------------------------------------------- /src/test/java/org/joda/collect/grid/TestImmutableGrid.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014-present Stephen Colebourne 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.joda.collect.grid; 17 | 18 | import static org.junit.Assert.assertEquals; 19 | import static org.junit.Assert.assertSame; 20 | 21 | import org.joda.collect.grid.Grid.Cell; 22 | import org.junit.Test; 23 | 24 | import com.google.common.collect.ImmutableList; 25 | import com.google.common.collect.ImmutableSet; 26 | 27 | /** 28 | * Test ImmutableGrid factories. 29 | */ 30 | public class TestImmutableGrid extends AbstractTestGrid { 31 | 32 | @Test 33 | public void test_copyOf_Grid_alreadyImmutable() { 34 | SparseGrid hash = SparseGrid.create(2, 3); 35 | hash.put(0, 0, "Hello"); 36 | hash.put(0, 1, "World"); 37 | ImmutableGrid base = ImmutableGrid.copyOf(hash); 38 | ImmutableGrid test = ImmutableGrid.copyOf(base); 39 | assertSame(base, test); 40 | } 41 | 42 | @Test 43 | public void test_copyOf_Grid_size0() { 44 | SparseGrid hash = SparseGrid.create(2, 3); 45 | ImmutableGrid test = ImmutableGrid.copyOf(hash); 46 | assertEquals(2, test.rowCount()); 47 | assertEquals(3, test.columnCount()); 48 | checkGrid(test); 49 | assertEquals(true, test instanceof EmptyGrid); 50 | } 51 | 52 | @Test 53 | public void test_copyOf_Grid_size1() { 54 | SparseGrid hash = SparseGrid.create(2, 3); 55 | hash.put(0, 1, "Hello"); 56 | ImmutableGrid test = ImmutableGrid.copyOf(hash); 57 | assertEquals(2, test.rowCount()); 58 | assertEquals(3, test.columnCount()); 59 | checkGrid(test, 0, 1, "Hello"); 60 | assertEquals(true, test instanceof SingletonGrid); 61 | } 62 | 63 | @Test 64 | public void test_copyOf_Grid_userGridImplementationSize1() { 65 | ImmutableGrid test = ImmutableGrid.copyOf(new MockSingletonGrid(2, 3, 0, 1, "Hello")); 66 | assertEquals(2, test.rowCount()); 67 | assertEquals(3, test.columnCount()); 68 | checkGrid(test, 0, 1, "Hello"); 69 | assertEquals(true, test instanceof SingletonGrid); 70 | } 71 | 72 | @Test(expected = IllegalArgumentException.class) 73 | public void test_copyOf_Grid_badGrid_negativeRowCount() { 74 | ImmutableGrid.copyOf(new MockSingletonGrid(-2, 3, 0, 1, "World")); 75 | } 76 | 77 | @Test(expected = IllegalArgumentException.class) 78 | public void test_copyOf_Grid_badGrid_negativeColumnCount() { 79 | ImmutableGrid.copyOf(new MockSingletonGrid(2, -3, 0, 1, "World")); 80 | } 81 | 82 | @Test(expected = IndexOutOfBoundsException.class) 83 | public void test_copyOf_Grid_badGrid_negativeRow() { 84 | ImmutableGrid.copyOf(new MockSingletonGrid(2, 2, -1, 2, "Hello")); 85 | } 86 | 87 | @Test(expected = IndexOutOfBoundsException.class) 88 | public void test_copyOf_Grid_badGrid_negativeColumn() { 89 | ImmutableGrid.copyOf(new MockSingletonGrid(2, 2, 1, -2, "Hello")); 90 | } 91 | 92 | @Test(expected = IndexOutOfBoundsException.class) 93 | public void test_copyOf_Grid_badGrid_rowEqualRowCount() { 94 | ImmutableGrid.copyOf(new MockSingletonGrid(1, 1, 1, 0, "World")); 95 | } 96 | 97 | @Test(expected = IndexOutOfBoundsException.class) 98 | public void test_copyOf_Grid_badGrid_columnEqualColumnCount() { 99 | ImmutableGrid.copyOf(new MockSingletonGrid(1, 1, 0, 1, "World")); 100 | } 101 | 102 | @Test(expected = IllegalArgumentException.class) 103 | public void test_copyOf_Grid_badGrid_nullValue() { 104 | ImmutableGrid.copyOf(new MockSingletonGrid(2, 2, 1, 2, null)); 105 | } 106 | 107 | @Test(expected = IllegalArgumentException.class) 108 | public void test_copyOf_Grid_null() { 109 | ImmutableGrid.copyOf((Grid) null); 110 | } 111 | 112 | //----------------------------------------------------------------------- 113 | @Test 114 | public void test_copyOf_intIntCell() { 115 | ImmutableGrid test = ImmutableGrid.copyOf(2, 3, ImmutableCell.of(0, 1, "Hello")); 116 | assertEquals(2, test.rowCount()); 117 | assertEquals(3, test.columnCount()); 118 | checkGrid(test, 0, 1, "Hello"); 119 | assertEquals(true, test instanceof SingletonGrid); 120 | } 121 | 122 | @Test(expected = IllegalArgumentException.class) 123 | public void test_copyOf_intIntCell_negativeRowCount() { 124 | ImmutableGrid.copyOf(-2, 3, new MockCell(0, 1, "World")); 125 | } 126 | 127 | @Test(expected = IllegalArgumentException.class) 128 | public void test_copyOf_intIntCell_negativeColumnCount() { 129 | ImmutableGrid.copyOf(2, -3, new MockCell(0, 1, "World")); 130 | } 131 | 132 | @Test(expected = IndexOutOfBoundsException.class) 133 | public void test_copyOf_intIntCell_negativeRow() { 134 | ImmutableGrid.copyOf(2, 2, new MockCell(-1, 2, "Hello")); 135 | } 136 | 137 | @Test(expected = IndexOutOfBoundsException.class) 138 | public void test_copyOf_intIntCell_negativeColumn() { 139 | ImmutableGrid.copyOf(2, 2, new MockCell(1, -2, "Hello")); 140 | } 141 | 142 | @Test(expected = IndexOutOfBoundsException.class) 143 | public void test_copyOf_intIntCell_rowEqualRowCount() { 144 | ImmutableGrid.copyOf(1, 1, new MockCell(1, 0, "World")); 145 | } 146 | 147 | @Test(expected = IndexOutOfBoundsException.class) 148 | public void test_copyOf_intIntCell_columnEqualColumnCount() { 149 | ImmutableGrid.copyOf(1, 1, new MockCell(0, 1, "World")); 150 | } 151 | 152 | @Test(expected = IllegalArgumentException.class) 153 | public void test_copyOf_intIntCell_nullValue() { 154 | ImmutableGrid.copyOf(2, 2, new MockCell(0, 1, null)); 155 | } 156 | 157 | @Test(expected = IllegalArgumentException.class) 158 | public void test_copyOf_intIntCell_null() { 159 | ImmutableGrid.copyOf(2, 2, (Cell) null); 160 | } 161 | 162 | //----------------------------------------------------------------------- 163 | @Test 164 | public void test_copyOf_intIntCells() { 165 | ImmutableGrid test = ImmutableGrid.copyOf(2, 3, ImmutableList.of(ImmutableCell.of(0, 1, "Hello"), ImmutableCell.of(0, 2, "World"))); 166 | assertEquals(2, test.rowCount()); 167 | assertEquals(3, test.columnCount()); 168 | checkGrid(test, 0, 1, "Hello", 0, 2, "World"); 169 | } 170 | 171 | @Test 172 | public void test_copyOf_intIntCells_empty() { 173 | ImmutableGrid test = ImmutableGrid.copyOf(2, 3, ImmutableList.>of()); 174 | assertEquals(2, test.rowCount()); 175 | assertEquals(3, test.columnCount()); 176 | checkGrid(test); 177 | assertEquals(true, test instanceof EmptyGrid); 178 | } 179 | 180 | @Test(expected = IllegalArgumentException.class) 181 | public void test_copyOf_intIntCells_negativeRowCount() { 182 | ImmutableGrid.copyOf(-2, 3, ImmutableList.of(new MockCell(0, 1, "World"))); 183 | } 184 | 185 | @Test(expected = IllegalArgumentException.class) 186 | public void test_copyOf_intIntCells_negativeColumnCount() { 187 | ImmutableGrid.copyOf(2, -3, ImmutableList.of(new MockCell(0, 1, "World"))); 188 | } 189 | 190 | @Test(expected = IndexOutOfBoundsException.class) 191 | public void test_copyOf_intIntCells_negativeRow() { 192 | ImmutableGrid.copyOf(2, 2, ImmutableList.of(new MockCell(-1, 2, "Hello"))); 193 | } 194 | 195 | @Test(expected = IndexOutOfBoundsException.class) 196 | public void test_copyOf_intIntCells_negativeColumn() { 197 | ImmutableGrid.copyOf(2, 2, ImmutableList.of(new MockCell(1, -2, "Hello"))); 198 | } 199 | 200 | @Test(expected = IndexOutOfBoundsException.class) 201 | public void test_copyOf_intIntCells_rowEqualRowCount() { 202 | ImmutableGrid.copyOf(1, 1, ImmutableList.of(new MockCell(1, 0, "World"))); 203 | } 204 | 205 | @Test(expected = IndexOutOfBoundsException.class) 206 | public void test_copyOf_intIntCells_columnEqualColumnCount() { 207 | ImmutableGrid.copyOf(1, 1, ImmutableList.of(new MockCell(0, 1, "World"))); 208 | } 209 | 210 | @Test(expected = IllegalArgumentException.class) 211 | public void test_copyOf_intIntCells_nullValue() { 212 | ImmutableGrid.copyOf(2, 2, ImmutableList.of(new MockCell(0, 1, null))); 213 | } 214 | 215 | @Test(expected = IllegalArgumentException.class) 216 | public void test_copyOf_intIntCells_null() { 217 | ImmutableGrid.copyOf(2, 2, (Iterable>) null); 218 | } 219 | 220 | //----------------------------------------------------------------------- 221 | @Test 222 | public void test_copyOfDeriveCounts_Cells_size0() { 223 | SparseGrid hash = SparseGrid.create(2, 2); 224 | ImmutableGrid test = ImmutableGrid.copyOfDeriveCounts(hash.cells()); 225 | assertEquals(0, test.rowCount()); 226 | assertEquals(0, test.columnCount()); 227 | checkGrid(test); 228 | assertEquals(true, test instanceof EmptyGrid); 229 | } 230 | 231 | @Test(expected = IllegalArgumentException.class) 232 | public void test_copyOfDeriveCounts_null() { 233 | ImmutableGrid.copyOfDeriveCounts((Iterable>) null); 234 | } 235 | 236 | @Test(expected = IndexOutOfBoundsException.class) 237 | public void test_copyOfDeriveCounts_badGrid_negativeRow() { 238 | ImmutableSet set = ImmutableSet.of(new MockCell(-1, 2, "Hello")); 239 | ImmutableGrid.copyOfDeriveCounts(set); 240 | } 241 | 242 | @Test(expected = IndexOutOfBoundsException.class) 243 | public void test_copyOfDeriveCounts_badGrid_negativeColumn() { 244 | ImmutableSet set = ImmutableSet.of(new MockCell(1, -2, "Hello")); 245 | ImmutableGrid.copyOfDeriveCounts(set); 246 | } 247 | 248 | @Test(expected = IllegalArgumentException.class) 249 | public void test_copyOfDeriveCounts_badGrid_nullValue() { 250 | ImmutableSet set = ImmutableSet.of(new MockCell(1, 2, null)); 251 | ImmutableGrid.copyOfDeriveCounts(set); 252 | } 253 | 254 | } 255 | -------------------------------------------------------------------------------- /src/main/java/org/joda/collect/grid/DenseImmutableGrid.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014-present Stephen Colebourne 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.joda.collect.grid; 17 | 18 | import com.google.common.base.Objects; 19 | import com.google.common.base.Preconditions; 20 | import com.google.common.collect.ImmutableCollection; 21 | import com.google.common.collect.ImmutableList; 22 | import java.io.Serial; 23 | import java.io.Serializable; 24 | import java.util.AbstractList; 25 | import java.util.AbstractSet; 26 | import java.util.Arrays; 27 | import java.util.Iterator; 28 | import java.util.List; 29 | import java.util.NoSuchElementException; 30 | import java.util.Set; 31 | 32 | /** 33 | * Immutable implementation of the {@code Grid} data structure based on an array. 34 | *

35 | * This uses one item of memory for each possible combination of row and column. 36 | * 37 | * @param the type of the value 38 | * @author Stephen Colebourne 39 | */ 40 | final class DenseImmutableGrid extends ImmutableGrid implements Serializable { 41 | 42 | /** Serialization version. */ 43 | @Serial 44 | private static final long serialVersionUID = 1L; 45 | 46 | /** 47 | * The number of rows. 48 | */ 49 | private final int rowCount; 50 | /** 51 | * The number of columns. 52 | */ 53 | private final int columnCount; 54 | /** 55 | * The size. 56 | */ 57 | private final int size; 58 | /** 59 | * The values. 60 | */ 61 | private final V[] values; 62 | 63 | //----------------------------------------------------------------------- 64 | /** 65 | * Creates a {@code DenseImmutableGrid} copying from another grid. 66 | * 67 | * @param the type of the value 68 | * @param grid the grid to copy, not null 69 | * @return the mutable grid, not null 70 | */ 71 | @SuppressWarnings("unchecked") 72 | static DenseImmutableGrid create(Grid grid) { 73 | if (grid instanceof DenseGrid) { 74 | return new DenseImmutableGrid<>((DenseGrid) grid); 75 | } 76 | int rowCount = grid.rowCount(); 77 | int columnCount = grid.columnCount(); 78 | validateCounts(rowCount, columnCount); 79 | V[] values = (V[]) new Object[rowCount * columnCount]; 80 | for (Cell cell : grid.cells()) { 81 | values[cell.getRow() * columnCount + cell.getColumn()] = cell.getValue(); 82 | } 83 | return new DenseImmutableGrid<>(rowCount, columnCount, grid.size(), values); 84 | } 85 | 86 | //----------------------------------------------------------------------- 87 | /** 88 | * Restricted constructor. 89 | */ 90 | private DenseImmutableGrid(int rowCount, int columnCount, int size, V[] values) { 91 | validateCounts(rowCount, columnCount); 92 | this.rowCount = rowCount; 93 | this.columnCount = columnCount; 94 | this.size = size; 95 | this.values = values; 96 | } 97 | 98 | /** 99 | * Restricted constructor. 100 | */ 101 | DenseImmutableGrid(DenseGrid grid) { 102 | this.rowCount = grid.rowCount(); 103 | this.columnCount = grid.columnCount(); 104 | this.size = grid.size(); 105 | this.values = grid.valuesArray(); 106 | } 107 | 108 | //----------------------------------------------------------------------- 109 | @Override 110 | public int rowCount() { 111 | return rowCount; 112 | } 113 | 114 | @Override 115 | public int columnCount() { 116 | return columnCount; 117 | } 118 | 119 | @Override 120 | public int size() { 121 | return size; 122 | } 123 | 124 | @Override 125 | public boolean contains(int row, int column) { 126 | if (exists(row, column)) { 127 | return values[row * columnCount + column] != null; 128 | } 129 | return false; 130 | } 131 | 132 | @Override 133 | public boolean containsValue(Object valueToFind) { 134 | if (valueToFind != null) { 135 | for (Object value : values) { 136 | if (valueToFind.equals(value)) { 137 | return true; 138 | } 139 | } 140 | } 141 | return false; 142 | } 143 | 144 | @Override 145 | public V get(int row, int column) { 146 | if (exists(row, column)) { 147 | return values[row * columnCount + column]; 148 | } 149 | return null; 150 | } 151 | 152 | @Override 153 | public Cell cell(int row, int column) { 154 | V value = get(row, column); 155 | return (value != null ? ImmutableCell.of(row, column, value) : null); 156 | } 157 | 158 | //----------------------------------------------------------------------- 159 | @Override 160 | public Set> cells() { 161 | return new Cells<>(this); 162 | } 163 | 164 | /** 165 | * View onto the grid. 166 | */ 167 | static class Cells extends AbstractSet> { 168 | private final DenseImmutableGrid grid; 169 | 170 | Cells(DenseImmutableGrid grid) { 171 | this.grid = grid; 172 | } 173 | 174 | @Override 175 | public int size() { 176 | return grid.size; 177 | } 178 | 179 | @Override 180 | public boolean contains(Object obj) { 181 | Cell cell = (Cell) obj; 182 | return Objects.equal(cell.getValue(), grid.get(cell.getRow(), cell.getColumn())); 183 | } 184 | 185 | @Override 186 | public Iterator> iterator() { 187 | return new Iterator<>() { 188 | private final MutableCell cell = new MutableCell<>(); 189 | private int count; 190 | private int current = -1; 191 | 192 | @Override 193 | public boolean hasNext() { 194 | return (count < grid.size); 195 | } 196 | @Override 197 | public Cell next() { 198 | if (!hasNext()) { 199 | throw new NoSuchElementException("No more elements"); 200 | } 201 | current++; 202 | for (; current < grid.values.length; current++) { 203 | if (grid.values[current] != null) { 204 | break; 205 | } 206 | } 207 | V value = grid.values[current]; 208 | count++; 209 | cell.set(current / grid.columnCount, current % grid.columnCount, value); 210 | return cell; 211 | } 212 | @Override 213 | public void remove() { 214 | throw new UnsupportedOperationException("Immutable"); 215 | } 216 | }; 217 | } 218 | } 219 | 220 | //----------------------------------------------------------------------- 221 | @Override 222 | @SuppressWarnings("unchecked") 223 | public ImmutableCollection values() { 224 | Object[] array = new Object[size]; 225 | int index = 0; 226 | for (Object object : values) { 227 | if (object != null) { 228 | array[index++] = object; 229 | } 230 | } 231 | return ImmutableList.copyOf((V[]) array); 232 | } 233 | 234 | //----------------------------------------------------------------------- 235 | @Override 236 | public List row(int row) { 237 | Preconditions.checkElementIndex(row, rowCount(), "Row index"); 238 | int base = row * rowCount; 239 | return new Inner<>(this, base, columnCount, 1); 240 | } 241 | 242 | @Override 243 | public List> rows() { 244 | return new Outer<>(this, rowCount, columnCount, columnCount, 1); 245 | } 246 | 247 | @Override 248 | public List column(int column) { 249 | Preconditions.checkElementIndex(column, columnCount(), "Column index"); 250 | return new Inner<>(this, column, rowCount, columnCount); 251 | } 252 | 253 | @Override 254 | public List> columns() { 255 | return new Outer<>(this, columnCount, 1, rowCount, columnCount); 256 | } 257 | 258 | static class Outer extends AbstractList> { 259 | private final DenseImmutableGrid grid; 260 | private final int size; 261 | private final int gap; 262 | private final int innerSize; 263 | private final int innerGap; 264 | 265 | Outer(DenseImmutableGrid grid, int size, int gap, int innerSize, int innerGap) { 266 | this.grid = grid; 267 | this.size = size; 268 | this.gap = gap; 269 | this.innerSize = innerSize; 270 | this.innerGap = innerGap; 271 | } 272 | 273 | @Override 274 | public int size() { 275 | return size; 276 | } 277 | 278 | @Override 279 | public List get(int index) { 280 | Preconditions.checkElementIndex(index, size); 281 | int base = index * gap; 282 | return new Inner<>(grid, base, innerSize, innerGap); 283 | } 284 | } 285 | 286 | static class Inner extends AbstractList { 287 | private final DenseImmutableGrid grid; 288 | private final int size; 289 | private final int offset; 290 | private final int gap; 291 | 292 | Inner(DenseImmutableGrid grid, int offset, int size, int gap) { 293 | this.grid = grid; 294 | this.size = size; 295 | this.offset = offset; 296 | this.gap = gap; 297 | } 298 | 299 | @Override 300 | public int size() { 301 | return size; 302 | } 303 | 304 | @Override 305 | public V get(int index) { 306 | Preconditions.checkElementIndex(index, size); 307 | int arrayIndex = index * gap + offset; 308 | return grid.values[arrayIndex]; 309 | } 310 | } 311 | 312 | //----------------------------------------------------------------------- 313 | /** 314 | * Returns a clone of the internal array. 315 | * 316 | * @return the array, not null 317 | */ 318 | V[] valuesArray() { 319 | return values.clone(); 320 | } 321 | 322 | //----------------------------------------------------------------------- 323 | @Override 324 | public boolean equals(Object obj) { 325 | return obj == this || 326 | (obj instanceof DenseImmutableGrid other && Arrays.equals(values, other.values)) || 327 | super.equals(obj); 328 | } 329 | 330 | @Override 331 | public int hashCode() { 332 | int hash = 0; 333 | for (int i = 0; i < values.length; i++) { 334 | Object value = values[i]; 335 | if (value != null) { 336 | int row = i / columnCount; 337 | int column = i % columnCount; 338 | hash += (row ^ Integer.rotateLeft(column, 16) ^ value.hashCode()); 339 | } 340 | } 341 | return rowCount ^ Integer.rotateLeft(columnCount, 16) ^ hash; 342 | } 343 | 344 | } 345 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /src/main/java/org/joda/collect/grid/Grid.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014-present Stephen Colebourne 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.joda.collect.grid; 17 | 18 | import com.google.common.collect.ImmutableCollection; 19 | import java.util.List; 20 | import java.util.Set; 21 | 22 | /** 23 | * A data structure representing a grid keyed by {@code int} row and {@code int} column. 24 | *

25 | * A grid has a fixed number of rows and columns, but not all cells must be occupied. 26 | * Dense and sparse implementations are provided that handle high and low numbers of cells 27 | * relative to the potential capacity. 28 | * 29 | * @param the type of the value 30 | * @author Stephen Colebourne 31 | */ 32 | public interface Grid { 33 | 34 | /** 35 | * Gets the number of rows in the grid. 36 | *

37 | * A grid has a fixed number of rows and columns, but not all cells must be occupied. 38 | * This returns the row capacity, not the number of occupied rows. 39 | * It is guaranteed that {@link #contains(int, int)} will return {@code false} 40 | * for indices larger than the row count. 41 | * 42 | * @return the number of rows, zero or greater 43 | */ 44 | int rowCount(); 45 | 46 | /** 47 | * Gets the number of columns in the grid. 48 | *

49 | * A grid has a fixed number of rows and columns, but not all cells must be occupied. 50 | * This returns the column capacity, not the number of occupied columns. 51 | * It is guaranteed that {@link #contains(int, int)} will return {@code false} 52 | * for indices larger than the column count. 53 | * 54 | * @return the number of columns, zero or greater 55 | */ 56 | int columnCount(); 57 | 58 | /** 59 | * Checks if the specified row-column exists. 60 | *

61 | * This simply checks that the row and column indices are between 62 | * zero and the row and column counts. 63 | * 64 | * @param row the row 65 | * @param column the column 66 | * @return true if the row-column exists 67 | */ 68 | boolean exists(int row, int column); 69 | 70 | //----------------------------------------------------------------------- 71 | /** 72 | * Checks if the grid is full. 73 | *

74 | * A full grid has a cell at every combination of row and column. 75 | * 76 | * @return true if full 77 | */ 78 | boolean isFull(); 79 | 80 | /** 81 | * Checks if the grid is empty. 82 | * 83 | * @return true if empty 84 | */ 85 | boolean isEmpty(); 86 | 87 | /** 88 | * Gets the number of cells that are present. 89 | * 90 | * @return the size of the set of cells 91 | */ 92 | int size(); 93 | 94 | /** 95 | * Checks if a value is present at the specified row-column. 96 | *

97 | * If either index does not exist, false is returned. 98 | * 99 | * @param row the row 100 | * @param column the column 101 | * @return true if there is a value at the row-column 102 | */ 103 | boolean contains(int row, int column); 104 | 105 | /** 106 | * Checks if the specified value is contained in the grid. 107 | * 108 | * @param valueToFind the value to find, null returns false 109 | * @return true if the grid contains the value 110 | */ 111 | boolean containsValue(Object valueToFind); 112 | 113 | /** 114 | * Gets the value at the specified row-column. 115 | *

116 | * If either index does not exist, null is returned. 117 | * 118 | * @param row the row 119 | * @param column the column 120 | * @return the value at the row-column, null if not found 121 | */ 122 | V get(int row, int column); 123 | 124 | /** 125 | * Gets the cell at the specified row-column. 126 | *

127 | * If either index does not exist, null is returned. 128 | * 129 | * @param row the row 130 | * @param column the column 131 | * @return the cell at the row-column, null if not found 132 | */ 133 | Cell cell(int row, int column); 134 | 135 | //----------------------------------------------------------------------- 136 | /** 137 | * Checks if this grid equals another grid. 138 | *

139 | * Two grids are equal if they are the same size and contain the same set of cells. 140 | * 141 | * @param obj the object to compare to, null returns false 142 | * @return true if equal 143 | */ 144 | @Override 145 | boolean equals(Object obj); 146 | 147 | /** 148 | * Gets a suitable hash code. 149 | *

150 | * The hash code is {@code rowCount ^ Integer.rotateLeft(columnCount, 16) ^ cells.hashCode()}. 151 | * 152 | * @return the hash code 153 | */ 154 | @Override 155 | int hashCode(); 156 | 157 | //----------------------------------------------------------------------- 158 | /** 159 | * Clears the grid. 160 | *

161 | * The grid will be empty after calling this method. 162 | * 163 | * @throws UnsupportedOperationException if read-only 164 | */ 165 | void clear(); 166 | 167 | /** 168 | * Puts a value into this grid. 169 | *

170 | * The value at the specified row-column is set. 171 | * Any previous value at the row-column is replaced. 172 | *

173 | * If either index does not exist, {@code IndexOutOfBoundsException} is thrown. 174 | * 175 | * @param row the row, zero or greater 176 | * @param column the column, zero or greater 177 | * @param value the value to put into the grid, not null 178 | * @throws IndexOutOfBoundsException if either index does not exist 179 | * @throws UnsupportedOperationException if read-only 180 | */ 181 | void put(int row, int column, V value); 182 | 183 | /** 184 | * Puts all cells from a grid into this grid. 185 | *

186 | * The value at the specified row-column is set. 187 | * Any previous value at the row-column is replaced. 188 | * 189 | * @param grid the grid to put into this grid, not null 190 | * @throws IndexOutOfBoundsException if a cell has an invalid index 191 | * @throws UnsupportedOperationException if read-only 192 | */ 193 | void putAll(Grid grid); 194 | 195 | /** 196 | * Removes the value at the specified row-column. 197 | *

198 | * If either index does not exist, no action occurs and false is returned. 199 | * 200 | * @param row the row 201 | * @param column the column 202 | * @return true if the grid is altered 203 | * @throws UnsupportedOperationException if read-only 204 | */ 205 | boolean remove(int row, int column); 206 | 207 | //----------------------------------------------------------------------- 208 | /** 209 | * Gets the complete set of cells. 210 | *

211 | * If the grid is mutable then cells may be added or removed from the set. 212 | * The cells are returned in order, looping around rows, then columns. 213 | *

214 | * The cell returned from the set iterator may be a mutable {@code Cell} 215 | * implementation that cannot be stored beyond the lifetime of an iteration. 216 | * 217 | * @return the set of all cells, not null 218 | */ 219 | Set> cells(); 220 | 221 | /** 222 | * Gets the values in order through rows, then columns. 223 | *

224 | * The returned data structure is an ordered collection. 225 | * The values are returned in order, looping around rows, then columns. 226 | * 227 | * @return the collection of all values, not null 228 | */ 229 | ImmutableCollection values(); 230 | 231 | /** 232 | * Gets the columns of a single row. 233 | *

234 | * The list will contain all columns from zero to {@code columnCount}. 235 | * Where there is no value for a cell, the list will contain null. 236 | *

237 | * The returned list is immutable, except for {@link List#set(int, Object)}, 238 | * which adds, updates or deletes from the underlying grid. 239 | * 240 | * @param row the row, zero or greater 241 | * @return the columns of the specified row, not null 242 | * @throws IndexOutOfBoundsException if the row is invalid 243 | */ 244 | List row(int row); 245 | 246 | /** 247 | * Gets the entire grid of values, by row then column. 248 | *

249 | * The outer list contains all rows from zero to {@code rowCount}. 250 | * Each inner list contains all columns from zero to {@code columnCount}. 251 | * Where there is no value for a cell, the value is null. 252 | *

253 | * The returned list is immutable, except for the {@link List#set(int, Object)} 254 | * method on the inner list, which adds, updates or deletes from the underlying grid. 255 | * 256 | * @return the entire grid, by row then column, not null 257 | */ 258 | List> rows(); 259 | 260 | /** 261 | * Gets the rows of a single column. 262 | *

263 | * The list will contain all rows from zero to {@code rowCount}. 264 | * Where data is not present, the list will contain null. 265 | *

266 | * The returned list is immutable, except for {@link List#set(int, Object)}, 267 | * which adds, updates or deletes from the underlying grid. 268 | * 269 | * @param column the column, zero or greater 270 | * @return the rows of the specified column, not null 271 | * @throws IndexOutOfBoundsException if the column is invalid 272 | */ 273 | List column(int column); 274 | 275 | /** 276 | * Gets the entire grid of values, by column then row. 277 | *

278 | * The outer list contains all columns from zero to {@code columnCount}. 279 | * Each inner list contains all rows from zero to {@code rowCount}. 280 | * Where there is no value for a cell, the value is null. 281 | *

282 | * The returned list is immutable, except for the {@link List#set(int, Object)} 283 | * method on the inner list, which adds, updates or deletes from the underlying grid. 284 | * 285 | * @return the entire grid, by row then column, not null 286 | */ 287 | List> columns(); 288 | 289 | //----------------------------------------------------------------------- 290 | /** 291 | * A cell within the grid compared only using row and column. 292 | * 293 | * @param the type of the value 294 | */ 295 | public interface Cell { 296 | 297 | /** 298 | * Gets the row index. 299 | * 300 | * @return the row, zero or greater 301 | */ 302 | int getRow(); 303 | 304 | /** 305 | * Gets the column index. 306 | * 307 | * @return the column, zero or greater 308 | */ 309 | int getColumn(); 310 | 311 | /** 312 | * Gets the value of the cell. 313 | * 314 | * @return the cell value, not null 315 | */ 316 | V getValue(); 317 | 318 | /** 319 | * Checks if the row-column of this cell matches the specified row and column. 320 | * 321 | * @param row the row to check 322 | * @param column the column to check 323 | * @return true if equal 324 | */ 325 | boolean equalRowColumn(int row, int column); 326 | 327 | /** 328 | * Checks if the value of this cell matches the specified value. 329 | * 330 | * @param value the row to check, null returns false 331 | * @return true if equal 332 | */ 333 | boolean equalValue(Object value); 334 | 335 | /** 336 | * Checks if this cell equals another cell. 337 | *

338 | * Two cells are equal if they have equal row, column and value. 339 | * 340 | * @param obj the object to compare to, null returns false 341 | * @return true if equal 342 | */ 343 | @Override 344 | boolean equals(Object obj); 345 | 346 | /** 347 | * Gets a suitable hash code. 348 | *

349 | * The hash code is {@code row ^ Integer.rotateLeft(column, 16) ^ value.hashCode()}. 350 | * 351 | * @return the hash code 352 | */ 353 | @Override 354 | int hashCode(); 355 | } 356 | 357 | } 358 | -------------------------------------------------------------------------------- /src/test/java/org/joda/collect/grid/AbstractTestGrid.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014-present Stephen Colebourne 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.joda.collect.grid; 17 | 18 | import static org.junit.Assert.assertEquals; 19 | import static org.junit.Assert.fail; 20 | 21 | import java.util.Iterator; 22 | import java.util.NoSuchElementException; 23 | 24 | import org.joda.collect.grid.Grid.Cell; 25 | 26 | /** 27 | * Test abstract Grid. 28 | */ 29 | public abstract class AbstractTestGrid { 30 | 31 | private static final Object DUMMY = new Object() {}; 32 | 33 | protected void checkGrid(Grid test) { 34 | assertEquals(0, test.size()); 35 | assertEquals(true, test.isEmpty()); 36 | assertEquals(test.rowCount() == 0 && test.columnCount() == 0, test.isFull()); 37 | for (int i = -1; i < test.rowCount(); i++) { 38 | for (int j = -1; j < test.columnCount(); j++) { 39 | if (i < 0 || j < 0 || i >= test.rowCount() || j >= test.columnCount()) { 40 | assertEquals(false, test.exists(i, j)); 41 | assertEquals(false, test.contains(i, j)); 42 | assertEquals(null, test.get(i, j)); 43 | assertEquals(null, test.cell(i, j)); 44 | } else { 45 | assertEquals(true, test.exists(i, j)); 46 | assertEquals(false, test.contains(i, j)); 47 | assertEquals(null, test.get(i, j)); 48 | assertEquals(null, test.cell(i, j)); 49 | assertEquals(null, test.row(i).get(j)); 50 | assertEquals(null, test.rows().get(i).get(j)); 51 | assertEquals(null, test.column(j).get(i)); 52 | assertEquals(null, test.columns().get(j).get(i)); 53 | assertEquals(test.columnCount(), test.row(i).size()); 54 | assertEquals(test.rowCount(), test.rows().size()); 55 | assertEquals(test.columnCount(), test.rows().get(i).size()); 56 | assertEquals(test.rowCount(), test.column(j).size()); 57 | assertEquals(test.columnCount(), test.columns().size()); 58 | assertEquals(test.rowCount(), test.columns().get(j).size()); 59 | } 60 | } 61 | } 62 | assertEquals(false, test.containsValue(null)); 63 | assertEquals(false, test.containsValue(DUMMY)); 64 | 65 | assertEquals(0, test.cells().size()); 66 | Iterator> cellIt = test.cells().iterator(); 67 | assertIteratorEnd(cellIt); 68 | 69 | assertEquals(0, test.values().size()); 70 | Iterator valueIt = test.values().iterator(); 71 | assertIteratorEnd(valueIt); 72 | } 73 | 74 | protected void checkGrid(Grid test, int row1, int column1, R value1) { 75 | assertEquals(1, test.size()); 76 | assertEquals(false, test.isEmpty()); 77 | assertEquals(test.rowCount() * test.columnCount() == 1, test.isFull()); 78 | for (int i = -1; i < test.rowCount(); i++) { 79 | for (int j = -1; j < test.columnCount(); j++) { 80 | if (i < 0 || j < 0 || i >= test.rowCount() || j >= test.columnCount()) { 81 | assertEquals(false, test.exists(i, j)); 82 | assertEquals(false, test.contains(i, j)); 83 | assertEquals(null, test.get(i, j)); 84 | assertEquals(null, test.cell(i, j)); 85 | } else if (i == row1 && j == column1) { 86 | assertEquals(true, test.exists(i, j)); 87 | assertEquals(true, test.contains(i, j)); 88 | assertEquals(value1, test.get(i, j)); 89 | assertEquals(ImmutableCell.of(i, j, value1), test.cell(i, j)); 90 | assertEquals(value1, test.row(i).get(j)); 91 | assertEquals(value1, test.rows().get(i).get(j)); 92 | assertEquals(value1, test.column(j).get(i)); 93 | assertEquals(value1, test.columns().get(j).get(i)); 94 | } else { 95 | assertEquals(true, test.exists(i, j)); 96 | assertEquals(false, test.contains(i, j)); 97 | assertEquals(null, test.get(i, j)); 98 | assertEquals(null, test.cell(i, j)); 99 | assertEquals(null, test.row(i).get(j)); 100 | assertEquals(null, test.rows().get(i).get(j)); 101 | assertEquals(null, test.column(j).get(i)); 102 | assertEquals(null, test.columns().get(j).get(i)); 103 | assertEquals(test.columnCount(), test.row(i).size()); 104 | assertEquals(test.rowCount(), test.rows().size()); 105 | assertEquals(test.columnCount(), test.rows().get(i).size()); 106 | assertEquals(test.rowCount(), test.column(j).size()); 107 | assertEquals(test.columnCount(), test.columns().size()); 108 | assertEquals(test.rowCount(), test.columns().get(j).size()); 109 | } 110 | } 111 | } 112 | assertEquals(true, test.containsValue(value1)); 113 | assertEquals(false, test.containsValue(null)); 114 | assertEquals(false, test.containsValue(DUMMY)); 115 | 116 | assertEquals(1, test.cells().size()); 117 | Iterator> cellIt = test.cells().iterator(); 118 | assertEquals(true, cellIt.hasNext()); 119 | Cell cell = cellIt.next(); 120 | assertEquals(row1, cell.getRow()); 121 | assertEquals(column1, cell.getColumn()); 122 | assertEquals(value1, cell.getValue()); 123 | assertIteratorEnd(cellIt); 124 | 125 | assertEquals(1, test.values().size()); 126 | Iterator valueIt = test.values().iterator(); 127 | assertEquals(true, valueIt.hasNext()); 128 | assertEquals(value1, valueIt.next()); 129 | assertIteratorEnd(valueIt); 130 | } 131 | 132 | protected void checkGrid(Grid test, int row1, int column1, R value1, int row2, int column2, R value2) { 133 | assertEquals(2, test.size()); 134 | assertEquals(false, test.isEmpty()); 135 | assertEquals(test.rowCount() * test.columnCount() == 2, test.isFull()); 136 | for (int i = -1; i < test.rowCount(); i++) { 137 | for (int j = -1; j < test.columnCount(); j++) { 138 | if (i < 0 || j < 0 || i >= test.rowCount() || j >= test.columnCount()) { 139 | assertEquals(false, test.exists(i, j)); 140 | assertEquals(false, test.contains(i, j)); 141 | assertEquals(null, test.get(i, j)); 142 | assertEquals(null, test.cell(i, j)); 143 | } else if (i == row1 && j == column1) { 144 | assertEquals(true, test.exists(i, j)); 145 | assertEquals(true, test.contains(i, j)); 146 | assertEquals(value1, test.get(i, j)); 147 | assertEquals(ImmutableCell.of(i, j, value1), test.cell(i, j)); 148 | assertEquals(value1, test.row(i).get(j)); 149 | assertEquals(value1, test.rows().get(i).get(j)); 150 | assertEquals(value1, test.column(j).get(i)); 151 | assertEquals(value1, test.columns().get(j).get(i)); 152 | } else if (i == row2 && j == column2) { 153 | assertEquals(true, test.exists(i, j)); 154 | assertEquals(true, test.contains(i, j)); 155 | assertEquals(value2, test.get(i, j)); 156 | assertEquals(ImmutableCell.of(i, j, value2), test.cell(i, j)); 157 | assertEquals(value2, test.row(i).get(j)); 158 | assertEquals(value2, test.rows().get(i).get(j)); 159 | assertEquals(value2, test.column(j).get(i)); 160 | assertEquals(value2, test.columns().get(j).get(i)); 161 | } else { 162 | assertEquals(true, test.exists(i, j)); 163 | assertEquals(false, test.contains(i, j)); 164 | assertEquals(null, test.get(i, j)); 165 | assertEquals(null, test.cell(i, j)); 166 | assertEquals(null, test.row(i).get(j)); 167 | assertEquals(null, test.rows().get(i).get(j)); 168 | assertEquals(null, test.column(j).get(i)); 169 | assertEquals(null, test.columns().get(j).get(i)); 170 | assertEquals(test.columnCount(), test.row(i).size()); 171 | assertEquals(test.rowCount(), test.rows().size()); 172 | assertEquals(test.columnCount(), test.rows().get(i).size()); 173 | assertEquals(test.rowCount(), test.column(j).size()); 174 | assertEquals(test.columnCount(), test.columns().size()); 175 | assertEquals(test.rowCount(), test.columns().get(j).size()); 176 | } 177 | } 178 | } 179 | assertEquals(true, test.containsValue(value1)); 180 | assertEquals(true, test.containsValue(value2)); 181 | assertEquals(false, test.containsValue(null)); 182 | assertEquals(false, test.containsValue(DUMMY)); 183 | 184 | assertEquals(2, test.cells().size()); 185 | Iterator> cellIt = test.cells().iterator(); 186 | assertEquals(true, cellIt.hasNext()); 187 | Cell cell = cellIt.next(); 188 | assertEquals(row1, cell.getRow()); 189 | assertEquals(column1, cell.getColumn()); 190 | assertEquals(value1, cell.getValue()); 191 | assertEquals(true, cellIt.hasNext()); 192 | cell = cellIt.next(); 193 | assertEquals(row2, cell.getRow()); 194 | assertEquals(column2, cell.getColumn()); 195 | assertEquals(value2, cell.getValue()); 196 | assertIteratorEnd(cellIt); 197 | 198 | assertEquals(2, test.values().size()); 199 | Iterator valueIt = test.values().iterator(); 200 | assertEquals(true, valueIt.hasNext()); 201 | assertEquals(value1, valueIt.next()); 202 | assertEquals(true, valueIt.hasNext()); 203 | assertEquals(value2, valueIt.next()); 204 | assertIteratorEnd(valueIt); 205 | } 206 | 207 | protected void checkGrid(Grid test, int row1, int column1, R value1, int row2, int column2, R value2, int row3, int column3, R value3) { 208 | assertEquals(3, test.size()); 209 | assertEquals(false, test.isEmpty()); 210 | assertEquals(test.rowCount() * test.columnCount() == 3, test.isFull()); 211 | for (int i = -1; i < test.rowCount(); i++) { 212 | for (int j = -1; j < test.columnCount(); j++) { 213 | if (i < 0 || j < 0 || i >= test.rowCount() || j >= test.columnCount()) { 214 | assertEquals(false, test.exists(i, j)); 215 | assertEquals(false, test.contains(i, j)); 216 | assertEquals(null, test.get(i, j)); 217 | assertEquals(null, test.cell(i, j)); 218 | } else if (i == row1 && j == column1) { 219 | assertEquals(true, test.exists(i, j)); 220 | assertEquals(true, test.contains(i, j)); 221 | assertEquals(value1, test.get(i, j)); 222 | assertEquals(ImmutableCell.of(i, j, value1), test.cell(i, j)); 223 | assertEquals(value1, test.row(i).get(j)); 224 | assertEquals(value1, test.rows().get(i).get(j)); 225 | assertEquals(value1, test.column(j).get(i)); 226 | assertEquals(value1, test.columns().get(j).get(i)); 227 | } else if (i == row2 && j == column2) { 228 | assertEquals(true, test.exists(i, j)); 229 | assertEquals(true, test.contains(i, j)); 230 | assertEquals(value2, test.get(i, j)); 231 | assertEquals(ImmutableCell.of(i, j, value2), test.cell(i, j)); 232 | assertEquals(value2, test.row(i).get(j)); 233 | assertEquals(value2, test.rows().get(i).get(j)); 234 | assertEquals(value2, test.column(j).get(i)); 235 | assertEquals(value2, test.columns().get(j).get(i)); 236 | } else if (i == row3 && j == column3) { 237 | assertEquals(true, test.exists(i, j)); 238 | assertEquals(true, test.contains(i, j)); 239 | assertEquals(value3, test.get(i, j)); 240 | assertEquals(ImmutableCell.of(i, j, value3), test.cell(i, j)); 241 | assertEquals(value3, test.row(i).get(j)); 242 | assertEquals(value3, test.rows().get(i).get(j)); 243 | assertEquals(value3, test.column(j).get(i)); 244 | assertEquals(value3, test.columns().get(j).get(i)); 245 | } else { 246 | assertEquals(true, test.exists(i, j)); 247 | assertEquals(false, test.contains(i, j)); 248 | assertEquals(null, test.get(i, j)); 249 | assertEquals(null, test.cell(i, j)); 250 | assertEquals(null, test.row(i).get(j)); 251 | assertEquals(null, test.rows().get(i).get(j)); 252 | assertEquals(null, test.column(j).get(i)); 253 | assertEquals(null, test.columns().get(j).get(i)); 254 | assertEquals(test.columnCount(), test.row(i).size()); 255 | assertEquals(test.rowCount(), test.rows().size()); 256 | assertEquals(test.columnCount(), test.rows().get(i).size()); 257 | assertEquals(test.rowCount(), test.column(j).size()); 258 | assertEquals(test.columnCount(), test.columns().size()); 259 | assertEquals(test.rowCount(), test.columns().get(j).size()); 260 | } 261 | } 262 | } 263 | assertEquals(true, test.containsValue(value1)); 264 | assertEquals(true, test.containsValue(value2)); 265 | assertEquals(true, test.containsValue(value3)); 266 | assertEquals(false, test.containsValue(null)); 267 | assertEquals(false, test.containsValue(DUMMY)); 268 | 269 | assertEquals(3, test.cells().size()); 270 | Iterator> cellIt = test.cells().iterator(); 271 | assertEquals(true, cellIt.hasNext()); 272 | Cell cell = cellIt.next(); 273 | assertEquals(row1, cell.getRow()); 274 | assertEquals(column1, cell.getColumn()); 275 | assertEquals(value1, cell.getValue()); 276 | assertEquals(true, cellIt.hasNext()); 277 | cell = cellIt.next(); 278 | assertEquals(row2, cell.getRow()); 279 | assertEquals(column2, cell.getColumn()); 280 | assertEquals(value2, cell.getValue()); 281 | assertEquals(true, cellIt.hasNext()); 282 | cell = cellIt.next(); 283 | assertEquals(row3, cell.getRow()); 284 | assertEquals(1, cell.getColumn()); 285 | assertEquals(value3, cell.getValue()); 286 | assertIteratorEnd(cellIt); 287 | 288 | assertEquals(3, test.values().size()); 289 | Iterator valueIt = test.values().iterator(); 290 | assertEquals(true, valueIt.hasNext()); 291 | assertEquals(value1, valueIt.next()); 292 | assertEquals(true, valueIt.hasNext()); 293 | assertEquals(value2, valueIt.next()); 294 | assertEquals(true, valueIt.hasNext()); 295 | assertEquals(value3, valueIt.next()); 296 | assertIteratorEnd(valueIt); 297 | } 298 | 299 | private void assertIteratorEnd(Iterator it) { 300 | assertEquals(false, it.hasNext()); 301 | try { 302 | it.next(); 303 | fail(); 304 | } catch (NoSuchElementException ex) { 305 | // expected 306 | } 307 | } 308 | 309 | } 310 | -------------------------------------------------------------------------------- /src/main/java/org/joda/collect/grid/DenseGrid.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014-present Stephen Colebourne 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.joda.collect.grid; 17 | 18 | import com.google.common.base.Objects; 19 | import com.google.common.base.Preconditions; 20 | import com.google.common.collect.ImmutableCollection; 21 | import com.google.common.collect.ImmutableList; 22 | import java.io.Serial; 23 | import java.io.Serializable; 24 | import java.util.AbstractList; 25 | import java.util.AbstractSet; 26 | import java.util.Arrays; 27 | import java.util.Iterator; 28 | import java.util.List; 29 | import java.util.NoSuchElementException; 30 | import java.util.Set; 31 | 32 | /** 33 | * Mutable implementation of the {@code Grid} data structure based on an array. 34 | *

35 | * This uses one item of memory for each possible combination of row and column. 36 | * 37 | * @param the type of the value 38 | * @author Stephen Colebourne 39 | */ 40 | public final class DenseGrid extends AbstractGrid implements Serializable { 41 | 42 | /** Serialization version. */ 43 | @Serial 44 | private static final long serialVersionUID = 1L; 45 | 46 | /** 47 | * The number of rows. 48 | */ 49 | private final int rowCount; 50 | /** 51 | * The number of columns. 52 | */ 53 | private final int columnCount; 54 | /** 55 | * The size. 56 | */ 57 | private int size; 58 | /** 59 | * The values. 60 | */ 61 | private final V[] values; 62 | 63 | //----------------------------------------------------------------------- 64 | /** 65 | * Creates an empty {@code DenseGrid} of the specified size. 66 | * 67 | * @param the type of the value 68 | * @param rowCount the number of rows, zero or greater 69 | * @param columnCount the number of rows, zero or greater 70 | * @return the mutable grid, not null 71 | */ 72 | public static DenseGrid create(int rowCount, int columnCount) { 73 | return new DenseGrid<>(rowCount, columnCount); 74 | } 75 | 76 | /** 77 | * Creates a {@code DenseGrid} copying from another grid. 78 | * 79 | * @param the type of the value 80 | * @param grid the grid to copy, not null 81 | * @return the mutable grid, not null 82 | */ 83 | @SuppressWarnings("unchecked") 84 | public static DenseGrid create(Grid grid) { 85 | if (grid == null) { 86 | throw new IllegalArgumentException("Grid must not be null"); 87 | } 88 | if (grid instanceof DenseImmutableGrid) { 89 | return new DenseGrid<>((DenseImmutableGrid) grid); 90 | } 91 | DenseGrid created = DenseGrid.create(grid.rowCount(), grid.columnCount()); 92 | created.putAll(grid); 93 | return created; 94 | } 95 | 96 | /** 97 | * Creates a {@code DenseGrid} copying from an array. 98 | *

99 | * The row count and column count are derived from the maximum size of the array. 100 | * The grid is initialized from the non-null values. 101 | * 102 | * @param the type of the value 103 | * @param array the array, first by row, then by column 104 | * @return the mutable grid, not null 105 | */ 106 | public static DenseGrid create(V[][] array) { 107 | if (array == null) { 108 | throw new IllegalArgumentException("Array must not be null"); 109 | } 110 | int rowCount = array.length; 111 | if (rowCount == 0) { 112 | return new DenseGrid<>(0, 0); 113 | } 114 | int columnCount = array[0].length; 115 | for (int i = 1; i < rowCount; i++) { 116 | columnCount = Math.max(columnCount, array[i].length); 117 | } 118 | DenseGrid created = DenseGrid.create(rowCount, columnCount); 119 | for (int row = 0; row < array.length; row++) { 120 | V[] rowValues = array[row]; 121 | for (int column = 0; column < rowValues.length; column++) { 122 | V cellValue = rowValues[column]; 123 | if (cellValue != null) { 124 | created.values[row * columnCount + column] = cellValue; 125 | created.size++; 126 | } 127 | } 128 | } 129 | return created; 130 | } 131 | 132 | //----------------------------------------------------------------------- 133 | /** 134 | * Restricted constructor. 135 | */ 136 | @SuppressWarnings("unchecked") 137 | private DenseGrid(int rowCount, int columnCount) { 138 | validateCounts(rowCount, columnCount); 139 | this.rowCount = rowCount; 140 | this.columnCount = columnCount; 141 | this.values = (V[]) new Object[rowCount * columnCount]; 142 | } 143 | 144 | /** 145 | * Restricted constructor. 146 | */ 147 | private DenseGrid(DenseImmutableGrid grid) { 148 | this.rowCount = grid.rowCount(); 149 | this.columnCount = grid.columnCount(); 150 | this.size = grid.size(); 151 | this.values = grid.valuesArray(); 152 | } 153 | 154 | //----------------------------------------------------------------------- 155 | @Override 156 | public int rowCount() { 157 | return rowCount; 158 | } 159 | 160 | @Override 161 | public int columnCount() { 162 | return columnCount; 163 | } 164 | 165 | @Override 166 | public int size() { 167 | return size; 168 | } 169 | 170 | @Override 171 | public boolean contains(int row, int column) { 172 | if (exists(row, column)) { 173 | return values[row * columnCount + column] != null; 174 | } 175 | return false; 176 | } 177 | 178 | @Override 179 | public boolean containsValue(Object valueToFind) { 180 | if (valueToFind != null) { 181 | for (Object value : values) { 182 | if (valueToFind.equals(value)) { 183 | return true; 184 | } 185 | } 186 | } 187 | return false; 188 | } 189 | 190 | @Override 191 | public V get(int row, int column) { 192 | if (exists(row, column)) { 193 | return values[row * columnCount + column]; 194 | } 195 | return null; 196 | } 197 | 198 | @Override 199 | public Cell cell(int row, int column) { 200 | V value = get(row, column); 201 | return (value != null ? ImmutableCell.of(row, column, value) : null); 202 | } 203 | 204 | //----------------------------------------------------------------------- 205 | @Override 206 | public Set> cells() { 207 | return new Cells<>(this); 208 | } 209 | 210 | /** 211 | * View onto the grid. 212 | */ 213 | static class Cells extends AbstractSet> { 214 | private final DenseGrid grid; 215 | 216 | Cells(DenseGrid grid) { 217 | this.grid = grid; 218 | } 219 | 220 | @Override 221 | public int size() { 222 | return grid.size; 223 | } 224 | 225 | @Override 226 | public boolean contains(Object obj) { 227 | Cell cell = (Cell) obj; 228 | return Objects.equal(cell.getValue(), grid.get(cell.getRow(), cell.getColumn())); 229 | } 230 | 231 | @Override 232 | public Iterator> iterator() { 233 | return new Iterator<>() { 234 | private final MutableCell cell = new MutableCell<>(); 235 | private int count; 236 | private int current = -1; 237 | 238 | @Override 239 | public boolean hasNext() { 240 | return (count < grid.size); 241 | } 242 | @Override 243 | public Cell next() { 244 | if (!hasNext()) { 245 | throw new NoSuchElementException("No more elements"); 246 | } 247 | current++; 248 | for (; current < grid.values.length; current++) { 249 | if (grid.values[current] != null) { 250 | break; 251 | } 252 | } 253 | V value = grid.values[current]; 254 | count++; 255 | cell.set(current / grid.columnCount, current % grid.columnCount, value); 256 | return cell; 257 | } 258 | @Override 259 | public void remove() { 260 | if (current < 0) { 261 | throw new IllegalStateException("Unable to remove, next() not called yet"); 262 | } 263 | if (grid.values[current] == null) { 264 | throw new IllegalStateException("Unable to remove, element has been removed"); 265 | } 266 | grid.values[current] = null; 267 | grid.size--; 268 | count--; 269 | } 270 | }; 271 | } 272 | 273 | @Override 274 | public boolean add(Cell cell) { 275 | Preconditions.checkArgument(cell != null, "Cell must not be null"); 276 | int oldSize = grid.size; 277 | grid.put(cell.getRow(), cell.getColumn(), cell.getValue()); 278 | return grid.size > oldSize; 279 | } 280 | 281 | @Override 282 | public boolean remove(Object obj) { 283 | Cell cell = (Cell) obj; 284 | return grid.remove(cell.getRow(), cell.getColumn()); 285 | } 286 | 287 | @Override 288 | public void clear() { 289 | grid.clear(); 290 | } 291 | } 292 | 293 | //----------------------------------------------------------------------- 294 | @Override 295 | @SuppressWarnings("unchecked") 296 | public ImmutableCollection values() { 297 | Object[] array = new Object[size]; 298 | int index = 0; 299 | for (Object object : values) { 300 | if (object != null) { 301 | array[index++] = object; 302 | } 303 | } 304 | return ImmutableList.copyOf((V[]) array); 305 | } 306 | 307 | //----------------------------------------------------------------------- 308 | @Override 309 | public List row(int row) { 310 | Preconditions.checkElementIndex(row, rowCount(), "Row index"); 311 | int base = row * rowCount; 312 | return new Inner<>(this, base, columnCount, 1); 313 | } 314 | 315 | @Override 316 | public List> rows() { 317 | return new Outer<>(this, rowCount, columnCount, columnCount, 1); 318 | } 319 | 320 | @Override 321 | public List column(int column) { 322 | Preconditions.checkElementIndex(column, columnCount(), "Column index"); 323 | return new Inner<>(this, column, rowCount, columnCount); 324 | } 325 | 326 | @Override 327 | public List> columns() { 328 | return new Outer<>(this, columnCount, 1, rowCount, columnCount); 329 | } 330 | 331 | static class Outer extends AbstractList> { 332 | private final DenseGrid grid; 333 | private final int size; 334 | private final int gap; 335 | private final int innerSize; 336 | private final int innerGap; 337 | 338 | Outer(DenseGrid grid, int size, int gap, int innerSize, int innerGap) { 339 | this.grid = grid; 340 | this.size = size; 341 | this.gap = gap; 342 | this.innerSize = innerSize; 343 | this.innerGap = innerGap; 344 | } 345 | 346 | @Override 347 | public int size() { 348 | return size; 349 | } 350 | 351 | @Override 352 | public List get(int index) { 353 | Preconditions.checkElementIndex(index, size); 354 | int base = index * gap; 355 | return new Inner<>(grid, base, innerSize, innerGap); 356 | } 357 | } 358 | 359 | static class Inner extends AbstractList { 360 | private final DenseGrid grid; 361 | private final int size; 362 | private final int offset; 363 | private final int gap; 364 | 365 | Inner(DenseGrid grid, int offset, int size, int gap) { 366 | this.grid = grid; 367 | this.size = size; 368 | this.offset = offset; 369 | this.gap = gap; 370 | } 371 | 372 | @Override 373 | public int size() { 374 | return size; 375 | } 376 | 377 | @Override 378 | public V get(int index) { 379 | Preconditions.checkElementIndex(index, size); 380 | int arrayIndex = index * gap + offset; 381 | return grid.values[arrayIndex]; 382 | } 383 | 384 | @Override 385 | public V set(int index, V newValue) { 386 | Preconditions.checkElementIndex(index, size); 387 | int arrayIndex = index * gap + offset; 388 | V old = grid.values[arrayIndex]; 389 | grid.values[arrayIndex] = newValue; 390 | if (old == null && newValue != null) { 391 | grid.size++; 392 | } else if (old != null && newValue == null) { 393 | grid.size--; 394 | } 395 | return old; 396 | } 397 | } 398 | 399 | //----------------------------------------------------------------------- 400 | @Override 401 | public void clear() { 402 | Arrays.fill(values, null); 403 | size = 0; 404 | } 405 | 406 | @Override 407 | public void put(int row, int column, V value) { 408 | if (!exists(row, column)) { 409 | throw new IndexOutOfBoundsException("Invalid row-column: " + row + "," + column); 410 | } 411 | if (value == null) { 412 | throw new IllegalArgumentException("Value must not be null"); 413 | } 414 | Object current = values[row * columnCount + column]; 415 | values[row * columnCount + column] = value; 416 | if (current == null) { 417 | size++; 418 | } 419 | } 420 | 421 | @Override 422 | public void putAll(Grid grid) { 423 | if (grid == null) { 424 | throw new IllegalArgumentException("Grid must nor be null"); 425 | } 426 | for (Cell cell : grid.cells()) { 427 | put(cell.getRow(), cell.getColumn(), cell.getValue()); 428 | } 429 | } 430 | 431 | @Override 432 | public boolean remove(int row, int column) { 433 | V current = get(row, column); 434 | if (current != null) { 435 | values[row * columnCount + column] = null; 436 | size--; 437 | return true; 438 | } 439 | return false; 440 | } 441 | 442 | //----------------------------------------------------------------------- 443 | /** 444 | * Returns a clone of the internal array. 445 | * 446 | * @return the array, not null 447 | */ 448 | V[] valuesArray() { 449 | return values.clone(); 450 | } 451 | 452 | //----------------------------------------------------------------------- 453 | @Override 454 | public boolean equals(Object obj) { 455 | return obj == this || 456 | (obj instanceof DenseGrid other && Arrays.equals(values, other.values)) || 457 | super.equals(obj); 458 | } 459 | 460 | @Override 461 | public int hashCode() { 462 | int hash = 0; 463 | for (int i = 0; i < values.length; i++) { 464 | Object value = values[i]; 465 | if (value != null) { 466 | int row = i / columnCount; 467 | int column = i % columnCount; 468 | hash += (row ^ Integer.rotateLeft(column, 16) ^ value.hashCode()); 469 | } 470 | } 471 | return rowCount ^ Integer.rotateLeft(columnCount, 16) ^ hash; 472 | } 473 | 474 | } 475 | -------------------------------------------------------------------------------- /src/test/java/org/joda/collect/grid/AbstractTestMutableGrid.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014-present Stephen Colebourne 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.joda.collect.grid; 17 | 18 | import static org.junit.Assert.assertEquals; 19 | import static org.junit.Assert.fail; 20 | 21 | import java.util.Arrays; 22 | import java.util.Iterator; 23 | import java.util.NoSuchElementException; 24 | 25 | import org.joda.collect.grid.Grid.Cell; 26 | import org.junit.Test; 27 | 28 | /** 29 | * Test abstract Grid. 30 | */ 31 | public abstract class AbstractTestMutableGrid extends AbstractTestGrid { 32 | 33 | @Test 34 | public void test_create_intInt() { 35 | Grid test = create(2, 3); 36 | test.put(0, 0, "Hello"); 37 | test.put(0, 1, "World"); 38 | assertEquals(2, test.rowCount()); 39 | assertEquals(3, test.columnCount()); 40 | checkGrid(test, 0, 0, "Hello", 0, 1, "World"); 41 | assertEquals("[2x3:(0,0)=Hello, (0,1)=World]", test.toString()); 42 | } 43 | 44 | @Test 45 | public void test_create_intInt_empty() { 46 | Grid test = create(2, 3); 47 | assertEquals(2, test.rowCount()); 48 | assertEquals(3, test.columnCount()); 49 | checkGrid(test); 50 | assertEquals("[2x3:]", test.toString()); 51 | } 52 | 53 | //----------------------------------------------------------------------- 54 | @Test(expected = IllegalArgumentException.class) 55 | public void test_create_intInt_negativeRowCount() { 56 | create(-1, 2); 57 | } 58 | 59 | @Test(expected = IllegalArgumentException.class) 60 | public void test_create_intInt_negativeColumnCount() { 61 | create(1, -2); 62 | } 63 | 64 | @Test(expected = IllegalArgumentException.class) 65 | public void test_create_intInt_negativeRowColumnCount() { 66 | create(-1, -2); 67 | } 68 | 69 | //----------------------------------------------------------------------- 70 | @Test 71 | public void test_create_Grid() { 72 | Grid test = create(new MockSingletonGrid(2, 3, 0, 1, "World")); 73 | assertEquals(2, test.rowCount()); 74 | assertEquals(3, test.columnCount()); 75 | checkGrid(test, 0, 1, "World"); 76 | } 77 | 78 | @Test(expected = IndexOutOfBoundsException.class) 79 | public void test_create_intIntGrid_negativeRow() { 80 | create(new MockSingletonGrid(2, 3, -1, 1, "World")); 81 | } 82 | 83 | @Test(expected = IndexOutOfBoundsException.class) 84 | public void test_create_intIntGrid_negativeColumn() { 85 | create(new MockSingletonGrid(2, 3, 0, -1, "World")); 86 | } 87 | 88 | @Test(expected = IllegalArgumentException.class) 89 | public void test_create_intIntGrid_nullValue() { 90 | create(new MockSingletonGrid(2, 3, 0, 1, null)); 91 | } 92 | 93 | @Test(expected = IllegalArgumentException.class) 94 | public void test_create_intIntGrid_negativeRowCount() { 95 | create(new MockSingletonGrid(-2, 3, 0, 1, "World")); 96 | } 97 | 98 | @Test(expected = IllegalArgumentException.class) 99 | public void test_create_intIntGrid_negativeColumnCount() { 100 | create(new MockSingletonGrid(2, -3, 0, 1, "World")); 101 | } 102 | 103 | @Test(expected = IllegalArgumentException.class) 104 | public void test_create_intIntGrid_null() { 105 | create((Grid) null); 106 | } 107 | 108 | //----------------------------------------------------------------------- 109 | @Test 110 | public void test_containsValue_Object() { 111 | Grid test = create3x3(); 112 | test.put(0, 0, "Hello"); 113 | test.put(0, 1, "World"); 114 | assertEquals(true, test.containsValue("Hello")); 115 | assertEquals(true, test.containsValue("World")); 116 | assertEquals(false, test.containsValue("Spicy")); 117 | assertEquals(false, test.containsValue("")); 118 | assertEquals(false, test.containsValue(null)); 119 | assertEquals(false, test.containsValue(Integer.valueOf(6))); 120 | } 121 | 122 | @SuppressWarnings("unlikely-arg-type") 123 | @Test 124 | public void test_equalsHashCode() { 125 | Grid test = create3x3(); 126 | test.put(0, 0, "Hello"); 127 | test.put(0, 1, "World"); 128 | assertEquals(true, test.equals(test)); 129 | assertEquals(true, test.equals(SparseGrid.create(test))); 130 | assertEquals(true, test.equals(DenseGrid.create(test))); 131 | assertEquals(true, test.equals(ImmutableGrid.copyOf(test))); 132 | assertEquals(false, test.equals(null)); 133 | assertEquals(false, test.equals("")); 134 | 135 | assertEquals(3 ^ Integer.rotateLeft(3, 16) ^ test.cells().hashCode(), test.hashCode()); 136 | } 137 | 138 | //----------------------------------------------------------------------- 139 | @Test 140 | public void test_clear() { 141 | Grid test = create3x3(); 142 | test.put(0, 0, "Hello"); 143 | test.put(0, 1, "World"); 144 | assertEquals(2, test.size()); 145 | assertEquals(2, test.cells().size()); 146 | assertEquals(2, test.values().size()); 147 | 148 | test.clear(); 149 | 150 | assertEquals(0, test.size()); 151 | assertEquals(0, test.cells().size()); 152 | assertEquals(0, test.values().size()); 153 | } 154 | 155 | @Test 156 | public void test_clear_empty() { 157 | Grid test = create3x3(); 158 | assertEquals(0, test.size()); 159 | assertEquals(0, test.cells().size()); 160 | assertEquals(0, test.values().size()); 161 | 162 | test.clear(); 163 | 164 | assertEquals(0, test.size()); 165 | assertEquals(0, test.cells().size()); 166 | assertEquals(0, test.values().size()); 167 | } 168 | 169 | //----------------------------------------------------------------------- 170 | @Test 171 | public void test_put_first() { 172 | Grid test = create3x3(); 173 | test.put(0, 0, "Hello"); 174 | checkGrid(test, 0, 0, "Hello"); 175 | } 176 | 177 | @Test 178 | public void test_put_second() { 179 | Grid test = create3x3(); 180 | test.put(0, 1, "World"); 181 | checkGrid(test, 0, 1, "World"); 182 | test.put(0, 0, "Hello"); 183 | checkGrid(test, 0, 0, "Hello", 0, 1, "World"); 184 | } 185 | 186 | @Test 187 | public void test_put_replace() { 188 | Grid test = create3x3(); 189 | test.put(0, 0, "Hello"); 190 | checkGrid(test, 0, 0, "Hello"); 191 | test.put(0, 0, "Update"); 192 | checkGrid(test, 0, 0, "Update"); 193 | } 194 | 195 | @Test(expected = IndexOutOfBoundsException.class) 196 | public void test_put_negativeRow() { 197 | Grid test = create3x3(); 198 | test.put(-1, 2, "Hello"); 199 | } 200 | 201 | @Test(expected = IndexOutOfBoundsException.class) 202 | public void test_put_negativeColumn() { 203 | Grid test = create3x3(); 204 | test.put(1, -2, "Hello"); 205 | } 206 | 207 | @Test(expected = IllegalArgumentException.class) 208 | public void test_put_nullValue() { 209 | Grid test = create3x3(); 210 | test.put(1, 2, null); 211 | } 212 | 213 | //----------------------------------------------------------------------- 214 | @Test 215 | public void test_putAll_emptyPlusNonEmpty() { 216 | Grid test = create3x3(); 217 | test.putAll(ImmutableGrid.of(2, 2, 0, 1, "Hello")); 218 | checkGrid(test, 0, 1, "Hello"); 219 | } 220 | 221 | @Test 222 | public void test_putAll_nonEmptyPlusEmpty() { 223 | Grid test = create3x3(); 224 | test.put(0, 1, "Hello"); 225 | checkGrid(test, 0, 1, "Hello"); 226 | test.putAll(ImmutableGrid.of()); 227 | checkGrid(test, 0, 1, "Hello"); 228 | } 229 | 230 | @Test 231 | public void test_putAll_emptyPlusEmpty() { 232 | Grid test = create3x3(); 233 | test.putAll(ImmutableGrid.of()); 234 | checkGrid(test); 235 | } 236 | 237 | @Test(expected = IllegalArgumentException.class) 238 | public void test_putAll_null() { 239 | Grid test = create3x3(); 240 | test.putAll((Grid) null); 241 | } 242 | 243 | //----------------------------------------------------------------------- 244 | @Test 245 | public void test_remove_last() { 246 | Grid test = create3x3(); 247 | test.put(0, 0, "Hello"); 248 | test.put(0, 1, "World"); 249 | checkGrid(test, 0, 0, "Hello", 0, 1, "World"); 250 | assertEquals(true, test.remove(0, 1)); 251 | checkGrid(test, 0, 0, "Hello"); 252 | } 253 | 254 | @Test 255 | public void test_remove_first() { 256 | Grid test = create3x3(); 257 | test.put(0, 0, "Hello"); 258 | test.put(0, 1, "World"); 259 | checkGrid(test, 0, 0, "Hello", 0, 1, "World"); 260 | assertEquals(true, test.remove(0, 0)); 261 | checkGrid(test, 0, 1, "World"); 262 | } 263 | 264 | @Test 265 | public void test_remove_notPresent_empty() { 266 | Grid test = create3x3(); 267 | checkGrid(test); 268 | assertEquals(false, test.remove(1, 2)); 269 | checkGrid(test); 270 | } 271 | 272 | @Test 273 | public void test_remove_notPresent_nonEmpty() { 274 | Grid test = create3x3(); 275 | test.put(0, 0, "Hello"); 276 | test.put(2, 2, "World"); 277 | checkGrid(test, 0, 0, "Hello", 2, 2, "World"); 278 | assertEquals(false, test.remove(1, 1)); 279 | assertEquals(false, test.remove(1, 2)); 280 | assertEquals(false, test.remove(2, 1)); 281 | checkGrid(test, 0, 0, "Hello", 2, 2, "World"); 282 | } 283 | 284 | @Test 285 | public void test_remove_largeIndex() { 286 | DenseGrid test = DenseGrid.create(2, 2); 287 | assertEquals(false, test.remove(999, 1000)); 288 | checkGrid(test); 289 | } 290 | 291 | @Test 292 | public void test_remove_invalidIndex() { 293 | Grid test = create3x3(); 294 | test.put(0, 0, "Hello"); 295 | checkGrid(test, 0, 0, "Hello"); 296 | assertEquals(false, test.remove(-1, -1)); 297 | assertEquals(false, test.remove(1, -1)); 298 | assertEquals(false, test.remove(-1, 1)); 299 | checkGrid(test, 0, 0, "Hello"); 300 | } 301 | 302 | @Test 303 | public void test_remove_empty() { 304 | Grid test = create3x3(); 305 | assertEquals(false, test.remove(0, 0)); 306 | checkGrid(test); 307 | } 308 | 309 | //----------------------------------------------------------------------- 310 | @Test 311 | public void test_cells() { 312 | Grid test = create3x3(); 313 | test.put(0, 0, "Hello"); 314 | test.put(0, 1, "World"); 315 | test.put(1, 1, "Space"); 316 | assertEquals(3, test.cells().size()); 317 | Iterator> it = test.cells().iterator(); 318 | assertEquals(true, it.hasNext()); 319 | assertEquals(ImmutableCell.of(0, 0, "Hello"), it.next()); 320 | assertEquals(true, it.hasNext()); 321 | assertEquals(ImmutableCell.of(0, 1, "World"), it.next()); 322 | assertEquals(true, it.hasNext()); 323 | assertEquals(ImmutableCell.of(1, 1, "Space"), it.next()); 324 | assertEquals(false, it.hasNext()); 325 | try { 326 | it.next(); 327 | fail(); 328 | } catch (NoSuchElementException ex) { 329 | // expected 330 | } 331 | } 332 | 333 | @Test 334 | public void test_cells_contains() { 335 | Grid test = create3x3(); 336 | test.put(0, 0, "Hello"); 337 | test.put(0, 1, "World"); 338 | assertEquals(true, test.cells().contains(ImmutableCell.of(0, 0, "Hello"))); 339 | assertEquals(true, test.cells().contains(ImmutableCell.of(0, 1, "World"))); 340 | assertEquals(false, test.cells().contains(ImmutableCell.of(1, 1, ""))); 341 | } 342 | 343 | @SuppressWarnings("unlikely-arg-type") 344 | @Test(expected = ClassCastException.class) 345 | public void test_cells_contains_nonCell() { 346 | Grid test = create3x3(); 347 | test.put(0, 0, "Hello"); 348 | test.put(0, 1, "World"); 349 | test.cells().contains("NonCell"); 350 | } 351 | 352 | @Test(expected = NullPointerException.class) 353 | public void test_cells_contains_null() { 354 | Grid test = create3x3(); 355 | test.put(0, 0, "Hello"); 356 | test.put(0, 1, "World"); 357 | test.cells().contains(null); 358 | } 359 | 360 | //----------------------------------------------------------------------- 361 | @Test 362 | public void test_cells_iterator_remove_first() { 363 | Grid test = create3x3(); 364 | test.put(0, 0, "Hello"); 365 | test.put(0, 1, "World"); 366 | assertEquals(2, test.cells().size()); 367 | Iterator> it = test.cells().iterator(); 368 | assertEquals(true, it.hasNext()); 369 | assertEquals(ImmutableCell.of(0, 0, "Hello"), it.next()); 370 | assertEquals(true, it.hasNext()); 371 | it.remove(); 372 | assertEquals(true, it.hasNext()); 373 | assertEquals(ImmutableCell.of(0, 1, "World"), it.next()); 374 | assertEquals(false, it.hasNext()); 375 | 376 | assertEquals(1, test.size()); 377 | assertEquals(null, test.get(0, 0)); 378 | assertEquals("World", test.get(0, 1)); 379 | assertEquals(null, test.get(1, 0)); 380 | assertEquals(null, test.get(1, 1)); 381 | } 382 | 383 | @Test 384 | public void test_cells_iterator_remove_second() { 385 | Grid test = create3x3(); 386 | test.put(0, 0, "Hello"); 387 | test.put(1, 1, "World"); 388 | assertEquals(2, test.cells().size()); 389 | Iterator> it = test.cells().iterator(); 390 | assertEquals(true, it.hasNext()); 391 | assertEquals(ImmutableCell.of(0, 0, "Hello"), it.next()); 392 | assertEquals(true, it.hasNext()); 393 | assertEquals(ImmutableCell.of(1, 1, "World"), it.next()); 394 | assertEquals(false, it.hasNext()); 395 | it.remove(); 396 | assertEquals(false, it.hasNext()); 397 | assertEquals(1, test.size()); 398 | assertEquals("Hello", test.get(0, 0)); 399 | assertEquals(null, test.get(0, 1)); 400 | assertEquals(null, test.get(1, 0)); 401 | assertEquals(null, test.get(1, 1)); 402 | } 403 | 404 | @Test(expected = IllegalStateException.class) 405 | public void test_cells_iterator_removeBeforeNext() { 406 | Grid test = create3x3(); 407 | test.put(0, 0, "Hello"); 408 | test.put(1, 1, "World"); 409 | test.cells().iterator().remove(); 410 | } 411 | 412 | @Test(expected = IllegalStateException.class) 413 | public void test_cells_iterator_removeTwice() { 414 | Grid test = create3x3(); 415 | test.put(0, 0, "Hello"); 416 | test.put(1, 1, "World"); 417 | Iterator> it = test.cells().iterator(); 418 | it.next(); 419 | it.remove(); 420 | it.remove(); 421 | } 422 | 423 | //----------------------------------------------------------------------- 424 | @Test 425 | public void test_cells_add() { 426 | Grid test = create3x3(); 427 | test.put(0, 0, "Hello"); 428 | test.put(0, 1, "World"); 429 | test.cells().add(ImmutableCell.of(1, 2, "Extra")); 430 | 431 | assertEquals(3, test.size()); 432 | assertEquals(true, test.contains(0, 0)); 433 | assertEquals(true, test.contains(0, 1)); 434 | assertEquals(true, test.contains(1, 2)); 435 | assertEquals("Hello", test.get(0, 0)); 436 | assertEquals("World", test.get(0, 1)); 437 | assertEquals("Extra", test.get(1, 2)); 438 | assertEquals(3, test.cells().size()); 439 | assertEquals(3, test.values().size()); 440 | } 441 | 442 | @Test 443 | public void test_cells_add_replace() { 444 | Grid test = create3x3(); 445 | test.put(0, 0, "Hello"); 446 | test.put(0, 1, "World"); 447 | test.cells().add(ImmutableCell.of(0, 1, "World")); 448 | 449 | assertEquals(2, test.size()); 450 | assertEquals(true, test.contains(0, 0)); 451 | assertEquals(true, test.contains(0, 1)); 452 | assertEquals("Hello", test.get(0, 0)); 453 | assertEquals("World", test.get(0, 1)); 454 | assertEquals(2, test.cells().size()); 455 | assertEquals(2, test.values().size()); 456 | } 457 | 458 | @Test(expected = IndexOutOfBoundsException.class) 459 | public void test_cells_add_negativeRow() { 460 | Grid test = create3x3(); 461 | test.cells().add(new MockCell(-1, 2, "Hello")); 462 | } 463 | 464 | @Test(expected = IndexOutOfBoundsException.class) 465 | public void test_cells_add_negativeColumn() { 466 | Grid test = create3x3(); 467 | test.cells().add(new MockCell(1, -2, "Hello")); 468 | } 469 | 470 | @Test(expected = IllegalArgumentException.class) 471 | public void test_cells_add_nullValue() { 472 | Grid test = create3x3(); 473 | test.cells().add(new MockCell(1, 2, null)); 474 | } 475 | 476 | @Test(expected = IllegalArgumentException.class) 477 | public void test_cells_add_null() { 478 | Grid test = create3x3(); 479 | test.cells().add(null); 480 | } 481 | 482 | //----------------------------------------------------------------------- 483 | @Test 484 | public void test_cells_addAll() { 485 | Grid test = create3x3(); 486 | test.put(0, 0, "Hello"); 487 | test.put(0, 1, "World"); 488 | test.cells().addAll(Arrays.asList(ImmutableCell.of(1, 2, "Extra"), ImmutableCell.of(2, 2, "Lots"))); 489 | 490 | assertEquals(4, test.size()); 491 | assertEquals(true, test.contains(0, 0)); 492 | assertEquals(true, test.contains(0, 1)); 493 | assertEquals(true, test.contains(1, 2)); 494 | assertEquals(true, test.contains(2, 2)); 495 | assertEquals("Hello", test.get(0, 0)); 496 | assertEquals("World", test.get(0, 1)); 497 | assertEquals("Extra", test.get(1, 2)); 498 | assertEquals("Lots", test.get(2, 2)); 499 | assertEquals(4, test.cells().size()); 500 | assertEquals(4, test.values().size()); 501 | } 502 | 503 | //----------------------------------------------------------------------- 504 | @Test 505 | public void test_cells_remove_first() { 506 | Grid test = create3x3(); 507 | test.put(0, 0, "Hello"); 508 | test.put(0, 1, "World"); 509 | assertEquals(2, test.cells().size()); 510 | assertEquals(true, test.cells().remove(ImmutableCell.of(0, 0, "Hello"))); 511 | 512 | assertEquals(1, test.size()); 513 | assertEquals(null, test.get(0, 0)); 514 | assertEquals("World", test.get(0, 1)); 515 | } 516 | 517 | @Test 518 | public void test_cells_remove_second() { 519 | Grid test = create3x3(); 520 | test.put(0, 0, "Hello"); 521 | test.put(0, 1, "World"); 522 | assertEquals(2, test.cells().size()); 523 | assertEquals(true, test.cells().remove(ImmutableCell.of(0, 1, "World"))); 524 | 525 | assertEquals(1, test.size()); 526 | assertEquals("Hello", test.get(0, 0)); 527 | assertEquals(null, test.get(0, 1)); 528 | } 529 | 530 | @Test 531 | public void test_cells_remove_goodIndicesBadValue() { 532 | Grid test = create3x3(); 533 | test.put(0, 0, "Hello"); 534 | test.put(0, 1, "World"); 535 | assertEquals(2, test.cells().size()); 536 | assertEquals(true, test.cells().remove(ImmutableCell.of(0, 1, "Rubbish"))); 537 | 538 | assertEquals(1, test.size()); 539 | assertEquals("Hello", test.get(0, 0)); 540 | assertEquals(null, test.get(0, 1)); 541 | } 542 | 543 | @Test 544 | public void test_cells_remove_badCell_negativeRow() { 545 | Grid test = create3x3(); 546 | test.put(0, 0, "Hello"); 547 | test.put(0, 1, "World"); 548 | assertEquals(2, test.cells().size()); 549 | assertEquals(false, test.cells().remove(new MockCell(-1, 1, "Hello"))); 550 | 551 | assertEquals(2, test.size()); 552 | assertEquals("Hello", test.get(0, 0)); 553 | assertEquals("World", test.get(0, 1)); 554 | } 555 | 556 | @SuppressWarnings("unlikely-arg-type") 557 | @Test(expected = ClassCastException.class) 558 | public void test_cells_remove_nonCell() { 559 | Grid test = create3x3(); 560 | test.put(0, 0, "Hello"); 561 | test.put(0, 1, "World"); 562 | test.cells().remove("NonCell"); 563 | } 564 | 565 | @Test(expected = NullPointerException.class) 566 | public void test_cells_remove_null() { 567 | Grid test = create3x3(); 568 | test.put(0, 0, "Hello"); 569 | test.put(0, 1, "World"); 570 | test.cells().remove(null); 571 | } 572 | 573 | //----------------------------------------------------------------------- 574 | @Test 575 | public void test_cells_clear() { 576 | Grid test = create3x3(); 577 | test.put(0, 0, "Hello"); 578 | test.put(0, 1, "World"); 579 | test.cells().clear(); 580 | 581 | assertEquals(0, test.size()); 582 | assertEquals(0, test.cells().size()); 583 | assertEquals(0, test.values().size()); 584 | } 585 | 586 | @Test 587 | public void test_cells_clear_empty() { 588 | Grid test = create3x3(); 589 | assertEquals(0, test.size()); 590 | assertEquals(0, test.cells().size()); 591 | assertEquals(0, test.values().size()); 592 | 593 | test.cells().clear(); 594 | 595 | assertEquals(0, test.size()); 596 | assertEquals(0, test.cells().size()); 597 | assertEquals(0, test.values().size()); 598 | } 599 | 600 | //----------------------------------------------------------------------- 601 | protected abstract Grid create3x3(); 602 | 603 | protected abstract Grid create(int rowCount, int columnCount); 604 | 605 | protected abstract Grid create(Grid grid); 606 | 607 | } 608 | --------------------------------------------------------------------------------