├── .gitignore ├── .travis.yml ├── .travis ├── deploy-jars.sh ├── deploy-javadocs.sh └── settings.xml ├── CHANGELOG.md ├── CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── pom.xml └── src ├── main └── java │ └── com │ └── marcospassos │ └── phpserializer │ ├── AdapterRegistry.java │ ├── Context.java │ ├── FieldExclusionStrategy.java │ ├── NamingStrategy.java │ ├── Serializer.java │ ├── SerializerBuilder.java │ ├── SerializerFactory.java │ ├── TypeAdapter.java │ ├── Writer.java │ ├── WriterState.java │ ├── adapter │ ├── ArrayAdapter.java │ ├── BooleanAdapter.java │ ├── CollectionAdapter.java │ ├── DoubleAdapter.java │ ├── IntegerAdapter.java │ ├── LongAdapter.java │ ├── MapAdapter.java │ ├── ObjectAdapter.java │ ├── ReferableObjectAdapter.java │ └── StringAdapter.java │ ├── exclusion │ ├── DisjunctionExclusionStrategy.java │ └── NoExclusionStrategy.java │ ├── naming │ └── PsrNamingStrategy.java │ ├── state │ ├── AbstractState.java │ ├── FinishedState.java │ ├── WritingArrayState.java │ ├── WritingObjectState.java │ ├── WritingSerializableObjectState.java │ └── WritingValueState.java │ └── util │ └── ReflectionUtils.java └── test └── java └── com └── marcospassos └── phpserializer ├── AdapterRegistryTest.java ├── ContextTest.java ├── SerializerBuilderTest.java ├── SerializerTest.java ├── WriterTest.java ├── adapter ├── ArrayAdapterTest.java ├── BooleanAdapterTest.java ├── CollectionAdapterTest.java ├── DoubleAdapterTest.java ├── IntegerAdapterTest.java ├── LongAdapterTest.java ├── MapAdapterTest.java ├── ObjectAdapterTest.java ├── ReferableObjectAdapterTest.java └── StringAdapterTest.java ├── exclusion ├── DisjunctionExclusionStrategyTest.java └── NoExclusionStrategyTest.java ├── naming └── PsrNamingStrategyTest.java ├── state ├── EndStateTest.java ├── WritingArrayStateTest.java ├── WritingObjectStateTest.java ├── WritingSerializableObjectStateTest.java └── WritingValueStateTest.java └── util └── ReflectionUtilsTest.java /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled class file 2 | *.class 3 | 4 | # Package Files # 5 | *.jar 6 | *.war 7 | *.ear 8 | *.zip 9 | *.tar.gz 10 | *.rar -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: java 2 | jdk: 3 | - oraclejdk8 4 | - openjdk7 5 | install: 6 | - mvn --settings .travis/settings.xml install -DskipTests=true -Dgpg.skip -Dmaven.javadoc.skip=true -B -V 7 | before_install: 8 | - if [ ! -z "$GPG_SECRET_KEYS" ]; then echo $GPG_SECRET_KEYS | base64 --decode | $GPG_EXECUTABLE --import; fi 9 | - if [ ! -z "$GPG_OWNERTRUST" ]; then echo $GPG_OWNERTRUST | base64 --decode | $GPG_EXECUTABLE --import-ownertrust; fi 10 | after_success: 11 | - mvn clean cobertura:cobertura coveralls:report -DrepoToken=${COVERALL_TOKEN} 12 | addons: 13 | apt: 14 | packages: 15 | - libxml2-utils 16 | deploy: 17 | - 18 | provider: script 19 | script: .travis/deploy-jars.sh 20 | skip_cleanup: true 21 | on: 22 | repo: marcospassos/java-php-serializer 23 | tags: true 24 | jdk: oraclejdk8 25 | - 26 | provider: script 27 | script: .travis/deploy-javadocs.sh 28 | skip_cleanup: true 29 | on: 30 | repo: marcospassos/java-php-serializer 31 | tags: true 32 | jdk: oraclejdk8 33 | -------------------------------------------------------------------------------- /.travis/deploy-jars.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | cd `dirname $0`/.. 3 | 4 | if [ -z "$SONATYPE_USERNAME" ] 5 | then 6 | echo "error: please set SONATYPE_USERNAME and SONATYPE_PASSWORD environment variable" 7 | exit 1 8 | fi 9 | 10 | if [ -z "$SONATYPE_PASSWORD" ] 11 | then 12 | echo "error: please set SONATYPE_PASSWORD environment variable" 13 | exit 1 14 | fi 15 | 16 | if [ ! -z "$TRAVIS_TAG" ] 17 | then 18 | echo "on a tag -> set pom.xml to $TRAVIS_TAG" 19 | mvn --settings .travis/settings.xml org.codehaus.mojo:versions-maven-plugin:2.1:set -DnewVersion=$TRAVIS_TAG 1>/dev/null 2>/dev/null 20 | else 21 | echo "not on a tag -> keep snapshot version in pom.xml" 22 | fi 23 | 24 | mvn clean deploy --settings .travis/settings.xml -DskipTests=true -B -U -------------------------------------------------------------------------------- /.travis/deploy-javadocs.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e 4 | 5 | if [ -z ${GH_TOKEN} ]; then 6 | echo "\$GH_TOKEN is unset" 7 | exit 1 8 | fi 9 | 10 | if [ -z ${TRAVIS_REPO_SLUG} ]; then 11 | echo "\$TRAVIS_REPO_SLUG is unset" 12 | exit 1 13 | fi 14 | 15 | # Build parameters 16 | REPOSITORY=${REPOSITORY:-"https://${GH_TOKEN}@github.com/${TRAVIS_REPO_SLUG}.git"} 17 | BRANCH=${BRANCH:-"gh-pages"} 18 | POM_VERSION=$(xmllint --xpath "//*[local-name()='project']/*[local-name()='version']/text()" pom.xml) 19 | VERSION_MAJOR="$(echo "${POM_VERSION}" | cut -d '.' -f 1)" 20 | VERSION_MINOR="$(echo "${POM_VERSION}" | cut -d '.' -f 2)" 21 | VERSION_SHORT="${VERSION_MAJOR}.${VERSION_MINOR}" 22 | 23 | # Commit settings 24 | COMMIT_AUTHOR_NAME="Travis" 25 | COMMIT_AUTHOR_EMAIL="travis@travis-ci.org" 26 | COMMIT_MESSAGE="Update documentation to version ${VERSION_SHORT}" 27 | 28 | # Documentation paths 29 | API_DOCS_SRC="${TRAVIS_BUILD_DIR}/target/apidocs" 30 | API_DOCS="docs/api" 31 | API_LATEST="${API_DOCS}/latest" 32 | API_DIR=VERSION_SHORT 33 | API_VERSION="${API_DOCS}/${API_DIR}" 34 | 35 | echo "Updating javadocs to version ${VERSION_SHORT}" 36 | 37 | # Import repository 38 | echo "Cloning repository..." 39 | git clone "${REPOSITORY}" gh-pages --branch "${BRANCH}" --depth 1 40 | 41 | # Change working directory 42 | cd "gh-pages" 43 | 44 | # Set identity 45 | git config user.name "${COMMIT_AUTHOR_NAME}" 46 | git config user.email "${COMMIT_AUTHOR_EMAIL}" 47 | 48 | rm -Rf "${API_VERSION}" 49 | mkdir -p "${API_VERSION}" 50 | cp -Rf "${API_DOCS_SRC}/." "${API_VERSION}" 51 | # Create a latest link which points to the last version 52 | rm -f "${API_LATEST}" 53 | ln -sf "${API_DIR}" "${API_LATEST}" 54 | 55 | # Record changes to the repository 56 | git add . 57 | git commit -m "${COMMIT_MESSAGE}" 58 | 59 | # Push changes to remote server 60 | git push origin HEAD 61 | 62 | echo "Documentation successfully updated to version ${VERSION_SHORT}" -------------------------------------------------------------------------------- /.travis/settings.xml: -------------------------------------------------------------------------------- 1 | 5 | 6 | 7 | 8 | ossrh 9 | ${env.SONATYPE_USERNAME} 10 | ${env.SONATYPE_PASSWORD} 11 | 12 | 13 | 14 | 15 | ossrh 16 | 17 | true 18 | 19 | 20 | ${env.GPG_EXECUTABLE} 21 | ${env.GPG_PASSPHRASE} 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | All notable changes to this project will be documented in this file. 3 | 4 | The format is based on [Keep a Changelog](http://keepachangelog.com/) 5 | and this project adheres to [Semantic Versioning](http://semver.org/). 6 | 7 | ## Unreleased 8 | There are currently no unreleased changes. 9 | 10 | ## [0.8.0](https://github.com/marcospassos/java-php-serializer/releases/tag/0.8.0) (2018-02-28) 11 | 12 | ### Changed 13 | 14 | - Fix bug that registers `DoubleAdapter` for type `Integer` instead of `Double`. 15 | - Add tests to ensure that calling `registerBuiltinAdapters()` registers all builtin adapters. 16 | 17 | ## [0.7.0](https://github.com/marcospassos/java-php-serializer/releases/tag/0.7.0) (2018-02-28) 18 | 19 | ### Changed 20 | 21 | - Remove parameter `charset` from `SerializerBuilder::registerBuiltinAdapters()` in favor of `SerializerBuilder::setCharset()` 22 | 23 | ## [0.6.0](https://github.com/marcospassos/java-php-serializer/releases/tag/0.6.0) (2018-02-28) 24 | 25 | ### Changed 26 | 27 | - Introduce adapter for `Long` type as discussed in [#2](https://github.com/marcospassos/java-php-serializer/issues/2). 28 | - Fix missing builtin adapters reported in [#2](https://github.com/marcospassos/java-php-serializer/issues/2). 29 | - Add support for string encoding, fixing cases where strings are badly encoded, as reported in [#3](https://github.com/marcospassos/java-php-serializer/issues/3). 30 | 31 | ## [0.5.2](https://github.com/marcospassos/java-php-serializer/releases/tag/0.5.2) (2017-07-12) 32 | 33 | ### Changed 34 | 35 | - Remove unnecessary quote escaping from string serialization. 36 | 37 | 38 | ## [0.5.1](https://github.com/marcospassos/java-php-serializer/releases/tag/0.5.1) (2017-07-09) 39 | 40 | ### Changed 41 | 42 | - Fix reference counting logic. 43 | 44 | ## [0.5.0](https://github.com/marcospassos/java-php-serializer/releases/tag/0.5.0) (2017-07-09) 45 | 46 | ### Changed 47 | 48 | - Fix visibility of method `Writer::writeSerializableObjectEnd()`. 49 | 50 | ## [0.4.1](https://github.com/marcospassos/java-php-serializer/releases/tag/0.4.1) (2017-07-09) 51 | 52 | ### Changed 53 | 54 | - Fix reference counting logic. 55 | - Set pointer in sub writes to keep parent's writer count. 56 | 57 | ## [0.4.0](https://github.com/marcospassos/java-php-serializer/releases/tag/0.4.0) (2017-07-09) 58 | 59 | ### Changed 60 | 61 | - Improve `Writer`'s API for writing serializable objects. 62 | - Fix reference counting that breaks after writing serializable objects. 63 | 64 | ## [0.3.0](https://github.com/marcospassos/java-php-serializer/releases/tag/0.3.0) (2017-07-07) 65 | 66 | ### Changed 67 | 68 | - Calling `SerializerBuilder::registerBuiltinAdapters()` now registers 69 | `CollectionAdapter` for any collection and not only for `Map` and `Set`. 70 | 71 | ## [0.2.0](https://github.com/marcospassos/java-php-serializer/releases/tag/0.2.0) (2017-07-06) 72 | 73 | ### Changed 74 | 75 | - Rename `ListMap` to `CollectionAdapter` since it supports any collection. 76 | 77 | ### Added 78 | 79 | - Adapter for `Set` in the list of builtin adapters registered by 80 | `SerializerBuilder::registerBuiltinAdapters()`. 81 | 82 | ## [0.1.0](https://github.com/marcospassos/java-php-serializer/releases/tag/0.1.0) (2017-06-29) 83 | 84 | - First beta release 85 | -------------------------------------------------------------------------------- /CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, gender identity and expression, level of experience, 9 | nationality, personal appearance, race, religion, or sexual identity and 10 | orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at marcos@marcospassos.com. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 71 | version 1.4, available at [http://contributor-covenant.org/version/1/4][version] 72 | 73 | [homepage]: http://contributor-covenant.org 74 | [version]: http://contributor-covenant.org/version/1/4/ -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | Contributions are **welcome** and will be fully **credited**. 4 | 5 | We accept contributions via Pull Requests on [Github][repository]. 6 | 7 | 8 | ## Pull Requests 9 | 10 | - **Add tests!** - Your patch won't be accepted if it doesn't have tests. 11 | 12 | - **Document any change in behaviour** - Make sure the `README.md` and any 13 | other relevant documentation are kept up-to-date. 14 | 15 | - **Consider our release cycle** - We try to follow 16 | [SemVer v2.0.0][semver]. Randomly breaking public APIs is not an option. 17 | 18 | - **Create feature branches** - Don't ask us to pull from your master branch. 19 | 20 | - **One pull request per feature** - If you want to do more than one thing, 21 | send multiple pull requests. 22 | 23 | - **Send coherent history** - Make sure each individual commit in your pull 24 | request is meaningful. If you had to make multiple intermediate commits while 25 | developing, please [squash them][git-book-squash] before submitting. 26 | 27 | 28 | ## Running Tests 29 | 30 | ``` bash 31 | $ mvn test 32 | ``` 33 | 34 | **Happy coding**! 35 | 36 | 37 | [repository]: https://github.com/marcospassos/java-php-serializer 38 | [semver]: http://semver.org/ 39 | [git-book-squash]: http://www.git-scm.com/book/en/v2/Git-Tools-Rewriting-History#Changing-Multiple-Commit-Messages -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2017 Marcos Passos 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Java PHP Serializer 2 | =================== 3 | [![Build Status][travis-badge]][travis-status] 4 | [![Coverage Status][coverall-badge]][coverall-status] 5 | [![Java 7+][java-badge]][java] 6 | [![License][mit-license-badge]](LICENSE) 7 | 8 | Latest release: [![Maven Central][maven-central-badge]][maven-central-latest] 9 | 10 | A Java library for serializing objects as PHP serialization format. 11 | 12 | The library fully implements the PHP serialization format specification, which 13 | includes: 14 | 15 | - Scalar values 16 | - Objects 17 | - Serializable (custom serializable objects) 18 | - Object and variable references 19 | 20 | The [API documentation][api-docs] is available on GitHub Pages more convenient viewing in 21 | browser. 22 | 23 | > **A word of notice** 24 | > 25 | > This library does not provide any mechanism for creating a communication 26 | > channel between Java and PHP. For such purpose, consider using 27 | [Soluble Java][soluble-java]. 28 | 29 | ## Use case 30 | 31 | One of the easier way to exchange data between Java and PHP consists in 32 | serializing the value to a data-interchanging format, such as JSON or XML, 33 | send it through a communication channel and finally deserialize it back to the 34 | original form on the PHP side. The problem with this approach is that the cost 35 | of deserializing complex objects in PHP is very high. 36 | 37 | Most of the serialization libraries in PHP use reflection for re-hydrating 38 | objects, and it becomes an issue when you have to deserialize large structures 39 | with hundreds of objects. Fortunately, PHP's native serialization is fast and 40 | the `unserialize()` function can handle such cases in a few milliseconds. 41 | 42 | This library implements the full format specification through a friendly API 43 | that encapsulates the complexity of the serialization process. 44 | 45 | ## Installation 46 | 47 | ### Maven 48 | 49 | The PHP Serializer is available in the 50 | [Maven Central repository][maven-central-latest]. 51 | Any Maven based project can use it directly by adding the appropriate entries 52 | to the `dependencies` section of its `pom.xml` file: 53 | 54 | ```xml 55 | 56 | 57 | com.marcospassos 58 | phpserializer 59 | 0.8.0 60 | 61 | 62 | ``` 63 | 64 | ### Binaries 65 | 66 | Packaged JARs can be downloaded directly from the [releases page][releases-page] 67 | and extracted using tar or unzip. 68 | 69 | ## Usage 70 | 71 | ### How to serialize data 72 | 73 | The first step to serialize a Java object into a PHP serialization format 74 | string is to create a Serializer instance according to your application domain 75 | model. The library ships a builder that help us with this task: 76 | 77 | ```java 78 | Serializer serializer = new SerializerBuilder() 79 | 80 | // Adds a custom exclusion strategy to determine which field 81 | // should be serialized or not (default: no exclusion) 82 | .addExclusionStrategy(new MyCustomExclusionStrategy()) 83 | 84 | // Sets the naming strategy to convert the name of classes 85 | // and fields from Java to PHP (default: PsrNamingStrategy) 86 | .setNamingStrategy(new MyCustomNamingStrategy()) 87 | 88 | // Registers all builtin adapters, using UTF-8 for encoding strings 89 | // (default: all built-in adapters, UTF-8 charset) 90 | .registerBuiltinAdapters() 91 | 92 | // Sets ISO-8859-1 as the default charset 93 | // 94 | // Notice that setCharset() register a new adapter configured with the 95 | // specified charset. Calling setCharset() prior to registerBuiltinAdapters() 96 | // will have no effect as the previous configuration will get overriden 97 | // by the default adapter which encodes strings in UTF-8. 98 | .setCharset(Charset.forName("ISO-8859-1")) 99 | 100 | // Register a custom type adapter 101 | .registerAdapter(CustomObject.class, new CustomObjectAdapter()) 102 | 103 | // Creates the serialized based on the given configuration 104 | .build(); 105 | ``` 106 | 107 | Now you have a `Serializer` instance configured according to your application 108 | domain model. To serialize a value, just invoke `serialize(Object object)` on 109 | the serializer instance: 110 | 111 | ```java 112 | List list = new ArrayList(); 113 | list.add("A string"); 114 | list.add(12345); 115 | list.add(true); 116 | 117 | // Outputs: a:3:{i:0;s:8:"A string";i:1;i:12345;i:2;b:1;} 118 | System.out.println(serializer.serialize(list)); 119 | ``` 120 | 121 | ### Naming strategies 122 | 123 | A naming strategy allows you to translate the name of classes and fields 124 | according to the target domain model. The library ships with a built-in adapter 125 | that converts Java packages to PHP namespaces in accordance with PSR-1 rules. 126 | If the PSR strategy does not fit your needs you can easily implement a custom 127 | naming strategy. Takes as reference the following strategy that appends an 128 | underscore to all private fields: 129 | 130 | ```java 131 | public class UnderscoreNamingStrategy implements NamingStrategy 132 | { 133 | public String getClassName(Class type) 134 | { 135 | return type.getName(); 136 | } 137 | 138 | public String getFieldName(Field field) 139 | { 140 | if (Modifier.isPrivate(field.getModifiers())) { 141 | return "_" + field.getName(); 142 | } 143 | 144 | return field.getName(); 145 | } 146 | } 147 | ``` 148 | 149 | ### Type Adapters 150 | 151 | A type adapter provides the serializer the logic for how to encode a specific 152 | type. The following example shows how to create a custom type adapter 153 | to serialize Enums as string using the name of the enum constant: 154 | 155 | ```java 156 | public class EnumTypeAdapter implements TypeAdapter 157 | { 158 | public void write(Enum value, Writer writer, Context context) 159 | { 160 | writer.writeString(value.name()); 161 | } 162 | } 163 | ``` 164 | 165 | Notice that circular references are handled as per case basis, once it is not 166 | always the desirable. For instance, you may not want to serialize lists as 167 | objects references, as array is probably the most appropriate corresponding 168 | type in PHP. The following example shows how to handle such cases: 169 | 170 | ```java 171 | public class MyCustomAdapter implements TypeAdapter 172 | { 173 | @Override 174 | public void write(CustomObject object, Writer writer, Context context) 175 | { 176 | int reference = context.getReference(object); 177 | 178 | if (reference > 0) { 179 | writer.writeObjectReference(reference); 180 | 181 | return; 182 | } 183 | 184 | context.setReference(reference, object); 185 | 186 | // Custom serialization logic 187 | } 188 | } 189 | ``` 190 | 191 | ## Change log 192 | 193 | Please see [CHANGELOG](CHANGELOG.md) for more information what has changed 194 | recently. 195 | 196 | ## Contributing 197 | 198 | Contributions to the package are always welcome! 199 | 200 | * Report any bugs or issues you find on the [issue tracker][issue-tracker]. 201 | * You can grab the source code at the package's 202 | [Git repository][repository]. 203 | 204 | Please see [CONTRIBUTING](CONTRIBUTING.md) and [CONDUCT](CONDUCT.md) for 205 | details. 206 | 207 | ## Security 208 | 209 | If you discover any security related issues, please email 210 | marcos@marcospassos.com instead of using the issue tracker. 211 | 212 | ## Credits 213 | 214 | * [Marcos Passos][author-page] 215 | - [All Contributors][contributors-page] 216 | 217 | 218 | ## License 219 | 220 | All contents of this package are licensed under the [MIT license](LICENSE). 221 | 222 | ``` 223 | Copyright (c) 2018 Marcos Passos 224 | 225 | Permission is hereby granted, free of charge, to any person obtaining a 226 | copy of this software and associated documentation files (the "Software"), 227 | to deal in the Software without restriction, including without limitation 228 | the rights to use, copy, modify, merge, publish, distribute, sublicense, 229 | and/or sell copies of the Software, and to permit persons to whom the 230 | Software is furnished to do so, subject to the following conditions: 231 | 232 | The above copyright notice and this permission notice shall be included in 233 | all copies or substantial portions of the Software. 234 | 235 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 236 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 237 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 238 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 239 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 240 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 241 | DEALINGS IN THE SOFTWARE. 242 | ``` 243 | 244 | [maven-central-badge]: https://img.shields.io/badge/maven%20central-v0.8.0-blue.svg 245 | [maven-central-latest]: http://search.maven.org/#artifactdetails%7Ccom.marcospassos%7Cphpserializer%7C0.8.0%7Cjar 246 | [coverall-status]: https://coveralls.io/github/marcospassos/java-php-serializer 247 | [coverall-badge]: https://coveralls.io/repos/github/marcospassos/java-php-serializer/badge.svg 248 | [travis-badge]: https://travis-ci.org/marcospassos/java-php-serializer.svg?branch=master 249 | [travis-status]: https://travis-ci.org/marcospassos/java-php-serializer 250 | [java-badge]: https://img.shields.io/badge/java-7+-4c7e9f.svg 251 | [java]: http://java.oracle.com 252 | [mit-license-badge]: https://img.shields.io/badge/license-MIT-blue.svg 253 | [api-docs]: https://marcospassos.github.io/java-php-serializer/docs/api/latest/ 254 | [soluble-java]: https://github.com/belgattitude/soluble-japha 255 | [author-page]: http://github.com/marcospassos 256 | [contributors-page]: https://github.com/marcospassos/java-php-serializer/graphs/contributors 257 | [issue-tracker]: https://github.com/marcospassos/java-php-serializer/issues 258 | [repository]: https://github.com/marcospassos/java-php-serializer 259 | [releases-page]: https://github.com/marcospassos/java-php-serializer/releases 260 | [latest-release]: https://github.com/marcospassos/java-php-serializer/releases/tag/0.8.0 261 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | com.marcospassos 8 | phpserializer 9 | 0.8.0-SNAPSHOT 10 | jar 11 | 12 | Java PHP Serializer 13 | https://github.com/marcospassos/java-php-serializer 14 | PHP Serializer for Java 15 | 16 | Java PHP Serializer 17 | https://github.com/marcospassos/java-php-serializer 18 | 19 | 20 | 21 | https://github.com/marcospassos/java-php-serializer.git 22 | 23 | 24 | 25 | 26 | Marcos Passos 27 | https://github.com/marcospassos 28 | marcos@marcospassos.com 29 | 30 | Project lead 31 | 32 | 33 | 34 | 35 | 36 | 37 | MIT 38 | https://opensource.org/licenses/MIT 39 | repo 40 | 41 | 42 | 43 | 44 | UTF-8 45 | 46 | 47 | 48 | 49 | ossrh 50 | https://oss.sonatype.org/content/repositories/snapshots 51 | 52 | 53 | ossrh 54 | https://oss.sonatype.org/service/local/staging/deploy/maven2/ 55 | 56 | 57 | 58 | 59 | 60 | 61 | org.eluder.coveralls 62 | coveralls-maven-plugin 63 | 4.3.0 64 | 65 | 66 | org.codehaus.mojo 67 | cobertura-maven-plugin 68 | 2.7 69 | 70 | xml 71 | 256m 72 | 73 | true 74 | 75 | 76 | 77 | 78 | org.apache.maven.plugins 79 | maven-compiler-plugin 80 | 3.3 81 | 82 | 1.7 83 | 1.7 84 | 85 | 86 | 87 | org.sonatype.plugins 88 | nexus-staging-maven-plugin 89 | 1.6.8 90 | true 91 | 92 | ossrh 93 | https://oss.sonatype.org/ 94 | true 95 | 96 | 97 | 98 | org.apache.maven.plugins 99 | maven-source-plugin 100 | 3.0.1 101 | 102 | 103 | attach-sources 104 | 105 | jar-no-fork 106 | 107 | 108 | 109 | 110 | 111 | org.apache.maven.plugins 112 | maven-javadoc-plugin 113 | 2.10.4 114 | 115 | 116 | attach-javadocs 117 | 118 | jar 119 | 120 | 121 | 122 | 123 | 124 | org.apache.maven.plugins 125 | maven-gpg-plugin 126 | 1.6 127 | 128 | 129 | sign-artifacts 130 | verify 131 | 132 | sign 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | org.mockito 143 | mockito-core 144 | 2.8.9 145 | test 146 | 147 | 148 | junit 149 | junit 150 | 4.12 151 | test 152 | 153 | 154 | 155 | -------------------------------------------------------------------------------- /src/main/java/com/marcospassos/phpserializer/AdapterRegistry.java: -------------------------------------------------------------------------------- 1 | package com.marcospassos.phpserializer; 2 | 3 | import java.util.ArrayList; 4 | import java.util.HashMap; 5 | import java.util.LinkedHashMap; 6 | import java.util.List; 7 | import java.util.Map; 8 | 9 | /** 10 | * Stores and resolves adapters for types. 11 | * 12 | * @author Marcos Passos 13 | * @since 1.0 14 | */ 15 | public class AdapterRegistry 16 | { 17 | /** 18 | * The list of adapters. 19 | */ 20 | private List adapters; 21 | 22 | /** 23 | * The list of classes. 24 | */ 25 | private List classes; 26 | 27 | /** 28 | * The list of primitive types. 29 | */ 30 | private static Map primitives = new HashMap<>(); 31 | 32 | static { 33 | primitives.put(int.class, Integer.class); 34 | primitives.put(long.class, Long.class); 35 | primitives.put(double.class, Double.class); 36 | primitives.put(float.class, Float.class); 37 | primitives.put(boolean.class, Boolean.class); 38 | primitives.put(char.class, Character.class); 39 | primitives.put(byte.class, Byte.class); 40 | primitives.put(short.class, Short.class); 41 | } 42 | 43 | /** 44 | * Creates a registry containing the specified adapters. 45 | * 46 | * @param adapters The map of classes and adapters. 47 | */ 48 | public AdapterRegistry(Map adapters) 49 | { 50 | this(); 51 | 52 | for (Map.Entry entry : adapters.entrySet()) { 53 | registerAdapter(entry.getKey(), entry.getValue()); 54 | } 55 | } 56 | 57 | /** 58 | * Creates an empty registry. 59 | */ 60 | public AdapterRegistry() 61 | { 62 | this.adapters = new ArrayList<>(); 63 | this.classes = new ArrayList<>(); 64 | } 65 | 66 | /** 67 | * Returns the map of types and adapters. 68 | * 69 | * @return The map of adapters indexed by the supported type. 70 | */ 71 | public Map getAdapters() 72 | { 73 | LinkedHashMap map = new LinkedHashMap<>(); 74 | 75 | for (int index = 0, size = classes.size(); index < size; index++) { 76 | map.put(classes.get(index), adapters.get(index)); 77 | } 78 | 79 | return map; 80 | } 81 | 82 | /** 83 | * Registers an adapter for the specified type. 84 | * 85 | * This operation overrides any adapter already registered for the 86 | * specified type. 87 | * 88 | * @param type The type supported by the adapter. 89 | * @param adapter The adapter. 90 | */ 91 | public void registerAdapter(Class type, TypeAdapter adapter) 92 | { 93 | for (int index = 0, size = classes.size(); index < size; index++) { 94 | Class currentClass = classes.get(index); 95 | 96 | if (currentClass.isAssignableFrom(type)) { 97 | classes.add(index, type); 98 | adapters.add(index, adapter); 99 | 100 | return; 101 | } 102 | } 103 | 104 | classes.add(type); 105 | adapters.add(adapter); 106 | } 107 | 108 | /** 109 | * Returns the most specified adapter for the specified type. 110 | * 111 | * @param type The type which the adapter should support. 112 | * 113 | * @return The most specified adapter for the specified type. 114 | * 115 | * @throws IllegalArgumentException if no adapter for the specified type is 116 | * registered. 117 | */ 118 | public TypeAdapter getAdapter(Class type) 119 | { 120 | for (int index = 0, size = classes.size(); index < size; index++) { 121 | Class currentClass = classes.get(index); 122 | 123 | if (isAssignableFrom(currentClass, type)) { 124 | return adapters.get(index); 125 | } 126 | } 127 | 128 | throw new IllegalArgumentException(String.format( 129 | "No adapter registered for %s.", 130 | type 131 | )); 132 | } 133 | 134 | /** 135 | * Determines if the class or interface represented by {@code left} Class 136 | * object is either the same as, or is a superclass or superinterface of, 137 | * the class or interface represented by the {@code right} Class parameter. 138 | * 139 | * If the {@code left} Class object represents a primitive type, this method 140 | * returns true if the exactly {@code left} Class object or if the unboxed 141 | * type of this type is assignable from the {@code right} class. 142 | * 143 | * @param left The class to check if is the same as, or a subtype of the 144 | * {@code right} class. 145 | * @param right The class to check if is the same as or is a supertype of 146 | * the {@code left} class. 147 | * 148 | * @return the {@code boolean} value indicating whether objects of the 149 | * type {@code left} can be assigned to objects of type {@code right}. 150 | */ 151 | private static boolean isAssignableFrom(Class left, Class right) 152 | { 153 | if (left.isArray() && right.isArray()) { 154 | left = left.getComponentType(); 155 | right = right.getComponentType(); 156 | } 157 | 158 | if (primitives.containsKey(left)) { 159 | left = primitives.get(left); 160 | } 161 | 162 | if (primitives.containsKey(right)) { 163 | right = primitives.get(right); 164 | } 165 | 166 | return left.isAssignableFrom(right); 167 | } 168 | } 169 | -------------------------------------------------------------------------------- /src/main/java/com/marcospassos/phpserializer/Context.java: -------------------------------------------------------------------------------- 1 | package com.marcospassos.phpserializer; 2 | 3 | import java.util.Collections; 4 | import java.util.HashMap; 5 | import java.util.List; 6 | import java.util.Map; 7 | import java.util.Stack; 8 | 9 | /** 10 | * Manages the serialization process, including keeping track of references and 11 | * delegating adapters. 12 | * 13 | * @author Marcos Passos 14 | * @since 1.0 15 | */ 16 | public class Context 17 | { 18 | /** 19 | * Maps objects to reference indexes. 20 | */ 21 | private Map references; 22 | 23 | /** 24 | * The adapter registry. 25 | */ 26 | private AdapterRegistry registry; 27 | 28 | /** 29 | * The naming strategy. 30 | */ 31 | private NamingStrategy namingStrategy; 32 | 33 | /** 34 | * The exclusion strategy. 35 | */ 36 | private FieldExclusionStrategy exclusionStrategy; 37 | 38 | /** 39 | * The traversal stack. 40 | */ 41 | private Stack traversalStack; 42 | 43 | /** 44 | * Creates a context. 45 | * 46 | * @param registry The adapter registry. 47 | * @param namingStrategy The naming strategy. 48 | * @param exclusionStrategy The exclusion strategy. 49 | */ 50 | public Context( 51 | AdapterRegistry registry, 52 | NamingStrategy namingStrategy, 53 | FieldExclusionStrategy exclusionStrategy 54 | ) 55 | { 56 | this.registry = registry; 57 | this.references = new HashMap<>(); 58 | this.namingStrategy = namingStrategy; 59 | this.exclusionStrategy = exclusionStrategy; 60 | this.traversalStack = new Stack<>(); 61 | } 62 | 63 | /** 64 | * Returns the naming strategy. 65 | * 66 | * @return The naming strategy. 67 | */ 68 | public NamingStrategy getNamingStrategy() 69 | { 70 | return namingStrategy; 71 | } 72 | 73 | /** 74 | * Returns the exclusion strategy. 75 | * 76 | * @return The exclusion strategy. 77 | */ 78 | public FieldExclusionStrategy getExclusionStrategy() 79 | { 80 | return exclusionStrategy; 81 | } 82 | 83 | /** 84 | * Returns the traversal depth. 85 | * 86 | * The depth is equivalent to the stack size. If the stack is empty returns 87 | * 0; if the stack contains one element, returns 1 and so on. 88 | * 89 | * @return The traversal depth. 90 | */ 91 | public int getDepth() 92 | { 93 | return traversalStack.size(); 94 | } 95 | 96 | /** 97 | * Returns the parent object that contains the current object. 98 | * 99 | * @return The parent object. 100 | * 101 | * @throws IndexOutOfBoundsException if the traversal stack is empty or if 102 | * the object is on the top of the stack. 103 | */ 104 | public Object getParent() 105 | { 106 | return traversalStack.get(traversalStack.size() - 2); 107 | } 108 | 109 | /** 110 | * Return the traversal stack. 111 | * 112 | * @return The traversal stack. 113 | */ 114 | public List getTraversalStack() 115 | { 116 | return Collections.unmodifiableList(traversalStack); 117 | } 118 | 119 | /** 120 | * Writes the provided value using the specified writer. 121 | * 122 | * @param value The value to write. 123 | * @param writer The writer to write the value with. 124 | */ 125 | public void write(Object value, Writer writer) 126 | { 127 | if (value == null) { 128 | writer.writeNull(); 129 | 130 | return; 131 | } 132 | 133 | traversalStack.push(value); 134 | 135 | TypeAdapter adapter = getAdapter(value.getClass()); 136 | 137 | adapter.write(value, writer, this); 138 | 139 | traversalStack.pop(); 140 | } 141 | 142 | /** 143 | * Returns the adapter for the specified type. 144 | * 145 | * @param type The type which the adapter should support. 146 | * 147 | * @return The adapter for the specified type. 148 | */ 149 | @SuppressWarnings("unchecked") 150 | private TypeAdapter getAdapter(Class type) 151 | { 152 | return (TypeAdapter) registry.getAdapter(type); 153 | } 154 | 155 | /** 156 | * Tracks the reference of the specified object. 157 | * 158 | * @param pointer The reference index. 159 | * @param object The value. 160 | */ 161 | public void setReference(int pointer, Object object) 162 | { 163 | references.put(object, pointer); 164 | } 165 | 166 | /** 167 | * Returns the index of the reference for the specified object. 168 | * 169 | * @param object The object which the reference points to. 170 | * 171 | * @return A positive integer representing the index of the reference for 172 | * the specified object or {@code -1}, if no reference for the specified 173 | * value exists. 174 | */ 175 | public int getReference(Object object) 176 | { 177 | if (!references.containsKey(object)) { 178 | return -1; 179 | } 180 | 181 | return references.get(object); 182 | } 183 | } 184 | -------------------------------------------------------------------------------- /src/main/java/com/marcospassos/phpserializer/FieldExclusionStrategy.java: -------------------------------------------------------------------------------- 1 | package com.marcospassos.phpserializer; 2 | 3 | import java.lang.reflect.Field; 4 | 5 | /** 6 | * A strategy definition to decide whether or not a field should be excluded 7 | * from serialization result. 8 | * 9 | * @author Marcos Passos 10 | * @since 1.0 11 | */ 12 | public interface FieldExclusionStrategy 13 | { 14 | /** 15 | * Checks whether the specified field should be ignored. 16 | * 17 | * @param field The field that is under test. 18 | * 19 | * @return {@code true} if the field should be ignored, or {@code false} 20 | * otherwise. 21 | */ 22 | boolean shouldSkipField(Field field); 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/com/marcospassos/phpserializer/NamingStrategy.java: -------------------------------------------------------------------------------- 1 | package com.marcospassos.phpserializer; 2 | 3 | import java.lang.reflect.Field; 4 | 5 | /** 6 | * Translates the name of classes and properties according to the target domain. 7 | * 8 | * @author Marcos Passos 9 | * @since 1.0 10 | */ 11 | public interface NamingStrategy 12 | { 13 | /** 14 | * Returns the fully-qualified name for the specified class. 15 | * 16 | * @param type The class to determine the serialized name. 17 | * 18 | * @return The fully-qualified name for the specified class. 19 | */ 20 | String getClassName(Class type); 21 | 22 | /** 23 | * Returns the name for the specified field. 24 | * 25 | * @param field The field to determine the serialized name. 26 | * 27 | * @return The serialized name for the specified field. 28 | */ 29 | String getFieldName(Field field); 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/com/marcospassos/phpserializer/Serializer.java: -------------------------------------------------------------------------------- 1 | package com.marcospassos.phpserializer; 2 | 3 | /** 4 | * Serializes Java objects as PHP serialization format. 5 | * 6 | * @author Marcos Passos 7 | * @since 1.0 8 | */ 9 | public class Serializer 10 | { 11 | /** 12 | * The naming strategy. 13 | */ 14 | NamingStrategy namingStrategy; 15 | 16 | /** 17 | * The field exclusion strategy. 18 | */ 19 | FieldExclusionStrategy exclusionStrategy; 20 | 21 | /** 22 | * The adapter registry. 23 | */ 24 | AdapterRegistry adapterRegistry; 25 | 26 | /** 27 | * Creates a serializer with the specified configuration. 28 | * 29 | * @param namingStrategy The strategy for naming classes and fields. 30 | * @param exclusionStrategy The strategy for excluding fields. 31 | * @param adapterRegistry The registry of type adapters. 32 | */ 33 | public Serializer( 34 | NamingStrategy namingStrategy, 35 | FieldExclusionStrategy exclusionStrategy, 36 | AdapterRegistry adapterRegistry 37 | ) 38 | { 39 | this.namingStrategy = namingStrategy; 40 | this.exclusionStrategy = exclusionStrategy; 41 | this.adapterRegistry = adapterRegistry; 42 | } 43 | 44 | /** 45 | * Serializes the specified object as PHP serialization format. 46 | * 47 | * @param object The object to serialize. 48 | * 49 | * @return The serialized data. 50 | */ 51 | public String serialize(Object object) 52 | { 53 | Writer writer = new Writer(); 54 | Context context = createContext(); 55 | 56 | context.write(object, writer); 57 | 58 | return writer.getResult(); 59 | } 60 | 61 | /** 62 | * Creates the serialization context based on the current configuration. 63 | * 64 | * @return The serialization context based on the current configuration. 65 | */ 66 | private Context createContext() 67 | { 68 | return new Context(adapterRegistry, namingStrategy, exclusionStrategy); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/main/java/com/marcospassos/phpserializer/SerializerBuilder.java: -------------------------------------------------------------------------------- 1 | package com.marcospassos.phpserializer; 2 | 3 | import java.nio.charset.Charset; 4 | import java.util.ArrayList; 5 | import java.util.Collection; 6 | import java.util.HashMap; 7 | import java.util.List; 8 | import java.util.Map; 9 | import com.marcospassos.phpserializer.adapter.ArrayAdapter; 10 | import com.marcospassos.phpserializer.adapter.BooleanAdapter; 11 | import com.marcospassos.phpserializer.adapter.CollectionAdapter; 12 | import com.marcospassos.phpserializer.adapter.DoubleAdapter; 13 | import com.marcospassos.phpserializer.adapter.IntegerAdapter; 14 | import com.marcospassos.phpserializer.adapter.LongAdapter; 15 | import com.marcospassos.phpserializer.adapter.MapAdapter; 16 | import com.marcospassos.phpserializer.adapter.ObjectAdapter; 17 | import com.marcospassos.phpserializer.adapter.ReferableObjectAdapter; 18 | import com.marcospassos.phpserializer.adapter.StringAdapter; 19 | import com.marcospassos.phpserializer.exclusion.DisjunctionExclusionStrategy; 20 | import com.marcospassos.phpserializer.exclusion.NoExclusionStrategy; 21 | import com.marcospassos.phpserializer.naming.PsrNamingStrategy; 22 | 23 | /** 24 | * Provides a friendly API for creating instances of {@link Serializer}. 25 | * 26 | * @author Marcos Passos 27 | * @since 1.0 28 | */ 29 | public class SerializerBuilder 30 | { 31 | /** 32 | * The naming strategy. 33 | */ 34 | private NamingStrategy namingStrategy; 35 | 36 | /** 37 | * The exclusion strategies. 38 | */ 39 | private List exclusionStrategies; 40 | 41 | /** 42 | * The map of adapters. 43 | */ 44 | private Map adapterMap; 45 | 46 | /** 47 | * The serializer factory. 48 | */ 49 | private SerializerFactory factory; 50 | 51 | /** 52 | * Creates a builder with the specified factory. 53 | * 54 | * @param factory The factory to create the serializer. 55 | */ 56 | public SerializerBuilder(SerializerFactory factory) 57 | { 58 | this.adapterMap = new HashMap<>(); 59 | this.exclusionStrategies = new ArrayList<>(); 60 | this.factory = factory; 61 | } 62 | 63 | /** 64 | * Creates a builder using an internal factory. 65 | */ 66 | public SerializerBuilder() 67 | { 68 | this(new SerializerFactory()); 69 | } 70 | 71 | /** 72 | * Creates a {@link Serializer} instance based on the current configuration. 73 | * 74 | * This method is free of side-effects to this instance and hence can be 75 | * called multiple times. 76 | * 77 | * @return An instance of {@link Serializer} configured with the options 78 | * currently set in this builder. 79 | */ 80 | public Serializer build() 81 | { 82 | AdapterRegistry adapterRegistry = getAdapterRegistry(); 83 | FieldExclusionStrategy exclusionStrategy = getExclusionStrategy(); 84 | NamingStrategy namingStrategy = getNamingStrategy(); 85 | 86 | return factory.create( 87 | namingStrategy, 88 | exclusionStrategy, 89 | adapterRegistry 90 | ); 91 | } 92 | 93 | /** 94 | * Configures the serializer to use the specified charset for 95 | * serializing strings. 96 | * 97 | * Calling this method will override any registered adapter for 98 | * {@code String}. 99 | * 100 | * @param charset The default charset to serialize strings. 101 | * 102 | * @return The current builder. 103 | */ 104 | public SerializerBuilder setCharset(Charset charset) 105 | { 106 | registerAdapter(String.class, new StringAdapter(charset)); 107 | 108 | return this; 109 | } 110 | 111 | /** 112 | * Configures the serializer to apply a specific naming policy strategy to 113 | * objects during serialization. 114 | * 115 | * @param strategy The naming strategy to apply to objects. 116 | * 117 | * @return The current builder. 118 | */ 119 | public SerializerBuilder setNamingStrategy(NamingStrategy strategy) 120 | { 121 | this.namingStrategy = strategy; 122 | 123 | return this; 124 | } 125 | 126 | /** 127 | * Configures the serializer to apply the specified exclusion strategy 128 | * during serialization. 129 | * 130 | * If this method is invoked numerous times with different exclusion 131 | * strategies then the exclusion strategies that were added will be 132 | * merged into a disjunction strategy. This means that if at least one of 133 | * the added exclusion strategies suggests that a field (or class) should 134 | * be skipped then that field (or object) is skipped during its 135 | * deserialization. 136 | * 137 | * @param strategy The exclusion strategy. 138 | * 139 | * @return The current builder. 140 | */ 141 | public SerializerBuilder addExclusionStrategy(FieldExclusionStrategy strategy) 142 | { 143 | if (!this.exclusionStrategies.contains(strategy)) { 144 | this.exclusionStrategies.add(strategy); 145 | } 146 | 147 | return this; 148 | } 149 | 150 | /** 151 | * Registers the provided adapter for the specified type. 152 | * 153 | * This registers the type specified and all subclasses. For example, 154 | * an adapter registered for {@code Number.class} will be also applied to 155 | * {@code Integer.class}. 156 | * 157 | * If a type adapter was previously registered for the specified type, it 158 | * is overwritten. 159 | * 160 | * @param type The base class or interface supported by the adapter. 161 | * @param adapter The type adapter. 162 | * 163 | * @return The current builder. 164 | */ 165 | public SerializerBuilder registerAdapter(Class type, TypeAdapter adapter) 166 | { 167 | adapterMap.put(type, adapter); 168 | 169 | return this; 170 | } 171 | 172 | /** 173 | * Registers all builtin adapters. 174 | * 175 | * Calling this method will override any adapter already registered for 176 | * the types handled by the builtin adapters. 177 | * 178 | * Strings are serialized in UTF-8 by default. 179 | * 180 | * @return The current builder. 181 | */ 182 | public SerializerBuilder registerBuiltinAdapters() 183 | { 184 | registerAdapter(Object[].class, new ArrayAdapter<>()); 185 | registerAdapter(Map.class, new MapAdapter<>()); 186 | registerAdapter(Collection.class, new CollectionAdapter<>()); 187 | registerAdapter(Boolean.class, new BooleanAdapter()); 188 | registerAdapter(Double.class, new DoubleAdapter()); 189 | registerAdapter(Integer.class, new IntegerAdapter()); 190 | registerAdapter(Long.class, new LongAdapter()); 191 | registerAdapter(String.class, new StringAdapter()); 192 | registerAdapter(Object.class, new ReferableObjectAdapter<>( 193 | new ObjectAdapter<>() 194 | )); 195 | 196 | return this; 197 | } 198 | 199 | /** 200 | * Returns the configured naming strategy or the default one. 201 | * 202 | * @return The naming strategy. 203 | */ 204 | private NamingStrategy getNamingStrategy() 205 | { 206 | if (namingStrategy == null) { 207 | return new PsrNamingStrategy(); 208 | } 209 | 210 | return namingStrategy; 211 | } 212 | 213 | /** 214 | * Returns the adapter registry containing the configured adapters. 215 | * 216 | * @return The adapter registry. 217 | */ 218 | private AdapterRegistry getAdapterRegistry() 219 | { 220 | Map adapterMap = this.adapterMap; 221 | 222 | if (adapterMap.isEmpty()) { 223 | registerBuiltinAdapters(); 224 | 225 | this.adapterMap = new HashMap<>(); 226 | } 227 | 228 | return new AdapterRegistry(adapterMap); 229 | } 230 | 231 | /** 232 | * Returns the configured the exclusion strategy or the default one. 233 | * 234 | * @return The exclusion strategy. 235 | */ 236 | private FieldExclusionStrategy getExclusionStrategy() 237 | { 238 | if (exclusionStrategies.isEmpty()) { 239 | return new NoExclusionStrategy(); 240 | } 241 | 242 | if (exclusionStrategies.size() > 1) { 243 | return new DisjunctionExclusionStrategy( 244 | exclusionStrategies.toArray(new FieldExclusionStrategy[0]) 245 | ); 246 | } 247 | 248 | return exclusionStrategies.get(0); 249 | } 250 | } -------------------------------------------------------------------------------- /src/main/java/com/marcospassos/phpserializer/SerializerFactory.java: -------------------------------------------------------------------------------- 1 | package com.marcospassos.phpserializer; 2 | 3 | /** 4 | * Creates instances of Serializer. 5 | * 6 | * @author Marcos Passos 7 | * @since 1.0 8 | */ 9 | public class SerializerFactory 10 | { 11 | /** 12 | * Creates a serializer with the specified configuration. 13 | * 14 | * @param namingStrategy The strategy for naming classes and fields. 15 | * @param exclusionStrategy The strategy for excluding fields. 16 | * @param adapterRegistry The registry of type adapters. 17 | * 18 | * @return The serializer instance. 19 | */ 20 | public Serializer create( 21 | NamingStrategy namingStrategy, 22 | FieldExclusionStrategy exclusionStrategy, 23 | AdapterRegistry adapterRegistry 24 | ) 25 | { 26 | return new Serializer( 27 | namingStrategy, 28 | exclusionStrategy, 29 | adapterRegistry 30 | ); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/com/marcospassos/phpserializer/TypeAdapter.java: -------------------------------------------------------------------------------- 1 | package com.marcospassos.phpserializer; 2 | 3 | /** 4 | * Writes Java objects in PHP's Serialization format. 5 | * 6 | * @param The type of object supported. 7 | * 8 | * @author Marcos Passos 9 | * @since 1.0 10 | */ 11 | public interface TypeAdapter 12 | { 13 | /** 14 | * Encodes the specified value in PHP's Serialization format. 15 | * 16 | * @param value The value to write. 17 | * @param writer The writer. 18 | * @param context The serialization context. 19 | */ 20 | void write(T value, Writer writer, Context context); 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/com/marcospassos/phpserializer/Writer.java: -------------------------------------------------------------------------------- 1 | 2 | package com.marcospassos.phpserializer; 3 | 4 | import java.lang.reflect.Modifier; 5 | import java.nio.charset.Charset; 6 | import com.marcospassos.phpserializer.state.FinishedState; 7 | import com.marcospassos.phpserializer.state.WritingValueState; 8 | 9 | /** 10 | * Writes a values in PHP's serialization format. 11 | * 12 | * @author Marcos Passos 13 | * @since 1.0 14 | */ 15 | public class Writer 16 | { 17 | /** 18 | * The buffer that stores the data. 19 | */ 20 | private StringBuffer buffer; 21 | 22 | /** 23 | * The reference counter. 24 | */ 25 | private int pointer = 1; 26 | 27 | /** 28 | * The current state of the writer. 29 | */ 30 | private WriterState state; 31 | 32 | /** 33 | * The current sub writer. 34 | */ 35 | private Writer subWriter; 36 | 37 | /** 38 | * Creates a new writer using an internal buffer. 39 | */ 40 | public Writer() 41 | { 42 | this(new StringBuffer()); 43 | } 44 | 45 | /** 46 | * Creates a writer specifying a buffer to write the data to. 47 | * 48 | * @param buffer The buffer to write the data to. 49 | */ 50 | public Writer(StringBuffer buffer) 51 | { 52 | this.buffer = buffer; 53 | this.state = new WritingValueState(); 54 | } 55 | 56 | /** 57 | * Returns the current result. 58 | * 59 | * @return The serialized data. 60 | */ 61 | public String getResult() 62 | { 63 | return this.buffer.toString(); 64 | } 65 | 66 | /** 67 | * Writes the start of an object to the buffer and returns an object-level 68 | * sub writer. 69 | * 70 | * @param className The fully-qualified name of the class. 71 | * 72 | * @return An object-level sub writer to write the object's data. 73 | */ 74 | public Writer writeSerializableObjectStart(String className) 75 | { 76 | setState(state.serializableBegin()); 77 | 78 | buffer.append("C:"); 79 | buffer.append(className.length()); 80 | buffer.append(":\""); 81 | buffer.append(className); 82 | buffer.append("\":"); 83 | 84 | subWriter = new Writer(); 85 | subWriter.pointer = pointer + 1; 86 | 87 | return subWriter; 88 | } 89 | 90 | /** 91 | * Writes the end of a serializable object to the buffer. 92 | */ 93 | public void writeSerializableObjectEnd() { 94 | setState(state.serializableEnd()); 95 | 96 | if (!(subWriter.state instanceof FinishedState)) { 97 | throw new IllegalStateException(); 98 | } 99 | 100 | String data = subWriter.getResult(); 101 | 102 | buffer.append(data.length()); 103 | buffer.append(":{"); 104 | buffer.append(data); 105 | buffer.append("}"); 106 | 107 | pointer = subWriter.getPointer(); 108 | 109 | subWriter = null; 110 | } 111 | 112 | /** 113 | * Writes the start of an object to the buffer. 114 | * 115 | * @param className The fully-qualified name of the class. 116 | * @param fieldCount The number of fields. 117 | */ 118 | public void writeObjectStart(String className, int fieldCount) 119 | { 120 | setState(state.objectBegin()); 121 | 122 | buffer.append("O:"); 123 | buffer.append(className.length()); 124 | buffer.append(":\""); 125 | buffer.append(className); 126 | buffer.append("\":"); 127 | buffer.append(fieldCount); 128 | buffer.append(":{"); 129 | } 130 | 131 | /** 132 | * Writes an object property to the buffer. 133 | * 134 | * @param name The property name. 135 | * @param className The fully-qualified name of the class. 136 | * @param modifiers The property modifiers. 137 | */ 138 | public void writeProperty(String name, String className, int modifiers) 139 | { 140 | if (Modifier.isPrivate(modifiers)) { 141 | writePrivateProperty(name, className); 142 | } else if (Modifier.isProtected(modifiers)) { 143 | writeProtectedProperty(name); 144 | } else { 145 | writeProperty(name); 146 | } 147 | } 148 | 149 | /** 150 | * Writes an object property to the buffer. 151 | * 152 | * @param name The property name. 153 | */ 154 | private void writeProperty(String name) 155 | { 156 | setState(state.property()); 157 | 158 | buffer.append("s:"); 159 | buffer.append(name.length()); 160 | buffer.append(":\""); 161 | buffer.append(name); 162 | buffer.append("\";"); 163 | } 164 | 165 | /** 166 | * Writes a public object property to the buffer. 167 | * 168 | * @param name The property name. 169 | */ 170 | public void writePublicProperty(String name) 171 | { 172 | writeProperty(name); 173 | } 174 | 175 | /** 176 | * Writes a protected object property to the buffer. 177 | * 178 | * @param name The property name. 179 | */ 180 | public void writeProtectedProperty(String name) 181 | { 182 | writeProperty("\0*\0" + name); 183 | } 184 | 185 | /** 186 | * Writes a private object property to the buffer. 187 | * 188 | * @param name The property name. 189 | * @param className The name of the class which the property is part of. 190 | */ 191 | public void writePrivateProperty(String name, String className) 192 | { 193 | writeProperty("\0" + className + "\0" + name); 194 | } 195 | 196 | /** 197 | * Writes the end of an object to the buffer. 198 | */ 199 | public void writeObjectEnd() 200 | { 201 | setState(state.objectEnd()); 202 | 203 | buffer.append("}"); 204 | } 205 | 206 | /** 207 | * Writes a variable reference to the buffer. 208 | * 209 | * @param index The index of the value within the serialized data. 210 | */ 211 | public void writeVariableReference(Integer index) 212 | { 213 | setState(state.value()); 214 | 215 | buffer.append("R:"); 216 | buffer.append(index); 217 | buffer.append(";"); 218 | } 219 | 220 | /** 221 | * Writes an object reference to the buffer. 222 | * 223 | * @param index The index of the object within the serialized data. 224 | */ 225 | public void writeObjectReference(Integer index) 226 | { 227 | setState(state.value()); 228 | 229 | buffer.append("r:"); 230 | buffer.append(index); 231 | buffer.append(";"); 232 | } 233 | 234 | /** 235 | * Writes NULL to the buffer. 236 | */ 237 | public void writeNull() 238 | { 239 | setState(state.value()); 240 | 241 | buffer.append("N;"); 242 | } 243 | 244 | /** 245 | * Writes a string value to the buffer. 246 | * 247 | * @param value The value. 248 | */ 249 | public void writeString(String value) 250 | { 251 | writeString(value, Charset.forName("UTF-8")); 252 | } 253 | 254 | /** 255 | * Writes a string in the specified charset to the buffer. 256 | * 257 | * @param value The value. 258 | * @param charset The charset to be used to serialize the value. 259 | */ 260 | public void writeString(String value, Charset charset) 261 | { 262 | setState(state.value()); 263 | 264 | byte[] bytes = value.getBytes(charset); 265 | 266 | value = new String(bytes, charset); 267 | 268 | buffer.append("s:"); 269 | buffer.append(bytes.length); 270 | buffer.append(":\""); 271 | buffer.append(value); 272 | buffer.append("\";"); 273 | } 274 | 275 | /** 276 | * Writes a boolean value to the buffer. 277 | * 278 | * @param value The value. 279 | */ 280 | public void writeBoolean(Boolean value) 281 | { 282 | setState(state.value()); 283 | 284 | buffer.append("b:"); 285 | buffer.append(value ? 1 : 0); 286 | buffer.append(';'); 287 | } 288 | 289 | /** 290 | * Writes an integer value to the buffer. 291 | * 292 | * @param value The value. 293 | */ 294 | public void writeInteger(Integer value) 295 | { 296 | setState(state.value()); 297 | 298 | buffer.append("i:"); 299 | buffer.append(value); 300 | buffer.append(';'); 301 | } 302 | 303 | /** 304 | * Writes an long value to the buffer. 305 | * 306 | * @param value The value. 307 | */ 308 | public void writeInteger(Long value) 309 | { 310 | setState(state.value()); 311 | 312 | buffer.append("i:"); 313 | buffer.append(value); 314 | buffer.append(';'); 315 | } 316 | 317 | /** 318 | * Writes a float value to the buffer. 319 | * 320 | * @param value The value. 321 | */ 322 | public void writeFloat(Double value) 323 | { 324 | setState(state.value()); 325 | 326 | buffer.append("d:"); 327 | buffer.append(value); 328 | buffer.append(';'); 329 | } 330 | 331 | /** 332 | * Writes the start of an array to the buffer. 333 | * 334 | * @param length The length of the array. 335 | */ 336 | public void writeArrayStart(int length) 337 | { 338 | setState(state.arrayBegin()); 339 | 340 | buffer.append("a:"); 341 | buffer.append(length); 342 | buffer.append(":{"); 343 | } 344 | 345 | /** 346 | * Writes the key of an array entry to the buffer. 347 | * 348 | * @param index The key of the array entry. 349 | */ 350 | public void writeKey(int index) 351 | { 352 | setState(state.key()); 353 | 354 | buffer.append("i:"); 355 | buffer.append(index); 356 | buffer.append(';'); 357 | } 358 | 359 | /** 360 | * Writes the key of an array entry to the buffer. 361 | * 362 | * @param key The key of the array entry. 363 | */ 364 | public void writeKey(String key) 365 | { 366 | setState(state.key()); 367 | 368 | buffer.append("s:"); 369 | buffer.append(key.length()); 370 | buffer.append(":\""); 371 | buffer.append(key.replaceAll("\"", "\\\\\"")); 372 | buffer.append("\";"); 373 | } 374 | 375 | /** 376 | * Writes the end of an array to the buffer. 377 | */ 378 | public void writeArrayEnd() 379 | { 380 | setState(state.arrayEnd()); 381 | 382 | buffer.append("}"); 383 | } 384 | 385 | /** 386 | * Get the current reference count. 387 | * 388 | * @return The current reference count. 389 | */ 390 | public int getPointer() 391 | { 392 | return this.pointer; 393 | } 394 | 395 | /** 396 | * Sets the state of the writer. 397 | * 398 | * @param state The new state. 399 | */ 400 | protected void setState(WriterState state) 401 | { 402 | if (state.isReferable()) { 403 | pointer++; 404 | } 405 | 406 | this.state = state; 407 | } 408 | } 409 | -------------------------------------------------------------------------------- /src/main/java/com/marcospassos/phpserializer/WriterState.java: -------------------------------------------------------------------------------- 1 | 2 | package com.marcospassos.phpserializer; 3 | 4 | /** 5 | * Represents a state of the {@link Writer}. 6 | * 7 | * @author Marcos Passos 8 | * @since 1.0 9 | */ 10 | public interface WriterState 11 | { 12 | 13 | /** 14 | * Checks whether the current state represents a referable value. 15 | * 16 | * @return {@code true} if the current state represents a referable value, 17 | * or {@code false} otherwise. 18 | */ 19 | boolean isReferable(); 20 | 21 | /** 22 | * Returns the new state generated as a result of transitions from the 23 | * current state to the new state. 24 | * 25 | * @return WriterState The new state. 26 | * 27 | * @throws IllegalStateException if the current state does not allow the 28 | * transition to the new state. 29 | */ 30 | WriterState serializableBegin(); 31 | 32 | /** 33 | * Returns the new state generated as a result of transitions from the 34 | * current state to the new state. 35 | * 36 | * @return WriterState The new state. 37 | * 38 | * @throws IllegalStateException if the current state does not allow the 39 | * transition to the new state. 40 | */ 41 | WriterState serializableEnd(); 42 | 43 | /** 44 | * Returns the new state generated as a result of transitions from the 45 | * current state to the new state. 46 | * 47 | * @return WriterState The new state. 48 | * 49 | * @throws IllegalStateException if the current state does not allow the 50 | * transition to the new state. 51 | */ 52 | WriterState objectBegin(); 53 | 54 | /** 55 | * Returns the new state generated as a result of transitions from the 56 | * current state to the new state. 57 | * 58 | * @return WriterState The new state. 59 | * 60 | * @throws IllegalStateException if the current state does not allow the 61 | * transition to the new state. 62 | */ 63 | WriterState property(); 64 | 65 | /** 66 | * Returns the new state generated as a result of transitions from the 67 | * current state to the new state. 68 | * 69 | * @return WriterState The new state. 70 | * 71 | * @throws IllegalStateException if the current state does not allow the 72 | * transition to the new state. 73 | */ 74 | WriterState value(); 75 | 76 | /** 77 | * Returns the new state generated as a result of transitions from the 78 | * current state to the new state. 79 | * 80 | * @return WriterState The new state. 81 | * 82 | * @throws IllegalStateException if the current state does not allow the 83 | * transition to the new state. 84 | */ 85 | WriterState objectEnd(); 86 | 87 | /** 88 | * Returns the new state generated as a result of transitions from the 89 | * current state to the new state. 90 | * 91 | * @return WriterState The new state. 92 | * 93 | * @throws IllegalStateException if the current state does not allow the 94 | * transition to the new state. 95 | */ 96 | WriterState arrayBegin(); 97 | 98 | /** 99 | * Returns the new state generated as a result of transitions from the 100 | * current state to the new state. 101 | * 102 | * @return WriterState The new state. 103 | * 104 | * @throws IllegalStateException if the current state does not allow the 105 | * transition to the new state. 106 | */ 107 | WriterState key(); 108 | 109 | /** 110 | * Returns the new state generated as a result of transitions from the 111 | * current state to the new state. 112 | * 113 | * @return WriterState The new state. 114 | * 115 | * @throws IllegalStateException if the current state does not allow the 116 | * transition to the new state. 117 | */ 118 | WriterState arrayEnd(); 119 | } 120 | -------------------------------------------------------------------------------- /src/main/java/com/marcospassos/phpserializer/adapter/ArrayAdapter.java: -------------------------------------------------------------------------------- 1 | package com.marcospassos.phpserializer.adapter; 2 | 3 | import java.lang.reflect.Array; 4 | import com.marcospassos.phpserializer.Context; 5 | import com.marcospassos.phpserializer.TypeAdapter; 6 | import com.marcospassos.phpserializer.Writer; 7 | 8 | /** 9 | * Adapter for {@code Array} type. 10 | * 11 | * @param The type of values in the list. 12 | * 13 | * @author Marcos Passos 14 | * @since 1.0 15 | */ 16 | public class ArrayAdapter implements TypeAdapter 17 | { 18 | @Override 19 | public void write(T array, Writer writer, Context context) 20 | { 21 | Class type = array.getClass(); 22 | 23 | if (!type.isArray()) { 24 | throw new IllegalArgumentException( 25 | "Expected array, but got " + type.getName()); 26 | } 27 | 28 | int length = Array.getLength(array); 29 | 30 | writer.writeArrayStart(length); 31 | 32 | for (int index = 0; index < length; index++) { 33 | writer.writeKey(index); 34 | 35 | context.write(Array.get(array, index), writer); 36 | } 37 | 38 | writer.writeArrayEnd(); 39 | } 40 | } -------------------------------------------------------------------------------- /src/main/java/com/marcospassos/phpserializer/adapter/BooleanAdapter.java: -------------------------------------------------------------------------------- 1 | package com.marcospassos.phpserializer.adapter; 2 | 3 | import com.marcospassos.phpserializer.Context; 4 | import com.marcospassos.phpserializer.TypeAdapter; 5 | import com.marcospassos.phpserializer.Writer; 6 | 7 | /** 8 | * Adapter for {@code Boolean} type. 9 | * 10 | * @author Marcos Passos 11 | * @since 1.0 12 | */ 13 | public class BooleanAdapter implements TypeAdapter 14 | { 15 | @Override 16 | public void write(Boolean value, Writer writer, Context context) 17 | { 18 | writer.writeBoolean(value); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/com/marcospassos/phpserializer/adapter/CollectionAdapter.java: -------------------------------------------------------------------------------- 1 | package com.marcospassos.phpserializer.adapter; 2 | 3 | import java.util.Collection; 4 | import com.marcospassos.phpserializer.Context; 5 | import com.marcospassos.phpserializer.TypeAdapter; 6 | import com.marcospassos.phpserializer.Writer; 7 | 8 | /** 9 | * Adapter for {@code Collection} type. 10 | * 11 | * @param The type of values in the collection. 12 | * 13 | * @author Marcos Passos 14 | * @since 1.0 15 | */ 16 | public class CollectionAdapter implements TypeAdapter> 17 | { 18 | @Override 19 | public void write(Collection collection, Writer writer, Context context) 20 | { 21 | int size = collection.size(); 22 | 23 | writer.writeArrayStart(size); 24 | 25 | int index = 0; 26 | for (Object value: collection) { 27 | writer.writeKey(index++); 28 | context.write(value, writer); 29 | } 30 | 31 | writer.writeArrayEnd(); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/com/marcospassos/phpserializer/adapter/DoubleAdapter.java: -------------------------------------------------------------------------------- 1 | package com.marcospassos.phpserializer.adapter; 2 | 3 | import com.marcospassos.phpserializer.Context; 4 | import com.marcospassos.phpserializer.TypeAdapter; 5 | import com.marcospassos.phpserializer.Writer; 6 | 7 | /** 8 | * Adapter for {@code Double} type. 9 | * 10 | * @author Marcos Passos 11 | * @since 1.0 12 | */ 13 | public class DoubleAdapter implements TypeAdapter 14 | { 15 | @Override 16 | public void write(Double value, Writer writer, Context context) 17 | { 18 | writer.writeFloat(value); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/com/marcospassos/phpserializer/adapter/IntegerAdapter.java: -------------------------------------------------------------------------------- 1 | package com.marcospassos.phpserializer.adapter; 2 | 3 | import com.marcospassos.phpserializer.Context; 4 | import com.marcospassos.phpserializer.TypeAdapter; 5 | import com.marcospassos.phpserializer.Writer; 6 | 7 | /** 8 | * Adapter for {@code Integer} values. 9 | * 10 | * @author Marcos Passos 11 | * @since 1.0 12 | */ 13 | public class IntegerAdapter implements TypeAdapter 14 | { 15 | @Override 16 | public void write(Integer value, Writer writer, Context context) 17 | { 18 | writer.writeInteger(value); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/com/marcospassos/phpserializer/adapter/LongAdapter.java: -------------------------------------------------------------------------------- 1 | package com.marcospassos.phpserializer.adapter; 2 | 3 | import com.marcospassos.phpserializer.Context; 4 | import com.marcospassos.phpserializer.TypeAdapter; 5 | import com.marcospassos.phpserializer.Writer; 6 | 7 | /** 8 | * Adapter for {@code Long} values. 9 | * 10 | * @author Marcos Passos 11 | * @since 1.0 12 | */ 13 | public class LongAdapter implements TypeAdapter 14 | { 15 | @Override 16 | public void write(Long value, Writer writer, Context context) 17 | { 18 | writer.writeInteger(value); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/com/marcospassos/phpserializer/adapter/MapAdapter.java: -------------------------------------------------------------------------------- 1 | package com.marcospassos.phpserializer.adapter; 2 | 3 | import java.util.Map; 4 | import com.marcospassos.phpserializer.Context; 5 | import com.marcospassos.phpserializer.TypeAdapter; 6 | import com.marcospassos.phpserializer.Writer; 7 | 8 | /** 9 | * Adapter for {@code Map} type. 10 | * 11 | * @param the type of keys maintained by the map. 12 | * @param the type of mapped values. 13 | * 14 | * @author Marcos Passos 15 | * @since 1.0 16 | */ 17 | public class MapAdapter implements TypeAdapter> 18 | { 19 | @Override 20 | public void write(Map map, Writer writer, Context context) 21 | { 22 | int size = map.size(); 23 | 24 | writer.writeArrayStart(size); 25 | 26 | for (Map.Entry entry : map.entrySet()) { 27 | Object key = entry.getKey(); 28 | 29 | if (key instanceof Integer) { 30 | writer.writeKey((Integer) key); 31 | } else { 32 | writer.writeKey(key.toString()); 33 | } 34 | 35 | context.write(entry.getValue(), writer); 36 | } 37 | 38 | writer.writeArrayEnd(); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/main/java/com/marcospassos/phpserializer/adapter/ObjectAdapter.java: -------------------------------------------------------------------------------- 1 | package com.marcospassos.phpserializer.adapter; 2 | 3 | import java.lang.reflect.Field; 4 | import java.util.ArrayList; 5 | import java.util.List; 6 | import com.marcospassos.phpserializer.Context; 7 | import com.marcospassos.phpserializer.FieldExclusionStrategy; 8 | import com.marcospassos.phpserializer.NamingStrategy; 9 | import com.marcospassos.phpserializer.TypeAdapter; 10 | import com.marcospassos.phpserializer.Writer; 11 | import com.marcospassos.phpserializer.util.ReflectionUtils; 12 | 13 | /** 14 | * Base adapter for {@code Object} type. 15 | * 16 | * @author Marcos Passos 17 | * @since 1.0 18 | */ 19 | public class ObjectAdapter implements TypeAdapter 20 | { 21 | @Override 22 | public void write(T object, Writer writer, Context context) 23 | { 24 | Class type = object.getClass(); 25 | Field[] fields = getFields(type, context.getExclusionStrategy()); 26 | 27 | NamingStrategy namingStrategy = context.getNamingStrategy(); 28 | String className = namingStrategy.getClassName(type); 29 | 30 | writer.writeObjectStart(className, fields.length); 31 | 32 | writeFields(object, fields, writer, context); 33 | 34 | writer.writeObjectEnd(); 35 | } 36 | 37 | /** 38 | * Writes fields using the specified writer. 39 | * 40 | * @param object The object that owns the field. 41 | * @param fields The fields to be written. 42 | * @param writer The writer to use for writing the fields. 43 | * @param context The serialization context. 44 | */ 45 | protected void writeFields( 46 | T object, 47 | Field[] fields, 48 | Writer writer, 49 | Context context 50 | ) 51 | { 52 | NamingStrategy namingStrategy = context.getNamingStrategy(); 53 | 54 | for (Field field : fields) { 55 | String name = namingStrategy.getFieldName(field); 56 | Class declaringClass = field.getDeclaringClass(); 57 | String fieldClass = namingStrategy.getClassName(declaringClass); 58 | 59 | writer.writeProperty(name, fieldClass, getModifiers(field)); 60 | context.write(ReflectionUtils.getValue(object, field), writer); 61 | } 62 | } 63 | 64 | /** 65 | * Returns the access modifier for the specified field. 66 | * 67 | * This method can be overridden to implement custom logic for porting 68 | * property modifiers from Java to PHP. 69 | * 70 | * @param field The field to define the access modifier. 71 | * 72 | * @return The modifier. 73 | * 74 | * @see java.lang.reflect.Modifier 75 | */ 76 | protected int getModifiers(Field field) 77 | { 78 | return field.getModifiers(); 79 | } 80 | 81 | /** 82 | * Returns the list of fields of a class filtered according to the 83 | * specified exclusion strategy. 84 | * 85 | * @param type The class to collect the fields from. 86 | * @param exclusionStrategy The strategy for filtering fields. 87 | * 88 | * @return The modifier. 89 | * 90 | * @see java.lang.reflect.Modifier 91 | */ 92 | protected Field[] getFields( 93 | Class type, 94 | FieldExclusionStrategy exclusionStrategy 95 | ) 96 | { 97 | List filtered = new ArrayList<>(); 98 | 99 | for (Field field : ReflectionUtils.getFields(type)) { 100 | if (exclusionStrategy.shouldSkipField(field)) { 101 | continue; 102 | } 103 | 104 | filtered.add(field); 105 | } 106 | 107 | return filtered.toArray(new Field[0]); 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /src/main/java/com/marcospassos/phpserializer/adapter/ReferableObjectAdapter.java: -------------------------------------------------------------------------------- 1 | package com.marcospassos.phpserializer.adapter; 2 | 3 | import com.marcospassos.phpserializer.Context; 4 | import com.marcospassos.phpserializer.TypeAdapter; 5 | import com.marcospassos.phpserializer.Writer; 6 | 7 | /** 8 | * Decorates adapters for handling object references. 9 | * 10 | * This adapter checks whether an object has already been serialized. If it is 11 | * the case, it serializes the reference instead of the object itself. 12 | * Otherwise, it sets the reference to the object in the context for later use 13 | * before calling the decorated adapter. 14 | * 15 | * @param The type of object. 16 | * 17 | * @author Marcos Passos 18 | * @since 1.0 19 | */ 20 | public class ReferableObjectAdapter implements TypeAdapter 21 | { 22 | /** 23 | * The decorated adapter. 24 | */ 25 | private TypeAdapter adapter; 26 | 27 | /** 28 | * Decorates the specified adapter. 29 | * 30 | * @param adapter The adapter to decorate. 31 | */ 32 | public ReferableObjectAdapter(TypeAdapter adapter) 33 | { 34 | this.adapter = adapter; 35 | } 36 | 37 | @Override 38 | public void write(T object, Writer writer, Context context) 39 | { 40 | int pointer = context.getReference(object); 41 | 42 | if (pointer > -1) { 43 | writer.writeObjectReference(pointer); 44 | 45 | return; 46 | } 47 | 48 | context.setReference(writer.getPointer(), object); 49 | 50 | adapter.write(object, writer, context); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/main/java/com/marcospassos/phpserializer/adapter/StringAdapter.java: -------------------------------------------------------------------------------- 1 | package com.marcospassos.phpserializer.adapter; 2 | 3 | import java.nio.charset.Charset; 4 | import com.marcospassos.phpserializer.Context; 5 | import com.marcospassos.phpserializer.TypeAdapter; 6 | import com.marcospassos.phpserializer.Writer; 7 | 8 | /** 9 | * Adapter for string type. 10 | * 11 | * @author Marcos Passos 12 | * @since 1.0 13 | */ 14 | public class StringAdapter implements TypeAdapter 15 | { 16 | /** 17 | * The charset. 18 | */ 19 | private Charset charset; 20 | 21 | /** 22 | * Creates a adapter for strings encoded in UTF-8. 23 | */ 24 | public StringAdapter() { 25 | this(Charset.forName("UTF-8")); 26 | } 27 | 28 | /** 29 | * Creates a adapter for strings encoded with the specified charset. 30 | * 31 | * @param charset The charset to encode strings. 32 | */ 33 | public StringAdapter(Charset charset) { 34 | this.charset = charset; 35 | } 36 | 37 | @Override 38 | public void write(String value, Writer writer, Context context) 39 | { 40 | writer.writeString(value, this.charset); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/main/java/com/marcospassos/phpserializer/exclusion/DisjunctionExclusionStrategy.java: -------------------------------------------------------------------------------- 1 | package com.marcospassos.phpserializer.exclusion; 2 | 3 | import java.lang.reflect.Field; 4 | import com.marcospassos.phpserializer.FieldExclusionStrategy; 5 | 6 | /** 7 | * Aggregates multiple exclusion strategies to perform a short-circuit OR 8 | * operation. 9 | * 10 | * @author Marcos Passos 11 | * @since 1.0 12 | */ 13 | public class DisjunctionExclusionStrategy implements FieldExclusionStrategy 14 | { 15 | /** 16 | * The underlying strategies. 17 | */ 18 | private FieldExclusionStrategy[] strategies; 19 | 20 | /** 21 | * Creates a disjunction from the specified strategies. 22 | * 23 | * @param strategies The list of strategies. 24 | */ 25 | public DisjunctionExclusionStrategy(FieldExclusionStrategy... strategies) 26 | { 27 | this.strategies = strategies; 28 | } 29 | 30 | @Override 31 | public boolean shouldSkipField(Field field) 32 | { 33 | for (FieldExclusionStrategy strategy : strategies) { 34 | if (strategy.shouldSkipField(field)) { 35 | return true; 36 | } 37 | } 38 | 39 | return false; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/main/java/com/marcospassos/phpserializer/exclusion/NoExclusionStrategy.java: -------------------------------------------------------------------------------- 1 | package com.marcospassos.phpserializer.exclusion; 2 | 3 | import java.lang.reflect.Field; 4 | import com.marcospassos.phpserializer.FieldExclusionStrategy; 5 | 6 | /** 7 | * A strategy to exclude nothing. 8 | * 9 | * @author Marcos Passos 10 | * @since 1.0 11 | */ 12 | public class NoExclusionStrategy implements FieldExclusionStrategy 13 | { 14 | @Override 15 | public boolean shouldSkipField(Field field) 16 | { 17 | return false; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/com/marcospassos/phpserializer/naming/PsrNamingStrategy.java: -------------------------------------------------------------------------------- 1 | package com.marcospassos.phpserializer.naming; 2 | 3 | import java.lang.reflect.Field; 4 | import com.marcospassos.phpserializer.NamingStrategy; 5 | 6 | /** 7 | * Translates names of classes and methods from Java to PHP PSR-1 standard. 8 | * 9 | * @author Marcos Passos 10 | * @see PHP PSR-1 11 | * @since 1.0 12 | */ 13 | public class PsrNamingStrategy implements NamingStrategy 14 | { 15 | @Override 16 | public String getClassName(Class type) 17 | { 18 | String className = type.getCanonicalName(); 19 | StringBuilder adapted = new StringBuilder(); 20 | 21 | for (String part : className.split("\\.")) { 22 | adapted.append("\\"); 23 | 24 | adapted.append(part.substring(0, 1).toUpperCase()); 25 | adapted.append(part.substring(1)); 26 | } 27 | 28 | return adapted.substring(1); 29 | } 30 | 31 | @Override 32 | public String getFieldName(Field field) 33 | { 34 | return field.getName(); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/com/marcospassos/phpserializer/state/AbstractState.java: -------------------------------------------------------------------------------- 1 | 2 | package com.marcospassos.phpserializer.state; 3 | 4 | import com.marcospassos.phpserializer.WriterState; 5 | 6 | /** 7 | * An abstract state that implements the operations in such a way that all 8 | * methods raise an {@link IllegalStateException} by default. 9 | * 10 | * Child classes must overwrite the operations appropriately to return the 11 | * object that represents the new state. 12 | * 13 | * @author Marcos Passos 14 | * @since 1.0 15 | */ 16 | abstract class AbstractState implements WriterState 17 | { 18 | public WriterState serializableBegin() 19 | { 20 | throw new IllegalStateException(); 21 | } 22 | 23 | public WriterState serializableEnd() 24 | { 25 | throw new IllegalStateException(); 26 | } 27 | 28 | public WriterState objectBegin() 29 | { 30 | throw new IllegalStateException(); 31 | } 32 | 33 | public WriterState property() 34 | { 35 | throw new IllegalStateException(); 36 | } 37 | 38 | public WriterState value() 39 | { 40 | throw new IllegalStateException(); 41 | } 42 | 43 | public WriterState objectEnd() 44 | { 45 | throw new IllegalStateException(); 46 | } 47 | 48 | public WriterState arrayBegin() 49 | { 50 | throw new IllegalStateException(); 51 | } 52 | 53 | public WriterState key() 54 | { 55 | throw new IllegalStateException(); 56 | } 57 | 58 | public WriterState arrayEnd() 59 | { 60 | throw new IllegalStateException(); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/main/java/com/marcospassos/phpserializer/state/FinishedState.java: -------------------------------------------------------------------------------- 1 | 2 | package com.marcospassos.phpserializer.state; 3 | 4 | /** 5 | * Represents the final state of the writing process. 6 | * 7 | * @author Marcos Passos 8 | * @since 1.0 9 | */ 10 | public class FinishedState extends AbstractState 11 | { 12 | @Override 13 | public boolean isReferable() 14 | { 15 | return false; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/com/marcospassos/phpserializer/state/WritingArrayState.java: -------------------------------------------------------------------------------- 1 | 2 | package com.marcospassos.phpserializer.state; 3 | 4 | import com.marcospassos.phpserializer.WriterState; 5 | 6 | /** 7 | * Represents the state of the writer while writing an array. 8 | * 9 | * @author Marcos Passos 10 | * @since 1.0 11 | */ 12 | public class WritingArrayState extends AbstractState 13 | { 14 | /** 15 | * The parent state. 16 | */ 17 | WriterState parent; 18 | 19 | /** 20 | * Creates a new state from the specified parent state. 21 | * 22 | * @param parent The parent state. 23 | */ 24 | public WritingArrayState(WriterState parent) 25 | { 26 | this.parent = parent; 27 | } 28 | 29 | @Override 30 | public boolean isReferable() 31 | { 32 | return false; 33 | } 34 | 35 | @Override 36 | public WriterState key() 37 | { 38 | return new WritingValueState(this); 39 | } 40 | 41 | @Override 42 | public WriterState arrayEnd() 43 | { 44 | return parent; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/main/java/com/marcospassos/phpserializer/state/WritingObjectState.java: -------------------------------------------------------------------------------- 1 | 2 | package com.marcospassos.phpserializer.state; 3 | 4 | import com.marcospassos.phpserializer.WriterState; 5 | 6 | /** 7 | * Represents the state of the writer while writing an object. 8 | * 9 | * @author Marcos Passos 10 | * @since 1.0 11 | */ 12 | public class WritingObjectState extends AbstractState 13 | { 14 | /** 15 | * The parent state. 16 | */ 17 | WriterState parent; 18 | 19 | /** 20 | * Creates a new state from the specified parent state. 21 | * 22 | * @param parent The parent state. 23 | */ 24 | public WritingObjectState(WriterState parent) 25 | { 26 | this.parent = parent; 27 | } 28 | 29 | @Override 30 | public boolean isReferable() 31 | { 32 | return false; 33 | } 34 | 35 | @Override 36 | public WriterState property() 37 | { 38 | return new WritingValueState(this); 39 | } 40 | 41 | @Override 42 | public WriterState objectEnd() 43 | { 44 | return parent; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/main/java/com/marcospassos/phpserializer/state/WritingSerializableObjectState.java: -------------------------------------------------------------------------------- 1 | 2 | package com.marcospassos.phpserializer.state; 3 | 4 | import com.marcospassos.phpserializer.WriterState; 5 | 6 | /** 7 | * Represents the state of the writer while writing a serializable object. 8 | * 9 | * @author Marcos Passos 10 | * @since 1.0 11 | */ 12 | public class WritingSerializableObjectState extends AbstractState 13 | { 14 | /** 15 | * The parent state. 16 | */ 17 | WriterState parent; 18 | 19 | /** 20 | * Creates a new state from the specified parent state. 21 | * 22 | * @param parent The parent state. 23 | */ 24 | public WritingSerializableObjectState(WriterState parent) 25 | { 26 | this.parent = parent; 27 | } 28 | 29 | @Override 30 | public boolean isReferable() 31 | { 32 | return false; 33 | } 34 | 35 | @Override 36 | public WriterState serializableEnd() 37 | { 38 | return parent; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/main/java/com/marcospassos/phpserializer/state/WritingValueState.java: -------------------------------------------------------------------------------- 1 | 2 | package com.marcospassos.phpserializer.state; 3 | 4 | import com.marcospassos.phpserializer.WriterState; 5 | 6 | /** 7 | * Represents the state of the writer while writing a value. 8 | * 9 | * @author Marcos Passos 10 | * @since 1.0 11 | */ 12 | public class WritingValueState extends AbstractState 13 | { 14 | /** 15 | * The parent state. 16 | */ 17 | WriterState parent; 18 | 19 | /** 20 | * Creates an initial state. 21 | */ 22 | public WritingValueState() 23 | { 24 | this(new FinishedState()); 25 | } 26 | 27 | /** 28 | * Creates a new state from the specified parent state. 29 | * 30 | * @param parent The parent state. 31 | */ 32 | public WritingValueState(WriterState parent) 33 | { 34 | this.parent = parent; 35 | } 36 | 37 | @Override 38 | public boolean isReferable() 39 | { 40 | return true; 41 | } 42 | 43 | @Override 44 | public WriterState serializableBegin() 45 | { 46 | return new WritingSerializableObjectState(parent); 47 | } 48 | 49 | @Override 50 | public WriterState objectBegin() 51 | { 52 | return new WritingObjectState(parent); 53 | } 54 | 55 | @Override 56 | public WriterState value() 57 | { 58 | return parent; 59 | } 60 | 61 | @Override 62 | public WriterState arrayBegin() 63 | { 64 | return new WritingArrayState(parent); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/main/java/com/marcospassos/phpserializer/util/ReflectionUtils.java: -------------------------------------------------------------------------------- 1 | package com.marcospassos.phpserializer.util; 2 | 3 | import java.lang.reflect.Field; 4 | import java.util.ArrayList; 5 | import java.util.Arrays; 6 | import java.util.List; 7 | 8 | /** 9 | * Simple utility class for working with the reflection API and handling 10 | * reflection exceptions. 11 | * 12 | * @author Marcos Passos 13 | * @since 1.0 14 | */ 15 | public class ReflectionUtils 16 | { 17 | /** 18 | * Get all declared fields on the specified class and all superclasses. 19 | * 20 | * The fields follow the order in which they are declared (direct fields 21 | * take precedence over inherited fields). 22 | * 23 | * The list does not include synthetic fields, such as {@code this$0}, 24 | * {@code this$1}.. 25 | * 26 | * @param type The class to collect the fields. 27 | * 28 | * @return The list of fields. 29 | */ 30 | public static Field[] getFields(Class type) 31 | { 32 | List declaredFields = new ArrayList<>( 33 | Arrays.asList(type.getDeclaredFields()) 34 | ); 35 | 36 | Class current = type; 37 | 38 | while (current.getSuperclass() != null) { 39 | current = current.getSuperclass(); 40 | declaredFields.addAll(Arrays.asList(current.getDeclaredFields())); 41 | } 42 | 43 | List fields = new ArrayList<>(); 44 | 45 | for (Field field : declaredFields) { 46 | // Excludes special properties from inner classes 47 | if (field.getName().indexOf("this$") != 0) { 48 | fields.add(field); 49 | } 50 | } 51 | 52 | return fields.toArray(new Field[0]); 53 | } 54 | 55 | /** 56 | * Gets the value of the specified field from the provided object. 57 | * 58 | * Java language access checking is suppressed to ensure that no exception 59 | * will be thrown for inaccessible fields. 60 | * 61 | * @param object Object from which the represented field's value is to be 62 | * extracted. 63 | * @param field The field to get the value from. 64 | * 65 | * @return The value of the represented field in object ; primitive values 66 | * are wrapped in an appropriate object before being returned. 67 | */ 68 | public static Object getValue(Object object, Field field) 69 | { 70 | field.setAccessible(true); 71 | 72 | try { 73 | return field.get(object); 74 | } catch (IllegalAccessException exception) { 75 | throw new AssertionError(exception); 76 | } 77 | } 78 | 79 | } 80 | -------------------------------------------------------------------------------- /src/test/java/com/marcospassos/phpserializer/AdapterRegistryTest.java: -------------------------------------------------------------------------------- 1 | package com.marcospassos.phpserializer; 2 | 3 | import java.util.Collection; 4 | import java.util.HashSet; 5 | import java.util.LinkedHashMap; 6 | import java.util.List; 7 | import java.util.Map; 8 | import java.util.Set; 9 | import java.util.TreeSet; 10 | import org.junit.Test; 11 | 12 | import static org.junit.Assert.assertEquals; 13 | import static org.junit.Assert.assertSame; 14 | import static org.junit.Assert.assertTrue; 15 | import static org.mockito.Mockito.mock; 16 | 17 | /** 18 | * @author Marcos Passos 19 | * @since 1.0 20 | */ 21 | public class AdapterRegistryTest 22 | { 23 | @Test 24 | public void getAdapterReturnsMostSpecializedAdapter() throws Exception 25 | { 26 | TypeAdapter numberAdapter = mock(TypeAdapter.class); 27 | TypeAdapter byteAdapter = mock(TypeAdapter.class); 28 | TypeAdapter longAdapter = mock(TypeAdapter.class); 29 | TypeAdapter booleanAdapter = mock(TypeAdapter.class); 30 | TypeAdapter charAdapter = mock(TypeAdapter.class); 31 | TypeAdapter shortAdapter = mock(TypeAdapter.class); 32 | TypeAdapter integerAdapter = mock(TypeAdapter.class); 33 | TypeAdapter doubleAdapter = mock(TypeAdapter.class); 34 | TypeAdapter collectionAdapter = mock(TypeAdapter.class); 35 | TypeAdapter setAdapter = mock(TypeAdapter.class); 36 | TypeAdapter hashSetAdapter = mock(TypeAdapter.class); 37 | TypeAdapter arrayOfIntegerAdapter = mock(TypeAdapter.class); 38 | TypeAdapter arrayOfObjectsAdapter = mock(TypeAdapter.class); 39 | 40 | Map adapters = new LinkedHashMap<>(); 41 | adapters.put(Double.class, doubleAdapter); 42 | adapters.put(Byte.class, byteAdapter); 43 | adapters.put(Long.class, longAdapter); 44 | adapters.put(Boolean.class, booleanAdapter); 45 | adapters.put(Character.class, charAdapter); 46 | adapters.put(Short.class, shortAdapter); 47 | adapters.put(Set.class, setAdapter); 48 | adapters.put(Collection.class, collectionAdapter); 49 | adapters.put(Number.class, numberAdapter); 50 | adapters.put(HashSet.class, hashSetAdapter); 51 | adapters.put(Integer.class, integerAdapter); 52 | adapters.put(int[].class, arrayOfIntegerAdapter); 53 | adapters.put(Object[].class, arrayOfObjectsAdapter); 54 | 55 | AdapterRegistry registry = new AdapterRegistry(adapters); 56 | 57 | assertSame(doubleAdapter, registry.getAdapter(Double.class)); 58 | assertSame(doubleAdapter, registry.getAdapter(double.class)); 59 | assertSame(integerAdapter, registry.getAdapter(Integer.class)); 60 | assertSame(integerAdapter, registry.getAdapter(int.class)); 61 | assertSame(charAdapter, registry.getAdapter(Character.class)); 62 | assertSame(charAdapter, registry.getAdapter(char.class)); 63 | assertSame(longAdapter, registry.getAdapter(Long.class)); 64 | assertSame(longAdapter, registry.getAdapter(long.class)); 65 | assertSame(byteAdapter, registry.getAdapter(Byte.class)); 66 | assertSame(byteAdapter, registry.getAdapter(byte.class)); 67 | assertSame(shortAdapter, registry.getAdapter(Short.class)); 68 | assertSame(shortAdapter, registry.getAdapter(short.class)); 69 | assertSame(booleanAdapter, registry.getAdapter(Boolean.class)); 70 | assertSame(booleanAdapter, registry.getAdapter(boolean.class)); 71 | assertSame(numberAdapter, registry.getAdapter(Float.class)); 72 | assertSame(numberAdapter, registry.getAdapter(float.class)); 73 | assertSame(hashSetAdapter, registry.getAdapter(HashSet.class)); 74 | assertSame(setAdapter, registry.getAdapter(TreeSet.class)); 75 | assertSame(collectionAdapter, registry.getAdapter(List.class)); 76 | assertSame(arrayOfIntegerAdapter, registry.getAdapter(int[].class)); 77 | assertSame(arrayOfObjectsAdapter, registry.getAdapter(double[].class)); 78 | assertSame(arrayOfObjectsAdapter, registry.getAdapter(Object[].class)); 79 | assertSame(arrayOfObjectsAdapter, registry.getAdapter(Double[].class)); 80 | } 81 | 82 | @Test(expected = IllegalArgumentException.class) 83 | public void getAdapterFailsIfNoAdapterIsFound() throws Exception 84 | { 85 | AdapterRegistry registry = new AdapterRegistry(); 86 | registry.getAdapter(Integer.class); 87 | } 88 | 89 | @Test 90 | public void getAdaptersReturnsMapOfAdapters() throws Exception 91 | { 92 | TypeAdapter adapterInteger = mock(TypeAdapter.class); 93 | TypeAdapter adapterFloat = mock(TypeAdapter.class); 94 | 95 | AdapterRegistry registry = new AdapterRegistry(); 96 | registry.registerAdapter(Integer.class, adapterInteger); 97 | registry.registerAdapter(Float.class, adapterFloat); 98 | 99 | Map adapters = registry.getAdapters(); 100 | 101 | assertEquals(2, adapters.size()); 102 | assertTrue(adapters.containsKey(Integer.class)); 103 | assertTrue(adapters.containsValue(adapterInteger)); 104 | assertTrue(adapters.containsKey(Float.class)); 105 | assertTrue(adapters.containsValue(adapterFloat)); 106 | } 107 | } -------------------------------------------------------------------------------- /src/test/java/com/marcospassos/phpserializer/ContextTest.java: -------------------------------------------------------------------------------- 1 | package com.marcospassos.phpserializer; 2 | 3 | import java.util.ArrayList; 4 | import org.junit.Before; 5 | import org.junit.Test; 6 | 7 | import static org.junit.Assert.assertArrayEquals; 8 | import static org.junit.Assert.assertEquals; 9 | import static org.junit.Assert.assertSame; 10 | import static org.mockito.Mockito.mock; 11 | import static org.mockito.Mockito.spy; 12 | import static org.mockito.Mockito.verify; 13 | import static org.mockito.Mockito.when; 14 | 15 | /** 16 | * @author Marcos Passos 17 | * @since 1.0 18 | */ 19 | public class ContextTest 20 | { 21 | FieldExclusionStrategy exclusionStrategy; 22 | AdapterRegistry adapterRegistry; 23 | NamingStrategy namingStrategy; 24 | Context context; 25 | 26 | @Before 27 | public void setUp() throws Exception 28 | { 29 | exclusionStrategy = mock(FieldExclusionStrategy.class); 30 | adapterRegistry = mock(AdapterRegistry.class); 31 | namingStrategy = mock(NamingStrategy.class); 32 | 33 | context = new Context( 34 | adapterRegistry, 35 | namingStrategy, 36 | exclusionStrategy 37 | ); 38 | } 39 | 40 | @Test 41 | public void getNamingStrategy() throws Exception 42 | { 43 | assertSame(namingStrategy, context.getNamingStrategy()); 44 | } 45 | 46 | @Test 47 | public void getExclusionStrategy() throws Exception 48 | { 49 | assertSame(exclusionStrategy, context.getExclusionStrategy()); 50 | } 51 | 52 | @Test 53 | public void getDepthReturnsCurrentStackSize() throws Exception 54 | { 55 | Writer writer = mock(Writer.class); 56 | 57 | final ArrayList depthStack = new ArrayList<>(); 58 | final Integer[] values = {10, 20, 30}; 59 | 60 | TypeAdapter adapter = spy(new TypeAdapter() 61 | { 62 | @Override 63 | public void write(Integer value, Writer writer, Context context) 64 | { 65 | depthStack.add(context.getDepth()); 66 | 67 | if (context.getDepth() < 3) { 68 | context.write(values[context.getDepth()], writer); 69 | } 70 | } 71 | }); 72 | 73 | when(adapterRegistry.getAdapter(Integer.class)).thenReturn(adapter); 74 | 75 | context.write(values[0], writer); 76 | 77 | verify(adapter).write(values[0], writer, context); 78 | verify(adapter).write(values[1], writer, context); 79 | verify(adapter).write(values[2], writer, context); 80 | 81 | assertEquals(1, depthStack.get(0)); 82 | assertEquals(2, depthStack.get(1)); 83 | assertEquals(3, depthStack.get(2)); 84 | } 85 | 86 | @Test(expected = IndexOutOfBoundsException.class) 87 | public void getParentFailsIfNoParent() throws Exception 88 | { 89 | context.getParent(); 90 | } 91 | 92 | @Test 93 | public void getParentReturnsObjectThatContainsCurrentObject() 94 | throws Exception 95 | { 96 | Writer writer = mock(Writer.class); 97 | 98 | final ArrayList parentStack = new ArrayList<>(); 99 | final Integer[] values = {10, 20, 30}; 100 | 101 | TypeAdapter adapter = spy(new TypeAdapter() 102 | { 103 | @Override 104 | public void write(Integer value, Writer writer, Context context) 105 | { 106 | if (context.getDepth() > 1) { 107 | parentStack.add(context.getParent()); 108 | } 109 | 110 | if (context.getDepth() < 3) { 111 | context.write(values[context.getDepth()], writer); 112 | } 113 | } 114 | }); 115 | 116 | when(adapterRegistry.getAdapter(Integer.class)).thenReturn(adapter); 117 | 118 | context.write(values[0], writer); 119 | 120 | verify(adapter).write(values[0], writer, context); 121 | verify(adapter).write(values[1], writer, context); 122 | verify(adapter).write(values[2], writer, context); 123 | 124 | assertEquals(2, parentStack.size()); 125 | assertEquals(10, parentStack.get(0)); 126 | assertEquals(20, parentStack.get(1)); 127 | } 128 | 129 | @Test 130 | public void getTraversalStack() throws Exception 131 | { 132 | Writer writer = mock(Writer.class); 133 | 134 | final ArrayList stacks = new ArrayList<>(); 135 | final Integer[] values = {10, 20, 30}; 136 | 137 | TypeAdapter adapter = spy(new TypeAdapter() 138 | { 139 | @Override 140 | public void write(Integer value, Writer writer, Context context) 141 | { 142 | stacks.add(context.getTraversalStack().toArray()); 143 | 144 | if (context.getDepth() < 3) { 145 | context.write(values[context.getDepth()], writer); 146 | } 147 | } 148 | }); 149 | 150 | when(adapterRegistry.getAdapter(Integer.class)).thenReturn(adapter); 151 | 152 | context.write(values[0], writer); 153 | 154 | verify(adapter).write(values[0], writer, context); 155 | verify(adapter).write(values[1], writer, context); 156 | verify(adapter).write(values[2], writer, context); 157 | 158 | Object[] expected = new Object[]{ 159 | new Object[]{10}, 160 | new Object[]{10, 20}, 161 | new Object[]{10, 20, 30} 162 | }; 163 | 164 | assertArrayEquals(expected, stacks.toArray()); 165 | } 166 | 167 | @Test 168 | public void writeDelegatesCallToAdapter() throws Exception 169 | { 170 | Writer writer = mock(Writer.class); 171 | TypeAdapter adapter = spy(new TypeAdapter() 172 | { 173 | @Override 174 | public void write(Integer value, Writer writer, Context context) 175 | { 176 | } 177 | }); 178 | 179 | when(adapterRegistry.getAdapter(Integer.class)).thenReturn(adapter); 180 | 181 | context.write(1, writer); 182 | 183 | verify(adapterRegistry).getAdapter(Integer.class); 184 | verify(adapter).write(1, writer, context); 185 | } 186 | 187 | @Test 188 | public void writeWritesNullForNullValues() throws Exception 189 | { 190 | Writer writer = mock(Writer.class); 191 | context.write(null, writer); 192 | 193 | verify(writer).writeNull(); 194 | } 195 | 196 | @Test 197 | public void getReferenceReturnsMinusOneIfNoReferenceForValueExists() 198 | throws Exception 199 | { 200 | assertEquals(-1, context.getReference(new Object())); 201 | } 202 | 203 | @Test 204 | public void setReferenceStoresAssociatesValueToPointer() throws Exception 205 | { 206 | Object a = new Object(); 207 | Object b = new Object(); 208 | 209 | context.setReference(1, a); 210 | context.setReference(2, b); 211 | 212 | assertEquals(1, context.getReference(a)); 213 | assertEquals(2, context.getReference(b)); 214 | } 215 | } -------------------------------------------------------------------------------- /src/test/java/com/marcospassos/phpserializer/SerializerBuilderTest.java: -------------------------------------------------------------------------------- 1 | package com.marcospassos.phpserializer; 2 | 3 | import java.lang.reflect.Field; 4 | import java.nio.charset.Charset; 5 | import java.util.Collection; 6 | import java.util.Map; 7 | import com.marcospassos.phpserializer.adapter.ArrayAdapter; 8 | import com.marcospassos.phpserializer.adapter.BooleanAdapter; 9 | import com.marcospassos.phpserializer.adapter.CollectionAdapter; 10 | import com.marcospassos.phpserializer.adapter.DoubleAdapter; 11 | import com.marcospassos.phpserializer.adapter.IntegerAdapter; 12 | import com.marcospassos.phpserializer.adapter.LongAdapter; 13 | import com.marcospassos.phpserializer.adapter.MapAdapter; 14 | import com.marcospassos.phpserializer.adapter.ObjectAdapter; 15 | import com.marcospassos.phpserializer.adapter.ReferableObjectAdapter; 16 | import com.marcospassos.phpserializer.adapter.StringAdapter; 17 | import com.marcospassos.phpserializer.exclusion.DisjunctionExclusionStrategy; 18 | import com.marcospassos.phpserializer.exclusion.NoExclusionStrategy; 19 | import com.marcospassos.phpserializer.naming.PsrNamingStrategy; 20 | import org.hamcrest.CoreMatchers; 21 | import org.junit.Test; 22 | import org.mockito.ArgumentCaptor; 23 | 24 | import static org.hamcrest.CoreMatchers.*; 25 | import static org.junit.Assert.assertFalse; 26 | import static org.junit.Assert.assertSame; 27 | import static org.junit.Assert.assertThat; 28 | import static org.junit.Assert.assertTrue; 29 | import static org.mockito.ArgumentMatchers.any; 30 | import static org.mockito.ArgumentMatchers.isA; 31 | import static org.mockito.Mockito.mock; 32 | import static org.mockito.Mockito.verify; 33 | import static org.mockito.Mockito.verifyNoMoreInteractions; 34 | import static org.mockito.Mockito.when; 35 | 36 | /** 37 | * @author Marcos Passos 38 | * @since 1.0 39 | */ 40 | public class SerializerBuilderTest 41 | { 42 | private class Foo 43 | { 44 | public int field; 45 | } 46 | 47 | @Test 48 | public void build() throws Exception 49 | { 50 | SerializerFactory factory = mock(SerializerFactory.class); 51 | FieldExclusionStrategy exclusionStrategy = mock(FieldExclusionStrategy.class); 52 | NamingStrategy namingStrategy = mock(NamingStrategy.class); 53 | TypeAdapter adapter = mock(TypeAdapter.class); 54 | 55 | new SerializerBuilder(factory) 56 | .registerBuiltinAdapters() 57 | .addExclusionStrategy(exclusionStrategy) 58 | .registerAdapter(Foo.class, adapter) 59 | .setNamingStrategy(namingStrategy) 60 | .build() 61 | ; 62 | 63 | ArgumentCaptor exclusionStrategyArgument = 64 | ArgumentCaptor.forClass(FieldExclusionStrategy.class); 65 | 66 | ArgumentCaptor adapterRegistryArgument = 67 | ArgumentCaptor.forClass(AdapterRegistry.class); 68 | 69 | ArgumentCaptor namingStrategyArgument = 70 | ArgumentCaptor.forClass(NamingStrategy.class); 71 | 72 | verify(factory).create( 73 | namingStrategyArgument.capture(), 74 | exclusionStrategyArgument.capture(), 75 | adapterRegistryArgument.capture() 76 | ); 77 | 78 | AdapterRegistry registry = adapterRegistryArgument.getValue(); 79 | Map adapters = registry.getAdapters(); 80 | 81 | assertTrue(adapters.size() > 1); 82 | assertTrue(adapters.containsKey(Foo.class)); 83 | assertTrue(adapters.containsValue(adapter)); 84 | 85 | assertSame(namingStrategy, namingStrategyArgument.getValue()); 86 | assertSame(exclusionStrategy, exclusionStrategyArgument.getValue()); 87 | } 88 | 89 | @Test 90 | public void builderInstantiatesFactoryIfNotSpecified() throws Exception 91 | { 92 | new SerializerBuilder().build(); 93 | } 94 | 95 | @Test 96 | public void exclusionStrategiesAreAggregatedIntoDisjunction() 97 | throws Exception 98 | { 99 | SerializerFactory factory = mock(SerializerFactory.class); 100 | FieldExclusionStrategy first = mock(FieldExclusionStrategy.class); 101 | FieldExclusionStrategy second = mock(FieldExclusionStrategy.class); 102 | Field field = Foo.class.getField("field"); 103 | 104 | when(first.shouldSkipField(field)).thenReturn(false); 105 | when(second.shouldSkipField(field)).thenReturn(false); 106 | 107 | new SerializerBuilder(factory) 108 | .registerBuiltinAdapters() 109 | .addExclusionStrategy(first) 110 | .addExclusionStrategy(second) 111 | .build() 112 | ; 113 | 114 | ArgumentCaptor exclusionStrategyArgument = 115 | ArgumentCaptor.forClass(FieldExclusionStrategy.class); 116 | 117 | verify(factory).create( 118 | any(NamingStrategy.class), 119 | exclusionStrategyArgument.capture(), 120 | any(AdapterRegistry.class) 121 | ); 122 | 123 | FieldExclusionStrategy disjunction = exclusionStrategyArgument 124 | .getValue(); 125 | assertTrue(disjunction instanceof DisjunctionExclusionStrategy); 126 | 127 | assertFalse(disjunction.shouldSkipField(field)); 128 | 129 | verify(first).shouldSkipField(field); 130 | verify(second).shouldSkipField(field); 131 | } 132 | 133 | @Test 134 | public void namingStrategyIsPsrByDefault() throws Exception 135 | { 136 | SerializerFactory factory = mock(SerializerFactory.class); 137 | 138 | new SerializerBuilder(factory).build(); 139 | 140 | ArgumentCaptor namingStrategyArgument = 141 | ArgumentCaptor.forClass(NamingStrategy.class); 142 | 143 | verify(factory).create( 144 | namingStrategyArgument.capture(), 145 | any(FieldExclusionStrategy.class), 146 | any(AdapterRegistry.class) 147 | ); 148 | 149 | NamingStrategy strategy = namingStrategyArgument.getValue(); 150 | 151 | assertTrue(strategy instanceof PsrNamingStrategy); 152 | } 153 | 154 | @Test 155 | public void exclusionStrategyIsNoExclusionByDefault() throws Exception 156 | { 157 | SerializerFactory factory = mock(SerializerFactory.class); 158 | 159 | new SerializerBuilder(factory).build(); 160 | 161 | ArgumentCaptor exclusionStrategyArgument = 162 | ArgumentCaptor.forClass(FieldExclusionStrategy.class); 163 | 164 | verify(factory).create( 165 | any(NamingStrategy.class), 166 | exclusionStrategyArgument.capture(), 167 | any(AdapterRegistry.class) 168 | ); 169 | 170 | FieldExclusionStrategy strategy = exclusionStrategyArgument.getValue(); 171 | 172 | assertTrue(strategy instanceof NoExclusionStrategy); 173 | } 174 | 175 | @Test 176 | public void builderRegistersBuiltinAdaptersIfNoAdapterIsRegistered() 177 | throws Exception 178 | { 179 | SerializerFactory factory = mock(SerializerFactory.class); 180 | 181 | new SerializerBuilder(factory).build(); 182 | 183 | ArgumentCaptor adapterRegistryArgument = 184 | ArgumentCaptor.forClass(AdapterRegistry.class); 185 | 186 | verify(factory).create( 187 | any(NamingStrategy.class), 188 | any(FieldExclusionStrategy.class), 189 | adapterRegistryArgument.capture() 190 | ); 191 | 192 | AdapterRegistry registry = adapterRegistryArgument.getValue(); 193 | Map adapters = registry.getAdapters(); 194 | 195 | assertFalse(adapters.isEmpty()); 196 | } 197 | 198 | @Test 199 | public void builderRegistersAllBuiltinAdapters() throws Exception 200 | { 201 | SerializerFactory factory = mock(SerializerFactory.class); 202 | 203 | SerializerBuilder builder = new SerializerBuilder(factory); 204 | 205 | builder.registerBuiltinAdapters(); 206 | builder.build(); 207 | 208 | ArgumentCaptor adapterRegistryArgument = 209 | ArgumentCaptor.forClass(AdapterRegistry.class); 210 | 211 | verify(factory).create( 212 | any(NamingStrategy.class), 213 | any(FieldExclusionStrategy.class), 214 | adapterRegistryArgument.capture() 215 | ); 216 | 217 | AdapterRegistry registry = adapterRegistryArgument.getValue(); 218 | 219 | assertThat( 220 | registry.getAdapter(Object[].class), 221 | instanceOf(ArrayAdapter.class) 222 | ); 223 | 224 | assertThat( 225 | registry.getAdapter(Map.class), 226 | instanceOf(MapAdapter.class) 227 | ); 228 | 229 | assertThat( 230 | registry.getAdapter(Collection.class), 231 | instanceOf(CollectionAdapter.class) 232 | ); 233 | 234 | assertThat( 235 | registry.getAdapter(Boolean.class), 236 | instanceOf(BooleanAdapter.class) 237 | ); 238 | 239 | assertThat( 240 | registry.getAdapter(Double.class), 241 | instanceOf(DoubleAdapter.class) 242 | ); 243 | 244 | assertThat( 245 | registry.getAdapter(Integer.class), 246 | instanceOf(IntegerAdapter.class) 247 | ); 248 | 249 | assertThat( 250 | registry.getAdapter(Long.class), 251 | instanceOf(LongAdapter.class) 252 | ); 253 | 254 | assertThat( 255 | registry.getAdapter(String.class), 256 | instanceOf(StringAdapter.class) 257 | ); 258 | 259 | assertThat( 260 | registry.getAdapter(Object.class), 261 | instanceOf(ReferableObjectAdapter.class) 262 | ); 263 | } 264 | 265 | @Test 266 | public void builderRegisterStringAdapterUsingUtf8CharsetByDefault() throws Exception 267 | { 268 | SerializerFactory factory = mock(SerializerFactory.class); 269 | 270 | new SerializerBuilder(factory).build(); 271 | 272 | ArgumentCaptor adapterRegistryArgument = 273 | ArgumentCaptor.forClass(AdapterRegistry.class); 274 | 275 | verify(factory).create( 276 | any(NamingStrategy.class), 277 | any(FieldExclusionStrategy.class), 278 | adapterRegistryArgument.capture() 279 | ); 280 | 281 | AdapterRegistry registry = adapterRegistryArgument.getValue(); 282 | Writer writer = mock(Writer.class); 283 | Context context = mock(Context.class); 284 | 285 | TypeAdapter adapter = registry.getAdapter(String.class); 286 | 287 | adapter.write("foo", writer, context); 288 | 289 | verify(writer).writeString("foo", Charset.forName("UTF-8")); 290 | } 291 | 292 | @Test 293 | public void builderRegisterStringAdapterUsingSpecifiedCharset() throws Exception 294 | { 295 | SerializerFactory factory = mock(SerializerFactory.class); 296 | 297 | Charset charset = Charset.forName("ISO-8859-1"); 298 | 299 | SerializerBuilder builder = new SerializerBuilder(factory); 300 | builder.setCharset(charset); 301 | 302 | builder.build(); 303 | 304 | ArgumentCaptor adapterRegistryArgument = 305 | ArgumentCaptor.forClass(AdapterRegistry.class); 306 | 307 | verify(factory).create( 308 | any(NamingStrategy.class), 309 | any(FieldExclusionStrategy.class), 310 | adapterRegistryArgument.capture() 311 | ); 312 | 313 | AdapterRegistry registry = adapterRegistryArgument.getValue(); 314 | Writer writer = mock(Writer.class); 315 | Context context = mock(Context.class); 316 | 317 | TypeAdapter adapter = registry.getAdapter(String.class); 318 | 319 | adapter.write("foo", writer, context); 320 | 321 | verify(writer).writeString("foo", charset); 322 | } 323 | } -------------------------------------------------------------------------------- /src/test/java/com/marcospassos/phpserializer/SerializerTest.java: -------------------------------------------------------------------------------- 1 | package com.marcospassos.phpserializer; 2 | 3 | import java.util.HashMap; 4 | import java.util.Map; 5 | import com.marcospassos.phpserializer.adapter.IntegerAdapter; 6 | import com.marcospassos.phpserializer.adapter.MapAdapter; 7 | import com.marcospassos.phpserializer.adapter.StringAdapter; 8 | import com.marcospassos.phpserializer.exclusion.NoExclusionStrategy; 9 | import com.marcospassos.phpserializer.naming.PsrNamingStrategy; 10 | import org.junit.Test; 11 | 12 | import static org.junit.Assert.assertEquals; 13 | 14 | /** 15 | * @author Marcos Passos 16 | * @since 1.0 17 | */ 18 | public class SerializerTest 19 | { 20 | @Test 21 | public void serialize() throws Exception 22 | { 23 | AdapterRegistry registry = new AdapterRegistry(); 24 | registry.registerAdapter(Integer.class, new IntegerAdapter()); 25 | 26 | Serializer serializer = new Serializer( 27 | new PsrNamingStrategy(), 28 | new NoExclusionStrategy(), 29 | registry 30 | ); 31 | 32 | assertEquals("i:1;", serializer.serialize(1)); 33 | } 34 | 35 | @Test 36 | public void serializeGenericAdapter() throws Exception 37 | { 38 | AdapterRegistry registry = new AdapterRegistry(); 39 | registry.registerAdapter(Map.class, new MapAdapter()); 40 | registry.registerAdapter(String.class, new StringAdapter()); 41 | registry.registerAdapter(Integer.class, new IntegerAdapter()); 42 | 43 | Map map = new HashMap<>(); 44 | 45 | map.put(1, "foo"); 46 | map.put(2, "bar"); 47 | 48 | Serializer serializer = new Serializer( 49 | new PsrNamingStrategy(), 50 | new NoExclusionStrategy(), 51 | registry 52 | ); 53 | 54 | String result = serializer.serialize(map); 55 | 56 | assertEquals("a:2:{i:1;s:3:\"foo\";i:2;s:3:\"bar\";}", result); 57 | } 58 | } -------------------------------------------------------------------------------- /src/test/java/com/marcospassos/phpserializer/WriterTest.java: -------------------------------------------------------------------------------- 1 | package com.marcospassos.phpserializer; 2 | 3 | import java.lang.reflect.Modifier; 4 | import java.nio.charset.Charset; 5 | import org.junit.Test; 6 | 7 | import static org.junit.Assert.assertEquals; 8 | 9 | /** 10 | * @author Marcos Passos 11 | * @since 1.0 12 | */ 13 | public class WriterTest 14 | { 15 | @Test 16 | public void writeObject() throws Exception 17 | { 18 | Writer writer = new Writer(); 19 | writer.writeObjectStart("Foo", 3); 20 | 21 | writer.writeProperty("a", "Foo", Modifier.PRIVATE); 22 | writer.writeInteger(1); 23 | 24 | writer.writeProperty("b", "Foo", Modifier.PROTECTED); 25 | writer.writeInteger(2); 26 | 27 | writer.writeProperty("c", "Foo", Modifier.PUBLIC); 28 | writer.writeInteger(3); 29 | 30 | writer.writeObjectEnd(); 31 | 32 | assertEquals( 33 | "O:3:\"Foo\":3:{s:6:\"\u0000Foo\u0000a\";i:1;" + 34 | "s:4:\"\u0000*\u0000b\";i:2;s:1:\"c\";i:3;}", 35 | writer.getResult() 36 | ); 37 | } 38 | 39 | @Test 40 | public void writeSerializableObject() throws Exception 41 | { 42 | Writer writer = new Writer(); 43 | Writer objectWriter = writer.writeSerializableObjectStart("Foo"); 44 | 45 | objectWriter.writeArrayStart(1); 46 | objectWriter.writeKey(0); 47 | objectWriter.writeString("bar"); 48 | objectWriter.writeArrayEnd(); 49 | 50 | writer.writeSerializableObjectEnd(); 51 | 52 | assertEquals("C:3:\"Foo\":20:{a:1:{i:0;s:3:\"bar\";}}", writer.getResult()); 53 | assertEquals(3, writer.getPointer()); 54 | } 55 | 56 | @Test(expected = IllegalStateException.class) 57 | public void writeSerializableObjectEndFailsIfNothingIsWritten() throws Exception 58 | { 59 | Writer writer = new Writer(); 60 | writer.writeSerializableObjectStart("Foo"); 61 | writer.writeSerializableObjectEnd(); 62 | } 63 | 64 | @Test 65 | public void writePublicProperty() throws Exception 66 | { 67 | Writer writer = new Writer(); 68 | writer.writeObjectStart("Foo", 3); 69 | 70 | writer.writePublicProperty("a"); 71 | writer.writeInteger(1); 72 | 73 | writer.writeObjectEnd(); 74 | 75 | assertEquals("O:3:\"Foo\":3:{s:1:\"a\";i:1;}", writer.getResult()); 76 | } 77 | 78 | @Test 79 | public void writeProtectedProperty() throws Exception 80 | { 81 | Writer writer = new Writer(); 82 | writer.writeObjectStart("Foo", 3); 83 | 84 | writer.writeProtectedProperty("a"); 85 | writer.writeInteger(1); 86 | 87 | writer.writeObjectEnd(); 88 | 89 | assertEquals( 90 | "O:3:\"Foo\":3:{s:4:\"\u0000*\u0000a\";i:1;}", 91 | writer.getResult() 92 | ); 93 | } 94 | 95 | @Test 96 | public void writePrivateProperty() throws Exception 97 | { 98 | Writer writer = new Writer(); 99 | writer.writeObjectStart("Foo", 3); 100 | 101 | writer.writePrivateProperty("a", "Foo"); 102 | writer.writeInteger(1); 103 | 104 | writer.writeObjectEnd(); 105 | 106 | assertEquals( 107 | "O:3:\"Foo\":3:{s:6:\"\u0000Foo\u0000a\";i:1;}", 108 | writer.getResult() 109 | ); 110 | } 111 | 112 | @Test 113 | public void writeVariableReference() throws Exception 114 | { 115 | Writer writer = new Writer(); 116 | writer.writeVariableReference(1); 117 | 118 | assertEquals("R:1;", writer.getResult()); 119 | } 120 | 121 | @Test 122 | public void writeObjectReference() throws Exception 123 | { 124 | Writer writer = new Writer(); 125 | writer.writeObjectReference(1); 126 | 127 | assertEquals("r:1;", writer.getResult()); 128 | } 129 | 130 | @Test 131 | public void writeNull() throws Exception 132 | { 133 | Writer writer = new Writer(); 134 | writer.writeNull(); 135 | 136 | assertEquals("N;", writer.getResult()); 137 | } 138 | 139 | @Test 140 | public void writeStringUtf8() throws Exception 141 | { 142 | Writer writer = new Writer(); 143 | writer.writeString("Informations générales"); 144 | 145 | assertEquals("s:24:\"Informations générales\";", writer.getResult()); 146 | } 147 | 148 | @Test 149 | public void writeStringIsoCharset() throws Exception 150 | { 151 | Writer writer = new Writer(); 152 | Charset charset = Charset.forName("ISO-8859-1"); 153 | writer.writeString("Informations générales", charset); 154 | 155 | assertEquals("s:22:\"Informations générales\";", writer.getResult()); 156 | } 157 | 158 | @Test 159 | public void writeStringDoesNotEscapeQuotes() throws Exception 160 | { 161 | Writer writer = new Writer(); 162 | writer.writeString("\"foo\""); 163 | 164 | assertEquals("s:5:\"\"foo\"\";", writer.getResult()); 165 | } 166 | 167 | @Test 168 | public void writeEmptyString() throws Exception 169 | { 170 | Writer writer = new Writer(); 171 | writer.writeString(""); 172 | 173 | assertEquals("s:0:\"\";", writer.getResult()); 174 | } 175 | 176 | @Test 177 | public void writeBooleanTrue() throws Exception 178 | { 179 | Writer writer = new Writer(); 180 | writer.writeBoolean(true); 181 | 182 | assertEquals("b:1;", writer.getResult()); 183 | } 184 | 185 | @Test 186 | public void writeBooleanFalse() throws Exception 187 | { 188 | Writer writer = new Writer(); 189 | writer.writeBoolean(false); 190 | 191 | assertEquals("b:0;", writer.getResult()); 192 | } 193 | 194 | @Test 195 | public void writeInteger() throws Exception 196 | { 197 | Writer writer = new Writer(); 198 | writer.writeInteger(1); 199 | 200 | assertEquals("i:1;", writer.getResult()); 201 | } 202 | 203 | @Test 204 | public void writeLong() throws Exception 205 | { 206 | Writer writer = new Writer(); 207 | writer.writeInteger(1L); 208 | 209 | assertEquals("i:1;", writer.getResult()); 210 | } 211 | 212 | @Test 213 | public void writeFloat() throws Exception 214 | { 215 | Writer writer = new Writer(); 216 | writer.writeFloat(1.0); 217 | 218 | assertEquals("d:1.0;", writer.getResult()); 219 | } 220 | 221 | @Test 222 | public void writeArray() throws Exception 223 | { 224 | Writer writer = new Writer(); 225 | writer.writeArrayStart(3); 226 | 227 | writer.writeKey("a"); 228 | writer.writeInteger(1); 229 | 230 | writer.writeKey("b"); 231 | writer.writeInteger(2); 232 | 233 | writer.writeKey(3); 234 | writer.writeInteger(3); 235 | 236 | writer.writeArrayEnd(); 237 | 238 | assertEquals( 239 | "a:3:{s:1:\"a\";i:1;s:1:\"b\";i:2;i:3;i:3;}", 240 | writer.getResult() 241 | ); 242 | } 243 | 244 | @Test 245 | public void pointerTracksReferableValues() throws Exception 246 | { 247 | Writer writer = new Writer(); 248 | 249 | assertEquals(1, writer.getPointer()); 250 | 251 | writer.writeArrayStart(2); 252 | 253 | assertEquals(1, writer.getPointer()); 254 | 255 | writer.writeKey("a"); 256 | 257 | assertEquals(2, writer.getPointer()); 258 | 259 | writer.writeInteger(1); 260 | 261 | assertEquals(2, writer.getPointer()); 262 | 263 | writer.writeKey("b"); 264 | 265 | assertEquals(3, writer.getPointer()); 266 | 267 | writer.writeInteger(2); 268 | 269 | assertEquals(3, writer.getPointer()); 270 | 271 | writer.writeArrayEnd(); 272 | 273 | assertEquals(3, writer.getPointer()); 274 | } 275 | } -------------------------------------------------------------------------------- /src/test/java/com/marcospassos/phpserializer/adapter/ArrayAdapterTest.java: -------------------------------------------------------------------------------- 1 | package com.marcospassos.phpserializer.adapter; 2 | 3 | import com.marcospassos.phpserializer.Context; 4 | import com.marcospassos.phpserializer.Writer; 5 | import org.junit.Test; 6 | import org.mockito.InOrder; 7 | 8 | import static org.mockito.Mockito.inOrder; 9 | import static org.mockito.Mockito.mock; 10 | 11 | /** 12 | * @author Marcos Passos 13 | * @since 1.0 14 | */ 15 | public class ArrayAdapterTest 16 | { 17 | @Test 18 | public void write() throws Exception 19 | { 20 | ArrayAdapter adapter = new ArrayAdapter<>(); 21 | Writer writer = mock(Writer.class); 22 | Context context = mock(Context.class); 23 | String[] array = new String[]{"a", "b", "c"}; 24 | 25 | adapter.write(array, writer, context); 26 | 27 | InOrder order = inOrder(writer, context); 28 | 29 | order.verify(writer).writeArrayStart(3); 30 | order.verify(writer).writeKey(0); 31 | order.verify(context).write("a", writer); 32 | order.verify(writer).writeKey(1); 33 | order.verify(context).write("b", writer); 34 | order.verify(writer).writeKey(2); 35 | order.verify(context).write("c", writer); 36 | order.verify(writer).writeArrayEnd(); 37 | 38 | order.verifyNoMoreInteractions(); 39 | } 40 | 41 | @Test(expected = IllegalArgumentException.class) 42 | public void writeFailsIfNotArray() throws Exception 43 | { 44 | ArrayAdapter adapter = new ArrayAdapter<>(); 45 | Writer writer = mock(Writer.class); 46 | Context context = mock(Context.class); 47 | 48 | adapter.write(new Object(), writer, context); 49 | } 50 | } -------------------------------------------------------------------------------- /src/test/java/com/marcospassos/phpserializer/adapter/BooleanAdapterTest.java: -------------------------------------------------------------------------------- 1 | package com.marcospassos.phpserializer.adapter; 2 | 3 | import com.marcospassos.phpserializer.Context; 4 | import com.marcospassos.phpserializer.Writer; 5 | import org.junit.Test; 6 | 7 | import static org.mockito.Mockito.mock; 8 | import static org.mockito.Mockito.verify; 9 | import static org.mockito.Mockito.verifyNoMoreInteractions; 10 | 11 | /** 12 | * @author Marcos Passos 13 | * @since 1.0 14 | */ 15 | public class BooleanAdapterTest 16 | { 17 | @Test 18 | public void write() throws Exception 19 | { 20 | BooleanAdapter adapter = new BooleanAdapter(); 21 | Writer writer = mock(Writer.class); 22 | Context context = mock(Context.class); 23 | 24 | adapter.write(true, writer, context); 25 | 26 | verify(writer).writeBoolean(true); 27 | 28 | verifyNoMoreInteractions(writer); 29 | } 30 | } -------------------------------------------------------------------------------- /src/test/java/com/marcospassos/phpserializer/adapter/CollectionAdapterTest.java: -------------------------------------------------------------------------------- 1 | package com.marcospassos.phpserializer.adapter; 2 | 3 | import java.util.Arrays; 4 | import java.util.Collection; 5 | import com.marcospassos.phpserializer.Context; 6 | import com.marcospassos.phpserializer.Writer; 7 | import org.junit.Test; 8 | import org.mockito.InOrder; 9 | 10 | import static org.mockito.Mockito.inOrder; 11 | import static org.mockito.Mockito.mock; 12 | 13 | /** 14 | * @author Marcos Passos 15 | * @since 1.0 16 | */ 17 | public class CollectionAdapterTest 18 | { 19 | @Test 20 | public void write() throws Exception 21 | { 22 | CollectionAdapter adapter = new CollectionAdapter<>(); 23 | Writer writer = mock(Writer.class); 24 | Context context = mock(Context.class); 25 | Collection array = Arrays.asList("a", "b", "c"); 26 | 27 | adapter.write(array, writer, context); 28 | 29 | InOrder order = inOrder(writer, context); 30 | 31 | order.verify(writer).writeArrayStart(3); 32 | order.verify(writer).writeKey(0); 33 | order.verify(context).write("a", writer); 34 | order.verify(writer).writeKey(1); 35 | order.verify(context).write("b", writer); 36 | order.verify(writer).writeKey(2); 37 | order.verify(context).write("c", writer); 38 | order.verify(writer).writeArrayEnd(); 39 | 40 | order.verifyNoMoreInteractions(); 41 | } 42 | } -------------------------------------------------------------------------------- /src/test/java/com/marcospassos/phpserializer/adapter/DoubleAdapterTest.java: -------------------------------------------------------------------------------- 1 | package com.marcospassos.phpserializer.adapter; 2 | 3 | import com.marcospassos.phpserializer.Context; 4 | import com.marcospassos.phpserializer.Writer; 5 | import org.junit.Test; 6 | 7 | import static org.mockito.Mockito.mock; 8 | import static org.mockito.Mockito.verify; 9 | import static org.mockito.Mockito.verifyNoMoreInteractions; 10 | 11 | /** 12 | * @author Marcos Passos 13 | * @since 1.0 14 | */ 15 | public class DoubleAdapterTest 16 | { 17 | @Test 18 | public void write() throws Exception 19 | { 20 | DoubleAdapter adapter = new DoubleAdapter(); 21 | Writer writer = mock(Writer.class); 22 | Context context = mock(Context.class); 23 | 24 | adapter.write(1.5, writer, context); 25 | 26 | verify(writer).writeFloat(1.5); 27 | 28 | verifyNoMoreInteractions(writer); 29 | } 30 | } -------------------------------------------------------------------------------- /src/test/java/com/marcospassos/phpserializer/adapter/IntegerAdapterTest.java: -------------------------------------------------------------------------------- 1 | package com.marcospassos.phpserializer.adapter; 2 | 3 | import com.marcospassos.phpserializer.Context; 4 | import com.marcospassos.phpserializer.Writer; 5 | import org.junit.Test; 6 | 7 | import static org.mockito.Mockito.mock; 8 | import static org.mockito.Mockito.verify; 9 | import static org.mockito.Mockito.verifyNoMoreInteractions; 10 | 11 | /** 12 | * @author Marcos Passos 13 | * @since 1.0 14 | */ 15 | public class IntegerAdapterTest 16 | { 17 | @Test 18 | public void write() throws Exception 19 | { 20 | IntegerAdapter adapter = new IntegerAdapter(); 21 | Writer writer = mock(Writer.class); 22 | Context context = mock(Context.class); 23 | 24 | adapter.write(10, writer, context); 25 | 26 | verify(writer).writeInteger(10); 27 | 28 | verifyNoMoreInteractions(writer); 29 | } 30 | } -------------------------------------------------------------------------------- /src/test/java/com/marcospassos/phpserializer/adapter/LongAdapterTest.java: -------------------------------------------------------------------------------- 1 | package com.marcospassos.phpserializer.adapter; 2 | 3 | import com.marcospassos.phpserializer.Context; 4 | import com.marcospassos.phpserializer.Writer; 5 | import org.junit.Test; 6 | 7 | import static org.mockito.Mockito.mock; 8 | import static org.mockito.Mockito.verify; 9 | import static org.mockito.Mockito.verifyNoMoreInteractions; 10 | 11 | /** 12 | * @author Marcos Passos 13 | * @since 1.0 14 | */ 15 | public class LongAdapterTest 16 | { 17 | @Test 18 | public void write() throws Exception 19 | { 20 | LongAdapter adapter = new LongAdapter(); 21 | Writer writer = mock(Writer.class); 22 | Context context = mock(Context.class); 23 | 24 | adapter.write(10L, writer, context); 25 | 26 | verify(writer).writeInteger(10L); 27 | 28 | verifyNoMoreInteractions(writer); 29 | } 30 | } -------------------------------------------------------------------------------- /src/test/java/com/marcospassos/phpserializer/adapter/MapAdapterTest.java: -------------------------------------------------------------------------------- 1 | package com.marcospassos.phpserializer.adapter; 2 | 3 | import java.util.HashMap; 4 | import java.util.Map; 5 | import com.marcospassos.phpserializer.Context; 6 | import com.marcospassos.phpserializer.Writer; 7 | import org.junit.Test; 8 | import org.mockito.InOrder; 9 | 10 | import static org.mockito.Mockito.inOrder; 11 | import static org.mockito.Mockito.mock; 12 | 13 | /** 14 | * @author Marcos Passos 15 | * @since 1.0 16 | */ 17 | public class MapAdapterTest 18 | { 19 | @Test 20 | public void write() throws Exception 21 | { 22 | MapAdapter adapter = new MapAdapter<>(); 23 | Writer writer = mock(Writer.class); 24 | Context context = mock(Context.class); 25 | Map map = new HashMap<>(); 26 | map.put(1, "a"); 27 | map.put("2", "b"); 28 | map.put(3, "c"); 29 | 30 | // The verify check is split by entries once HashMap is an unordered map 31 | 32 | adapter.write(map, writer, context); 33 | 34 | InOrder order = inOrder(writer, context); 35 | 36 | order.verify(writer).writeArrayStart(3); 37 | 38 | InOrder first = inOrder(writer, context); 39 | 40 | first.verify(writer).writeKey(1); 41 | first.verify(context).write("a", writer); 42 | 43 | InOrder second = inOrder(writer, context); 44 | 45 | second.verify(writer).writeKey("2"); 46 | second.verify(context).write("b", writer); 47 | 48 | InOrder third = inOrder(writer, context); 49 | 50 | third.verify(writer).writeKey(3); 51 | third.verify(context).write("c", writer); 52 | 53 | order.verify(writer).writeArrayEnd(); 54 | 55 | order.verifyNoMoreInteractions(); 56 | } 57 | } -------------------------------------------------------------------------------- /src/test/java/com/marcospassos/phpserializer/adapter/ObjectAdapterTest.java: -------------------------------------------------------------------------------- 1 | package com.marcospassos.phpserializer.adapter; 2 | 3 | import java.lang.reflect.Field; 4 | import java.lang.reflect.Modifier; 5 | import com.marcospassos.phpserializer.Context; 6 | import com.marcospassos.phpserializer.FieldExclusionStrategy; 7 | import com.marcospassos.phpserializer.NamingStrategy; 8 | import com.marcospassos.phpserializer.Writer; 9 | import org.junit.Test; 10 | import org.mockito.InOrder; 11 | import org.mockito.invocation.InvocationOnMock; 12 | import org.mockito.stubbing.Answer; 13 | 14 | import static org.mockito.ArgumentMatchers.any; 15 | import static org.mockito.Mockito.inOrder; 16 | import static org.mockito.Mockito.mock; 17 | import static org.mockito.Mockito.when; 18 | 19 | public class ObjectAdapterTest 20 | { 21 | private class Subject 22 | { 23 | private int privateField; 24 | protected int protectedField; 25 | public int publicField; 26 | public int excludedField; 27 | 28 | public Subject( 29 | int privateField, 30 | int protectedField, 31 | int publicField, 32 | int excludedField 33 | ) 34 | { 35 | this.privateField = privateField; 36 | this.protectedField = protectedField; 37 | this.publicField = publicField; 38 | this.excludedField = excludedField; 39 | } 40 | } 41 | 42 | @Test 43 | public void write() throws Exception 44 | { 45 | ObjectAdapter adapter = new ObjectAdapter<>(); 46 | Writer writer = mock(Writer.class); 47 | Context context = mock(Context.class); 48 | FieldExclusionStrategy exclusionStrategy = mock(FieldExclusionStrategy.class); 49 | NamingStrategy namingStrategy = mock(NamingStrategy.class); 50 | 51 | Field privateField = Subject.class.getDeclaredField("privateField"); 52 | Field protectedField = Subject.class.getDeclaredField("protectedField"); 53 | Field publicField = Subject.class.getDeclaredField("publicField"); 54 | 55 | when(context.getExclusionStrategy()).thenReturn(exclusionStrategy); 56 | when(context.getNamingStrategy()).thenReturn(namingStrategy); 57 | 58 | when(namingStrategy.getClassName(Subject.class)).thenReturn("Subject"); 59 | when(namingStrategy.getFieldName(privateField)) 60 | .thenReturn("PrivateField"); 61 | when(namingStrategy.getFieldName(protectedField)) 62 | .thenReturn("ProtectedField"); 63 | when(namingStrategy.getFieldName(publicField)) 64 | .thenReturn("PublicField"); 65 | 66 | when(exclusionStrategy.shouldSkipField(any(Field.class))) 67 | .thenAnswer(new Answer() 68 | { 69 | public Boolean answer(InvocationOnMock invocation) 70 | { 71 | Field field = invocation.getArgument(0); 72 | 73 | return field.getName().equals("excludedField"); 74 | } 75 | }); 76 | 77 | Subject subject = new Subject(1, 2, 3, 4); 78 | 79 | adapter.write(subject, writer, context); 80 | 81 | InOrder order = inOrder(writer, context); 82 | 83 | order.verify(writer).writeObjectStart("Subject", 3); 84 | 85 | order.verify(writer).writeProperty( 86 | "PrivateField", 87 | "Subject", 88 | Modifier.PRIVATE 89 | ); 90 | 91 | order.verify(context).write(1, writer); 92 | 93 | order.verify(writer).writeProperty( 94 | "ProtectedField", 95 | "Subject", 96 | Modifier.PROTECTED 97 | ); 98 | 99 | order.verify(context).write(2, writer); 100 | 101 | order.verify(writer).writeProperty( 102 | "PublicField", 103 | "Subject", 104 | Modifier.PUBLIC 105 | ); 106 | 107 | order.verify(context).write(3, writer); 108 | 109 | order.verify(writer).writeObjectEnd(); 110 | 111 | order.verifyNoMoreInteractions(); 112 | } 113 | } -------------------------------------------------------------------------------- /src/test/java/com/marcospassos/phpserializer/adapter/ReferableObjectAdapterTest.java: -------------------------------------------------------------------------------- 1 | package com.marcospassos.phpserializer.adapter; 2 | 3 | import com.marcospassos.phpserializer.Context; 4 | import com.marcospassos.phpserializer.TypeAdapter; 5 | import com.marcospassos.phpserializer.Writer; 6 | import org.junit.Test; 7 | 8 | import static org.mockito.Mockito.mock; 9 | import static org.mockito.Mockito.times; 10 | import static org.mockito.Mockito.verify; 11 | import static org.mockito.Mockito.when; 12 | 13 | /** 14 | * @author Marcos Passos 15 | * @since 1.0 16 | */ 17 | public class ReferableObjectAdapterTest 18 | { 19 | @Test 20 | public void writeDelegatesWritingIfReferenceDoesNotExist() throws Exception 21 | { 22 | Writer writer = mock(Writer.class); 23 | Context context = mock(Context.class); 24 | 25 | @SuppressWarnings("unchecked") 26 | TypeAdapter decorated = 27 | (TypeAdapter) mock(TypeAdapter.class); 28 | 29 | ReferableObjectAdapter adapter = 30 | new ReferableObjectAdapter<>( 31 | decorated 32 | ); 33 | 34 | Object subject = new Object(); 35 | 36 | when(context.getReference(subject)).thenReturn(-1); 37 | 38 | adapter.write(subject, writer, context); 39 | 40 | verify(context).getReference(subject); 41 | verify(writer, times(0)).writeObjectReference(-1); 42 | verify(decorated).write(subject, writer, context); 43 | } 44 | 45 | @Test 46 | public void writeWritesReferenceIfExists() throws Exception 47 | { 48 | Writer writer = mock(Writer.class); 49 | Context context = mock(Context.class); 50 | 51 | @SuppressWarnings("unchecked") 52 | TypeAdapter decorated = 53 | (TypeAdapter) mock(TypeAdapter.class); 54 | 55 | ReferableObjectAdapter adapter = 56 | new ReferableObjectAdapter<>( 57 | decorated 58 | ); 59 | 60 | Object subject = new Object(); 61 | 62 | when(context.getReference(subject)).thenReturn(1); 63 | 64 | adapter.write(subject, writer, context); 65 | 66 | verify(context).getReference(subject); 67 | verify(writer).writeObjectReference(1); 68 | verify(decorated, times(0)).write(subject, writer, context); 69 | } 70 | } -------------------------------------------------------------------------------- /src/test/java/com/marcospassos/phpserializer/adapter/StringAdapterTest.java: -------------------------------------------------------------------------------- 1 | package com.marcospassos.phpserializer.adapter; 2 | 3 | import java.nio.charset.Charset; 4 | import com.marcospassos.phpserializer.Context; 5 | import com.marcospassos.phpserializer.Writer; 6 | import org.junit.Test; 7 | 8 | import static org.mockito.Mockito.mock; 9 | import static org.mockito.Mockito.verify; 10 | import static org.mockito.Mockito.verifyNoMoreInteractions; 11 | 12 | /** 13 | * @author Marcos Passos 14 | * @since 1.0 15 | */ 16 | public class StringAdapterTest 17 | { 18 | @Test 19 | public void writeUsesUtf8ByDefault() throws Exception 20 | { 21 | Charset charset = Charset.forName("UTF-8"); 22 | 23 | StringAdapter adapter = new StringAdapter(); 24 | Writer writer = mock(Writer.class); 25 | Context context = mock(Context.class); 26 | 27 | adapter.write("foo", writer, context); 28 | 29 | verify(writer).writeString("foo", charset); 30 | 31 | verifyNoMoreInteractions(writer); 32 | } 33 | 34 | @Test 35 | public void writeCustomCharset() throws Exception 36 | { 37 | Charset charset = Charset.forName("ISO-8859-1"); 38 | StringAdapter adapter = new StringAdapter(charset); 39 | Writer writer = mock(Writer.class); 40 | Context context = mock(Context.class); 41 | 42 | adapter.write("foo", writer, context); 43 | 44 | verify(writer).writeString("foo", charset); 45 | 46 | verifyNoMoreInteractions(writer); 47 | } 48 | } -------------------------------------------------------------------------------- /src/test/java/com/marcospassos/phpserializer/exclusion/DisjunctionExclusionStrategyTest.java: -------------------------------------------------------------------------------- 1 | package com.marcospassos.phpserializer.exclusion; 2 | 3 | import java.lang.reflect.Field; 4 | import com.marcospassos.phpserializer.FieldExclusionStrategy; 5 | import org.junit.Test; 6 | 7 | import static org.junit.Assert.assertFalse; 8 | import static org.junit.Assert.assertTrue; 9 | import static org.mockito.Mockito.mock; 10 | import static org.mockito.Mockito.times; 11 | import static org.mockito.Mockito.verify; 12 | import static org.mockito.Mockito.when; 13 | 14 | /** 15 | * @author Marcos Passos 16 | * @since 1.0 17 | */ 18 | public class DisjunctionExclusionStrategyTest 19 | { 20 | class A 21 | { 22 | private int field; 23 | } 24 | 25 | @Test 26 | public void shouldSkipFieldPerformsShortCircuitOrOperation() 27 | throws Exception 28 | { 29 | FieldExclusionStrategy a = mock(FieldExclusionStrategy.class); 30 | FieldExclusionStrategy b = mock(FieldExclusionStrategy.class); 31 | 32 | Field field = A.class.getDeclaredField("field"); 33 | 34 | when(a.shouldSkipField(field)).thenReturn(true); 35 | 36 | FieldExclusionStrategy strategy = new DisjunctionExclusionStrategy( 37 | a, 38 | b 39 | ); 40 | 41 | assertTrue(strategy.shouldSkipField(field)); 42 | 43 | verify(a).shouldSkipField(field); 44 | verify(b, times(0)).shouldSkipField(field); 45 | } 46 | 47 | @Test 48 | public void shouldSkipFieldReturnsFalseIfNoStrategyIsProvided() 49 | throws Exception 50 | { 51 | FieldExclusionStrategy strategy = new DisjunctionExclusionStrategy(); 52 | Field field = A.class.getDeclaredField("field"); 53 | 54 | assertFalse(strategy.shouldSkipField(field)); 55 | } 56 | } -------------------------------------------------------------------------------- /src/test/java/com/marcospassos/phpserializer/exclusion/NoExclusionStrategyTest.java: -------------------------------------------------------------------------------- 1 | package com.marcospassos.phpserializer.exclusion; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.junit.Assert.assertFalse; 6 | 7 | /** 8 | * @author Marcos Passos 9 | * @since 1.0 10 | */ 11 | public class NoExclusionStrategyTest 12 | { 13 | private class A 14 | { 15 | public int field; 16 | } 17 | 18 | private class B 19 | { 20 | public int field; 21 | } 22 | 23 | @Test 24 | public void shouldSkipFieldAlwaysReturnsFalse() throws Exception 25 | { 26 | NoExclusionStrategy strategy = new NoExclusionStrategy(); 27 | 28 | assertFalse(strategy.shouldSkipField(A.class.getField("field"))); 29 | assertFalse(strategy.shouldSkipField(B.class.getField("field"))); 30 | } 31 | } -------------------------------------------------------------------------------- /src/test/java/com/marcospassos/phpserializer/naming/PsrNamingStrategyTest.java: -------------------------------------------------------------------------------- 1 | package com.marcospassos.phpserializer.naming; 2 | 3 | import java.lang.reflect.Field; 4 | import org.junit.Test; 5 | 6 | import static org.junit.Assert.assertEquals; 7 | 8 | /** 9 | * @author Marcos Passos 10 | * @since 1.0 11 | */ 12 | public class PsrNamingStrategyTest 13 | { 14 | private class Foo 15 | { 16 | public int BaR; 17 | } 18 | 19 | @Test 20 | public void getClassNameConvertsNameInStudlyCaps() throws Exception 21 | { 22 | PsrNamingStrategy strategy = new PsrNamingStrategy(); 23 | String className = strategy.getClassName(PsrNamingStrategyTest.class); 24 | 25 | assertEquals( 26 | "Com\\Marcospassos\\Phpserializer\\Naming\\PsrNamingStrategyTest", 27 | className 28 | ); 29 | } 30 | 31 | @Test 32 | public void getFieldNameKeepsNameAsDeclared() throws Exception 33 | { 34 | Field field = Foo.class.getField("BaR"); 35 | 36 | PsrNamingStrategy strategy = new PsrNamingStrategy(); 37 | String fieldName = strategy.getFieldName(field); 38 | 39 | assertEquals("BaR", fieldName); 40 | } 41 | } -------------------------------------------------------------------------------- /src/test/java/com/marcospassos/phpserializer/state/EndStateTest.java: -------------------------------------------------------------------------------- 1 | package com.marcospassos.phpserializer.state; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.junit.Assert.assertFalse; 6 | 7 | /** 8 | * @author Marcos Passos 9 | * @since 1.0 10 | */ 11 | public class EndStateTest 12 | { 13 | private FinishedState state = new FinishedState(); 14 | 15 | @Test 16 | public void isReferableReturnsFalse() throws Exception 17 | { 18 | assertFalse(state.isReferable()); 19 | } 20 | 21 | @Test(expected = IllegalStateException.class) 22 | public void valueIsNotAllowed() throws Exception 23 | { 24 | state.value(); 25 | } 26 | 27 | @Test(expected = IllegalStateException.class) 28 | public void objectBeginIsNotAllowed() throws Exception 29 | { 30 | state.objectBegin(); 31 | } 32 | 33 | @Test(expected = IllegalStateException.class) 34 | public void objectEndIsNotAllowed() throws Exception 35 | { 36 | state.objectEnd(); 37 | } 38 | 39 | @Test(expected = IllegalStateException.class) 40 | public void serializableObjectBeginIsNotAllowed() throws Exception 41 | { 42 | state.serializableBegin(); 43 | } 44 | 45 | @Test(expected = IllegalStateException.class) 46 | public void serializableObjectEndIsNotAllowed() throws Exception 47 | { 48 | state.serializableEnd(); 49 | } 50 | 51 | @Test(expected = IllegalStateException.class) 52 | public void propertyIsNotAllowed() throws Exception 53 | { 54 | state.property(); 55 | } 56 | 57 | @Test(expected = IllegalStateException.class) 58 | public void arrayBeginIsNotAllowed() throws Exception 59 | { 60 | state.arrayBegin(); 61 | } 62 | 63 | @Test(expected = IllegalStateException.class) 64 | public void arrayEndIsNotAllowed() throws Exception 65 | { 66 | state.arrayEnd(); 67 | } 68 | 69 | @Test(expected = IllegalStateException.class) 70 | public void keyIsNotAllowed() throws Exception 71 | { 72 | state.key(); 73 | } 74 | } -------------------------------------------------------------------------------- /src/test/java/com/marcospassos/phpserializer/state/WritingArrayStateTest.java: -------------------------------------------------------------------------------- 1 | package com.marcospassos.phpserializer.state; 2 | 3 | import com.marcospassos.phpserializer.WriterState; 4 | import org.junit.Before; 5 | import org.junit.Test; 6 | 7 | import static org.junit.Assert.assertFalse; 8 | import static org.junit.Assert.assertSame; 9 | import static org.junit.Assert.assertTrue; 10 | import static org.mockito.Mockito.mock; 11 | 12 | /** 13 | * @author Marcos Passos 14 | * @since 1.0 15 | */ 16 | public class WritingArrayStateTest 17 | { 18 | private WritingArrayState state; 19 | private WriterState parent; 20 | 21 | @Before 22 | public void setUp() throws Exception 23 | { 24 | parent = mock(WriterState.class); 25 | state = new WritingArrayState(parent); 26 | } 27 | 28 | @Test 29 | public void isReferableReturnsFalse() throws Exception 30 | { 31 | assertFalse(state.isReferable()); 32 | } 33 | 34 | @Test(expected = IllegalStateException.class) 35 | public void valueIsNotAllowed() throws Exception 36 | { 37 | state.value(); 38 | } 39 | 40 | @Test(expected = IllegalStateException.class) 41 | public void objectBeginIsNotAllowed() throws Exception 42 | { 43 | state.objectBegin(); 44 | } 45 | 46 | @Test(expected = IllegalStateException.class) 47 | public void objectEndIsNotAllowed() throws Exception 48 | { 49 | state.objectEnd(); 50 | } 51 | 52 | @Test(expected = IllegalStateException.class) 53 | public void serializableObjectBeginIsNotAllowed() throws Exception 54 | { 55 | state.serializableBegin(); 56 | } 57 | 58 | @Test(expected = IllegalStateException.class) 59 | public void serializableObjectEndIsNotAllowed() throws Exception 60 | { 61 | state.serializableEnd(); 62 | } 63 | 64 | @Test(expected = IllegalStateException.class) 65 | public void propertyIsNotAllowed() throws Exception 66 | { 67 | state.property(); 68 | } 69 | 70 | @Test(expected = IllegalStateException.class) 71 | public void arrayBeginIsNotAllowed() throws Exception 72 | { 73 | state.arrayBegin(); 74 | } 75 | 76 | @Test 77 | public void arrayEndIsAllowed() throws Exception 78 | { 79 | assertSame(parent, state.arrayEnd()); 80 | } 81 | 82 | @Test 83 | public void keyIsAllowed() throws Exception 84 | { 85 | assertTrue(state.key() instanceof WritingValueState); 86 | } 87 | } -------------------------------------------------------------------------------- /src/test/java/com/marcospassos/phpserializer/state/WritingObjectStateTest.java: -------------------------------------------------------------------------------- 1 | package com.marcospassos.phpserializer.state; 2 | 3 | import com.marcospassos.phpserializer.WriterState; 4 | import org.junit.Before; 5 | import org.junit.Test; 6 | 7 | import static org.junit.Assert.assertFalse; 8 | import static org.junit.Assert.assertSame; 9 | import static org.junit.Assert.assertTrue; 10 | import static org.mockito.Mockito.mock; 11 | 12 | /** 13 | * @author Marcos Passos 14 | * @since 1.0 15 | */ 16 | public class WritingObjectStateTest 17 | { 18 | private WritingObjectState state; 19 | private WriterState parent; 20 | 21 | @Before 22 | public void setUp() throws Exception 23 | { 24 | parent = mock(WriterState.class); 25 | state = new WritingObjectState(parent); 26 | } 27 | 28 | @Test 29 | public void isReferableReturnsFalse() throws Exception 30 | { 31 | assertFalse(state.isReferable()); 32 | } 33 | 34 | @Test(expected = IllegalStateException.class) 35 | public void valueIsNotAllowed() throws Exception 36 | { 37 | state.value(); 38 | } 39 | 40 | @Test(expected = IllegalStateException.class) 41 | public void objectBeginIsNotAllowed() throws Exception 42 | { 43 | state.objectBegin(); 44 | } 45 | 46 | @Test 47 | public void objectEndIsAllowed() throws Exception 48 | { 49 | assertSame(parent, state.objectEnd()); 50 | } 51 | 52 | @Test 53 | public void propertyIsAllowed() throws Exception 54 | { 55 | assertTrue(state.property() instanceof WritingValueState); 56 | } 57 | 58 | @Test(expected = IllegalStateException.class) 59 | public void serializableObjectBeginIsNotAllowed() throws Exception 60 | { 61 | state.serializableBegin(); 62 | } 63 | 64 | @Test(expected = IllegalStateException.class) 65 | public void serializableObjectEndIsNotAllowed() throws Exception 66 | { 67 | state.serializableEnd(); 68 | } 69 | 70 | @Test(expected = IllegalStateException.class) 71 | public void arrayBeginIsNotAllowed() throws Exception 72 | { 73 | state.arrayBegin(); 74 | } 75 | 76 | @Test(expected = IllegalStateException.class) 77 | public void arrayEndIsNotAllowed() throws Exception 78 | { 79 | state.arrayEnd(); 80 | } 81 | 82 | @Test(expected = IllegalStateException.class) 83 | public void keyIsNotAllowed() throws Exception 84 | { 85 | state.key(); 86 | } 87 | } -------------------------------------------------------------------------------- /src/test/java/com/marcospassos/phpserializer/state/WritingSerializableObjectStateTest.java: -------------------------------------------------------------------------------- 1 | package com.marcospassos.phpserializer.state; 2 | 3 | import com.marcospassos.phpserializer.WriterState; 4 | import org.junit.Before; 5 | import org.junit.Test; 6 | 7 | import static org.junit.Assert.assertFalse; 8 | import static org.junit.Assert.assertSame; 9 | import static org.mockito.Mockito.mock; 10 | 11 | /** 12 | * @author Marcos Passos 13 | * @since 1.0 14 | */ 15 | public class WritingSerializableObjectStateTest 16 | { 17 | private WritingSerializableObjectState state; 18 | private WriterState parent; 19 | 20 | @Before 21 | public void setUp() throws Exception 22 | { 23 | parent = mock(WriterState.class); 24 | state = new WritingSerializableObjectState(parent); 25 | } 26 | 27 | @Test 28 | public void isReferableReturnsFalse() throws Exception 29 | { 30 | assertFalse(state.isReferable()); 31 | } 32 | 33 | @Test(expected = IllegalStateException.class) 34 | public void serializableObjectBeginIsNotAllowed() throws Exception 35 | { 36 | state.serializableBegin(); 37 | } 38 | 39 | @Test 40 | public void serializableObjectEndIsAllowed() throws Exception 41 | { 42 | assertSame(parent, state.serializableEnd()); 43 | } 44 | 45 | @Test(expected = IllegalStateException.class) 46 | public void valueIsNotAllowed() throws Exception 47 | { 48 | state.value(); 49 | } 50 | 51 | @Test(expected = IllegalStateException.class) 52 | public void objectBeginIsNotAllowed() throws Exception 53 | { 54 | state.objectBegin(); 55 | } 56 | 57 | @Test(expected = IllegalStateException.class) 58 | public void objectEndIsNotAllowed() throws Exception 59 | { 60 | state.objectEnd(); 61 | } 62 | 63 | @Test(expected = IllegalStateException.class) 64 | public void propertyIsNotAllowed() throws Exception 65 | { 66 | state.property(); 67 | } 68 | 69 | @Test(expected = IllegalStateException.class) 70 | public void arrayBeginIsNotAllowed() throws Exception 71 | { 72 | state.arrayBegin(); 73 | } 74 | 75 | @Test(expected = IllegalStateException.class) 76 | public void arrayEndIsNotAllowed() throws Exception 77 | { 78 | state.arrayEnd(); 79 | } 80 | 81 | @Test(expected = IllegalStateException.class) 82 | public void keyIsNotAllowed() throws Exception 83 | { 84 | state.key(); 85 | } 86 | } -------------------------------------------------------------------------------- /src/test/java/com/marcospassos/phpserializer/state/WritingValueStateTest.java: -------------------------------------------------------------------------------- 1 | package com.marcospassos.phpserializer.state; 2 | 3 | import com.marcospassos.phpserializer.WriterState; 4 | import org.junit.Before; 5 | import org.junit.Test; 6 | 7 | import static org.junit.Assert.assertSame; 8 | import static org.junit.Assert.assertTrue; 9 | import static org.mockito.Mockito.mock; 10 | 11 | /** 12 | * @author Marcos Passos 13 | * @since 1.0 14 | */ 15 | public class WritingValueStateTest 16 | { 17 | private WriterState parent; 18 | private WritingValueState state; 19 | 20 | @Before 21 | public void setUp() throws Exception 22 | { 23 | parent = mock(WriterState.class); 24 | state = new WritingValueState(parent); 25 | } 26 | 27 | @Test 28 | public void endStateIsParentByDefault() throws Exception 29 | { 30 | assertTrue(new WritingValueState().value() instanceof FinishedState); 31 | } 32 | 33 | @Test 34 | public void isReferableReturnsTrue() throws Exception 35 | { 36 | assertTrue(state.isReferable()); 37 | } 38 | 39 | @Test 40 | public void valueIsAllowed() throws Exception 41 | { 42 | assertSame(parent, state.value()); 43 | } 44 | 45 | @Test 46 | public void objectBeginIsAllowed() throws Exception 47 | { 48 | assertTrue(state.objectBegin() instanceof WritingObjectState); 49 | } 50 | 51 | @Test(expected = IllegalStateException.class) 52 | public void objectEndIsNotAllowed() throws Exception 53 | { 54 | state.objectEnd(); 55 | } 56 | 57 | @Test 58 | public void serializableObjectBeginIsAllowed() throws Exception 59 | { 60 | state.serializableBegin(); 61 | } 62 | 63 | @Test(expected = IllegalStateException.class) 64 | public void serializableObjectEndIsNotAllowed() throws Exception 65 | { 66 | state.serializableEnd(); 67 | } 68 | 69 | @Test(expected = IllegalStateException.class) 70 | public void propertyIsNotAllowed() throws Exception 71 | { 72 | state.property(); 73 | } 74 | 75 | @Test 76 | public void arrayBeginIsAllowed() throws Exception 77 | { 78 | assertTrue(state.arrayBegin() instanceof WritingArrayState); 79 | } 80 | 81 | @Test(expected = IllegalStateException.class) 82 | public void arrayEndIsNotAllowed() throws Exception 83 | { 84 | state.arrayEnd(); 85 | } 86 | 87 | @Test(expected = IllegalStateException.class) 88 | public void keyIsNotAllowed() throws Exception 89 | { 90 | state.key(); 91 | } 92 | } -------------------------------------------------------------------------------- /src/test/java/com/marcospassos/phpserializer/util/ReflectionUtilsTest.java: -------------------------------------------------------------------------------- 1 | package com.marcospassos.phpserializer.util; 2 | 3 | import java.lang.reflect.Field; 4 | import org.junit.Test; 5 | 6 | import static org.junit.Assert.assertArrayEquals; 7 | import static org.junit.Assert.assertEquals; 8 | 9 | /** 10 | * @author Marcos Passos 11 | * @since 1.0 12 | */ 13 | public class ReflectionUtilsTest 14 | { 15 | private class A 16 | { 17 | private int a; 18 | } 19 | 20 | private class B extends A 21 | { 22 | private int b; 23 | } 24 | 25 | private class C extends B 26 | { 27 | private int c; 28 | 29 | public C(int c) 30 | { 31 | this.c = c; 32 | } 33 | } 34 | 35 | @Test 36 | public void getFieldsReturnsAllFieldsFromClassAndSuperclasses() 37 | throws Exception 38 | { 39 | assertArrayEquals( 40 | new Field[]{A.class.getDeclaredField("a")}, 41 | ReflectionUtils.getFields(A.class) 42 | ); 43 | 44 | assertArrayEquals( 45 | new Field[]{ 46 | B.class.getDeclaredField("b"), 47 | A.class.getDeclaredField("a"), 48 | }, 49 | ReflectionUtils.getFields(B.class) 50 | ); 51 | 52 | assertArrayEquals( 53 | new Field[]{ 54 | C.class.getDeclaredField("c"), 55 | B.class.getDeclaredField("b"), 56 | A.class.getDeclaredField("a") 57 | }, 58 | ReflectionUtils.getFields(C.class) 59 | ); 60 | } 61 | 62 | @Test 63 | public void getValue() throws Exception 64 | { 65 | C object = new C(1); 66 | Field field = C.class.getDeclaredField("c"); 67 | 68 | assertEquals(1, ReflectionUtils.getValue(object, field)); 69 | } 70 | } --------------------------------------------------------------------------------